docs: describe the audio layer

Records what actually fires rather than what the plan intended: the
click sound is limited to Play and Back, and the error tone covers the
viewer failing to construct, not panorama-error, which falls back to a
flat image and leaves the round playable.

Also notes that the loop is mounted app-wide, which is what lets it play
unbroken across the menu and a round -- and means it plays on Credits
and the debug pages too.
This commit is contained in:
tiennm99 committed 2026-09-09 11:14:57 +07:00
1 parent 4d10f0a879
commit bf4b3ae465
2 files changed
+34

No files matched your search

+23
View File
@@ -83,6 +83,29 @@ district is credited the same points for the round (`submitRoundScore` in
on its `gameResult.levels` entry. Scores earned under the earlier region-scaled
ladder stay on the boards as they were recorded.
## Sound & Music
- **Sound effects**: nine one-shots on the play flow — a click when Play or
Back changes screen, a pop when the guess pin lands, a rising sound on
submit, a jingle tiered off the score (4-5 points, 1-3, a miss), a transition
into the next round, a flat blip on skip, and a low tone when the panorama
viewer fails to start or a guess is not recorded. The click is deliberately
limited to the two buttons that navigate: on every button in the chrome it
became noise within a few rounds
- **Background music**: one ambient loop at roughly a third of the effects'
volume, so it sits under the panorama rather than competing with it. It is
mounted app-wide rather than per page, which is what lets it play unbroken
across the menu and a round — and means it also plays on Credits and the
debug pages
- **Two switches, both on by default**: music and effects toggle independently
from the header, beside the theme switch, and both choices persist in
localStorage. The game header collapses them into a single mute-everything
button below the `sm` breakpoint, where there is no room for the pair
- **Nothing plays before a gesture**: every browser blocks audio until the
player interacts, so no audio file is even fetched until the first click or
keypress — which also means music on iOS starts at the first tap, not on load
- **All assets are CC0**: sourced from Kenney and OpenGameArt, with per-file
provenance in `public/audio/SOURCES.md` and credit on `/credits`
## Leaderboards
- **Rollup fan-out**: one guess credits the district its panorama sat in, then
that district's province, then Vietnam — three score boards and three
+11
View File
@@ -77,6 +77,11 @@ Next.js 16 App Router structure:
the panorama pane's top row via `PanoramaViewer`'s `topBarSlot`
- `DonateQRModal.js` - Donation QR code modal
- `ThemeToggle.js` - Light/dark switch
- `SoundToggle.js` - Music and sound-effect switches; `compact` renders one
mute-everything button for the game header below `sm`
- `MusicPlayer.js` - The background loop. Renders nothing and is mounted in the
root layout, not in a page, so a client navigation between the menu and a
round does not restart it
### Reusable Components (`src/components/`)
@@ -136,6 +141,9 @@ Neon Postgres, which is what the app queries at runtime.
- `session.js` - Redis-based session management with 30-min expiry
- `upstash.js` - Upstash Redis REST client adapter with multi-tenant key prefix
- `theme.js`, `use-count-up.js` - Theme persistence and a count-up hook
- `audio.js` - **Client-side only.** The audio context, its first-gesture
unlock, the decoded-buffer cache, one-shot playback, and the music/effects
preferences
## Build Scripts (`scripts/`)
Each carries a header comment with its flags and its cost.
@@ -183,4 +191,7 @@ only.
## Public Assets (`public/`)
- `zlp.jpg` - Donation QR code image
- `audio/` - Nine sound effects plus the music loop (shipped as both
WebM/Opus and MP3), with `SOURCES.md` recording each file's upstream source
and licence
- Static assets served directly by Next.js