fix(audio): Loading Universe splash; single-element in-gesture audio (Safari); first-video-on couples audio

Operator follow-ups:
- Remove the tap-to-start wall; show a 'Loading Universe…' splash until all media
  preloaded (main() awaits preloadAllMedia + a progress bar), then reveal the app.
- Both Video + Audio toggles default OFF (black + silent at start).
- FIX audio still silent on real Safari: the A/B-crossfade-after-gesture didn't
  unlock on Safari (priming srcless elements doesn't unlock them). Use ONE <audio>
  element played SYNCHRONOUSLY inside the toggle's click — the reliable Safari
  unlock; later altitude swaps reuse the unlocked element.
- First time Video turns on, also turn Audio on (one flip = full experience), set+
  played in the same gesture; Audio-on-first stays audio-only.
- Soundtrack follows Altitude via the single element (brief fade swap).
- Verified in headless Chromium AND WebKit: coupling works, soundtrack plays,
  video-off blanks, zero JS errors. 290 tests + 5 Playwright E2E pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-26 13:19:15 -07:00
parent fddb6d65d4
commit 30ec0c9c26
5 changed files with 163 additions and 119 deletions
@@ -13,6 +13,16 @@
> hide), and the soundtrack now plays on Safari/iOS (audio elements unlocked
> synchronously inside the start gesture). Sections below describe the fuller
> off/soundtrack/white-noise design; v1 live = off/soundtrack.
>
> **Startup revision (session 0022):** the tap-to-start wall is **removed** — the app
> opens behind a **"Loading Universe…"** splash (shown until all media is preloaded
> and the experience can run smoothly), then both toggles start **off** (black +
> silent). Autoplay is satisfied without a wall: a **single** `<audio>` element is
> played **synchronously inside the toggle's own click** (the reliable Safari unlock),
> replacing the A/B-crossfade-after-gesture approach that failed on real Safari. The
> first time **Video** is turned on, **Audio** turns on with it (one flip = the full
> experience); turning Audio on first stays audio-only. Altitude changes swap the
> single element's source with a brief fade.
**Extends / reframes:** [`2026-06-26-networked-control-surface-design.md`](./2026-06-26-networked-control-surface-design.md)
§3 (control inventory) and §5 (server contract) — its bundled **Content** selector
is split into two orthogonal controls. Everything else in that spec (server-of-truth