docs: the voyage app in DESIGN, PRODUCT, AGENTS, ARCHITECTURE, DECISIONS, TODO
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d355b3a41f
commit
a196c09b82
@@ -1,7 +1,8 @@
|
||||
# EPIC: The Musical Timeline
|
||||
|
||||
Unofficial fan site: an interactive timeline of *EPIC: The Musical* songs. Currently 7 design-direction
|
||||
mockups. Bun 1.3.14 (server, bundler, test runner), TypeScript, three.js (mockups 06/07/13–17 only), Docker.
|
||||
Unofficial fan site: an interactive timeline of *EPIC: The Musical* songs as a 3D sea voyage (the app at
|
||||
`/`, `src/app/`), plus the design-round mockups at `/mockups/`. Bun 1.3.14 (server, bundler, test runner),
|
||||
TypeScript, three.js (the app; mockups 06/07/13–17), Docker.
|
||||
|
||||
`AGENTS.md` is the single source of truth for agents here. `CLAUDE.md` and `GEMINI.md` only point here.
|
||||
Deeper context on demand: `docs/ARCHITECTURE.md`, `docs/DECISIONS.md`, `docs/TODO.md`.
|
||||
@@ -33,7 +34,7 @@ bun dist/server.js # wrong: crashes with 'Bundled file "./index-….js" no
|
||||
"Multiple files share the same output path". Give each one its own styles from the start.
|
||||
- Real audio, lyrics and cover art are Jorge Rivera-Herrans' copyrighted work: they live only in the
|
||||
mounted content folder (`CONTENT_DIR`, served under `/content/`), never in git or the Docker image.
|
||||
- Sample data (`src/data/sample.ts`): lyrics are placeholders, never real lyrics. Any fact, story time
|
||||
- Album data (`src/data/album/`) and sample data (`src/data/sample.ts`): never real lyrics. Any fact, story time
|
||||
or Odyssey reference you can't confirm gets `// TODO(verify)`.
|
||||
- Assets are bundled, never loaded from a CDN: fonts from `@fontsource/*`, three.js from npm.
|
||||
YouTube videos are link cards (`createVideoCards`), not embeds.
|
||||
@@ -52,7 +53,7 @@ Tick plan checkboxes as you go so an interrupted session is resumable.
|
||||
Trivial changes (typo, version bump, one-line config): say "trivial, skipping spec" and proceed.
|
||||
|
||||
Use impeccable for user-facing UI — `craft` for new, `audit`/`polish` on existing, `harden` before
|
||||
shipping. Design context: `PRODUCT.md`, `DESIGN.md`. No visual direction is chosen yet (see `DESIGN.md`).
|
||||
shipping. Design context: `PRODUCT.md`, `DESIGN.md` (the chosen direction: tokens, type, layout, 3D style).
|
||||
|
||||
Mockup rounds — any UI change beyond a trivial tweak, while brainstorming and before the spec:
|
||||
- Round 1: about three genuinely different alternatives, each a clickable mockup with realistic
|
||||
@@ -68,8 +69,11 @@ Mockup rounds — any UI change beyond a trivial tweak, while brainstorming and
|
||||
|
||||
## Architecture
|
||||
|
||||
`src/server.ts` -> `Bun.serve` with HTML-import routes: `/` and `/mockups/NN-slug/`
|
||||
`src/data/` -> `types.ts` (shapes), `sample.ts` (shared sample data), `query.ts` (helpers)
|
||||
`src/server.ts` -> `Bun.serve` with HTML-import routes: `/` (the app), `/mockups/` (gallery), `/mockups/NN-slug/`
|
||||
`src/app/` -> the voyage app: `state.ts` (URL state), `controller.ts` + `voyage.ts` (scene changes),
|
||||
`world/` (three.js world, landings, ribbons), `frieze/` (friezes, curtain), `panel/`, `timeline/`, `content.ts`
|
||||
`src/data/album/` -> the app's data: 9 sagas, 40 songs, motifs (`// TODO(verify)` where unconfirmed)
|
||||
`src/data/` (other files) -> the mockups' shapes and sample data; frozen
|
||||
`src/shared/` -> credits, audio slot (direct MP3, empty state), YouTube link cards, LRC parser
|
||||
`src/content/` -> `/content/*` routes over the mounted content folder: library.json, files (ranges), Homer books
|
||||
`src/mockups/NN-slug/` -> one design direction each: `index.html`, `main.ts`, `style.css`, plus optional helper modules (e.g. `scene.ts`, `layout.ts`)
|
||||
|
||||
@@ -1,29 +1,54 @@
|
||||
# Design
|
||||
|
||||
## Status: visual direction open
|
||||
No design system, palette or type scale is chosen yet. Seven mockups under `src/mockups/` each commit fully
|
||||
to a different direction; the maintainer will pick one, or combine several, and this file then records the
|
||||
chosen direction as tokens and rules. Until then, do not treat any single mockup's palette or fonts as the
|
||||
house style.
|
||||
## Status: direction chosen (2026-10-30)
|
||||
The app at `/` (`src/app/`) is the house style. Its record, with every chosen and deselected idea and the
|
||||
user's words: `docs/design/002-voyage-app.md`. The mockups under `src/mockups/` are history; don't take
|
||||
styling from the dropped ones. New options vary structure, flow and behaviour within this direction —
|
||||
never a new palette or font family.
|
||||
|
||||
## The candidate directions
|
||||
| # | Folder | Direction |
|
||||
## The direction: "two worlds" (look 2)
|
||||
A stylised 3D sea voyage, bright and naturalistic in colour, framed by a Greek black-figure frieze that
|
||||
drops like a curtain between scenes. The interface sits on parchment and borrows the frieze's terracotta,
|
||||
black and meander band so that frieze, world and panels read as one piece.
|
||||
|
||||
## Tokens (`src/app/style.css` `:root`)
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| 1 | `01-editorial` | Editorial scroll: horizontal line, parchment and serif, motifs as thin threads |
|
||||
| 2 | `02-vertical-story` | Vertical story: downward scroll with a sticky detail panel |
|
||||
| 3 | `03-transit-map` | Transit map: motifs as metro lines, songs as stations |
|
||||
| 4 | `04-greek-frieze` | Greek frieze: an unrolled black-figure vase frieze |
|
||||
| 5 | `05-score` | Score / waveform: staff or waveform, parts as segments |
|
||||
| 6 | `06-constellation` | Constellation (3D, three.js): songs as stars |
|
||||
| 7 | `07-sea-voyage` | Sea voyage (3D, three.js): ship, sea, saga islands |
|
||||
| `--clay` | `#c46a33` | frieze ground; accents, active tab, play button |
|
||||
| `--clay-deep` | `#9a4520` | eyebrows, hover, small accent text |
|
||||
| `--glaze` | `#1d140e` | frieze figures; rules, borders, dark buttons, page frame |
|
||||
| `--added-white` | `#efe3c8` | text on glaze |
|
||||
| `--vellum` / `--vellum-hi` / `--vellum-lo` | `#e3d6b4` / `#ece2c6` / `#e0d1ab` | panel, cards, timeline |
|
||||
| `--iron` / `--iron-soft` | `#2b2118` / `#5a4836` | body text / secondary text |
|
||||
| `--line` | `rgb(43 33 24 / 0.2)` | hairlines |
|
||||
| `--meander` | SVG data URI | the key band: 14px strip between two 2px glaze rules (`.meander-edge`) on top of every panel, card and bar |
|
||||
|
||||
Details per direction: `docs/specs/001-timeline-mockups.md`.
|
||||
Motif colours come from the data (`src/data/album/motifs.ts`) and are the same everywhere: timeline,
|
||||
panel, ribbons and chips. On parchment, text in a motif colour is darkened to 4.5:1
|
||||
(`src/app/timeline/ink.ts`); dots and knots keep the bright colour.
|
||||
|
||||
## Constraints that hold for every direction
|
||||
- Fonts are self-hosted from `@fontsource/*` (installed: Cormorant Garamond, Inter); nothing from a CDN.
|
||||
- three.js only where a direction is 3D.
|
||||
- Motif colours come from the data (`Motif.color` in `src/data/sample.ts`), so threads match across views.
|
||||
- The fan-credits footer (`src/shared/credits.css`) is present on every page; a direction may restyle it
|
||||
but not remove or hide it.
|
||||
- Avoid generic, templated "AI-looking" UI; a direction that is just another layout in different colours
|
||||
has failed.
|
||||
## Type
|
||||
- **Inter** for all UI, labels and controls: 13px minimum (`--fs-label`), 14px small, 15px panel body,
|
||||
weights 400–600, tracking ≤0.06em. Never small wide-tracked capitals (they were "very hard to read").
|
||||
- **Cormorant Garamond** only for large display titles (`--fs-title` 2rem), lyric lines, Odyssey reading
|
||||
text, and frieze inscriptions (≥19px on screen).
|
||||
|
||||
## Layout
|
||||
- Desktop reference 1440×900: stage (3D canvas + frieze layer + caption card + bottom timeline) | right
|
||||
panel 420px (collapsible to a 56px tab) | credits footer across the bottom.
|
||||
- Timeline ≤190px tall by default; the current saga expanded with titles, the others compact.
|
||||
- Below 900px: the panel becomes a bottom sheet; the timeline stays over the stage.
|
||||
|
||||
## 3D
|
||||
Stylised, not realistic: toon/flat shading, ink outlines, flat colour-band water with drawn wave strokes,
|
||||
a small flat sun, graphic silhouettes. Black-figure cut-outs (the giant, Athena) inside landings.
|
||||
Pixel ratio ≤2; distant places are not drawn.
|
||||
|
||||
## Motion
|
||||
Curtain drop ≈1.2s, parting lift ≈1.5s, doorway zoom ≈1.6s, fade ≈0.45/0.65s; ease-out curves.
|
||||
`prefers-reduced-motion`: every scene change is a 200ms crossfade.
|
||||
|
||||
## Constraints
|
||||
- Fonts self-hosted from `@fontsource/*`; three.js from npm; nothing from a CDN.
|
||||
- The fan-credits footer is on every page and never hidden.
|
||||
- Avoid generic, templated "AI-looking" UI.
|
||||
|
||||
+9
-7
@@ -19,16 +19,18 @@ recur across the songs and how the story maps onto the *Odyssey*. Desktop first
|
||||
(`src/shared/credits.ts`), never retyped.
|
||||
- **Accuracy is visible:** content that isn't verified is marked as such (`// TODO(verify)` in data) and never
|
||||
presented as fact-checked.
|
||||
- **Lyrics:** mockups use placeholder lyric lines only. Whether and how real lyrics are shown is an open
|
||||
copyright question, deferred by the maintainer.
|
||||
- **Media:** videos are YouTube link cards (no embedded player); audio supports direct MP3 URLs. The app
|
||||
works offline apart from following those links.
|
||||
- **Lyrics and audio:** real recordings, lyrics and cover art come only from a content folder mounted at
|
||||
runtime (spec 003); they are never committed or baked into the image. The broader copyright question is
|
||||
deferred by the maintainer.
|
||||
- **Media:** videos are YouTube link cards (no embedded player). The app works without the content folder
|
||||
and offline apart from following those links.
|
||||
|
||||
## Tone
|
||||
Polished and smooth; may use 3D (three.js). It should feel like a crafted piece made by a fan, not a generic
|
||||
template — see `DESIGN.md`.
|
||||
|
||||
## Current stage
|
||||
Spec `docs/specs/001-timeline-mockups.md`: 7 design-direction mockups over shared sample data (Troy and
|
||||
Cyclops sagas plus later callback songs). The maintainer picks or combines a direction; the real app gets
|
||||
its own spec after that.
|
||||
v1 of the app (spec `docs/specs/002-voyage-app.md`): the whole album, 9 sagas and 40 songs, as a stylised
|
||||
3D sea voyage with the frieze as a curtain between scenes, three landings, a side panel and a bottom
|
||||
timeline of motif threads. The design rounds that led here (mockups 01–17) are recorded in
|
||||
`docs/design/002-direction-round-notes.md`.
|
||||
|
||||
+25
-9
@@ -3,10 +3,12 @@
|
||||
How `epic-the-musical-timeline` is built today. Update in the same commit as any structural change.
|
||||
|
||||
## Overview
|
||||
A Bun server (`Bun.serve`) with HTML-import routes serves an index page and seven mockup pages. Bun's bundler
|
||||
turns each HTML page and its imported TS/CSS into hashed assets. `bun run build` bundles the server and every
|
||||
page into `dist/`; the Docker image runs that bundle. All pages read one typed sample dataset and mount one
|
||||
shared credits snippet.
|
||||
A Bun server (`Bun.serve`) with HTML-import routes serves the voyage app at `/`, the mockup gallery at
|
||||
`/mockups/` and the design-round mockups at `/mockups/NN-slug/`, plus `/content/*` from an optional mounted
|
||||
content folder. Bun's bundler turns each HTML page and its imported TS/CSS into hashed assets. `bun run
|
||||
build` bundles the server and every page into `dist/`; the Docker image runs that bundle. The app reads the
|
||||
album data (`src/data/album/`); the mockups read the frozen sample dataset. Every page mounts one shared
|
||||
credits snippet.
|
||||
|
||||
## Map
|
||||
| Path | Responsibility |
|
||||
@@ -26,18 +28,30 @@ shared credits snippet.
|
||||
| `src/content/library.ts` | `scanLibrary(dir)`: sagas (story order) and songs from `Songs and Lyrics/EPIC - <Saga> (<year>)/`; `.lrc` preferred over `.txt`. |
|
||||
| `src/content/homer.ts` | `splitBooks(text)`: Gutenberg Butler text → books (heading, summary, paragraphs). |
|
||||
| `src/shared/lrc.ts` | `parseLrc(text)`: timed lyric lines, sorted. |
|
||||
| `tests/` | `data.test.ts` (dataset invariants), `shared.test.ts` (DOM modules under happy-dom), `server.test.ts` (every route 200s, index links every registry slug, each mockup has the credits hook), `content.test.ts` (LRC, library scan, Homer split, `/content/*` routes on a temp fixture). |
|
||||
| `src/app/index.html`, `main.ts`, `style.css` | The app page: stage (canvas, frieze layer, caption, timeline) and panel. `main.ts` wires store → controller → world/curtain, panel, timeline, play/prev/next, keyboard (←/→, Esc), the no-WebGL frieze stage. `style.css` holds the design tokens (see `DESIGN.md`). |
|
||||
| `src/app/state.ts` | `AppState` (`songId`, `tab`, `motif`, `timeline`, `ribbons`, `read`), `parseState`/`stateToSearch` (URL), `createStore` (push on song change, replace otherwise, `popstate` restores). |
|
||||
| `src/app/voyage.ts` | `transitionFor(from, to)`: same saga → sail; adjacent saga → that saga's curtain; further → fade; plus the target's landing. |
|
||||
| `src/app/controller.ts` | Runs steps one at a time, last request wins, a cancelled step's curtain is lifted by the next. Order under a curtain: cover → sail → landing → horizon on the frieze ground line → lift. Play voyage (3.4s per song, silent), Back to sea. |
|
||||
| `src/app/world/` | `createWorld(canvas)` (null without WebGL 2): `route.ts` (9 places, 40 stops, pure), stylised islands, ship, ribbons ("threads on the sea"), `landings/` cave, arena, hall. |
|
||||
| `src/app/frieze/` | `friezeSvg(sagaId)` for the 9 sagas from a figure kit; `createCurtain(layer)`: drop, lift (part along the ground line, or zoom through a doorway), fade, `groundY()`. |
|
||||
| `src/app/panel/` | `mountPanel`: About (facts, Odyssey reading view), Listen & lyrics (real player, line-synced lyrics, Watch), Paths (strip when the timeline is hidden, motif rows, nine-place chart), Fan works. |
|
||||
| `src/app/timeline/` | `layoutTimeline` (pure: current saga expanded, others compact) and `mountTimeline`: stops, you-are-here, threads, hide/show, "Threads on the sea". |
|
||||
| `src/app/content.ts` | Client for `/content/*`: library match by normalised title, lyrics (`.lrc` timed / `.txt`), Homer books, passage start by keyword. |
|
||||
| `src/data/album/` | `sagas`, `songs` (40, album order, `landing`), `motifs` (character themes derived from singers, except Odysseus), `fanWorks` (empty in v1), query helpers. |
|
||||
| `tests/` | `data.test.ts` (dataset invariants), `shared.test.ts` (DOM modules under happy-dom), `server.test.ts` (every route 200s, index links every registry slug, each mockup has the credits hook), `content.test.ts` (LRC, library scan, Homer split, `/content/*` routes on a temp fixture), `album.test.ts`, `state.test.ts`, `voyage.test.ts` (transitions, controller with fakes), `route.test.ts`, `frieze.test.ts`, `timeline.test.ts`, `panel.test.ts`, `app-content.test.ts`. |
|
||||
| `Dockerfile` | Two-stage `oven/bun:1.3`: install + build, then copy `dist/` and run from `WORKDIR /app/dist`. |
|
||||
|
||||
## Data flow
|
||||
1. Request hits `Bun.serve`; an HTML-import route returns the bundled page (dev: bundled on demand with HMR
|
||||
via `bun --hot`; production: prebuilt assets from `dist/`).
|
||||
2. The page's `main.ts` imports `src/data/*` and `src/shared/*` (bundled in; no runtime API).
|
||||
3. `main.ts` renders the direction and calls `mountCredits()` into `<footer data-fan-credits>`.
|
||||
2. The page's `main.ts` imports `src/data/*` and `src/shared/*` (bundled in) and calls `mountCredits()`.
|
||||
3. App: the URL becomes `AppState`; every change goes through the store. The controller moves the stage
|
||||
(curtain + world), and the panel and timeline re-render from the store. The panel fetches
|
||||
`/content/library.json`, the song's audio and lyrics, and Homer books on demand; all of it is optional.
|
||||
|
||||
## External dependencies
|
||||
Optional at runtime: the content folder named by `CONTENT_DIR` (real audio, lyrics, cover art, Butler's
|
||||
Odyssey and Iliad). It is mounted read-only, never committed or baked into the image (spec 003). Without it
|
||||
Optional at runtime: the content folder named by `CONTENT_DIR` (real audio, lyrics, cover art, the Odyssey and
|
||||
Iliad: Butcher & Lang / Lang, Leaf & Myers preferred, Butler as fallback). It is mounted read-only, never committed or baked into the image (spec 003). Without it
|
||||
the site works and `/content/*` answers 404. Otherwise none. Fonts (`@fontsource/*`) and three.js are bundled from npm. Outbound links only: YouTube
|
||||
(video cards) and Project Gutenberg (Odyssey references) — the site works without them.
|
||||
|
||||
@@ -47,6 +61,8 @@ the site works and `/content/*` answers 404. Otherwise none. Fonts (`@fontsource
|
||||
(`tests/server.test.ts`).
|
||||
- Every motif id used by a part exists; every motif appears in at least one song (`tests/data.test.ts`).
|
||||
- No two mockups ship byte-identical `style.css` (the build fails otherwise).
|
||||
- The album has 40 songs whose titles and order match the content folder (`tests/album.test.ts`).
|
||||
- No lyric text in the repo; lyrics are fetched from `/content/` at runtime only.
|
||||
|
||||
## Known rough edges
|
||||
- The production bundle only runs with `dist/` as the cwd (`bun run start`, Docker `WORKDIR /app/dist`);
|
||||
|
||||
@@ -90,3 +90,18 @@ Roman names (Ulysses, Minerva, Jove); EPIC uses the Greek ones. Both are public
|
||||
**Decision:** the album data has no Odysseus character theme. **Why:** character themes are derived from
|
||||
singers, and Odysseus sings in 35 of 40 songs, so his thread marked almost every stop and crowded the
|
||||
timeline ("+28 more"). His story is carried by the musical motifs (Warrior of the Mind, Just a Man, …).
|
||||
|
||||
## 2026-10-30 — The voyage app (spec 002): structure and v1 choices
|
||||
|
||||
- **Album data is separate from the mockups' data** (`src/data/album/` vs `src/data/types.ts`/`sample.ts`),
|
||||
so the 17 mockups keep working unchanged.
|
||||
- **App at `/`, gallery at `/mockups/`.** The mockups stay served as the design record.
|
||||
- **Odyssey references are a book plus keywords**, not line numbers: the public-domain prose translations
|
||||
have no line numbers. Links point at Butcher & Lang (Gutenberg #1728).
|
||||
- **Landings:** cave = Polyphemus, Survive; arena = God Games; hall = all five Ithaca songs.
|
||||
- **Transitions:** same saga sails, the adjacent saga drops its frieze, further jumps fade. Last request
|
||||
wins; a curtain left down by a cancelled step is lifted by the next one.
|
||||
- **Play voyage is silent** (3.4s per song): starting audio without a click is close to autoplay.
|
||||
- **Fan works are empty in v1** with an empty state; no invented works are shown.
|
||||
- **Without WebGL** the frieze is the stage (dropped, never lifted), so the album stays browsable.
|
||||
- **Back to sea** leaves a landing but keeps the song.
|
||||
|
||||
@@ -88,3 +88,13 @@ Small, specific, independently actionable. Remove only when actually done.
|
||||
|
||||
- Homer books: with Butler's fallback text, the last book (Odyssey XXIV) also swallows the translator's
|
||||
notes after it. The preferred Butcher & Lang text does not have this problem.
|
||||
|
||||
- App v1 (spec 002) follow-ups:
|
||||
- 97 album values are `// TODO(verify)` (story years, some singers, Odyssey books/keywords, facts).
|
||||
- Parts and motifs are not time-aligned to the recordings; needs a mapping file in the content folder
|
||||
(part → first lyric line). Then the lyrics can show part headings and motif colours per line.
|
||||
- Passage starts in the Odyssey reading view are keyword guesses.
|
||||
- Fan works: decide sourcing, credit and permission process; the data type exists, the list is empty.
|
||||
- Mobile (<900px) is usable but not polished; no favicon yet (one 404 per page load).
|
||||
- Some frieze figures are small at five songs per saga; Charybdis reads mainly as a ring from the ship.
|
||||
- Timeline motif inks on parchment can look alike (Warrior / Thunder Bringer / Zeus).
|
||||
|
||||
Reference in New Issue
Block a user