From b3c519c310f0a5bd3e89907fd84e49c968d0c6f8 Mon Sep 17 00:00:00 2001 From: Josh Patra <30350506+SoPat712@users.noreply.github.com> Date: Sat, 19 Apr 2025 00:35:41 -0400 Subject: [PATCH] win screen, fixed progress bar, pinned play and progress to bottom, version bump --- src/lib/HeardleGame.svelte | 436 +++++++++++++++++++------------------ 1 file changed, 229 insertions(+), 207 deletions(-) diff --git a/src/lib/HeardleGame.svelte b/src/lib/HeardleGame.svelte index 2d4db08..b10fc2d 100644 --- a/src/lib/HeardleGame.svelte +++ b/src/lib/HeardleGame.svelte @@ -129,7 +129,7 @@ url: `https://soundcloud.com/${SC_USER}/${slug}` })); - // ─── DAILY‐SEEDED RANDOM TRACK ─────────────────────────────────────────────── + // ─── SEED & PICK TRACK ──────────────────────────────────────────────────────── let seed = parseInt(moment().format('YYYYMMDD'), 10); function seededRandom() { seed = (seed * 9301 + 49297) % 233280; @@ -139,16 +139,16 @@ // ─── SEGMENTS ─────────────────────────────────────────────────────────────── const SEGMENT_INCREMENTS = [2, 1, 2, 3, 4, 5]; - const segmentDurations = SEGMENT_INCREMENTS.reduce((acc, inc) => { - acc.push((acc.at(-1) ?? 0) + inc * 1000); - return acc; + const segmentDurations = SEGMENT_INCREMENTS.reduce((a, inc) => { + a.push((a.at(-1) ?? 0) + inc * 1000); + return a; }, []); const TOTAL_MS = segmentDurations.at(-1)!; const TOTAL_SECONDS = TOTAL_MS / 1000; const maxAttempts = SEGMENT_INCREMENTS.length; $: boundaries = segmentDurations.map((ms) => ms / 1000).slice(0, -1); - // ─── GAME STATE & PLAYER TIMERS ────────────────────────────────────────────── + // ─── STATE & TIMERS ───────────────────────────────────────────────────────── type Info = { status: 'skip' | 'wrong' | 'correct'; title?: string }; let attemptInfos: Info[] = []; let attemptCount = 0; @@ -158,10 +158,12 @@ let iframeElement: HTMLIFrameElement; let widget: any; let widgetReady = false; + let artworkUrl = ''; let isPlaying = false; let currentPosition = 0; let snippetTimeout: ReturnType; let progressInterval: ReturnType; + let fullDuration = 0; let showHowTo = false; let showInfo = false; @@ -174,10 +176,27 @@ let selectedTrack: Track | null = null; let inputEl: HTMLInputElement; + // ─── COUNTDOWN ─────────────────────────────────────────────────────────────── + let timeLeft = ''; + let countdownInterval: ReturnType; + function updateTime() { + const now = new Date(); + const midnight = new Date(now); + midnight.setHours(24, 0, 0, 0); + const diff = midnight.getTime() - now.getTime(); + const h = String(Math.floor(diff / 3600000)).padStart(2, '0'); + const m = String(Math.floor((diff % 3600000) / 60000)).padStart(2, '0'); + const s = String(Math.floor((diff % 60000) / 1000)).padStart(2, '0'); + timeLeft = `${h}:${m}:${s}`; + } + $: suggestions = userInput ? tracks.filter((t) => t.title.toLowerCase().includes(userInput.toLowerCase())).slice(0, 5) : []; - $: fillPercent = (currentPosition / TOTAL_MS) * 100; + // switch fill% between snippet & full track + $: fillPercent = gameOver + ? (currentPosition / fullDuration) * 100 + : (currentPosition / TOTAL_MS) * 100; $: nextIncrementSec = attemptCount < SEGMENT_INCREMENTS.length - 1 ? SEGMENT_INCREMENTS[attemptCount + 1] : 0; @@ -191,7 +210,7 @@ clearInterval(progressInterval); progressInterval = setInterval(() => { widget.getPosition((pos: number) => { - const limit = segmentDurations[attemptCount]; + const limit = gameOver ? fullDuration : segmentDurations[attemptCount]; currentPosition = Math.min(pos, limit); }); }, 100); @@ -204,20 +223,34 @@ } onMount(() => { - const mq = window.matchMedia('(prefers-color-scheme: dark)'); - mq.addEventListener('change', (e) => (darkMode = e.matches)); + // 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.bind(SC.Widget.Events.READY, () => { + widgetReady = true; + widget.getDuration((d: number) => (fullDuration = d)); + widget.getCurrentSound((sound: any) => { + artworkUrl = sound.artwork_url || ''; + }); + }); widget.bind(SC.Widget.Events.PLAY, () => { startPolling(); - snippetTimeout = setTimeout(() => widget.pause(), segmentDurations[attemptCount]); + if (!gameOver) { + snippetTimeout = setTimeout(() => widget.pause(), segmentDurations[attemptCount]); + } }); widget.bind(SC.Widget.Events.PAUSE, stopAllTimers); widget.bind(SC.Widget.Events.FINISH, stopAllTimers); widget.bind(SC.Widget.Events.PLAY_PROGRESS, (e: { currentPosition: number }) => { - const limit = segmentDurations[attemptCount]; + const limit = gameOver ? fullDuration : segmentDurations[attemptCount]; if (e.currentPosition >= limit) { currentPosition = limit; widget.pause(); @@ -229,18 +262,19 @@ onDestroy(() => { stopAllTimers(); + clearInterval(countdownInterval); widget?.unbind && Object.values(SC.Widget.Events).forEach((ev) => widget.unbind(ev)); }); function playSegment() { - if (!widgetReady || gameOver) return; + if (!widgetReady) return; currentPosition = 0; widget.seekTo(0); widget.play(); } function togglePlayPause() { - if (!widgetReady || gameOver) return; + if (!widgetReady) return; isPlaying ? widget.pause() : playSegment(); } @@ -305,31 +339,24 @@ {#if showHowTo}
-
+
-

- How to Play -

+

How to Play

  • 🎵 Play the snippet.
  • 🔊 Skips & wrongs unlock more.
  • 👍 Guess in as few tries as possible!
-
- -
+
{/if} @@ -337,98 +364,112 @@ {#if showInfo}
-
+
-

- {ARTIST_NAME} – Test your {ARTIST_NAME} knowledge! +

{ARTIST_NAME} – Test your knowledge!

+

All songs used are copyrighted and belong to {ARTIST_NAME}.

+
+

+ Prepared with SoundCloud, Svelte, Tailwind CSS, Inter font, svelte-hero-icons +
+
+ Game version: 1.2.0

-

- All songs used are copyrighted and belong to {ARTIST_NAME}. -

-
-

- Prepared with SoundCloud, Svelte, Tailwind, Inter font,
- svelte‑hero‑icons -

-

Game version: 1.0.0

-
- -
+

New track in {timeLeft}

+
{/if} - +
- +
- - +
+ + +

Heardle – {ARTIST_NAME}

+ +
+
-
- - + + {#if !gameOver} +
+ {#each attemptInfos as info} +
+ {#if info.status === 'skip'}▢ Skipped + {:else if info.status === 'wrong'}☒ {info.title} + {:else}✓ {info.title}{/if} +
+ {/each} + {#each Array(maxAttempts - attemptInfos.length) as _} +
+ {/each}
-
-
+ {/if} - - + {/if} - -
+ +
+
- {#each boundaries as b} -
- {/each} -
-
- {formatTime(currentPosition)} - {formatTime(TOTAL_MS)} -
-
- - -
- -
- - - {#if !gameOver} -
- - {#if suggestions.length} -
    - {#each suggestions as s} -
  • - -
  • - {/each} -
+ {#if !gameOver} + {#each boundaries as b} +
+ {/each} {/if}
-
+
+ {formatTime(currentPosition)} + {formatTime(gameOver ? fullDuration : TOTAL_MS)} +
+ + +
+
+ + + {#if !gameOver} +
+ + {#if suggestions.length} +
    + {#each suggestions as s} +
  • + +
  • + {/each} +
{/if} - - -
- {:else} -
- {message} -
- {/if} +
+
+ + +
+ {/if} +