2024-04-05 02:00:17 -04:00
|
|
|
<script lang="ts">
|
2024-04-09 00:10:23 -04:00
|
|
|
import { onMount } from 'svelte'
|
2024-04-13 00:45:35 -04:00
|
|
|
import { fade, slide } from 'svelte/transition'
|
2024-04-15 22:26:38 -04:00
|
|
|
import { queue } from '$lib/stores'
|
2024-04-13 00:45:35 -04:00
|
|
|
// import { FastAverageColor } from 'fast-average-color'
|
2024-04-09 00:10:23 -04:00
|
|
|
import Slider from '$lib/components/util/slider.svelte'
|
2024-05-28 00:46:34 -04:00
|
|
|
import Loader from '$lib/components/util/loader.svelte'
|
2024-04-09 00:10:23 -04:00
|
|
|
|
2024-04-22 14:18:42 -04:00
|
|
|
$: currentlyPlaying = $queue.current
|
|
|
|
|
|
|
|
|
|
let expanded = false
|
2024-04-15 22:26:38 -04:00
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
let paused = true,
|
2024-04-09 00:10:23 -04:00
|
|
|
shuffle = false,
|
|
|
|
|
repeat = false
|
|
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
let volume: number,
|
|
|
|
|
muted = false
|
2024-04-10 00:27:36 -04:00
|
|
|
|
2024-05-28 00:46:34 -04:00
|
|
|
const maxVolume = 0.5
|
|
|
|
|
|
|
|
|
|
let waiting: boolean
|
|
|
|
|
|
2024-04-22 14:18:42 -04:00
|
|
|
$: muted ? (volume = 0) : (volume = Number(localStorage.getItem('volume')))
|
|
|
|
|
$: if (volume && !muted) localStorage.setItem('volume', volume.toString())
|
2024-04-10 00:27:36 -04:00
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
const formatTime = (seconds: number): string => {
|
|
|
|
|
seconds = Math.floor(seconds)
|
|
|
|
|
const hours = Math.floor(seconds / 3600)
|
|
|
|
|
seconds = seconds - hours * 3600
|
|
|
|
|
const minutes = Math.floor(seconds / 60)
|
|
|
|
|
seconds = seconds - minutes * 60
|
|
|
|
|
return hours > 0 ? `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` : `${minutes}:${seconds.toString().padStart(2, '0')}`
|
|
|
|
|
}
|
2024-04-10 00:27:36 -04:00
|
|
|
|
2024-04-22 14:18:42 -04:00
|
|
|
$: if (currentlyPlaying) updateMediaSession(currentlyPlaying)
|
|
|
|
|
const updateMediaSession = (media: Song) => {
|
|
|
|
|
if ('mediaSession' in navigator) {
|
|
|
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
|
|
|
title: media.name,
|
2024-05-09 15:25:25 -04:00
|
|
|
artist: media.artists?.map((artist) => artist.name).join(', ') || media.uploader?.name,
|
2024-04-22 14:18:42 -04:00
|
|
|
album: media.album?.name,
|
2024-05-09 15:25:25 -04:00
|
|
|
artwork: [{ src: `/api/remoteImage?url=${media.thumbnailUrl}`, sizes: '256x256', type: 'image/png' }],
|
2024-04-22 14:18:42 -04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
onMount(() => {
|
|
|
|
|
const storedVolume = localStorage.getItem('volume')
|
|
|
|
|
if (storedVolume) {
|
|
|
|
|
volume = Number(storedVolume)
|
|
|
|
|
} else {
|
2024-05-28 00:46:34 -04:00
|
|
|
localStorage.setItem('volume', (maxVolume / 2).toString())
|
2024-04-13 00:45:35 -04:00
|
|
|
volume = 0.5
|
2024-04-10 00:27:36 -04:00
|
|
|
}
|
2024-04-22 14:18:42 -04:00
|
|
|
|
|
|
|
|
if ('mediaSession' in navigator) {
|
|
|
|
|
navigator.mediaSession.setActionHandler('play', () => (paused = false))
|
|
|
|
|
navigator.mediaSession.setActionHandler('pause', () => (paused = true))
|
|
|
|
|
navigator.mediaSession.setActionHandler('stop', () => $queue.clear())
|
|
|
|
|
navigator.mediaSession.setActionHandler('nexttrack', () => $queue.next())
|
|
|
|
|
navigator.mediaSession.setActionHandler('previoustrack', () => $queue.previous())
|
|
|
|
|
}
|
2024-04-13 00:45:35 -04:00
|
|
|
})
|
2024-04-10 00:27:36 -04:00
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
let currentTime: number = 0
|
|
|
|
|
let duration: number = 0
|
2024-04-10 00:27:36 -04:00
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
let currentTimeTimestamp: HTMLSpanElement
|
|
|
|
|
let progressBar: Slider
|
|
|
|
|
let durationTimestamp: HTMLSpanElement
|
2024-04-05 02:00:17 -04:00
|
|
|
|
2024-04-22 14:18:42 -04:00
|
|
|
let expandedCurrentTimeTimestamp: HTMLSpanElement
|
|
|
|
|
let expandedProgressBar: Slider
|
|
|
|
|
let expandedDurationTimestamp: HTMLSpanElement
|
|
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
let seeking: boolean = false
|
|
|
|
|
$: if (!seeking && currentTimeTimestamp) currentTimeTimestamp.innerText = formatTime(currentTime)
|
|
|
|
|
$: if (!seeking && progressBar) progressBar.$set({ value: currentTime })
|
|
|
|
|
$: if (!seeking && durationTimestamp) durationTimestamp.innerText = formatTime(duration)
|
2024-04-22 14:18:42 -04:00
|
|
|
$: if (!seeking && expandedCurrentTimeTimestamp) expandedCurrentTimeTimestamp.innerText = formatTime(currentTime)
|
|
|
|
|
$: if (!seeking && expandedProgressBar) expandedProgressBar.$set({ value: currentTime })
|
|
|
|
|
$: if (!seeking && expandedDurationTimestamp) expandedDurationTimestamp.innerText = formatTime(duration)
|
2024-05-28 00:46:34 -04:00
|
|
|
|
|
|
|
|
let slidingText: HTMLElement
|
|
|
|
|
let slidingTextWidth: number, slidingTextWrapperWidth: number
|
|
|
|
|
let scrollDirection: 1 | -1 = 1
|
|
|
|
|
$: scrollDistance = slidingTextWidth - slidingTextWrapperWidth
|
|
|
|
|
$: if (slidingText && scrollDistance > 0) slidingText.style.animationDuration = `${scrollDistance / 50}s`
|
|
|
|
|
|
|
|
|
|
let audioElement: HTMLAudioElement
|
2024-04-13 00:45:35 -04:00
|
|
|
</script>
|
2024-04-05 02:00:17 -04:00
|
|
|
|
2024-04-17 14:23:54 -04:00
|
|
|
{#if currentlyPlaying}
|
2024-04-22 14:18:42 -04:00
|
|
|
<div transition:slide class="{expanded ? 'h-full w-full' : 'm-4 h-20 w-[calc(100%_-_32px)] rounded-xl'} absolute bottom-0 z-40 overflow-clip bg-neutral-925 transition-all duration-500">
|
|
|
|
|
{#if !expanded}
|
|
|
|
|
<main in:fade={{ duration: 75, delay: 500 }} out:fade={{ duration: 75 }} class="relative grid h-20 w-full grid-cols-[minmax(auto,_20rem)_auto_minmax(auto,_20rem)] gap-4">
|
|
|
|
|
<section class="flex gap-3">
|
|
|
|
|
<div class="relative h-full w-20 min-w-20">
|
|
|
|
|
{#key currentlyPlaying}
|
2024-05-09 15:25:25 -04:00
|
|
|
<div transition:fade={{ duration: 500 }} class="absolute h-full w-full bg-cover bg-center bg-no-repeat" style="background-image: url(/api/remoteImage?url={currentlyPlaying.thumbnailUrl});" />
|
2024-04-22 14:18:42 -04:00
|
|
|
{/key}
|
|
|
|
|
</div>
|
|
|
|
|
<section class="flex flex-col justify-center gap-1">
|
|
|
|
|
<div class="line-clamp-2 text-sm">{currentlyPlaying.name}</div>
|
2024-05-28 00:46:34 -04:00
|
|
|
<div class="text-xs">{currentlyPlaying.artists?.map((artist) => artist.name).join(', ') ?? currentlyPlaying.uploader?.name}</div>
|
2024-04-22 14:18:42 -04:00
|
|
|
</section>
|
|
|
|
|
</section>
|
|
|
|
|
<section class="flex min-w-max flex-col items-center justify-center gap-1">
|
|
|
|
|
<div class="flex items-center gap-3 text-lg">
|
|
|
|
|
<button on:click={() => (shuffle = !shuffle)} class="aspect-square h-8">
|
|
|
|
|
<i class="fa-solid fa-shuffle" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="aspect-square h-8" on:click={() => $queue.previous()}>
|
|
|
|
|
<i class="fa-solid fa-backward-step" />
|
|
|
|
|
</button>
|
2024-05-28 00:46:34 -04:00
|
|
|
<button on:click={() => (paused = !paused)} class="relative grid aspect-square h-8 place-items-center rounded-full bg-white text-black">
|
|
|
|
|
{#if waiting}
|
|
|
|
|
<Loader size={1} />
|
|
|
|
|
{:else}
|
|
|
|
|
<i class="fa-solid {paused ? 'fa-play' : 'fa-pause'}" />
|
|
|
|
|
{/if}
|
2024-04-22 14:18:42 -04:00
|
|
|
</button>
|
|
|
|
|
<button class="aspect-square h-8" on:click={() => $queue.next()}>
|
|
|
|
|
<i class="fa-solid fa-forward-step" />
|
|
|
|
|
</button>
|
|
|
|
|
<button on:click={() => (repeat = !repeat)} class="aspect-square h-8">
|
|
|
|
|
<i class="fa-solid fa-repeat" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center justify-items-center gap-2">
|
|
|
|
|
<span bind:this={currentTimeTimestamp} class="w-16 text-right" />
|
|
|
|
|
<div class="w-72">
|
|
|
|
|
<Slider
|
|
|
|
|
bind:this={progressBar}
|
|
|
|
|
max={duration}
|
|
|
|
|
on:seeking={(event) => {
|
|
|
|
|
currentTimeTimestamp.innerText = formatTime(event.detail.value)
|
|
|
|
|
seeking = true
|
|
|
|
|
}}
|
|
|
|
|
on:seeked={(event) => {
|
|
|
|
|
currentTime = event.detail.value
|
|
|
|
|
seeking = false
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<span bind:this={durationTimestamp} class="w-16 text-left" />
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
<section class="flex items-center justify-end gap-2 pr-2 text-lg">
|
|
|
|
|
<div id="volume-slider" class="flex h-10 flex-row-reverse items-center gap-2">
|
|
|
|
|
<button on:click={() => (muted = !muted)} class="aspect-square h-8">
|
2024-05-28 00:46:34 -04:00
|
|
|
<i class="fa-solid {volume > maxVolume / 2 ? 'fa-volume-high' : volume > 0 ? 'fa-volume-low' : 'fa-volume-xmark'} w-full text-center" />
|
2024-04-22 14:18:42 -04:00
|
|
|
</button>
|
|
|
|
|
<div id="slider-wrapper" class="w-24 transition-all duration-500">
|
2024-05-28 00:46:34 -04:00
|
|
|
<Slider bind:value={volume} max={maxVolume} />
|
2024-04-22 14:18:42 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="aspect-square h-8" on:click={() => (expanded = true)}>
|
|
|
|
|
<i class="fa-solid fa-expand" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="aspect-square h-8" on:click={() => $queue.clear()}>
|
|
|
|
|
<i class="fa-solid fa-xmark" />
|
|
|
|
|
</button>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
{:else}
|
2024-05-28 00:46:34 -04:00
|
|
|
<main in:fade={{ delay: 500 }} out:fade={{ duration: 75 }} class="expanded-player relative h-full" style="--currentlyPlayingImage: url(/api/remoteImage?url={currentlyPlaying.thumbnailUrl});">
|
|
|
|
|
<img class="absolute -z-10 h-full w-full object-cover object-center blur-xl brightness-[25%]" src="/api/remoteImage?url={currentlyPlaying.thumbnailUrl}" alt="" />
|
2024-04-22 14:18:42 -04:00
|
|
|
<section class="song-queue-wrapper h-full px-24 py-20">
|
|
|
|
|
<section class="relative">
|
|
|
|
|
{#key currentlyPlaying}
|
2024-05-09 15:25:25 -04:00
|
|
|
<img transition:fade={{ duration: 300 }} class="absolute h-full max-w-full object-contain py-8" src="/api/remoteImage?url={currentlyPlaying.thumbnailUrl}" alt="" />
|
2024-04-22 14:18:42 -04:00
|
|
|
{/key}
|
|
|
|
|
</section>
|
2024-05-28 00:46:34 -04:00
|
|
|
<section class="no-scrollbar flex max-h-full flex-col gap-3 overflow-y-scroll">
|
|
|
|
|
<strong class="ml-2 text-2xl">UP NEXT</strong>
|
|
|
|
|
{#each $queue.list as item}
|
2024-04-22 14:18:42 -04:00
|
|
|
{@const isCurrent = item === currentlyPlaying}
|
|
|
|
|
<button
|
|
|
|
|
on:click={() => {
|
|
|
|
|
if (!isCurrent) $queue.current = item
|
|
|
|
|
}}
|
2024-05-28 00:46:34 -04:00
|
|
|
class="queue-item h-20 w-full shrink-0 items-center gap-3 overflow-clip rounded-lg bg-neutral-900 {isCurrent
|
|
|
|
|
? 'pointer-events-none border-[1px] border-neutral-300'
|
|
|
|
|
: 'hover:bg-neutral-800'}"
|
2024-04-22 14:18:42 -04:00
|
|
|
>
|
2024-05-28 00:46:34 -04:00
|
|
|
<div class="h-20 w-20 bg-cover bg-center" style="background-image: url('/api/remoteImage?url={item.thumbnailUrl}');" />
|
2024-04-22 14:18:42 -04:00
|
|
|
<div class="justify-items-left text-left">
|
2024-05-28 00:46:34 -04:00
|
|
|
<div class="line-clamp-1">{item.name}</div>
|
|
|
|
|
<div class="mt-[.15rem] line-clamp-1 text-neutral-400">{item.artists?.map((artist) => artist.name).join(', ') ?? item.uploader?.name}</div>
|
2024-04-22 14:18:42 -04:00
|
|
|
</div>
|
2024-05-28 00:46:34 -04:00
|
|
|
<span class="mr-4 text-right">{formatTime(item.duration)}</span>
|
2024-04-22 14:18:42 -04:00
|
|
|
</button>
|
|
|
|
|
{/each}
|
|
|
|
|
</section>
|
|
|
|
|
</section>
|
|
|
|
|
<section class="px-8">
|
|
|
|
|
<div class="progress-bar-expanded mb-8">
|
|
|
|
|
<span bind:this={expandedCurrentTimeTimestamp} class="text-right" />
|
|
|
|
|
<Slider
|
|
|
|
|
bind:this={expandedProgressBar}
|
|
|
|
|
max={duration}
|
|
|
|
|
on:seeking={(event) => {
|
|
|
|
|
expandedCurrentTimeTimestamp.innerText = formatTime(event.detail.value)
|
|
|
|
|
seeking = true
|
|
|
|
|
}}
|
|
|
|
|
on:seeked={(event) => {
|
|
|
|
|
currentTime = event.detail.value
|
|
|
|
|
seeking = false
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<span bind:this={expandedDurationTimestamp} class="text-left" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="expanded-controls">
|
2024-05-28 00:46:34 -04:00
|
|
|
<div class="flex flex-col gap-2 overflow-hidden">
|
|
|
|
|
<div bind:clientWidth={slidingTextWrapperWidth} class="relative h-9 w-full">
|
|
|
|
|
<strong
|
|
|
|
|
bind:this={slidingText}
|
|
|
|
|
bind:clientWidth={slidingTextWidth}
|
|
|
|
|
on:animationend={() => (scrollDirection *= -1)}
|
|
|
|
|
class="{scrollDistance > 0 ? (scrollDirection > 0 ? 'scrollLeft' : 'scrollRight') : ''} scrollingText absolute whitespace-nowrap text-3xl">{currentlyPlaying.name}</strong
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="line-clamp-1 flex flex-nowrap" style="font-size: 0;">
|
|
|
|
|
{#if 'artists' in currentlyPlaying && currentlyPlaying.artists && currentlyPlaying.artists.length > 0}
|
|
|
|
|
{#each currentlyPlaying.artists as artist, index}
|
|
|
|
|
<a
|
|
|
|
|
on:click={() => (expanded = false)}
|
|
|
|
|
class="line-clamp-1 flex-shrink-0 text-lg hover:underline focus:underline"
|
|
|
|
|
href="/details/artist?id={artist.id}&connection={currentlyPlaying.connection.id}">{artist.name}</a
|
|
|
|
|
>
|
|
|
|
|
{#if index < currentlyPlaying.artists.length - 1}
|
|
|
|
|
<span class="mr-1 text-lg">,</span>
|
|
|
|
|
{/if}
|
|
|
|
|
{/each}
|
|
|
|
|
{:else if 'uploader' in currentlyPlaying && currentlyPlaying.uploader}
|
|
|
|
|
<a
|
|
|
|
|
on:click={() => (expanded = false)}
|
|
|
|
|
class="line-clamp-1 flex-shrink-0 text-lg hover:underline focus:underline"
|
|
|
|
|
href="/details/user?id={currentlyPlaying.uploader.id}&connection={currentlyPlaying.connection.id}">{currentlyPlaying.uploader.name}</a
|
|
|
|
|
>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if currentlyPlaying.album}
|
|
|
|
|
<span class="mx-1.5 text-lg">-</span>
|
|
|
|
|
<a
|
|
|
|
|
on:click={() => (expanded = false)}
|
|
|
|
|
class="line-clamp-1 flex-shrink-0 text-lg hover:underline focus:underline"
|
|
|
|
|
href="/details/album?id={currentlyPlaying.album.id}&connection={currentlyPlaying.connection.id}">{currentlyPlaying.album.name}</a
|
|
|
|
|
>
|
|
|
|
|
{/if}
|
2024-04-22 14:18:42 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex w-full items-center justify-center gap-2 text-2xl">
|
|
|
|
|
<button on:click={() => (shuffle = !shuffle)} class="aspect-square h-16">
|
|
|
|
|
<i class="fa-solid fa-shuffle" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="aspect-square h-16" on:click={() => $queue.previous()}>
|
|
|
|
|
<i class="fa-solid fa-backward-step" />
|
|
|
|
|
</button>
|
2024-05-28 00:46:34 -04:00
|
|
|
<button on:click={() => (paused = !paused)} class="relative grid aspect-square h-16 place-items-center rounded-full bg-white text-black">
|
|
|
|
|
{#if waiting}
|
|
|
|
|
<Loader size={2.5} />
|
|
|
|
|
{:else}
|
|
|
|
|
<i class="fa-solid {paused ? 'fa-play' : 'fa-pause'}" />
|
|
|
|
|
{/if}
|
2024-04-22 14:18:42 -04:00
|
|
|
</button>
|
|
|
|
|
<button class="aspect-square h-16" on:click={() => $queue.next()}>
|
|
|
|
|
<i class="fa-solid fa-forward-step" />
|
|
|
|
|
</button>
|
|
|
|
|
<button on:click={() => (repeat = !repeat)} class="aspect-square h-16">
|
|
|
|
|
<i class="fa-solid fa-repeat" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<section class="flex items-center justify-end gap-2 text-xl">
|
|
|
|
|
<div id="volume-slider" class="flex h-10 flex-row-reverse items-center gap-2">
|
|
|
|
|
<button on:click={() => (muted = !muted)} class="aspect-square h-8">
|
2024-05-28 00:46:34 -04:00
|
|
|
<i class="fa-solid {volume > maxVolume / 2 ? 'fa-volume-high' : volume > 0 ? 'fa-volume-low' : 'fa-volume-xmark'} w-full text-center" />
|
2024-04-22 14:18:42 -04:00
|
|
|
</button>
|
|
|
|
|
<div id="slider-wrapper" class="w-24 transition-all duration-500">
|
2024-05-28 00:46:34 -04:00
|
|
|
<Slider bind:value={volume} max={maxVolume} />
|
2024-04-22 14:18:42 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="aspect-square h-8" on:click={() => (expanded = false)}>
|
|
|
|
|
<i class="fa-solid fa-compress" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="aspect-square h-8" on:click={() => $queue.clear()}>
|
|
|
|
|
<i class="fa-solid fa-xmark" />
|
|
|
|
|
</button>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
{/if}
|
2024-05-28 00:46:34 -04:00
|
|
|
<audio
|
|
|
|
|
bind:this={audioElement}
|
|
|
|
|
autoplay
|
|
|
|
|
bind:paused
|
|
|
|
|
bind:volume
|
|
|
|
|
bind:currentTime
|
|
|
|
|
bind:duration
|
|
|
|
|
on:canplay={() => (waiting = false)}
|
|
|
|
|
on:loadstart={() => (waiting = true)}
|
|
|
|
|
on:waiting={() => (waiting = true)}
|
|
|
|
|
on:ended={() => $queue.next()}
|
|
|
|
|
on:error={() => setTimeout(() => audioElement.load(), 5000)}
|
|
|
|
|
src="/api/audio?connection={currentlyPlaying.connection.id}&id={currentlyPlaying.id}"
|
|
|
|
|
/>
|
2024-04-22 14:18:42 -04:00
|
|
|
</div>
|
2024-04-13 00:45:35 -04:00
|
|
|
{/if}
|
2024-04-09 00:10:23 -04:00
|
|
|
|
2024-04-13 00:45:35 -04:00
|
|
|
<style>
|
2024-04-22 14:18:42 -04:00
|
|
|
.expanded-player {
|
|
|
|
|
display: grid;
|
2024-05-28 00:46:34 -04:00
|
|
|
grid-template-rows: calc(100% - 12rem) 12rem;
|
2024-04-22 14:18:42 -04:00
|
|
|
}
|
|
|
|
|
.song-queue-wrapper {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 3fr 2fr;
|
|
|
|
|
gap: 4rem;
|
|
|
|
|
}
|
|
|
|
|
.queue-item {
|
|
|
|
|
display: grid;
|
2024-05-28 00:46:34 -04:00
|
|
|
grid-template-columns: 5rem auto min-content;
|
2024-04-22 14:18:42 -04:00
|
|
|
}
|
|
|
|
|
.progress-bar-expanded {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: min-content auto min-content;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 1rem;
|
2024-04-13 00:45:35 -04:00
|
|
|
}
|
2024-04-22 14:18:42 -04:00
|
|
|
.expanded-controls {
|
|
|
|
|
display: grid;
|
2024-05-28 00:46:34 -04:00
|
|
|
gap: 1rem;
|
|
|
|
|
grid-template-columns: 1fr min-content 1fr !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scrollingText {
|
|
|
|
|
animation-timing-function: linear;
|
|
|
|
|
animation-fill-mode: both;
|
|
|
|
|
animation-delay: 10s;
|
|
|
|
|
}
|
|
|
|
|
.scrollingText:hover {
|
|
|
|
|
animation-play-state: paused;
|
|
|
|
|
}
|
|
|
|
|
.scrollLeft {
|
|
|
|
|
animation-name: scrollLeft;
|
|
|
|
|
}
|
|
|
|
|
.scrollRight {
|
|
|
|
|
animation-name: scrollRight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes scrollLeft {
|
|
|
|
|
0% {
|
|
|
|
|
left: 0%;
|
|
|
|
|
transform: translateX(0%);
|
|
|
|
|
}
|
|
|
|
|
100% {
|
|
|
|
|
left: 100%;
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes scrollRight {
|
|
|
|
|
0% {
|
|
|
|
|
left: 100%;
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
}
|
|
|
|
|
100% {
|
|
|
|
|
left: 0%;
|
|
|
|
|
transform: translateX(0%);
|
|
|
|
|
}
|
2024-04-05 02:00:17 -04:00
|
|
|
}
|
2024-04-13 00:45:35 -04:00
|
|
|
</style>
|