diff --git a/src/lib/HeardleGame.svelte b/src/lib/HeardleGame.svelte index 79b2b5b..b6a6b73 100644 --- a/src/lib/HeardleGame.svelte +++ b/src/lib/HeardleGame.svelte @@ -158,6 +158,7 @@ let iframeElement: HTMLIFrameElement; let widget: any; let widgetReady = false; + let loading = true; // ← disable play until preload let artworkUrl = ''; let isPlaying = false; let currentPosition = 0; @@ -190,7 +191,7 @@ timeLeft = `${h}:${m}:${s}`; } - // ─── CLAMPED FILL PERCENT & NEXT SEGMENT ───────────────────────────────────── + // ─── FILL % & NEXT SEGMENT ─────────────────────────────────────────────────── let fillPercent = 0; $: { const raw = gameOver @@ -224,22 +225,26 @@ } onMount(() => { - // dark‑mode listener window .matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', (e) => (darkMode = e.matches)); - // countdown updateTime(); countdownInterval = setInterval(updateTime, 1000); - // SoundCloud widget + widget = SC.Widget(iframeElement); widget.bind(SC.Widget.Events.READY, () => { - widgetReady = true; widget.getDuration((d: number) => (fullDuration = d)); widget.getCurrentSound((sound: any) => { artworkUrl = sound.artwork_url || ''; }); + // preload silently: + widget.play(); + widget.pause(); + widget.seekTo(0); + loading = false; + widgetReady = true; }); + widget.bind(SC.Widget.Events.PLAY, () => { startPolling(); if (!gameOver) { @@ -275,21 +280,17 @@ }); function playSegment() { - if (!widgetReady) return; + if (!widgetReady || loading) return; currentPosition = 0; widget.seekTo(0); widget.play(); } function togglePlayPause() { - if (!widgetReady) return; + if (!widgetReady || loading) return; isPlaying ? widget.pause() : playSegment(); } - function toggleDark() { - darkMode = !darkMode; - } - function skipIntro() { if (!widgetReady || gameOver) return; attemptInfos = [...attemptInfos, { status: 'skip' }]; @@ -301,8 +302,7 @@ } function submitGuess() { - if (!widgetReady || gameOver || !userInput) return; - // auto-select if none chosen + if (!widgetReady || gameOver) return; if (!selectedTrack && suggestions.length) { selectedTrack = suggestions.find((t) => t.title.toLowerCase() === userInput.toLowerCase()) || @@ -312,13 +312,17 @@ attemptCount++; const ans = currentTrack.title.toLowerCase(); - if (selectedTrack!.title.toLowerCase() === ans) { + if (selectedTrack.title.toLowerCase() === ans) { attemptInfos = [...attemptInfos, { status: 'correct', title: currentTrack.title }]; gameOver = true; - message = `✅ Correct! It was “${currentTrack.title}.” You got it in ${attemptCount} ${attemptCount === 1 ? 'try' : 'tries'}.`; + message = `✅ Correct! It was “${currentTrack.title}.” You ${ + attemptCount === maxAttempts + ? 'nailed it on the last try!' + : `got it in ${attemptCount} ${attemptCount === 1 ? 'try' : 'tries'}.` + }`; widget.pause(); } else { - attemptInfos = [...attemptInfos, { status: 'wrong', title: selectedTrack!.title }]; + attemptInfos = [...attemptInfos, { status: 'wrong', title: selectedTrack.title }]; userInput = ''; selectedTrack = null; if (attemptCount >= maxAttempts) revealAnswer(); @@ -345,7 +349,6 @@ inputEl.blur(); } - // update suggestions reactively, hide once a track is selected $: suggestions = userInput && !selectedTrack ? tracks.filter((t) => t.title.toLowerCase().includes(userInput.toLowerCase())).slice(0, 5) @@ -402,7 +405,7 @@

Prepared with SoundCloud, Svelte, Tailwind CSS, Inter font, svelte-hero-icons, and moment.js

- Game version: 2.0.0 + Game version: 2.1.0

New track in {timeLeft}

@@ -559,24 +573,21 @@ placeholder="Type song title…" bind:value={userInput} on:keydown={onInputKeydown} - on:focus={() => { - selectedTrack = null; - }} + on:focus={() => (selectedTrack = null)} class="w-full rounded border px-3 py-2" - style="border-color:{COLORS.primary};background:{darkMode - ? COLORS.text - : COLORS.background};color:{darkMode ? COLORS.background : COLORS.text}" + style=" + border-color:{COLORS.primary}; + background: {darkMode ? COLORS.text : COLORS.background}; + color: {darkMode ? COLORS.background : COLORS.text} + " /> {#if suggestions.length}