dark mode addition

This commit is contained in:
Josh Patra
2025-04-18 22:27:17 -04:00
parent 80e0fe38cb
commit e904405852

View File

@@ -1,7 +1,15 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import moment from 'moment'; import moment from 'moment';
import { Icon, Play, Pause, InformationCircle, QuestionMarkCircle } from 'svelte-hero-icons'; import {
Icon,
Play,
Pause,
InformationCircle,
QuestionMarkCircle,
Sun,
Moon
} from 'svelte-hero-icons';
declare const SC: any; declare const SC: any;
// ─── CONFIG & THEME ─────────────────────────────────────────────────────────── // ─── CONFIG & THEME ───────────────────────────────────────────────────────────
@@ -155,6 +163,10 @@
let showHowTo = false; let showHowTo = false;
let showInfo = false; let showInfo = false;
let darkMode =
typeof window !== 'undefined'
? window.matchMedia('(prefers-color-scheme: dark)').matches
: false;
let userInput = ''; let userInput = '';
let suggestions: Track[] = []; let suggestions: Track[] = [];
let selectedTrack: Track | null = null; let selectedTrack: Track | null = null;
@@ -174,10 +186,12 @@
} }
onMount(() => { onMount(() => {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
mq.addEventListener('change', (e) => (darkMode = e.matches));
widget = SC.Widget(iframeElement); widget = SC.Widget(iframeElement);
widget.bind(SC.Widget.Events.READY, () => (widgetReady = true)); widget.bind(SC.Widget.Events.READY, () => (widgetReady = true));
// precise snippet cutoff in PLAY_PROGRESS:
widget.bind(SC.Widget.Events.PLAY_PROGRESS, (e: { currentPosition: number }) => { widget.bind(SC.Widget.Events.PLAY_PROGRESS, (e: { currentPosition: number }) => {
const limit = segmentDurations[attemptCount]; const limit = segmentDurations[attemptCount];
if (e.currentPosition >= limit) { if (e.currentPosition >= limit) {
@@ -194,13 +208,11 @@
}); });
onDestroy(() => { onDestroy(() => {
widget?.unbind && Object.values(SC.Widget.Events).forEach((ev: string) => widget.unbind(ev)); widget?.unbind && Object.values(SC.Widget.Events).forEach((ev) => widget.unbind(ev));
}); });
function playSegment() { function playSegment() {
if (!widgetReady || gameOver) return; if (!widgetReady || gameOver) return;
const limit = segmentDurations[attemptCount];
currentPosition = 0;
widget.seekTo(0); widget.seekTo(0);
widget.play(); widget.play();
} }
@@ -210,6 +222,10 @@
isPlaying ? widget.pause() : playSegment(); isPlaying ? widget.pause() : playSegment();
} }
function toggleDark() {
darkMode = !darkMode;
}
function skipIntro() { function skipIntro() {
if (!widgetReady || gameOver) return; if (!widgetReady || gameOver) return;
attemptInfos = [...attemptInfos, { status: 'skip' }]; attemptInfos = [...attemptInfos, { status: 'skip' }];
@@ -333,9 +349,9 @@
<div <div
class="fixed inset-0 flex flex-col overflow-hidden" class="fixed inset-0 flex flex-col overflow-hidden"
style=" style="
background: {COLORS.background}; background: {darkMode ? COLORS.text : COLORS.background};
color: {COLORS.text}; color: {darkMode ? COLORS.background : COLORS.text};
font-family: 'Inter', sans-serif; font-family:'Inter', sans-serif
" "
> >
<!-- header --> <!-- header -->
@@ -343,14 +359,21 @@
<button on:click={() => (showInfo = true)}> <button on:click={() => (showInfo = true)}>
<Icon src={InformationCircle} class="h-6 w-6" style="color: {COLORS.primary}" /> <Icon src={InformationCircle} class="h-6 w-6" style="color: {COLORS.primary}" />
</button> </button>
<h1 class="flex-grow text-center font-serif text-3xl font-bold">
<h1 class="flex-1 text-center font-serif text-lg font-bold whitespace-nowrap sm:text-3xl">
Heardle {ARTIST_NAME} Heardle {ARTIST_NAME}
</h1> </h1>
<button on:click={() => (showHowTo = true)}>
<Icon src={QuestionMarkCircle} class="h-6 w-6" style="color: {COLORS.accent}" /> <div class="flex items-center space-x-2">
</button> <button on:click={toggleDark}>
<Icon src={darkMode ? Sun : Moon} class="h-6 w-6" style="color: {COLORS.primary}" />
</button>
<button on:click={() => (showHowTo = true)}>
<Icon src={QuestionMarkCircle} class="h-6 w-6" style="color: {COLORS.accent}" />
</button>
</div>
</div> </div>
<hr class="mx-4 my-3" style="border-color: {COLORS.text}" /> <hr class="mx-4 my-3" style="border-color: {darkMode ? COLORS.background : COLORS.text}" />
<!-- attempts --> <!-- attempts -->
<div class="mb-6 flex-shrink-0 space-y-2 px-4"> <div class="mb-6 flex-shrink-0 space-y-2 px-4">
@@ -358,19 +381,19 @@
<div <div
class="flex h-12 items-center rounded border px-3 font-semibold" class="flex h-12 items-center rounded border px-3 font-semibold"
style=" style="
border-color: border-color:
{info.status === 'skip' {info.status === 'skip'
? COLORS.primary ? COLORS.primary
: info.status === 'wrong' : info.status === 'wrong'
? COLORS.accent ? COLORS.accent
: COLORS.secondary}; : COLORS.secondary};
color: color:
{info.status === 'skip' {info.status === 'skip'
? COLORS.primary ? COLORS.primary
: info.status === 'wrong' : info.status === 'wrong'
? COLORS.accent ? COLORS.accent
: COLORS.secondary}; : COLORS.secondary};
" "
> >
{#if info.status === 'skip'} Skipped {#if info.status === 'skip'} Skipped
{:else if info.status === 'wrong'}☒ {info.title} {:else if info.status === 'wrong'}☒ {info.title}
@@ -378,7 +401,10 @@
</div> </div>
{/each} {/each}
{#each Array(maxAttempts - attemptInfos.length) as _} {#each Array(maxAttempts - attemptInfos.length) as _}
<div class="h-12 rounded border" style="border-color: {COLORS.text}"></div> <div
class="h-12 rounded border"
style="border-color: {darkMode ? COLORS.background : COLORS.text}"
></div>
{/each} {/each}
</div> </div>
@@ -394,19 +420,26 @@
<div class="mb-4 flex-shrink-0 px-4"> <div class="mb-4 flex-shrink-0 px-4">
<div <div
class="relative w-full overflow-hidden rounded border" class="relative w-full overflow-hidden rounded border"
style="height: 1.25rem; border-color: {COLORS.text}" style="
height: 1.25rem;
border-color: {darkMode ? COLORS.background : COLORS.text}
"
> >
<div <div
class="absolute top-0 left-0 h-full" class="absolute top-0 left-0 h-full"
style="width: {fillPercent}%; background: {COLORS.primary}; transition: width 0.1s;" style="
width: {fillPercent}%;
background: {COLORS.primary};
transition: width 0.1s;
"
></div> ></div>
{#each boundaries as b} {#each boundaries as b}
<div <div
class="absolute top-0 bottom-0" class="absolute top-0 bottom-0"
style=" style="
left: {(b / TOTAL_SECONDS) * 100}%; left: {(b / TOTAL_SECONDS) * 100}%;
border-left: 1px solid {COLORS.text}; border-left: 1px solid {darkMode ? COLORS.background : COLORS.text};
" "
></div> ></div>
{/each} {/each}
</div> </div>
@@ -438,25 +471,25 @@
on:keydown={onInputKeydown} on:keydown={onInputKeydown}
class="w-full rounded border px-3 py-2" class="w-full rounded border px-3 py-2"
style=" style="
border-color: {COLORS.primary}; border-color: {COLORS.primary};
background: {COLORS.background}; background: {darkMode ? COLORS.text : COLORS.background};
color: {COLORS.text}; color: {darkMode ? COLORS.background : COLORS.text};
" "
/> />
{#if suggestions.length} {#if suggestions.length}
<ul <ul
class="mt-1 max-h-36 overflow-y-auto rounded border" class="mt-1 max-h-36 overflow-y-auto rounded border"
style=" style="
border-color: {COLORS.text}; border-color: {darkMode ? COLORS.background : COLORS.text};
background: {COLORS.background}; background: {darkMode ? COLORS.text : COLORS.background};
" "
> >
{#each suggestions as s} {#each suggestions as s}
<li> <li>
<button <button
type="button" type="button"
class="w-full px-3 py-2 text-left" class="w-full px-3 py-2 text-left"
style="color: {COLORS.text}" style="color: {darkMode ? COLORS.background : COLORS.text}"
on:click={() => selectSuggestion(s)} on:click={() => selectSuggestion(s)}
> >
{s.title}  <span style="opacity: 0.7">{s.artist}</span> {s.title}  <span style="opacity: 0.7">{s.artist}</span>
@@ -488,7 +521,10 @@
</button> </button>
</div> </div>
{:else} {:else}
<div class="mt-6 text-center text-lg" style="color: {COLORS.text}"> <div
class="mt-6 text-center text-lg"
style="color: {darkMode ? COLORS.background : COLORS.text}"
>
{message} {message}
</div> </div>
{/if} {/if}