This commit is contained in:
Josh Patra
2025-04-20 14:20:37 -04:00
parent b619e6acb5
commit 11f92705d5

View File

@@ -158,7 +158,7 @@
let iframeElement: HTMLIFrameElement; let iframeElement: HTMLIFrameElement;
let widget: any; let widget: any;
let widgetReady = false; let widgetReady = false;
let loading = true; // ← stay disabled until probe finishes let loading = true; // disable play until we warm up the widget
let artworkUrl = ''; let artworkUrl = '';
let isPlaying = false; let isPlaying = false;
let currentPosition = 0; let currentPosition = 0;
@@ -224,33 +224,43 @@
clearTimeout(snippetTimeout); clearTimeout(snippetTimeout);
} }
onMount(() => { onMount(async () => {
// dynamically load SC API if needed
if (typeof window.SC === 'undefined') {
await new Promise<void>((resolve, reject) => {
const tag = document.createElement('script');
tag.src = 'https://w.soundcloud.com/player/api.js';
tag.async = true;
tag.onload = () => resolve();
tag.onerror = () => reject(new Error('Failed to load SoundCloud API'));
document.head.appendChild(tag);
});
}
// darkmode listener // darkmode listener
window window
.matchMedia('(prefers-color-scheme: dark)') .matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => (darkMode = e.matches)); .addEventListener('change', (e) => (darkMode = e.matches));
// countdown // countdown
updateTime(); updateTime();
countdownInterval = setInterval(updateTime, 1000); countdownInterval = setInterval(updateTime, 1000);
// SoundCloud widget // SoundCloud widget
widget = SC.Widget(iframeElement); widget = SC.Widget(iframeElement);
// bind READY immediately
widget.bind(SC.Widget.Events.READY, () => { widget.bind(SC.Widget.Events.READY, () => {
// grab duration & artwork
widget.getDuration((d: number) => (fullDuration = d)); widget.getDuration((d: number) => (fullDuration = d));
widget.getCurrentSound((sound: any) => { widget.getCurrentSound((sound: any) => {
artworkUrl = sound.artwork_url || ''; artworkUrl = sound.artwork_url || '';
}); });
// warmup probe after 1 s // warm up on Netlify
setTimeout(() => { setTimeout(() => {
widget.play(); widget.play();
widget.pause(); widget.pause();
widget.seekTo(0); widget.seekTo(0);
loading = false; loading = false;
widgetReady = true; widgetReady = true;
}, 1000); }, 2000);
}); });
widget.bind(SC.Widget.Events.PLAY, () => { widget.bind(SC.Widget.Events.PLAY, () => {
@@ -299,6 +309,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' }];
@@ -317,6 +331,7 @@
suggestions[0]; suggestions[0];
} }
if (!selectedTrack) return; if (!selectedTrack) return;
attemptCount++; attemptCount++;
const ans = currentTrack.title.toLowerCase(); const ans = currentTrack.title.toLowerCase();
if (selectedTrack.title.toLowerCase() === ans) { if (selectedTrack.title.toLowerCase() === ans) {
@@ -358,22 +373,15 @@
: []; : [];
</script> </script>
<!-- src/lib/HeardleGame.svelte -->
<svelte:head>
<!-- always load the SC Widget API, even on Netlify -->
<script src="https://w.soundcloud.com/player/api.js" async defer></script>
</svelte:head>
<!-- How to Play Modal --> <!-- How to Play Modal -->
{#if showHowTo} {#if showHowTo}
<div class="fixed inset-0 z-50 flex items-center justify-center"> <div class="fixed inset-0 z-50 flex items-center justify-center">
<div class="absolute inset-0 bg-black/40"></div> <div class="absolute inset-0 bg-black/40"></div>
<div <div
class="relative w-4/5 max-w-md rounded-lg p-8 text-center" class="relative w-4/5 max-w-md rounded-lg p-8 text-center"
style=" style="background:{darkMode ? COLORS.text : COLORS.background};color:{darkMode
background: {darkMode ? COLORS.text : COLORS.background}; ? COLORS.background
color: {darkMode ? COLORS.background : COLORS.text} : COLORS.text}"
"
> >
<h2 class="mb-4 text-2xl font-bold uppercase" style="color:{COLORS.primary}">How to Play</h2> <h2 class="mb-4 text-2xl font-bold uppercase" style="color:{COLORS.primary}">How to Play</h2>
<ul class="space-y-4 text-base"> <ul class="space-y-4 text-base">
@@ -383,10 +391,7 @@
</ul> </ul>
<button <button
class="mt-6 rounded px-6 py-2 font-semibold" class="mt-6 rounded px-6 py-2 font-semibold"
style=" style="background:{COLORS.primary};color:{darkMode ? COLORS.text : COLORS.background}"
background: {COLORS.primary};
color: {darkMode ? COLORS.text : COLORS.background}
"
on:click={() => (showHowTo = false)} on:click={() => (showHowTo = false)}
> >
Close Close
@@ -401,28 +406,23 @@
<div class="absolute inset-0 bg-black/40"></div> <div class="absolute inset-0 bg-black/40"></div>
<div <div
class="relative w-4/5 max-w-md space-y-4 rounded-lg p-8" class="relative w-4/5 max-w-md space-y-4 rounded-lg p-8"
style=" style="background:{darkMode ? COLORS.text : COLORS.background};color:{darkMode
background: {darkMode ? COLORS.text : COLORS.background}; ? COLORS.background
color: {darkMode ? COLORS.background : COLORS.text} : COLORS.text}"
"
> >
<p class="font-semibold">{ARTIST_NAME} Test your knowledge!</p> <p class="font-semibold">{ARTIST_NAME} Test your knowledge!</p>
<p>All songs used are copyrighted and belong to {ARTIST_NAME}.</p> <p>All songs used are copyrighted and belong to {ARTIST_NAME}.</p>
<p>I do not, and never will, collect any of your personal data.</p> <p>I do not, and never will, collect any of your personal data.</p>
<!-- (mostly because I don't want to pay a company to gather data I don't need or care about) -->
<hr class="my-4" style="border-color:{darkMode ? COLORS.background : COLORS.text}" /> <hr class="my-4" style="border-color:{darkMode ? COLORS.background : COLORS.text}" />
<p class="text-xs" style="color:{COLORS.accent}"> <p class="text-xs" style="color:{COLORS.accent}">
Prepared with SoundCloud, Svelte, Tailwind CSS, Inter font, svelte-hero-icons, and moment.js Prepared with SoundCloud, Svelte, Tailwind CSS, Inter font, svelte-hero-icons, and moment.js<br
<br /><br /> /><br />
Game version: 2.1.0 Game version: 2.3.1
</p> </p>
<p class="text-sm italic">New track in <strong>{timeLeft}</strong></p> <p class="text-sm italic">New track in <strong>{timeLeft}</strong></p>
<button <button
class="mt-4 rounded px-6 py-2 font-semibold" class="mt-4 rounded px-6 py-2 font-semibold"
style=" style="background:{COLORS.primary};color:{darkMode ? COLORS.text : COLORS.background}"
background: {COLORS.primary};
color: {darkMode ? COLORS.text : COLORS.background}
"
on:click={() => (showInfo = false)} on:click={() => (showInfo = false)}
> >
Close Close
@@ -434,10 +434,9 @@
<!-- Main UI --> <!-- Main UI -->
<div <div
class="fixed inset-0 flex flex-col overflow-hidden" class="fixed inset-0 flex flex-col overflow-hidden"
style=" style="background:{darkMode ? COLORS.text : COLORS.background};color:{darkMode
background: {darkMode ? COLORS.text : COLORS.background}; ? COLORS.background
color: {darkMode ? COLORS.background : COLORS.text} : COLORS.text}"
"
> >
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between px-4 pt-4"> <div class="flex items-center justify-between px-4 pt-4">
@@ -465,22 +464,17 @@
{#each attemptInfos as info} {#each attemptInfos as info}
<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:{info.status === 'skip'
border-color: {info.status === 'skip'
? COLORS.primary ? COLORS.primary
: info.status === 'wrong' : info.status === 'wrong'
? COLORS.accent ? COLORS.accent
: COLORS.secondary}; : COLORS.secondary};color:{info.status === 'skip'
color: {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}✓ {info.title}{/if}
{:else if info.status === 'wrong'}☒ {info.title}
{:else}✓ {info.title}{/if}
</div> </div>
{/each} {/each}
{#each Array(maxAttempts - attemptInfos.length) as _} {#each Array(maxAttempts - attemptInfos.length) as _}
@@ -499,9 +493,7 @@
href={currentTrack.url} href={currentTrack.url}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
class={`flex items-center overflow-hidden rounded-lg border-2 ${ class={`flex items-center overflow-hidden rounded-lg border-2 ${darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-100'}`}
darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-100'
}`}
style="border-color:{COLORS.primary}" style="border-color:{COLORS.primary}"
> >
{#if artworkUrl} {#if artworkUrl}
@@ -512,9 +504,7 @@
/> />
{/if} {/if}
<div class="px-4 py-2"> <div class="px-4 py-2">
<div class="font-semibold" style="color:{COLORS.primary}"> <div class="font-semibold" style="color:{COLORS.primary}">{currentTrack.title}</div>
{currentTrack.title}
</div>
<div class="text-sm" style="color:{COLORS.accent}">{ARTIST_NAME}</div> <div class="text-sm" style="color:{COLORS.accent}">{ARTIST_NAME}</div>
</div> </div>
</a> </a>
@@ -584,19 +574,16 @@
on:keydown={onInputKeydown} on:keydown={onInputKeydown}
on:focus={() => (selectedTrack = null)} on:focus={() => (selectedTrack = null)}
class="w-full rounded border px-3 py-2" class="w-full rounded border px-3 py-2"
style=" style="border-color:{COLORS.primary};background:{darkMode
border-color:{COLORS.primary}; ? COLORS.text
background: {darkMode ? COLORS.text : COLORS.background}; : COLORS.background};color:{darkMode ? COLORS.background : COLORS.text}"
color: {darkMode ? COLORS.background : COLORS.text}
"
/> />
{#if suggestions.length} {#if suggestions.length}
<ul <ul
class="absolute bottom-full left-0 z-10 mb-1 max-h-36 w-full overflow-y-auto rounded border" class="absolute bottom-full left-0 z-10 mb-1 max-h-36 w-full overflow-y-auto rounded border"
style=" style="border-color:{darkMode ? COLORS.background : COLORS.text};background:{darkMode
border-color: {darkMode ? COLORS.background : COLORS.text}; ? COLORS.text
background: {darkMode ? COLORS.text : COLORS.background} : COLORS.background}"
"
> >
{#each suggestions as s} {#each suggestions as s}
<li> <li>
@@ -619,11 +606,7 @@
class="rounded px-4 py-2 font-semibold" class="rounded px-4 py-2 font-semibold"
style="background:{COLORS.primary};color:{COLORS.background}" style="background:{COLORS.primary};color:{COLORS.background}"
> >
{#if nextIncrementSec > 0} {#if nextIncrementSec > 0}Skip (+{nextIncrementSec}s){:else}I don't know it{/if}
Skip (+{nextIncrementSec}s)
{:else}
I don't know it
{/if}
</button> </button>
<button <button
on:click={submitGuess} on:click={submitGuess}