feat(mockups): 17 voyage with real content (FLAC, synced lyrics, Odyssey passage)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6db056fba2
commit
93e9165a88
14 files changed
+4076
-1
No files matched your search
@@ -341,6 +341,13 @@ and brief one agent from a copy of 16, using `/content/library.json`, `parseLrc`
|
||||
- Resumed from §18. 17 (real content) is started now, as agreed; the user's reaction to 16 is asked
|
||||
in parallel.
|
||||
|
||||
- **17 is finished and committed**; report in `docs/design/002-sea-chart-spikes/17-voyage-real-report.md`.
|
||||
Real FLAC playback with seeking, line-synced lyrics (click a line to seek), the Butler Odyssey passage
|
||||
in a reading view, fallback to 16 without the folder. Agent's choices: "Play voyage" stays silent (no
|
||||
excerpts, avoids near-autoplay); part buttons dropped in real mode (sample part times don't match the
|
||||
recordings). Open: passage start points are keyword guesses; parts/motifs aren't timed to the
|
||||
recordings yet (report proposes a small mapping file in the content folder).
|
||||
|
||||
## Open questions (in priority order)
|
||||
|
||||
1. ~~Which sea-chart approach (08–11) reads best, or which mix?~~ 11's navigation + 10's style (§9).
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 17 Voyage with real content — report
|
||||
|
||||
Status: **done** (not committed). Built from `17-voyage-real-brief.md`, starting from a copy of 16.
|
||||
|
||||
## What was built
|
||||
|
||||
`src/mockups/17-voyage-real/` is 16 unchanged on the stage (look 2, Inter labels, frieze accents, five
|
||||
beats, stylised 3D, combined paths, fan works placeholders). Only the content side changed. URL params
|
||||
are as in 16, plus **`?read=1`**, which opens About's Odyssey reading view for the first song (used for
|
||||
screenshots).
|
||||
|
||||
- **`content.ts`** (new) reads the mounted folder at runtime:
|
||||
- `loadLibrary()` fetches `/content/library.json` before the panel is built (top-level await, 4 s
|
||||
timeout). A 404 or a failure means "not mounted".
|
||||
- `realFor(song)` matches a sample song to the library by normalised title. Case, accents,
|
||||
apostrophes and punctuation are ignored. A miss is logged once and does not crash.
|
||||
- `loadLyrics()` parses `.lrc` files with `parseLrc` (`src/shared/lrc.ts`) and splits `.txt` files
|
||||
into plain lines. `loadBook()` fetches `/content/homer/odyssey/<n>.json`. Both are cached.
|
||||
- It also holds the keywords that mark each song's passage, `passageStart()`, and a footnote-marker
|
||||
stripper for Gutenberg's inline note numbers.
|
||||
- Only types are imported from `src/content/`.
|
||||
|
||||
1. **Real player** (`player.ts`, `panel.ts`).
|
||||
- The `<audio>` element is real and sits hidden in the panel's DOM. Play/pause, elapsed time and
|
||||
total time all come from the element, and the scrub bar seeks it (click, drag, ←/→ ±5 s).
|
||||
- While the song plays, the clock is read every animation frame, so the highlight moves smoothly.
|
||||
`timeupdate` alone fires only about 4 times a second.
|
||||
- A seek made before the metadata has loaded (`?t_play`) sets the start position and is applied
|
||||
again once the metadata arrives.
|
||||
- The saga cover is a 56 px square in the player card. The card is sticky at the top of the tab, so
|
||||
the controls stay put while the lyrics scroll under it.
|
||||
- With real audio, the "Preview player" note, the part segment buttons and the part ticks are gone,
|
||||
because the sample's part times are invented and would be wrong against the recording.
|
||||
- The header play button and its eyebrow ("Playing · 1:00 / 3:36") use the real clock.
|
||||
- If the file fails to load, the player drops back to the simulated clock and the tab re-renders
|
||||
as 16.
|
||||
2. **Line-synced lyrics.**
|
||||
- The `.lrc` lines are shown in serif italic, with empty LRC lines drawn as a faint `· · ·` break.
|
||||
- The current line has a terracotta wash, an ink outline and bold type, and lines already sung turn
|
||||
iron-soft. The match leads the timestamp by 0.15 s.
|
||||
- **Keeping the line in view.** The current line is smoothly scrolled to about a third of the way
|
||||
down the space below the sticky player, once per line change.
|
||||
- **The reader's own scrolling wins.** A wheel, touch, scrollbar drag or keyboard scroll in the view
|
||||
pauses following for 4 s. After that the lyrics catch up on the next line. Seeking or clicking a
|
||||
line resumes following at once.
|
||||
- **Click-to-seek.** Clicking a line seeks to its timestamp and plays. The timestamp appears in the
|
||||
gutter on hover or focus.
|
||||
- **Plain `.txt` lyrics** are shown unsynced, with the note "These lyrics are not timed, so they do
|
||||
not follow the song".
|
||||
- **Missing or failed lyrics** each get a one-line note.
|
||||
- **Parts and motifs.** Part headings are gone from the lyrics. A compact summary above them reads
|
||||
"Parts: Verse 1 · Verse 2", followed by the motif chips (the motif pick works as before) and "From
|
||||
the sample data, not yet timed to the recording".
|
||||
3. **The Odyssey passage.**
|
||||
- About's Odyssey row now reads "Book IX". The line ranges are dropped because the prose has no line
|
||||
numbers.
|
||||
- The **Read the passage →** button replaces About with a reading view, which has "← Back to About",
|
||||
the book heading, Butler's summary line, the Roman-names note and the paragraphs.
|
||||
- **Where the passage starts.** The view opens at the first paragraph that mentions the song's
|
||||
keyword and puts a meander rule before it: "The passage for Polyphemus begins here · first mention
|
||||
of 'Cyclops'". Keywords are highlighted in the text.
|
||||
- **Earlier paragraphs.** "Show the 13 paragraphs before this passage" expands the whole book and
|
||||
scrolls to the rule. If no keyword is found, the view starts at the beginning of the book.
|
||||
- The text ends with "End of Book IX · Project Gutenberg #1727".
|
||||
- **Keywords (Butler's spelling).**
|
||||
- Horse and the Infant: horse (VIII, Demodocus).
|
||||
- Full Speed Ahead: Cicons / Ismarus.
|
||||
- Warrior of the Mind: Minerva (XIII).
|
||||
- Polyphemus: Polyphemus / Cyclops.
|
||||
- Survive: olive (the stake).
|
||||
- Remember Them: rams.
|
||||
- My Goodbye: "put your eye out" (the taunt).
|
||||
- Storm: Aeolus.
|
||||
- These are content guesses, the same as the sample's `TODO(verify)` references.
|
||||
4. **Fallback.** Without `CONTENT_DIR` the page behaves like 16: the simulated player, placeholder
|
||||
lyrics by part, and the Gutenberg link. The player note starts with **"Content folder not mounted."**,
|
||||
and the end of About has a small "Content folder not mounted" note. `body[data-content]` is
|
||||
`mounted` or `missing`.
|
||||
5. **Voyage play.** It stays **silent: no excerpts**. Beats hold for 3.4 s, so an excerpt would be a
|
||||
jarring audio stab, and starting sound without a click is close to the autoplay the brief rules out.
|
||||
As in 16, moving to a new song loads that song stopped at 0:00. The old `<audio>` element is removed.
|
||||
A song that is playing keeps playing while the beat changes but the song stays the same (Landfall →
|
||||
Curtain).
|
||||
|
||||
## Screenshots (1440×900, `screenshots/`)
|
||||
|
||||
- `17-voyage-real-listen.png`: beat 3, Listen & lyrics, `?t_play=60`. It is playing at 1:00 / 3:36,
|
||||
with the cover, the current line lit and kept in view under the sticky player.
|
||||
- `17-voyage-real-odyssey.png`: Polyphemus, Book IX reading view, opened at the first "Cyclops"
|
||||
paragraph.
|
||||
- `17-voyage-real-nocontent.png`: served without `CONTENT_DIR`. It shows the simulated player, the
|
||||
"Content folder not mounted" note and the placeholder lyrics.
|
||||
- `17-voyage-real-nocontent-about.png`: About without content (Gutenberg link and the note).
|
||||
- `17-voyage-real-beat1.png`: the Troy frieze, with About's "Book VIII · Read the passage →".
|
||||
- `-txt.png` was skipped. No sample song (tracks 1–10) maps to a `.txt` lyric; in the library only
|
||||
Scylla and two others outside the sample do.
|
||||
|
||||
The credits footer is fully visible in every one.
|
||||
|
||||
## Verified
|
||||
|
||||
- `bun test` (64 pass), `bun run typecheck` (clean) and `bun run build`. The scratchpad build was
|
||||
served on port 4117 with and without `CONTENT_DIR`.
|
||||
- A CDP script drove headless Chrome against the real server, with no virtual time:
|
||||
- The real FLAC loads (`readyState` 4, duration 216 s).
|
||||
- After the header play click, `currentTime` advances (2.94 → 4.95 s over 2 s).
|
||||
- A scrub click at 50 % seeks to 109.4 s.
|
||||
- Clicking a lyric line stamped 1:08 seeks there, and that line becomes current.
|
||||
- A range request on the audio URL returns `206 bytes 1000000-1000099/42425888`.
|
||||
- Following keeps the current line 249 px down a 520 px view.
|
||||
- After a user scroll to the top, the view stays at 0 for 2.5 s, then follows again by 7.5 s.
|
||||
- Moving to beat 5 swaps the element to Survive, stopped.
|
||||
- The reading view (Survive → "olive", 20 earlier paragraphs) expands to all 44 paragraphs, and Back
|
||||
returns to About.
|
||||
- The console has no errors; the only message is a three.js warning inherited from 16.
|
||||
|
||||
## What doesn't, or is rough
|
||||
|
||||
- **Parts and motifs are not time-aligned.** Later, each part could get a `startSec` (the type already
|
||||
has it), or better, an LRC line index, by marking the first line of each part once per song. That
|
||||
could be a small JSON sidecar in the content folder, keyed by song, mapping part → first line index;
|
||||
the line's timestamp then gives the time. Motifs could be stamped the same way, with a line range
|
||||
per motif. With that, the player could show "Verse 2" again, put the part ticks on the scrub bar, and
|
||||
light motif chips as they sound.
|
||||
- **Keyword starts are approximate.** The Polyphemus passage opens where the Cyclops comes home, not
|
||||
at the landing on the island (the first "Cyclopes", 8 paragraphs earlier). My Goodbye's Homer
|
||||
counterpart is debatable. The keywords should be confirmed with the content work.
|
||||
- Butler's summary line is in capitals from the source, and contains a doubled dash ("—-").
|
||||
- Headless virtual time does not run the audio clock, so `?t_play` screenshots show the seek point,
|
||||
not real progress. Real progress was checked over CDP instead.
|
||||
- Library loading delays the panel by one request (fast locally). A slow server would hold the panel
|
||||
for up to 4 s before it falls back.
|
||||
- Desktop 1440×900 only. The `.txt` path is implemented but has not been seen on screen.
|
||||
Reference in new issue
Block a user