WIP starbook demo
This commit is contained in:
@@ -4,27 +4,19 @@
|
||||
isFaceUp ? 'face-up' : 'face-down',
|
||||
showPlayButtonFaceUp ? 'show-play-button-face-up' : ''
|
||||
]" :tabindex="props.tabindex" :aria-disabled="false" @click="$emit('click', $event)"
|
||||
@keydown.enter="$emit('click', $event)" @keydown.space.prevent="$emit('click', $event)">
|
||||
@keydown.enter="$emit('click', $event)">
|
||||
<div class="flip-inner" ref="cardElement">
|
||||
<!-- Face-Up -->
|
||||
<main
|
||||
class="face-up backdrop-blur-sm border-2 z-10 card w-56 h-80 p-3 hover:shadow-xl hover:scale-110 transition-all rounded-2xl shadow-lg flex flex-col overflow-hidden">
|
||||
class="face-up backdrop-blur-sm border-2 z-10 card w-56 h-80 p-3 hover:shadow-xl hover:scale-110 transition-all shadow-lg flex flex-col overflow-hidden">
|
||||
|
||||
<div class="flex items-center justify-center size-7 absolute top-7 right-7">
|
||||
<div class="suit text-7xl absolute"
|
||||
:class="[isRedCard ? 'text-red-600' : 'text-slate-800', props.card?.suit]">
|
||||
<img :src="`/${props.card?.suit}.svg`" />
|
||||
</div>
|
||||
<div class="rank text-white font-bold absolute -mt-1">
|
||||
{{ props.card?.rank }}
|
||||
</div>
|
||||
</div>
|
||||
<Rank :card="props.card" />
|
||||
|
||||
<!-- Cover -->
|
||||
<figure class="pochette flex-1 flex justify-center items-center overflow-hidden rounded-xl cursor-pointer">
|
||||
<playButton />
|
||||
<figure class="flex-1 flex justify-center items-center overflow-hidden cursor-pointer">
|
||||
<PlayButton />
|
||||
<img :src="props.card.url_image" alt="Pochette de l'album" :loading="props.imageLoadingType"
|
||||
@load="$emit('image-loaded', $event)" class="w-full h-full object-cover object-center" />
|
||||
@load="$emit('image-loaded', $event)" class="pochette w-full h-full object-cover object-center" />
|
||||
</figure>
|
||||
|
||||
<!-- Body -->
|
||||
@@ -44,7 +36,7 @@
|
||||
class="face-down backdrop-blur-sm z-10 card w-56 h-80 p-3 bg-opacity-10 bg-white rounded-2xl shadow-lg flex flex-col overflow-hidden select-none">
|
||||
<figure class="h-full flex text-center rounded-xl justify-center items-center"
|
||||
:style="{ backgroundColor: cardColor }">
|
||||
<playButton />
|
||||
<PlayButton />
|
||||
<img src="/face-down.svg" />
|
||||
</figure>
|
||||
</footer>
|
||||
@@ -75,7 +67,6 @@ const props = withDefaults(defineProps<{
|
||||
import { getYearColor } from '~/utils/colors'
|
||||
|
||||
const cardColor = computed(() => getYearColor(props.card.year || 0))
|
||||
const isRedCard = computed(() => (props.card?.suit === '♥' || props.card?.suit === '♦'))
|
||||
|
||||
/* loading states of the card */
|
||||
const isApiLoaded = ref(false)
|
||||
@@ -103,6 +94,10 @@ const isTrackLoaded = ref(false)
|
||||
perspective: 1000px;
|
||||
@apply transition-all scale-100 w-56 h-80 min-w-56 min-h-80;
|
||||
|
||||
.pochette {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.flip-inner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -123,6 +118,7 @@ const isTrackLoaded = ref(false)
|
||||
}
|
||||
|
||||
.face-up {
|
||||
border-radius: 1rem;
|
||||
transform: rotateY(0deg);
|
||||
transition: box-shadow 0.6s;
|
||||
}
|
||||
@@ -158,7 +154,7 @@ const isTrackLoaded = ref(false)
|
||||
@apply shadow-2xl;
|
||||
transition:
|
||||
box-shadow 0.6s,
|
||||
transform 0.6s;
|
||||
transform 6s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +1,43 @@
|
||||
<template>
|
||||
<div class="platine" :class="{ 'loading': isLoadingCard, 'mounted': isMounted }" ref="platine">
|
||||
<div class="platine" :class="{ 'loading': isLoadingTrack, 'mounted': isMounted, 'playing': isPlaying }" ref="platine">
|
||||
<div v-if="true" class="debug">
|
||||
<button @click="Reverse">
|
||||
<b v-if="isReversed">reversed</b>
|
||||
<b v-else>normal</b>
|
||||
</button>
|
||||
<button @click="Rewind">
|
||||
rewind
|
||||
</button>
|
||||
<div>{{ progressPercentage }}</div>
|
||||
<div>{{ currentSpeed }}</div>
|
||||
</div>
|
||||
<div class="disc" ref="discRef" id="disc">
|
||||
<div class="bobine" :style="{
|
||||
height: progressPercentage + '%',
|
||||
width: progressPercentage + '%'
|
||||
}"></div>
|
||||
<div class="power-button" @mousedown.stop @click.stop="handlePowerButtonClick">
|
||||
<img class="power-logo" src="/favicon.svg">
|
||||
<div class="power-loading" v-if="isLoadingCard">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<button class="power-button" @click="Power" @touchstart="Power" :disabled="isLoadingTrack">
|
||||
<img class="macaron" src="/favicon.svg">
|
||||
<div class="spinner" v-if="isLoadingTrack" />
|
||||
</button>
|
||||
<div class="turn-point" v-if="!isLoadingTrack">
|
||||
</div>
|
||||
<div class="turn-point" v-if="!isLoadingCard">
|
||||
</div>
|
||||
</div>
|
||||
<div class="debug">
|
||||
<b>progressPercentage</b>: <br>
|
||||
</br>{{ Math.round(progressPercentage) }}%
|
||||
<br>
|
||||
<b>Disc</b>: <br>
|
||||
</br>{{ Math.round(disc?.secondsPlayed || 0) }}sec
|
||||
<br>
|
||||
</br>{{ Math.round(sampler?.duration || 0) }}sec
|
||||
<br>
|
||||
<!-- <pre>{{ sampler }}</pre> -->
|
||||
isPlaying:
|
||||
</br>{{ isPlaying }}
|
||||
<br>
|
||||
<b>isFirstDrag</b>: <br>
|
||||
</br>{{ isFirstDrag }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Card } from '~~/types/types'
|
||||
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, watch, computed, nextTick } from 'vue'
|
||||
import Disc from '~/utils/platine/disc'
|
||||
import Sampler from '~/utils/platine/sampler'
|
||||
|
||||
const props = defineProps<{ card?: Card }>()
|
||||
const props = defineProps<{ card?: Card, autoplay?: boolean }>()
|
||||
const autoplay = props.autoplay ?? false
|
||||
|
||||
// State
|
||||
const isLoadingCard = ref(false)
|
||||
const isFirstDrag = ref(true)
|
||||
const isLoadingTrack = ref(false)
|
||||
const isFirstPlay = ref(true)
|
||||
const progressPercentage = ref(0)
|
||||
const currentTurns = ref(0)
|
||||
const totalTurns = ref(0)// Refs pour les instances
|
||||
@@ -56,6 +50,8 @@ const platine = ref<HTMLElement>()
|
||||
const isMounted = ref(false)
|
||||
|
||||
const isPlaying = computed(() => Math.abs(Math.round(sampler.value?.currentSpeed || 0)) !== 0)
|
||||
const isReversed = computed(() => disc.value?.isReversed || false)
|
||||
const currentSpeed = computed(() => sampler.value?.currentSpeed || 0)
|
||||
|
||||
// Actions
|
||||
const initPlatine = (element: HTMLElement) => {
|
||||
@@ -72,23 +68,13 @@ const initPlatine = (element: HTMLElement) => {
|
||||
}
|
||||
|
||||
disc.value.callbacks.onDragStart = () => {
|
||||
// console.log('[DISC] On Drag Start')
|
||||
if (isFirstDrag.value) {
|
||||
isFirstDrag.value = false
|
||||
// togglePlay()
|
||||
if (sampler.value && disc.value) {
|
||||
sampler.value.play(disc.value.secondsPlayed)
|
||||
disc.value.powerOn()
|
||||
// console.log('[DISC] Power ON')
|
||||
}
|
||||
// Activer le son à chaque fois qu'on glisse, pas seulement au premier play
|
||||
if (sampler.value && disc.value) {
|
||||
// On joue toujours le son quand on glisse, même après une pause
|
||||
sampler.value.play(disc.value.secondsPlayed || 0)
|
||||
}
|
||||
}
|
||||
|
||||
disc.value.callbacks.onDragEnded = () => {
|
||||
// console.log('[DISC] On Drag END')
|
||||
sampler.value?.play(disc.value?.secondsPlayed || 0)
|
||||
}
|
||||
|
||||
disc.value.callbacks.onLoop = ({ playbackSpeed, isReversed, secondsPlayed }) => {
|
||||
// Ne mettre à jour que si nécessaire et s'assurer que la position est valide
|
||||
if (Math.abs((sampler.value?.currentSpeed || 0) - playbackSpeed) > 0.01) {
|
||||
@@ -106,10 +92,11 @@ const updateTurns = () => {
|
||||
const newTurns = disc.value.secondsPlayed * 0.75
|
||||
const newTotalTurns = (disc.value as any)._duration * 0.75
|
||||
|
||||
// Calcul du pourcentage de progression pour l'affichage visuel (22% à 100%)
|
||||
const minPercentage = 22
|
||||
// Calcul du pourcentage de progression pour l'affichage visuel (17% à 100%)
|
||||
const minPercentage = 17
|
||||
const maxPercentage = 100
|
||||
const progressRatio = disc.value.secondsPlayed / (disc.value as any)._duration
|
||||
const newProgressPercentage = minPercentage + (progressRatio * (100 - minPercentage))
|
||||
const newProgressPercentage = minPercentage + (progressRatio * (maxPercentage - minPercentage))
|
||||
|
||||
// Ne mettre à jour que si les valeurs ont changé de manière significative
|
||||
if (
|
||||
@@ -127,7 +114,7 @@ const loadCard = async (card: Card) => {
|
||||
// console.log('[LOAD CARD]', card)
|
||||
if (!sampler.value || !card) return
|
||||
|
||||
isLoadingCard.value = true
|
||||
isLoadingTrack.value = true
|
||||
// console.log(disc.value)
|
||||
try {
|
||||
await sampler.value.loadTrack(card.url_audio)
|
||||
@@ -137,12 +124,12 @@ const loadCard = async (card: Card) => {
|
||||
updateTurns()
|
||||
}
|
||||
} finally {
|
||||
isLoadingCard.value = false
|
||||
isLoadingTrack.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const play = (position = 0) => {
|
||||
// console.log('[PLAY]')
|
||||
isFirstPlay.value = false
|
||||
if (!disc.value || !sampler.value || !props.card) return
|
||||
|
||||
sampler.value.play(position)
|
||||
@@ -179,11 +166,24 @@ const cleanup = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handlePowerButtonClick = (e: MouseEvent) => {
|
||||
e.stopPropagation() // Empêcher la propagation de l'événement
|
||||
e.preventDefault() // Empêcher tout comportement par défaut
|
||||
const Power = (e: MouseEvent) => {
|
||||
togglePlay()
|
||||
return false // Empêcher tout autre comportement
|
||||
}
|
||||
|
||||
const Reverse = () => {
|
||||
if (!disc.value || !sampler.value) return
|
||||
|
||||
// Sauvegarder la position actuelle
|
||||
const currentPosition = disc.value.secondsPlayed || 0
|
||||
const wasPlaying = !disc.value.isStopped()
|
||||
|
||||
// Inverser la direction du disque et du sampler
|
||||
disc.value.reverse()
|
||||
sampler.value.reverse(wasPlaying ? currentPosition : 0)
|
||||
}
|
||||
|
||||
const Rewind = async () => {
|
||||
// ...
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -194,14 +194,17 @@ const handleKeyDown = (e: KeyboardEvent) => {
|
||||
}
|
||||
|
||||
// Initialisation du lecteur
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
isMounted.value = true
|
||||
if (discRef.value) {
|
||||
initPlatine(discRef.value)
|
||||
loadCard(props.card!)
|
||||
await loadCard(props.card!)
|
||||
if (autoplay) {
|
||||
await nextTick()
|
||||
play()
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter l'écouteur d'événement clavier
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
|
||||
@@ -224,134 +227,111 @@ watch(() => props.card, (newCard) => {
|
||||
|
||||
<style lang="scss">
|
||||
.platine {
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.disc {
|
||||
pointer-events: auto;
|
||||
position: fixed;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
cursor: grab;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
|
||||
.disc {
|
||||
pointer-events: auto;
|
||||
position: fixed;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
cursor: grab;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
padding: 14px;
|
||||
|
||||
.loading & {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.turn-point {
|
||||
@apply absolute top-1/2 right-8 size-1/12 rounded-full bg-esyellow;
|
||||
}
|
||||
|
||||
.disc.is-scratching {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.power-button {
|
||||
border-radius: 999px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 45%;
|
||||
aspect-ratio: 1/1;
|
||||
// background: no-repeat url(/favicon.svg) center center;
|
||||
background-size: 30%;
|
||||
border-radius: 50%;
|
||||
cursor: pointer !important;
|
||||
|
||||
.power-logo {
|
||||
@apply size-1/2 bg-black rounded-full p-5;
|
||||
.loading & {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.power-loading {
|
||||
.turn-point {
|
||||
@apply absolute top-1/2 right-8 size-1/12 rounded-full bg-esyellow;
|
||||
}
|
||||
|
||||
.disc.is-scratching {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.power-button {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
cursor: pointer;
|
||||
transition: all 0.1s;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform-origin: center;
|
||||
width: 33%;
|
||||
height: 33%;
|
||||
border-radius: 999px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.macaron {
|
||||
position: absolute;
|
||||
filter: grayscale(1);
|
||||
transition: transform 0.1s, filter 0.8s;
|
||||
@apply size-2/3;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
@apply size-1/2;
|
||||
}
|
||||
|
||||
&:active {
|
||||
.macaron {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.playing .power-button .macaron {
|
||||
filter: grayscale(0);
|
||||
}
|
||||
|
||||
.disc-middle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgb(26, 26, 26);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.disc-middle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgb(26, 26, 26);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
// @apply md:border-8;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
background: rgb(69, 69, 69);
|
||||
font-size: 0.75rem;
|
||||
padding: 0.4rem;
|
||||
color: #fff;
|
||||
line-height: 1.3;
|
||||
cursor: pointer;
|
||||
will-change: box-shadow;
|
||||
transition:
|
||||
box-shadow 0.2s ease-out,
|
||||
transform 0.05s ease-in;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.power.is-active {
|
||||
transform: translate(1px, 2px);
|
||||
color: red;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
.bobine {
|
||||
width: 17%;
|
||||
height: 17%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
@apply relative bg-black bg-opacity-30 backdrop-blur-sm rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
.bobine {
|
||||
@apply bg-slate-900 bg-opacity-50 backdrop-blur absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 rounded-full;
|
||||
}
|
||||
|
||||
.debug {
|
||||
@apply fixed top-4 right-4 bg-slate-200 rounded-md p-2;
|
||||
}
|
||||
</style>
|
||||
|
||||
24
app/components/Rank.vue
Normal file
24
app/components/Rank.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="rank flex items-center justify-center size-7 absolute top-7 right-7">
|
||||
<div class="suit text-7xl absolute" :class="[isRedCard ? 'text-red-600' : 'text-slate-800', props.card?.suit]">
|
||||
<img :src="`/${props.card?.suit}.svg`" />
|
||||
</div>
|
||||
<div class="rank text-white font-bold absolute -mt-1">
|
||||
{{ props.card?.rank }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Card } from '~~/types/types';
|
||||
|
||||
const props = defineProps<{ card?: Card }>()
|
||||
|
||||
const isRedCard = computed(() => (props.card?.suit === '♥' || props.card?.suit === '♦'))
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.rank {
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
39
app/components/UI/CloseButton.vue
Normal file
39
app/components/UI/CloseButton.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<button ref="buttonRef">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.7071 5.29289C16.0976 5.68342 16.0976 6.31658 15.7071 6.70711L10.4142 12L15.7071 17.2929C16.0976 17.6834 16.0976 18.3166 15.7071 18.7071C15.3165 19.0976 14.6834 19.0976 14.2929 18.7071L8.46963 12.8839C7.98148 12.3957 7.98148 11.6043 8.46963 11.1161L14.2929 5.29289C14.6834 4.90237 15.3165 4.90237 15.7071 5.29289Z"
|
||||
fill="#0F1729"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const buttonRef = ref<HTMLButtonElement | null>(null)
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && buttonRef.value) {
|
||||
buttonRef.value.click()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
button {
|
||||
@apply fixed bottom-4 md:top-4 left-4 size-20;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<button tabindex="-1"
|
||||
class="play-button pointer-events-none rounded-full size-24 flex items-center justify-center text-esyellow backdrop-blur-sm bg-black/25 transition-all duration-200 ease-in-out transform active:scale-90 scale-110 text-4xl font-bold"
|
||||
:class="{ loading: isLoading }" :disabled="isLoading">
|
||||
<button tabindex="-1" class="play-button" :class="{ loading: isLoading }" :disabled="isLoading">
|
||||
<template v-if="props.isLoading">
|
||||
<img src="/loader.svg" alt="Chargement" class="size-16" />
|
||||
</template>
|
||||
@@ -21,7 +19,11 @@ const props = withDefaults(defineProps<{
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped lang="scss">
|
||||
.play-button {
|
||||
@apply pointer-events-none rounded-full size-24 flex items-center justify-center text-esyellow backdrop-blur-sm bg-black/25 transition-all duration-100 ease-in-out transform active:scale-90 scale-110 text-4xl font-bold;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.play-button-changed {
|
||||
opacity: 1 !important;
|
||||
Reference in New Issue
Block a user