feat(mockups): slot 13 curtain call; allow three.js in 13

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-30 00:15:33 +01:00
co-authored by Claude Opus 5.5
parent b511d67f4e
commit df8ab8d174
12 changed files with 109 additions and 4 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
# 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 only), Docker.
mockups. Bun 1.3.14 (server, bundler, test runner), TypeScript, three.js (mockups 06/07/13 only), 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`.
@@ -10,7 +10,7 @@ Deeper context on demand: `docs/ARCHITECTURE.md`, `docs/DECISIONS.md`, `docs/TOD
Install: `bun install --frozen-lockfile`
Dev: `bun run dev` (`PORT` env, default 3000)
Test: `bun test` | Single file: `bun test tests/data.test.ts` | Single case: `bun test -t "registry has 12"`
Test: `bun test` | Single file: `bun test tests/data.test.ts` | Single case: `bun test -t "registry has 13"`
Typecheck: `bun run typecheck`
Build: `bun run build` | Run the build: `bun run start`
Docker: `docker build -t epic-timeline .` then `docker run --rm -p 3000:3000 epic-timeline`
+5
View File
@@ -48,3 +48,8 @@ merge request. Both override the superpowers default paths.
the repo every session — measurably slower and more tokens).
**Consequences:** Rule changes happen in one file; pointer files must stay thin. Plans are working
artefacts: kept after merge they go stale and contradict the code, so the spec is the record.
## 2026-10-30 — three.js allowed in mockup 13
**Decision:** mockup 13 (Curtain call) may use three.js, like 06 and 07. It mocks the frieze → 3D sea →
frieze → cave transition, which needs the 3D sea. three.js is already a dependency; nothing new is added.
+1 -1
View File
@@ -22,7 +22,7 @@ or `AGENTS.md` Commands.
| `bun install --frozen-lockfile` | worked (no changes) |
| `bun test` | worked — 34 pass, 0 fail, 3 files |
| `bun test tests/data.test.ts` | worked — 10 pass |
| `bun test -t "registry has 12"` | worked — 1 pass |
| `bun test -t "registry has 13"` | worked — 1 pass |
| `bun run typecheck` | worked (`tsc --noEmit`, exit 0) |
| `bun run build` | worked — `dist/server.js` + hashed page assets |
| `bun run dev` (`PORT=3113`) | worked — `/` and all mockup routes 200, no-slash form 302 |
+14
View File
@@ -184,6 +184,20 @@ This file exists because of that request. It gets updated after every user messa
- *(Claude's proposal)* The 2D sea chart is now 12 Vellum atlas with threads. Next open questions are
the look of the whole and how the frieze/3D layers attach to this chart.
### 13. A short mockup of the transition
*(Claude's question)* The look of the whole: 1 vellum throughout, 2 two worlds (vellum chart and 3D,
terracotta frieze only at transitions; Claude's recommendation), 3 frieze-led. Offered a short
mockup of the transition.
> "Short mockup please :)"
- The user did not pick 1/2/3 yet, so mockup **13 Curtain call** shows the transition sequence with
the look as a toggle (1/2/3), defaulting to 2.
- Sequence: frieze (2D) → morphs into the 3D sea → ship reaches Cyclops' Island → back to the frieze
→ into the cave. The vellum chart (12) appears as the map on the captain's table at sea.
- three.js is allowed in 13 (it is already a dependency; it reuses 07's sea).
## 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,60 @@
# 13 Curtain call — short transition mockup brief
## Why
The user's idea (verbatim, from `docs/design/002-direction-round-notes.md` §3):
> "Maybe we can have the freize's look be the one that tries the different stuff together, like a
> curtain call from the first section to go over in the second, where it starts off 2d with the
> freize, then slowly morphs into the sea voytage, then when he lands on cyclops islands, it goes
> back to the freize, then into the cave?"
The 2D chart is decided: **12 Vellum atlas with threads**. The open question is **the look of the
whole**, so the look is a toggle in this mockup:
1. **Vellum throughout** — the frieze is redrawn as ink on vellum (an illustrated border of the chart);
the 3D sea is tinted warm, parchment-like.
2. **Two worlds** (default) — chart and 3D are vellum/naturalistic; the frieze keeps 04's terracotta
and black and appears only at transitions, like a curtain dropping between scenes.
3. **Frieze-led** — terracotta/black sets the whole tone; the 3D sea is stylised in that palette
(black-figure silhouettes, terracotta sky), and the vellum chart is one object on the table.
Toggle: visible control "Look: 1 · 2 · 3", also `?look=1|2|3`.
## The sequence (short — about 20–30 s total with Play)
Five beats, each a step. Controls: Play/Pause, ← / → step, step dots with labels, `?beat=1..5`.
1. **Frieze.** 04's frieze band: Troy figures, the horse. Caption "Troy falls".
2. **Morph to sea.** The frieze band's ground line becomes the horizon; figures flatten away / the band
unrolls and the camera tilts down into 07's 3D sea with the ship. The vellum chart (12's general
chart, small, static is fine — a simplified SVG redraw is fine) lies as the captain's-table map in a
corner with "You are here".
3. **Landfall.** The ship sails to Cyclops' Island; the island rises from the sea.
4. **Back to the frieze.** The curtain drops: the frieze band returns with Cyclops-saga figures
(Polyphemus, sheep, the ship's crew), caption "Polyphemus".
5. **Into the cave.** The frieze parts / zooms through a cave mouth into a simple 3D cave interior
(dark rock, firelight, the giant's silhouette is enough). A "Back to sea" control returns to beat 2.
The transitions are the point. Make them smooth, honest and short (≈1–2 s each); respect
`prefers-reduced-motion` with crossfades. Content is light: a caption per beat, a song title chip
(e.g. "Survive") that opens nothing. No song detail panel needed.
## Rules
- Edit ONLY `src/mockups/13-curtain-call/` (stub files exist: index.html links credits; main.ts calls
`mountCredits()`; replace the stub style.css with unique content). three.js is allowed here (already a
dependency). Reuse by copying from `src/mockups/04-greek-frieze/` (figures.ts, glyphs.ts) and
`src/mockups/07-sea-voyage/` (scene.ts, route.ts) into your folder — don't import across mockup folders.
- Look at screenshots/04-*, 07-*, 12-* before designing. Credits footer fully visible in every beat.
- Build to `/tmp/claude-1000/-home-user-epic-the-musical-timeline/fb650f6d-47a0-47e4-8f6b-197d02417f3b/scratchpad/r3-13`,
serve with `cd <outdir> && PORT=4113 bun server.js`. Kill only your own server.
- Screenshots 1440×900, `--use-gl=swiftshader --virtual-time-budget=6000` (WebGL headless):
`screenshots/13-curtain-call-beat{1..5}.png` at look 2, plus `-beat2-look1.png`, `-beat2-look3.png`,
`-beat4-look1.png`, `-beat4-look3.png`. If possible also capture a mid-transition frame
(`-morph.png`, e.g. `?beat=2&t=0.5` to freeze a transition at 50%). Look at every one; fix what's off.
- `bun test` and `bun run typecheck` must pass. Don't commit. Don't spawn subagents.
- If you run low on budget, stop and write a pause report (done / checklist / next step) instead.
- Report to `docs/design/002-sea-chart-spikes/13-curtain-call-report.md`: what you built, what works,
what doesn't, screenshots, and an honest paragraph comparing looks 1/2/3. Reply with status,
screenshot paths and concerns only.
+1
View File
@@ -25,6 +25,7 @@
<li class="card"><a href="/mockups/10-chart-scroll/"><span class="card__num">10</span><h2>Unrolling chart</h2><p>An editorial horizontal scroll whose ground is a long chart unrolling as you pan; motif threads trail beneath the route.</p><p class="card__try">Try this: Pan sideways and watch the ship and coastline move with you.</p></a></li>
<li class="card"><a href="/mockups/11-chart-atlas/"><span class="card__num">11</span><h2>Atlas with insets</h2><p>An overview chart of the whole sea; each saga island opens an inset chart with its songs as ports.</p><p class="card__try">Try this: Open an island's inset, then a song, then zoom back out.</p></a></li>
<li class="card"><a href="/mockups/12-vellum-atlas/"><span class="card__num">12</span><h2>Vellum atlas</h2><p>The atlas overview and island plates of 11, drawn in the vellum portolan style of 10.</p><p class="card__try">Try this: Open Cyclops' Island, pick a theme, and switch the plate's motif style (A/B/C).</p></a></li>
<li class="card"><a href="/mockups/13-curtain-call/"><span class="card__num">13</span><h2>Curtain call</h2><p>The frieze curtain that carries you from the 2D story into the 3D sea and down into a landing.</p><p class="card__try">Try this: Press Play, or step through: frieze, sea, Cyclops' Island, frieze, cave. Switch the look (1/2/3).</p></a></li>
</ul>
</main>
<footer data-fan-credits></footer>
+15
View File
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Curtain call — EPIC Timeline mockup</title>
<link rel="stylesheet" href="../../shared/credits.css" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<main id="app"></main>
<footer data-fan-credits></footer>
<script type="module" src="./main.ts"></script>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
import { mountCredits } from "../../shared/credits";
mountCredits();
+1
View File
@@ -0,0 +1 @@
body { margin: 0; --stub-13: 1; }
+3
View File
@@ -37,4 +37,7 @@ export const mockups: MockupInfo[] = [
{ slug: "12-vellum-atlas", number: 12, name: "Vellum atlas",
pitch: "The atlas overview and island plates of 11, drawn in the vellum portolan style of 10.",
tryThis: "Open Cyclops' Island, pick a theme, and switch the plate's motif style (A/B/C)." },
{ slug: "13-curtain-call", number: 13, name: "Curtain call",
pitch: "The frieze curtain that carries you from the 2D story into the 3D sea and down into a landing.",
tryThis: "Press Play, or step through: frieze, sea, Cyclops' Island, frieze, cave. Switch the look (1/2/3)." },
];
+3
View File
@@ -12,6 +12,7 @@ import m8 from "./mockups/09-chart-transit/index.html";
import m9 from "./mockups/10-chart-scroll/index.html";
import m10 from "./mockups/11-chart-atlas/index.html";
import m11 from "./mockups/12-vellum-atlas/index.html";
import m12 from "./mockups/13-curtain-call/index.html";
export function startServer(port: number): Server<undefined> {
return Bun.serve({
@@ -42,6 +43,8 @@ export function startServer(port: number): Server<undefined> {
"/mockups/11-chart-atlas": Response.redirect("/mockups/11-chart-atlas/", 302),
"/mockups/12-vellum-atlas/": m11,
"/mockups/12-vellum-atlas": Response.redirect("/mockups/12-vellum-atlas/", 302),
"/mockups/13-curtain-call/": m12,
"/mockups/13-curtain-call": Response.redirect("/mockups/13-curtain-call/", 302),
},
development: process.env.NODE_ENV !== "production",
fetch() {
+1 -1
View File
@@ -8,7 +8,7 @@ let base: string;
beforeAll(() => { server = startServer(0); base = `http://localhost:${server.port}`; });
afterAll(() => server.stop(true));
test("registry has 12 mockups", () => expect(mockups).toHaveLength(12));
test("registry has 13 mockups", () => expect(mockups).toHaveLength(13));
test("index links every mockup", async () => {
const res = await fetch(base + "/");