feat(274): transect composition synced to the lyric

Song274Transect draws Route 274 in its true shape (the "Ground" view of
song-274/route/274-transect.html) and plays it along with the song.

- data.ts: reads song-274/route/ at load time (lyrics index, roster, route and
  the optional east/west stubs); nothing is copied into the code. Only sung
  nodes are shown; districts and 東に続く light nothing.
- progress.ts: a "covered so far" paint over the dimmed road. Its head is pinned
  to each sung object's km as that line starts and glides between pins on a
  monotone cubic, so it never runs backwards. After 東に続く it runs out along
  the east stub; the west stub is always painted.
- camera.ts: fixed close zoom during each narration part (lines under 20 s
  apart), following a Gaussian-smoothed head; pull back to frame the covered
  road plus some road ahead during breaks, and to the full map after the lyric.
- nodeAnimation.ts: lightUp / switchOff as reusable functions over a NodeLook,
  with long lines switching off after MAX_LIT_S.
- palette.ts: every colour as a named token (dark default, light alongside),
  including shrine and railway families.

remotion.config.ts sets song-274/ as the public dir so staticFile() reaches the
song's audio/ and route/ in place.

Verified: eslint + tsc clean; stills checked across parts, breaks and the ending.
Render with --muted and add audio with ffmpeg: Remotion's own AAC lands 42.75 ms
late.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
This commit is contained in:
2026-09-17 09:40:53 +09:00
parent 7da9a6df25
commit bc39455cfe
10 changed files with 969 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
/**
* Every colour the 274 transect video draws, named by what it is for rather than
* what it looks like. Change values here, or override `palette` in the Studio's
* input props, without touching the drawing code.
*
* The dark palette is the video's own, grown from the dark palette of
* `song-274/route/274-transect.html`; comments name the page's CSS variable each
* token corresponds to. The light palette is the page's, extended with the
* tokens the video added (shrine, railway).
*/
export type TransectPalette = {
/** Frame background. */
background: string;
/** The whole road, before the song has covered it. */
roadAhead: string;
/** The road the song has covered so far. */
roadCovered: string;
/** Node fill per feature family, as on the page's legend. */
bridge: string;
tunnel: string;
shed: string;
watercourse: string;
place: string;
/** Stations and every 信号場, whatever their roster kind. */
railway: string;
shrine: string;
other: string;
};
export type FeatureFamily =
| "bridge"
| "tunnel"
| "shed"
| "watercourse"
| "place"
| "railway"
| "shrine"
| "other";
export const darkPalette: TransectPalette = {
background: "#28323d", // --paper
roadAhead: "#49565f", // --rule
roadCovered: "#9dcde4", // --road
bridge: "#79d1c5", // --bridge
tunnel: "#b6c5c1", // --tunnel
shed: "#eba9eb", // --shed
watercourse: "#5FB6D6", // --water
place: "#129252", // --place
railway: "#129252", // --railway
shrine: "#b32c2c", // --shrine
other: "#ff9900", // --util
};
export const lightPalette: TransectPalette = {
background: "#E7ECEE",
roadAhead: "#C0CCD2",
roadCovered: "#2E4A5A",
bridge: "#116B62",
tunnel: "#3A4650",
shed: "#7A5AA6",
watercourse: "#2F7E9E",
place: "#8A6D1F",
railway: "#1F6E43",
shrine: "#9B2C2C",
other: "#5B6B75",
};