…
diff --git a/skills/hyperframes/SKILL.md b/skills/hyperframes/SKILL.md
index c3bf5cfaf..1b84d81b1 100644
--- a/skills/hyperframes/SKILL.md
+++ b/skills/hyperframes/SKILL.md
@@ -51,18 +51,18 @@ Routing needs to know **what the video is about** — its input and subject. If
## Workflow cheat-sheet
-| Workflow | Use it for |
-| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
-| `/product-launch-video` | Marketing / launching / promoting a **product** — from its URL, a brief, or a script (even if the site is only named) |
-| `/website-to-video` | Turning a **general website** into a video — site tour, portfolio / landing-page showcase, social clip from the site's visuals |
-| `/faceless-explainer` | **Explaining a topic / concept** from text — no product, no URL; every visual is LLM-invented |
-| `/pr-to-video` | A **GitHub PR / code change** → changelog / feature-reveal / fix / refactor explainer |
-| `/embedded-captions` | Adding **captions / subtitles** to an existing talking-head video (footage untouched) |
-| `/graphic-overlays` | Packaging an existing talking-head video with **designed graphic overlays** — lower-thirds, data callouts, kinetic titles, pull-quotes |
-| `/motion-graphics` | A short, **unnarrated, design-led motion graphic** — kinetic type, a stat / chart hit, a logo sting, a lower-third overlay |
+| Workflow | Use it for |
+| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `/product-launch-video` | Marketing / launching / promoting a **product** — from its URL, a brief, or a script (even if the site is only named) |
+| `/website-to-video` | Turning a **general website** into a video — site tour, portfolio / landing-page showcase, social clip from the site's visuals |
+| `/faceless-explainer` | **Explaining a topic / concept** from text — no product, no URL; every visual is LLM-invented |
+| `/pr-to-video` | A **GitHub PR / code change** → changelog / feature-reveal / fix / refactor explainer |
+| `/embedded-captions` | Adding **captions / subtitles** to an existing talking-head video (footage untouched) |
+| `/graphic-overlays` | Packaging an existing talking-head video with **designed graphic overlays** — lower-thirds, data callouts, kinetic titles, pull-quotes |
+| `/motion-graphics` | A short, **unnarrated, design-led motion graphic** — kinetic type, a stat / chart hit, a logo sting, a lower-third overlay |
| `/music-to-video` | A **music track** → a **beat-synced** video — lyric video, slideshow, or kinetic promo; the music drives pacing (optional user images / videos cut onto the beat grid) |
-| `/general-video` | **Anything else** — longer or multi-scene pieces, a static loop / poster, a custom composition |
-| `/remotion-to-hyperframes` | **Porting an existing Remotion (React) composition** to HyperFrames (migration, not creation) |
+| `/general-video` | **Anything else** — longer or multi-scene pieces, a static loop / poster, a custom composition |
+| `/remotion-to-hyperframes` | **Porting an existing Remotion (React) composition** to HyperFrames (migration, not creation) |
**Disambiguation (only where confusable):**
diff --git a/skills/music-to-video/SKILL.md b/skills/music-to-video/SKILL.md
index 63aca5dd6..fdddec53a 100644
--- a/skills/music-to-video/SKILL.md
+++ b/skills/music-to-video/SKILL.md
@@ -16,7 +16,7 @@ Workflow: Step 0 setup → `hyperframes.json` + `assets/bgm.mp3`; Step 1 analyze
## Two ideas that shape everything
- **One analyzer, and you trust it.** `analyze-beatgrid.py` is the only beat analyzer — never re-measure beats with another tool or by ear. Its energy / density / rolls / onsets / silences are always reliable. Its `bpm` and `beats_sec` are reliable **only when the music is genuinely rhythmic**; on calm music the grid is a metronome the tracker imposed, so pace by phrases and energy instead and never hard-cut to it. Deciding which case you're in is each frame's `pacing` (Step 2).
-- **One frame = one file; groups live inside.** Step 2 cuts the track into **frames**, and each frame becomes one composition file `compositions/frames/NN-
.html`, built by one frame-worker. A frame can subdivide into **groups** (each a template or a motion-primitives combo). Extra density goes *inside* a group, so **frame count tracks distinct treatments, not beats** — a fast track does not blow up the number of sub-agents.
+- **One frame = one file; groups live inside.** Step 2 cuts the track into **frames**, and each frame becomes one composition file `compositions/frames/NN-.html`, built by one frame-worker. A frame can subdivide into **groups** (each a template or a motion-primitives combo). Extra density goes _inside_ a group, so **frame count tracks distinct treatments, not beats** — a fast track does not blow up the number of sub-agents.
---
@@ -166,16 +166,16 @@ Inspect at `t=0`, each frame start, the strongest DROP / SURGE, every `hard_stop
**Scripts** under `scripts/`: `analyze-beatgrid.py` (the one analyzer), `validate-plan.mjs` (plan check), `assemble-index.mjs` (index assembly), `stage-assets.mjs` (stage user media), `lib/storyboard.mjs` (vendored parser). Everything else is the `hyperframes` CLI.
-| Read | When |
-| ------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
-| [`references/frame-skeleton.md`](references/frame-skeleton.md) | Step 2: read the music, lay out the frames, set pacing |
+| Read | When |
+| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
+| [`references/frame-skeleton.md`](references/frame-skeleton.md) | Step 2: read the music, lay out the frames, set pacing |
| [`references/planning.md`](references/planning.md) · [`storyboard-format.md`](references/storyboard-format.md) | Step 3: pick the brand, fill each frame, write the plan |
-| [`references/template-catalog.md`](references/template-catalog.md) | Step 3: pick a template per group |
-| [`references/motion-primitive-catalog.md`](references/motion-primitive-catalog.md) | Step 3/4: L0 recipes for free-compose |
-| [`references/montage.md`](references/montage.md) | Step 3/4: asset treatments (beat-cut / ken-burns) |
-| [`sub-agents/frame-worker.md`](sub-agents/frame-worker.md) | Step 4: dispatch + build one frame |
-| `../hyperframes-core/references/subagent-dispatch.md` | Step 4: dispatch sub-agents safely |
-| `../hyperframes-creative/references/design-spec.md` | Step 3: pick the preset (the brand) |
+| [`references/template-catalog.md`](references/template-catalog.md) | Step 3: pick a template per group |
+| [`references/motion-primitive-catalog.md`](references/motion-primitive-catalog.md) | Step 3/4: L0 recipes for free-compose |
+| [`references/montage.md`](references/montage.md) | Step 3/4: asset treatments (beat-cut / ken-burns) |
+| [`sub-agents/frame-worker.md`](sub-agents/frame-worker.md) | Step 4: dispatch + build one frame |
+| `../hyperframes-core/references/subagent-dispatch.md` | Step 4: dispatch sub-agents safely |
+| `../hyperframes-creative/references/design-spec.md` | Step 3: pick the preset (the brand) |
## Directory layout
diff --git a/skills/music-to-video/references/frame-skeleton.md b/skills/music-to-video/references/frame-skeleton.md
index 5293b68a6..76d566a9e 100644
--- a/skills/music-to-video/references/frame-skeleton.md
+++ b/skills/music-to-video/references/frame-skeleton.md
@@ -17,10 +17,10 @@ edits the same file.
`audiomap.json` is one analyzer's output. Some fields are robust on **any** music; some are
reliable only when the music is **actually rhythmic**. This decides each frame's `pacing`:
-| Field | Trust |
-| --- | --- |
-| `energy_phases[]` (level / energy / density / feel), `events[]` + `onset_rate`, `rolls[]` (and their **absence**), `silences[]`, `hard_stops[]`, `key_moments[]`, `phrases[]`, `audio.duration_sec` | **Always** — robust measurements |
-| `tempo.bpm`, `grid.beats_sec` / `downbeats_sec` **precision** | **Only when the music is rhythmic.** On calm / sparse material the beat grid is a metronome the tracker *imposes* (often octave-doubled) — usually **more grid beats than real onsets**. Do **not** anchor cuts to it there. |
+| Field | Trust |
+| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `energy_phases[]` (level / energy / density / feel), `events[]` + `onset_rate`, `rolls[]` (and their **absence**), `silences[]`, `hard_stops[]`, `key_moments[]`, `phrases[]`, `audio.duration_sec` | **Always** — robust measurements |
+| `tempo.bpm`, `grid.beats_sec` / `downbeats_sec` **precision** | **Only when the music is rhythmic.** On calm / sparse material the beat grid is a metronome the tracker _imposes_ (often octave-doubled) — usually **more grid beats than real onsets**. Do **not** anchor cuts to it there. |
- **Grid is reliable** when: rolls present, and/or dense phases, and/or high `onset_rate` with a steady grid.
- **Grid is fictional** when: `rolls`≈0, mostly `sparse` phases, low `onset_rate` → pace by `phrases[]` + `energy_phases[]`, not beats.
@@ -58,30 +58,36 @@ A valid `STORYBOARD.md` with the spine set and every frame's treatment left for
```markdown
---
compositionId: bgm
-duration_s: 30.0 # == audiomap.audio.duration_sec
+duration_s: 30.0 # == audiomap.audio.duration_sec
canvas: { w: 1920, h: 1080, fps: 30 }
-style: # blank — Step 3 fills it from the chosen frame.md preset
+style: # blank — Step 3 fills it from the chosen frame.md preset
build_notes: ["one paused timeline per frame", "no remote assets"]
---
## Frame 1 — f1
+
- src: compositions/frames/01-f1.html
-- duration: 7.198s # = span length; assembler sums these for cumulative data-start
-- span_sec: [0.0, 7.198] # track seconds; frames tile the track
+- duration: 7.198s # = span length; assembler sums these for cumulative data-start
+- span_sec: [0.0, 7.198] # track seconds; frames tile the track
- pacing: beat_cut
- mood: [hype]
- feel: accelerating onset stream building into a held downbeat
+
### Groups
+
- TBD (Step 3)
## Frame 2 — f2
+
- src: compositions/frames/02-f2.html
- duration: 10.4s
- span_sec: [7.198, 17.598]
- pacing: phrase_flow
- mood: [warm, cinematic]
- feel: calm held pad, one long onset desert
+
### Groups
+
- TBD (Step 3)
```
diff --git a/skills/music-to-video/references/montage.md b/skills/music-to-video/references/montage.md
index 2dc7bd9e3..2ed3af840 100644
--- a/skills/music-to-video/references/montage.md
+++ b/skills/music-to-video/references/montage.md
@@ -11,6 +11,7 @@ realizes it inside the frame file (HOW). **Obey the frame's `pacing`.**
## The three treatments
### `beat_cut` — one clip per anchor (only on a `beat_cut` frame)
+
The asset-driven analogue of a per-onset typographic group: cut to a new clip on each anchor
(the frame's beats/onsets from the audiomap). Each clip is a `class="clip"` element
(`
` for a photo, **muted** `