1<script lang="ts">
2 import {
3 List,
4 Pause,
5 Play,
6 Repeat,
7 Shuffle,
8 SkipBack,
9 SkipForward,
10 Volume2,
11 type Icon as LucideIcon,
12 } from "@lucide/svelte";
13 import { Slider } from "bits-ui";
14 import cn from "clsx";
15
16 let playing = $state(true);
17 let shuffle = $state(false);
18 let repeat = $state(false);
19
20 const MainIcon = $derived(playing ? Pause : Play);
21
22 const songLength = 256;
23 let playback = $state(0);
24</script>
25
26{#snippet plainButton(Icon: typeof LucideIcon, label: string)}
27 <button class="flex cursor-pointer" aria-label={label}>
28 <Icon />
29 </button>
30{/snippet}
31
32{#snippet clickable(content: string)}
33 <span class="cursor-pointer hover:underline">{content}</span>
34{/snippet}
35
36<!-- TODO: labelled by the artist & title -->
37<aside
38 class="fixed right-2 bottom-2 left-2 flex items-center gap-4 rounded-lg border border-slate-300 bg-white p-2 px-4 text-slate-500"
39>
40 <div class="flex items-center gap-2 text-slate-900">
41 {@render plainButton(SkipBack, "Previous song")}
42 <button
43 class="flex cursor-pointer items-center justify-center rounded-full bg-orange-500 p-2 text-white"
44 aria-label="Play"
45 onclick={() => (playing = !playing)}
46 >
47 <MainIcon />
48 </button>
49 {@render plainButton(SkipForward, "Next song")}
50 </div>
51
52 <div class="flex items-center gap-2">
53 <img
54 src="https://lh3.googleusercontent.com/0z6Kg2GFi8hFgZYxWm3c3UNul0gyaCQjuqmY-p1oeFC1n5EMOf1dxrownTzhzk-_cdtO_FLLktQcMecwGQ=w544-h544-l90-rj"
55 class="h-12 w-12 rounded object-cover object-center"
56 alt=""
57 />
58 <div class="flex flex-col">
59 <span class="text-sm font-semibold text-slate-900 opacity-70">
60 {@render clickable("Protostar")}, {@render clickable("Laminar")} & {@render clickable(
61 "imallryt",
62 )}
63 </span>
64 <span class="font-bolder text-sm font-semibold text-slate-900">
65 {@render clickable("Blood in the Water")}
66 <!-- <span class="opacity-50">| {@render clickable("Epic Album")}</span> -->
67 </span>
68 </div>
69 </div>
70
71 <div class="flex flex-1 px-30">
72 <Slider.Root
73 type="single"
74 bind:value={playback}
75 max={songLength}
76 class="relative flex flex-1 touch-none items-center select-none"
77 >
78 {#snippet children()}
79 <span
80 class="relative h-1 w-full cursor-pointer overflow-hidden rounded-full bg-slate-200"
81 >
82 <Slider.Range class="absolute h-full rounded-full bg-orange-500" />
83 </span>
84 <Slider.Thumb
85 index={0}
86 class="block size-4 cursor-pointer rounded-full border border-slate-900 bg-slate-50 focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-offset-2 "
87 />
88 {/snippet}
89 </Slider.Root>
90 </div>
91
92 <button
93 class={cn("flex", "cursor-pointer", { "text-orange-500": shuffle })}
94 onclick={() => (shuffle = !shuffle)}
95 >
96 <Shuffle />
97 </button>
98 <button
99 class={cn("flex", "cursor-pointer", { "text-orange-500": repeat })}
100 onclick={() => (repeat = !repeat)}
101 >
102 <Repeat />
103 </button>
104
105 <List class="cursor-pointer" />
106 <Volume2 class="cursor-pointer" />
107</aside>