diff --git a/docs/superpowers/specs/2026-06-30-welcome-screen-design.md b/docs/superpowers/specs/2026-06-30-welcome-screen-design.md new file mode 100644 index 0000000..861d643 --- /dev/null +++ b/docs/superpowers/specs/2026-06-30-welcome-screen-design.md @@ -0,0 +1,169 @@ +# Welcome screen — design + +**Date:** 2026-06-30 +**Status:** Approved (brainstorming) +**Surface:** `simulator/static/` (frontend only — `index.html`, `style.css`, `app.js`, `i18n.js`) + +## Summary + +Replace the current bare boot flow with a single **Welcome screen**: the +universal entry gate the visitor sees on every load. It carries the +photosensitivity "Heads up" notice, the four output controls (Video, Reduce +motion, Audio, Language) with welcome-appropriate defaults, a **Continue** +button, and the "Loading Universe" progress. The right control panel is hidden +until the visitor enters the simulation. + +## Why + +Today the universe loads behind a bare "Loading Universe" splash; the splash +fades to reveal a "Run simulation" button over the black stage; a first-visit +"Heads up" modal appears; clicking through begins the experience. The four +output controls live in the right panel and are visible the whole time — +including during loading, which the panel should not be. + +We want a deliberate, configurable front door: the visitor reads the heads-up, +sets their preferences, and presses one button to begin — while the universe +loads in the background so entry is instant when they are ready. + +## Current flow (being replaced) + +- `#loading` — fullscreen splash (`Loading Universe` title + `#loading-fill` + progress bar). `simulator/static/index.html:10-15`. +- `#run-sim` — "Run simulation" button revealed over the black stage after + phase-1 preload. `index.html:33`; wired at `app.js:1533`. +- `#motion-warning` — first-visit-only "Heads up" modal, persisted dismissed via + `WARN_KEY`. `index.html:34-40`; shown by `maybeShowMotionWarning()` + (`app.js:1161`). +- Four controls in `.panel` (`#visual`, `#audio`, `#reduce-motion`, + `#lang-select`). `index.html:46-65`. The panel is visible throughout. +- `beginExperience()` (`app.js:1505`) snaps Video on and Audio to + `START_AUDIO_LEVEL = "2"` in the click gesture (Safari audio-unlock). + +## Target design + +### 1. One unified `#welcome` overlay + +Fold `#loading`, `#motion-warning`, and `#run-sim` into a single fullscreen +`#welcome` overlay layered above everything. It absorbs all three roles. The +right `.panel` is **hidden** for the entire welcome + loading period and revealed +only on entry to the simulation. + +The overlay contains three blocks: + +1. **Welcome messaging** — the "Heads up — motion & flashing" title + body + (today's `warn.*` strings, reworded to point at the on-screen Reduce-motion + control rather than "the panel"). Structured so additional welcome copy can be + added later (see Forward notes). +2. **Controls** — the four output controls (below). +3. **Loading** — the "Loading Universe" title + progress bar. + +### 2. Two visual states + +**State A — Welcome (on load):** welcome messaging + controls + Continue button +shown; the Loading block sits at the **bottom**, its bar filling as phase-1 +preload runs in the background. + +**State B — Loading (after Continue, only if not yet loaded):** the welcome +messaging and Continue button **disappear**; the **controls remain**; the Loading +block moves to **center**. When phase-1 preload completes, auto-enter the +simulation. + +**Continue when already loaded** → skip State B; enter the simulation directly. + +State is driven by a class on `#welcome` (e.g. `.welcoming` → `.loading`), with +the "loaded yet?" decision made from a flag set when phase-1 preload resolves. + +### 3. Control defaults (welcome screen) + +| Control | Welcome default | Notes | +|---------------|-----------------|--------------------------------------------------| +| Video | **ON** | Was OFF in the panel; on by default here. | +| Reduce motion | **OFF** | Unless OS `prefers-reduced-motion` / stored pref.| +| Audio | **2** (0–10) | Was 0 in the panel. | +| Language | **English** | Global; changing it re-renders all welcome text. | + +Reduce motion keeps deferring to a stored value / the OS media query when those +exist (same precedence as `initReduceMotion()` today); "OFF" is the no-signal +default. + +### 4. Entry into the simulation + +Continue is the user gesture that unlocks audio (the role `#run-sim` had). On +Continue → enter (either immediately when loaded, or at end of State B): + +1. Copy the four welcome values into the existing panel inputs (`#visual`, + `#reduce-motion`, `#audio`, `#lang-select`). +2. Fire the existing handlers / `beginExperience()` so the experience starts in + the same code paths as today. +3. Remove the `#welcome` overlay and reveal `.panel`. + +Per decisions: the four controls **stay in the panel** for mid-experience +adjustment; **Video-off at entry** starts with video off (audio still plays per +the Audio level — `beginExperience` no longer forces Video on regardless). + +### 5. Controls drive shared state + +The welcome controls write the same underlying state as the panel controls — they +are not a parallel system: + +- **Language** routes through the global `setLanguage()`, which sets `activeLang` + and runs `applyUiStrings()` over **all** `[data-i18n]` elements — so the + heads-up copy, control labels, and Continue button re-render live, exactly like + the panel selector does today. The panel `#lang-select` value is synced to + `activeLang` on entry. +- **Reduce motion** sets the same `reduceMotion` variable + `RM_KEY` localStorage + and calls `applyReduceMotion()`; panel checkbox synced on entry. +- **Audio / Video** values are applied on entry via the panel inputs + + `beginExperience()`. + +### 6. Behavior changes (intentional) + +- The welcome screen shows on **every** load (it is the entry gate holding the + controls), so the first-visit-only persistence of the heads-up (`WARN_KEY`) is + **retired**. +- Entry uses the **chosen** Audio level rather than always snapping to 2 (2 is the + default, not a forced value). +- `#run-sim` is removed; **Continue** is the single entry button. + +## i18n + +New / changed `UI_STRINGS` keys in `simulator/static/i18n.js` (all four langs — +en/es/fr/ja, matching existing parity): + +- A Continue button label (reuse `warn.continue` if suitable, or add + `welcome.continue`). +- Reworded `warn.body` (point at the on-screen Reduce-motion control). +- Welcome-screen control labels reuse the existing `output.video`, + `output.audio`, `rm.label` keys (already translated). The language selector + needs no string (native names from the registry). + +All welcome text must live under `data-i18n` so `applyUiStrings()` re-renders it +on language change. + +## Out of scope / Forward notes + +- **Additional welcome copy** beyond the heads-up (intro / framing text) comes in + a later pass, once this flow works. The welcome-messaging block is structured so + adding more `data-i18n` paragraphs is trivial. +- No backend / API changes. No content-pipeline changes. + +## Testing + +Frontend lives behind Playwright e2e (`simulator/static/` tests) + node unit +tests. Plan to: + +- **Update** existing tests that reference `#loading`, `#run-sim`, and + `#motion-warning` to the new `#welcome` flow. +- **Add** coverage for: + - Welcome defaults (Video on, Reduce motion off, Audio 2, English). + - Language switch re-renders welcome text (heads-up + labels + Continue) live. + - Continue **while loading** → State B (messaging gone, controls remain, + progress centered), then auto-enter when loaded. + - Continue **when already loaded** → straight into the simulation. + - `.panel` hidden during welcome + loading; revealed on entry. + - Welcome control values carry into the panel inputs on entry. + +Environment caveats (from prior sessions): this box has only `python3` (start +uvicorn by hand; `reuseExistingServer`); a stale uvicorn is the recurring +"no transitions" root cause; `loop-recovery.spec.ts` fails on a clean baseline +here (boots without video).