feat: fine tuned bg analyzer and non glassmorphic volume slider

This commit is contained in:
2025-04-18 23:50:32 +02:00
parent 9ffe38fc8e
commit 8e1c5b1950
7 changed files with 145 additions and 11 deletions

View File

@@ -11,10 +11,10 @@
canvas.width = 32;
canvas.height = 32;
const sourceY = Math.floor(video.videoHeight * 0.67); // take the top two-thirds
const sourceHeight = Math.floor(video.videoHeight * 0.33); // take the bottom third
const sourceY = Math.floor(video.videoHeight * 0.8);
// Sample only 20% of the video height
const sourceHeight = Math.floor(video.videoHeight * 0.2);
// draw the bottom third of the video to the canvas
// prettier-ignore
ctx.drawImage(
video,
@@ -39,7 +39,7 @@
}
const avgBrightness = totalBrightness / pixelCount;
const isDark = avgBrightness < 0.45;
const isDark = avgBrightness < 0.3;
setMode(isDark ? 'dark' : 'light');

View File

@@ -3,6 +3,7 @@
import { state } from '@/state.svelte';
import Pause from '@lucide/svelte/icons/pause';
import Play from '@lucide/svelte/icons/play';
import Volume from './volume.svelte';
function togglePlay() {
state.togglePlay();
@@ -16,11 +17,14 @@
<p class="text-sm">{state.currentSong?.artists}</p>
</div>
<div class="flex-1"></div>
<Button size="icon" onclick={togglePlay} class="w-10 h-10 md:ml-4">
{#if state.isPlaying}
<Pause />
{:else}
<Play />
{/if}
</Button>
<div class="gap-4">
<Button size="icon" onclick={togglePlay} class="size-10 md:ml-4">
{#if state.isPlaying}
<Pause />
{:else}
<Play />
{/if}
</Button>
<Volume />
</div>
</div>

View File

@@ -0,0 +1,34 @@
<script lang="ts">
import { Slider } from "$lib/components/ui/slider";
import * as Popover from "$lib/components/ui/popover";
import { state as appState } from "@/state.svelte";
import VolumeZero from "@lucide/svelte/icons/volume";
import VolumeOne from "@lucide/svelte/icons/volume-1";
import VolumeTwo from "@lucide/svelte/icons/volume-2";
import VolumeX from "@lucide/svelte/icons/volume-x";
import { Button } from "../ui/button";
let value = $state(appState.volume);
$effect(() => {
appState.volume = value;
});
</script>
<Popover.Root>
<Popover.Trigger>
<Button size="icon" class="size-10">
{#if value === 0}
<VolumeX />
{:else if value > 0 && value <= 0.4}
<VolumeZero />
{:else if value > 0.4 && value <= 0.8}
<VolumeOne />
{:else}
<VolumeTwo />
{/if}
</Button>
</Popover.Trigger>
<Popover.Content class="w-2 h-32" side="top">
<Slider type="single" orientation="vertical" bind:value max={1} step={0.01} />
</Popover.Content>
</Popover.Root>

View File

@@ -0,0 +1,17 @@
import { Popover as PopoverPrimitive } from "bits-ui";
import Content from "./popover-content.svelte";
const Root = PopoverPrimitive.Root;
const Trigger = PopoverPrimitive.Trigger;
const Close = PopoverPrimitive.Close;
export {
Root,
Content,
Trigger,
Close,
//
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
Close as PopoverClose,
};

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import { Popover as PopoverPrimitive } from "bits-ui";
let {
ref = $bindable(null),
class: className,
align = "center",
sideOffset = 4,
portalProps,
...restProps
}: PopoverPrimitive.ContentProps & {
portalProps?: PopoverPrimitive.PortalProps;
} = $props();
</script>
<PopoverPrimitive.Portal {...portalProps}>
<PopoverPrimitive.Content
bind:ref
{align}
{sideOffset}
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none",
className
)}
{...restProps}
/>
</PopoverPrimitive.Portal>

View File

@@ -0,0 +1,7 @@
import Root from "./slider.svelte";
export {
Root,
//
Root as Slider,
};

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import { Slider as SliderPrimitive, type WithoutChildrenOrChild } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
value = $bindable(),
orientation = "horizontal",
class: className,
...restProps
}: WithoutChildrenOrChild<SliderPrimitive.RootProps> = $props();
</script>
<!--
Discriminated Unions + Destructing (required for bindable) do not
get along, so we shut typescript up by casting `value` to `never`.
-->
<SliderPrimitive.Root
bind:ref
bind:value={value as never}
{orientation}
class={cn(
"relative flex touch-none select-none items-center data-[orientation='vertical']:h-full data-[orientation='horizontal']:w-full data-[orientation='vertical']:w-auto data-[orientation='vertical']:flex-col",
className
)}
{...restProps}
>
{#snippet children({ thumbs })}
<span
data-orientation={orientation}
class="bg-primary/20 relative grow overflow-hidden rounded-full data-[orientation='horizontal']:h-1.5 data-[orientation='vertical']:h-full data-[orientation='horizontal']:w-full data-[orientation='vertical']:w-1.5"
>
<SliderPrimitive.Range
class="bg-primary absolute data-[orientation='horizontal']:h-full data-[orientation='vertical']:w-full"
/>
</span>
{#each thumbs as thumb (thumb)}
<SliderPrimitive.Thumb
index={thumb}
class="border-primary/50 bg-background focus-visible:ring-ring block size-4 rounded-full border shadow transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50"
/>
{/each}
{/snippet}
</SliderPrimitive.Root>