From da3581afea010aba736b0a96fa727501b99b4948 Mon Sep 17 00:00:00 2001 From: BenStullsBets Date: Thu, 2 Jul 2026 16:56:17 -0700 Subject: [PATCH] feat(audio): add the Audio source dropdown to the welcome screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The opening screen only had Volume + Video speed — no way to pick the sound mode before launching. Add a welcome Audio dropdown (None/Soundtrack/Music) mirroring the panel; applyWelcomeToPanel carries the choice into the session on launch, None greys the welcome Volume, and ?audio= presets it on the welcome screen too. +4 e2e (welcome-audio.spec.ts). Co-Authored-By: Claude Opus 4.8 --- simulator/e2e/tests/welcome-audio.spec.ts | 43 +++++++++++++++++++++++ simulator/static/app.js | 12 +++++++ simulator/static/index.html | 7 ++++ 3 files changed, 62 insertions(+) create mode 100644 simulator/e2e/tests/welcome-audio.spec.ts diff --git a/simulator/e2e/tests/welcome-audio.spec.ts b/simulator/e2e/tests/welcome-audio.spec.ts new file mode 100644 index 0000000..4a157cf --- /dev/null +++ b/simulator/e2e/tests/welcome-audio.spec.ts @@ -0,0 +1,43 @@ +import { test, expect, Page } from "@playwright/test"; + +// The welcome (opening) screen mirrors the panel controls. It must offer the Audio +// SOURCE choice (None/Soundtrack/Music) too — so a visitor can pick their sound mode +// before launching — carried into the session on entry. + +async function ready(page: Page) { + await page.waitForFunction(() => (window as any).__hefReady === true, null, { timeout: 45_000 }); +} + +test("the welcome screen has an Audio source dropdown with 3 options", async ({ page }) => { + await page.goto("/"); + await ready(page); + await expect(page.locator("#welcome")).toBeVisible(); + const opts = page.locator("#welcome-audio-source option"); + await expect(opts).toHaveCount(3); + await expect(opts).toHaveText(["None", "Soundtrack", "Music"]); +}); + +test("choosing Music on the welcome screen carries into the session", async ({ page }) => { + await page.goto("/"); + await ready(page); + await page.selectOption("#welcome-audio-source", "music"); + await page.locator("#welcome-launch").click(); + await page.waitForSelector("#welcome", { state: "detached", timeout: 10_000 }); + expect(await page.locator("#audio-source").inputValue()).toBe("music"); + await page.waitForTimeout(300); + const url = await page.evaluate(() => (document.querySelector("#aud") as HTMLAudioElement).dataset.url || ""); + expect(url).toMatch(/audio\/music\/\w+\.mp3/); +}); + +test("welcome None greys out the welcome Volume slider", async ({ page }) => { + await page.goto("/"); + await ready(page); + await page.selectOption("#welcome-audio-source", "none"); + await expect(page.locator("#welcome-audio")).toBeDisabled(); +}); + +test("?audio=music preselects Music on the welcome screen too", async ({ page }) => { + await page.goto("/?audio=music"); + await ready(page); + expect(await page.locator("#welcome-audio-source").inputValue()).toBe("music"); +}); diff --git a/simulator/static/app.js b/simulator/static/app.js index ea4f834..71729e2 100644 --- a/simulator/static/app.js +++ b/simulator/static/app.js @@ -1420,6 +1420,7 @@ function applyUrlAudioParams() { const src = (p.get("audio") || "").toLowerCase(); if (src === "none" || src === "soundtrack" || src === "music") { const el = $("audio-source"); if (el) el.value = src; + const w = $("welcome-audio-source"); if (w) w.value = src; // reflect on the opening screen too } const vol = p.get("vol"); if (vol !== null && vol !== "" && Number.isFinite(+vol)) { @@ -1681,11 +1682,18 @@ function applyWelcomeToPanel() { $("visual").checked = true; // the welcome screen assumes Video on (no toggle) const wa = $("welcome-audio"); if (wa) { $("audio").value = wa.value; updateAudioLevelLabel(); } + const ws = $("welcome-audio-source"); // carry the chosen source into the panel dropdown + if (ws) { $("audio-source").value = ws.value; syncVolumeDisabled(); } } function updateWelcomeAudioLabel() { const el = $("welcome-audio-val"); if (el) el.textContent = String(+($("welcome-audio") || {}).value || 0); } +// Volume is meaningless on None — grey the welcome slider too, mirroring the panel. +function syncWelcomeVolumeDisabled() { + const src = $("welcome-audio-source"), slider = $("welcome-audio"); + if (src && slider) slider.disabled = src.value === "none"; +} // Remove the welcome overlay (fade out) — the panel reveals via `body:has(#welcome)`. function enterSimulation() { @@ -1718,6 +1726,9 @@ function initWelcome() { updateWelcomeAudioLabel(); const wa = $("welcome-audio"); if (wa) wa.addEventListener("input", updateWelcomeAudioLabel); + const ws = $("welcome-audio-source"); + if (ws) ws.addEventListener("change", syncWelcomeVolumeDisabled); + syncWelcomeVolumeDisabled(); const launch = $("welcome-launch"); if (launch) launch.addEventListener("click", onLaunch); } @@ -1748,6 +1759,7 @@ async function main() { $("audio-source").addEventListener("change", () => { syncVolumeDisabled(); applyAudio(); debounced(); }); applyUrlAudioParams(); // shareable ?audio=/?vol= deep-link presets syncVolumeDisabled(); + syncWelcomeVolumeDisabled(); // reflect a URL-set source on the welcome slider too updateWelcomeAudioLabel(); // Video toggle (panel): the FIRST time video turns on without having entered via // Launch, begin the experience too — couple audio in. (Normally Launch entered diff --git a/simulator/static/index.html b/simulator/static/index.html index b0b6179..9b7eec7 100644 --- a/simulator/static/index.html +++ b/simulator/static/index.html @@ -26,6 +26,13 @@ list="play-speed-ticks" aria-describedby="welcome-play-speed-val" /> 1.00× +