#!/usr/bin/env node // captions.mjs — build the captions sub-composition from STORYBOARD + audio_meta. // // One mode: `build`. Reads STORYBOARD.md (frame order + durations → cumulative // frame starts) + audio_meta.json (voices[].words, frame-relative) → absolute- // timed caption groups → writes: // compositions/captions.html — a self-contained sub-composition the index // assembler mounts on its captions track (data-composition-id="captions"). // caption_groups.json — the computed groups (debug / inspection / --out). // caption-overrides.json — an empty `[]` shim (silences the captions runtime's // validate-time fetch; only written when captions.html is). // No narration / no words → legal skip: nothing written, assemble-index then omits // the captions track (it keys off compositions/captions.html existence). // // node captions.mjs build --storyboard ./STORYBOARD.md --audio-meta ./audio_meta.json --hyperframes . --out ./caption_groups.json // // CAPTION LOOK — two sources, picked automatically: // 1. PRESET SKIN (preferred). If a project-local `.hyperframes/caption-skin.html` // exists (Step 2 copies the chosen frame-preset's skin into the project), it is // the caption look. // It is a brand-token-strict skin with three reserved holes; this script fills them // and wraps the result in a