mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
style(skills): apply oxfmt to music-to-video and router docs
Fixes the Format / Preflight CI checks on the new skill files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
f012b8b846
commit
265b02738e
@@ -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-<frame_id>.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-<frame_id>.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
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
(`<img>` for a photo, **muted** `<video>` for a motion clip) placed at its anchor with
|
||||
@@ -21,12 +22,14 @@ crossfade the outgoing content to `opacity:0` ending **at** the next anchor, the
|
||||
Cut on the **strong** anchors; land a hero clip on a `key_moment`/downbeat.
|
||||
|
||||
### `ken_burns` — slow push on one clip (fits a `phrase_flow` frame)
|
||||
|
||||
For calm frames: one clip held over the span with a slow scale/translate push (e.g. scale
|
||||
1.0→1.08 + a small drift) eased across the whole `span_sec` — paced by the frame, not by
|
||||
beats. No hard cuts. Crossfade in/out at the frame edges. This is the right asset treatment
|
||||
when the beat grid is unreliable (calm music).
|
||||
|
||||
### `bg_under_text` — clip dimmed behind a template/free group
|
||||
|
||||
A full-bleed clip dimmed ~30–50% as the background of a group whose foreground is a template
|
||||
or free-compose typographic treatment. The text rides on the same anchors; the clip is the
|
||||
bed. Use when the user wants their footage present but the message must stay readable.
|
||||
|
||||
@@ -29,54 +29,54 @@ Quantified from frame-accurate reverse engineering of beat-synced reels:
|
||||
Each runnable recipe is self-contained and lint-clean: showcase chrome stripped, system-font
|
||||
fallback, shared `../assets/gsap.min.js`, one paused timeline on `window.__timelines["main"]`.
|
||||
|
||||
| id | anchor | what it does | recipe |
|
||||
| -------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| `hypercut-whip` | beat / hard_stop | fast whip-pan hard cut between frames | ✓ |
|
||||
| `kinetic-letter-in` | downbeat / phrase | per-letter kinetic entrance | ✓ |
|
||||
| `braam-punch` | drop / surge | big "braam" impact — scale + weight slam | ✓ |
|
||||
| `chromatic-split` | snare / glitch / surge | RGB channel split / glitch on a word | ✓ |
|
||||
| `mask-reveal` | section_start / downbeat | clip-path mask wipe reveal | ✓ |
|
||||
| `screen-shake` | drop / crash / kick | camera / screen shake jitter | ✓ |
|
||||
| `binary-decrypt` | roll / build | scramble→decode text (binary → word) | ✓ |
|
||||
| `dolly-zoom` | phrase / build | vertigo dolly-zoom (scale vs perspective) | ✓ |
|
||||
| `iris-open` | section_start / reveal | circular iris-open reveal | ✓ |
|
||||
| `electric-arc` | accent / glitch | electric arc / lightning accent | ✓ |
|
||||
| `neon-flicker` | hold / texture | neon-sign flicker | ✓ |
|
||||
| `chrome-sweep` | downbeat / reveal | metallic specular sweep across text | ✓ |
|
||||
| `slot-machine-reveal`| roll → downbeat | slot-machine spin-to-land character reveal | ✓ |
|
||||
| `liquid-morph` | phrase / transition | liquid / blob morph | ✓ |
|
||||
| `gooey-metaball` | build / drop | gooey metaball merge field | ✓ |
|
||||
| `3d-card-flip` | downbeat / swap | 3D card flip (rotateY) | ✓ |
|
||||
| `crash-zoom-in` | drop / surge | violent crash zoom-in | ✓ |
|
||||
| `spotlight-sweep` | reveal / hold | spotlight / gradient sweep over text | ✓ |
|
||||
| `outline-to-fill` | downbeat / reveal | stroke outline → solid fill | ✓ |
|
||||
| `counting-punch` | roll → downbeat | number count-up that punches & locks | ✓ |
|
||||
| `particle-burst` | drop / crash | particle explosion burst | ✓ |
|
||||
| `radial-burst-lines` | drop / surge | radial speed-lines burst | ✓ |
|
||||
| `pixel-dissolve` | transition / hard_stop | pixelated dissolve | ✓ |
|
||||
| `datamosh-smear` | glitch / transition | datamosh / motion smear | ✓ |
|
||||
| `text-wave-distort` | hold / texture | wavy text distortion | ✓ |
|
||||
| `bg-flow-field` | energy / whole span (bed) | generative WebGL curl-noise flow-field **background bed** — palette-driven, breathes on the energy envelope; compose any foreground move over it | ✓ |
|
||||
| `blur-resolve` | stop / final hold | blur-in to crisp focus, then blur-out on the cut | ✓ |
|
||||
| `chromatic-pressure` | snare / glitch | RGB split / digital tension on a transient | ≈ `chromatic-split` |
|
||||
| `color-grid-shuffle` | onset | grid of cells recoloured by a deterministic index per onset | — |
|
||||
| `content-swap` | beat | 0ms swap of stacked nodes — the workhorse percussive move | ≈ `slot-machine-reveal` |
|
||||
| `directional-fill` | beat / reveal | directional wipe-fill (scaleX) sweeping across bars | ✓ |
|
||||
| `flash-cut` | drop / crash | full-frame flash masking a word / colour state change | ✓ |
|
||||
| `freeze-hold` | hard_stop | freeze the moving system and hold it (desaturate + vignette) | — |
|
||||
| `hard-cut` | beat / hard_stop | sample-accurate 0ms colour-block + word cut | ✓ |
|
||||
| `mosaic-pack` | beat / build | scattered tiles fly in and pack into a grid | ✓ |
|
||||
| `negative-space-hold`| silence / hard_stop / final hold | kill busy layers, hold one readable mark in empty space | — |
|
||||
| `overlay-pop` | accent | badge / lower-third overlay pops in over a base | — |
|
||||
| `palette-flip` | section change | same layout re-skins via 0ms palette-variable flips | ✓ |
|
||||
| `staggered-exit` | phrase / transition | ordered cascade-out clearing the frame | ✓ |
|
||||
| `staggered-reveal` | build | ordered cascade-in of a stack / list | — |
|
||||
| `system-replace` | drop / regime change | hard-cut the entire visual system, then boot the new one | — |
|
||||
| `text-spectral-rays` | phrase / sweep (hero text) | volumetric spectral light-rays cast by a wordmark toward a sweeping light cursor — grain + RGB-split; **WebGL hero-text system** | ✓ |
|
||||
| `tile-mosaic` | build / reveal | grid of tiles revealed in a diagonal sweep, assembling a poster | ✓ |
|
||||
| `typewriter-reveal` | roll / build | char / word type-on, explicit per-span set (no `stagger`) + caret | ✓ |
|
||||
| `value-counter` | roll → downbeat | count-up that locks on a downbeat / hard_stop | ≈ `counting-punch` |
|
||||
| `word-grid-burst` | onsets → downbeat | grid of words revealed per onset, refocus one on a downbeat | ✓ |
|
||||
| id | anchor | what it does | recipe |
|
||||
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
|
||||
| `hypercut-whip` | beat / hard_stop | fast whip-pan hard cut between frames | ✓ |
|
||||
| `kinetic-letter-in` | downbeat / phrase | per-letter kinetic entrance | ✓ |
|
||||
| `braam-punch` | drop / surge | big "braam" impact — scale + weight slam | ✓ |
|
||||
| `chromatic-split` | snare / glitch / surge | RGB channel split / glitch on a word | ✓ |
|
||||
| `mask-reveal` | section_start / downbeat | clip-path mask wipe reveal | ✓ |
|
||||
| `screen-shake` | drop / crash / kick | camera / screen shake jitter | ✓ |
|
||||
| `binary-decrypt` | roll / build | scramble→decode text (binary → word) | ✓ |
|
||||
| `dolly-zoom` | phrase / build | vertigo dolly-zoom (scale vs perspective) | ✓ |
|
||||
| `iris-open` | section_start / reveal | circular iris-open reveal | ✓ |
|
||||
| `electric-arc` | accent / glitch | electric arc / lightning accent | ✓ |
|
||||
| `neon-flicker` | hold / texture | neon-sign flicker | ✓ |
|
||||
| `chrome-sweep` | downbeat / reveal | metallic specular sweep across text | ✓ |
|
||||
| `slot-machine-reveal` | roll → downbeat | slot-machine spin-to-land character reveal | ✓ |
|
||||
| `liquid-morph` | phrase / transition | liquid / blob morph | ✓ |
|
||||
| `gooey-metaball` | build / drop | gooey metaball merge field | ✓ |
|
||||
| `3d-card-flip` | downbeat / swap | 3D card flip (rotateY) | ✓ |
|
||||
| `crash-zoom-in` | drop / surge | violent crash zoom-in | ✓ |
|
||||
| `spotlight-sweep` | reveal / hold | spotlight / gradient sweep over text | ✓ |
|
||||
| `outline-to-fill` | downbeat / reveal | stroke outline → solid fill | ✓ |
|
||||
| `counting-punch` | roll → downbeat | number count-up that punches & locks | ✓ |
|
||||
| `particle-burst` | drop / crash | particle explosion burst | ✓ |
|
||||
| `radial-burst-lines` | drop / surge | radial speed-lines burst | ✓ |
|
||||
| `pixel-dissolve` | transition / hard_stop | pixelated dissolve | ✓ |
|
||||
| `datamosh-smear` | glitch / transition | datamosh / motion smear | ✓ |
|
||||
| `text-wave-distort` | hold / texture | wavy text distortion | ✓ |
|
||||
| `bg-flow-field` | energy / whole span (bed) | generative WebGL curl-noise flow-field **background bed** — palette-driven, breathes on the energy envelope; compose any foreground move over it | ✓ |
|
||||
| `blur-resolve` | stop / final hold | blur-in to crisp focus, then blur-out on the cut | ✓ |
|
||||
| `chromatic-pressure` | snare / glitch | RGB split / digital tension on a transient | ≈ `chromatic-split` |
|
||||
| `color-grid-shuffle` | onset | grid of cells recoloured by a deterministic index per onset | — |
|
||||
| `content-swap` | beat | 0ms swap of stacked nodes — the workhorse percussive move | ≈ `slot-machine-reveal` |
|
||||
| `directional-fill` | beat / reveal | directional wipe-fill (scaleX) sweeping across bars | ✓ |
|
||||
| `flash-cut` | drop / crash | full-frame flash masking a word / colour state change | ✓ |
|
||||
| `freeze-hold` | hard_stop | freeze the moving system and hold it (desaturate + vignette) | — |
|
||||
| `hard-cut` | beat / hard_stop | sample-accurate 0ms colour-block + word cut | ✓ |
|
||||
| `mosaic-pack` | beat / build | scattered tiles fly in and pack into a grid | ✓ |
|
||||
| `negative-space-hold` | silence / hard_stop / final hold | kill busy layers, hold one readable mark in empty space | — |
|
||||
| `overlay-pop` | accent | badge / lower-third overlay pops in over a base | — |
|
||||
| `palette-flip` | section change | same layout re-skins via 0ms palette-variable flips | ✓ |
|
||||
| `staggered-exit` | phrase / transition | ordered cascade-out clearing the frame | ✓ |
|
||||
| `staggered-reveal` | build | ordered cascade-in of a stack / list | — |
|
||||
| `system-replace` | drop / regime change | hard-cut the entire visual system, then boot the new one | — |
|
||||
| `text-spectral-rays` | phrase / sweep (hero text) | volumetric spectral light-rays cast by a wordmark toward a sweeping light cursor — grain + RGB-split; **WebGL hero-text system** | ✓ |
|
||||
| `tile-mosaic` | build / reveal | grid of tiles revealed in a diagonal sweep, assembling a poster | ✓ |
|
||||
| `typewriter-reveal` | roll / build | char / word type-on, explicit per-span set (no `stagger`) + caret | ✓ |
|
||||
| `value-counter` | roll → downbeat | count-up that locks on a downbeat / hard_stop | ≈ `counting-punch` |
|
||||
| `word-grid-burst` | onsets → downbeat | grid of words revealed per onset, refocus one on a downbeat | ✓ |
|
||||
|
||||
> **Template-private verbs.** Some templates declare moves realized only inside their own
|
||||
> impl — `held_lockup`, `anchor_pop_in`, `word_slot_cycle`, `per_line_color`,
|
||||
|
||||
@@ -51,19 +51,23 @@ frame is usually ONE group whose template absorbs the density internally (a meta
|
||||
## Step C — per group, pick a treatment (exactly one of three)
|
||||
|
||||
### A. Match a template
|
||||
|
||||
Read [`template-catalog.md`](template-catalog.md). Match the group's `feel` + `mood` + `pacing`
|
||||
to a template's **Reach for it when**; take the closest fit. Then bind it:
|
||||
|
||||
- Fill `params` (keys from the catalog entry) — your copy into text slots, palette from the brand spine, `duration` = the group's span length.
|
||||
- Fill `role_bindings` with this group's **real anchor seconds** read from `audiomap.json` over its span (not example times).
|
||||
- If the template's natural stop and the group's span end disagree, snap to the nearest anchor.
|
||||
|
||||
### B. Free-compose (no template fits)
|
||||
|
||||
Write a `free_design` — one visual thesis from [`motion-primitive-catalog.md`](motion-primitive-catalog.md)
|
||||
(a dominant system + the named L0 primitives + a density topology) + `anchors` (the real
|
||||
beat / onset seconds the moves ride). Free-compose is a **first-class** choice, written as
|
||||
carefully as a matched group — never a failure.
|
||||
|
||||
### C. Asset treatment (only when the user supplied assets and they fit)
|
||||
|
||||
Make it an `asset` group ([`montage.md`](montage.md)). **Obey `pacing`:** on a `beat_cut`
|
||||
frame use `beat_cut` (one clip per anchor) or `bg_under_text`; on a `phrase_flow` frame use
|
||||
`ken_burns` or a slow crossfade — **never** per-onset hard cuts. Assets are additive: if none
|
||||
|
||||
@@ -20,51 +20,54 @@ YAML frontmatter (the video-wide spine) + one `## Frame N — <frame_id>` block
|
||||
```markdown
|
||||
---
|
||||
compositionId: bgm
|
||||
duration_s: 30.0 # == audiomap.audio.duration_sec, exactly
|
||||
duration_s: 30.0 # == audiomap.audio.duration_sec, exactly
|
||||
canvas: { w: 1920, h: 1080, fps: 30 }
|
||||
style: # brand spine — from the chosen frame.md preset (Step 3)
|
||||
font: "EB Garamond / Inter / JetBrains Mono" # the preset's typography, verbatim
|
||||
palette: ["#FAF9F5", "#141413", "#CC785C", "#181715"] # ≤4–6 swatches from the preset's colors
|
||||
assets: false # false, or a note like "assets/ has 6 user photos"
|
||||
style: # brand spine — from the chosen frame.md preset (Step 3)
|
||||
font: "EB Garamond / Inter / JetBrains Mono" # the preset's typography, verbatim
|
||||
palette: ["#FAF9F5", "#141413", "#CC785C", "#181715"] # ≤4–6 swatches from the preset's colors
|
||||
assets: false # false, or a note like "assets/ has 6 user photos"
|
||||
build_notes: ["one paused timeline per frame", "no remote assets"]
|
||||
avoid: ["generic slideshow", "tiny unreadable hero text"]
|
||||
---
|
||||
|
||||
## Frame 1 — f1
|
||||
- src: compositions/frames/01-f1.html # worker writes here; assembler refs it; stem (01-f1) = composition id
|
||||
- duration: 7.198s # = span length; the assembler reads this for cumulative data-start
|
||||
- span_sec: [0.0, 7.198] # track seconds; frames tile the track
|
||||
- pacing: beat_cut # beat_cut | phrase_flow (from the skeleton; obey it)
|
||||
|
||||
- src: compositions/frames/01-f1.html # worker writes here; assembler refs it; stem (01-f1) = composition id
|
||||
- duration: 7.198s # = span length; the assembler reads this for cumulative data-start
|
||||
- span_sec: [0.0, 7.198] # track seconds; frames tile the track
|
||||
- pacing: beat_cut # beat_cut | phrase_flow (from the skeleton; obey it)
|
||||
- mood: [hype]
|
||||
- feel: accelerating onset stream into a held downbeat
|
||||
|
||||
### Groups
|
||||
|
||||
- **g1** — template: `intro-kinetic-cascade`
|
||||
- span_sec: [0.0, 4.017] # frame-LOCAL build is 0-based; these are TRACK seconds (worker subtracts frame start)
|
||||
- span_sec: [0.0, 4.017] # frame-LOCAL build is 0-based; these are TRACK seconds (worker subtracts frame start)
|
||||
- params: { theme: "light", icon: "bolt", phrases: "[…]", climax: "{…}" }
|
||||
- role_bindings: { phrase: { times: [0.14, 0.55, 0.87] }, climax: { in: 3.79, iconAt: 4.9 } }
|
||||
- copy: "GROWTH THROUGH CREATIVITY"
|
||||
- **g2** — free_design
|
||||
- span_sec: [4.017, 7.198]
|
||||
- free_design: { dominant_system: "per-onset typography", primitives: ["content-swap", "braam-punch"], density_topology: "accumulate" }
|
||||
- anchors: [4.10, 4.80, 5.50, 6.20] # onset seconds the reveals ride (from audiomap)
|
||||
- anchors: [4.10, 4.80, 5.50, 6.20] # onset seconds the reveals ride (from audiomap)
|
||||
- copy: ["BUILD", "SHIP", "REPEAT"]
|
||||
|
||||
## Frame 2 — f2
|
||||
|
||||
…
|
||||
```
|
||||
|
||||
## Frame block — required fields
|
||||
|
||||
| field | meaning |
|
||||
| --- | --- |
|
||||
| heading `## Frame N — <frame_id>` | `frame_id` matches the `src` stem; `N` = the 1-based index. |
|
||||
| `src` | `compositions/frames/NN-<frame_id>.html` — where the worker writes; the assembler references it. Stem = `data-composition-id`. |
|
||||
| `duration` | the frame span length in seconds (e.g. `7.198s`) — **required**; the assembler sums these for cumulative `data-start`. |
|
||||
| `span_sec` | `[start, end]` track seconds. `duration = end − start`. |
|
||||
| `pacing` | `beat_cut` \| `phrase_flow` — from the skeleton; the worker must obey (no hard-cut on `phrase_flow`). |
|
||||
| `mood`, `feel` | from the skeleton; tone + the one-line music situation the planner matched against. |
|
||||
| `### Groups` list | ≥1 group; groups tile the frame span in order. |
|
||||
| field | meaning |
|
||||
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| heading `## Frame N — <frame_id>` | `frame_id` matches the `src` stem; `N` = the 1-based index. |
|
||||
| `src` | `compositions/frames/NN-<frame_id>.html` — where the worker writes; the assembler references it. Stem = `data-composition-id`. |
|
||||
| `duration` | the frame span length in seconds (e.g. `7.198s`) — **required**; the assembler sums these for cumulative `data-start`. |
|
||||
| `span_sec` | `[start, end]` track seconds. `duration = end − start`. |
|
||||
| `pacing` | `beat_cut` \| `phrase_flow` — from the skeleton; the worker must obey (no hard-cut on `phrase_flow`). |
|
||||
| `mood`, `feel` | from the skeleton; tone + the one-line music situation the planner matched against. |
|
||||
| `### Groups` list | ≥1 group; groups tile the frame span in order. |
|
||||
|
||||
## Group entry — exactly one of three kinds
|
||||
|
||||
|
||||
@@ -25,46 +25,55 @@ Pacing tag: every template below is **beat_cut** except `held-message-living-fie
|
||||
---
|
||||
|
||||
### card-flyby
|
||||
|
||||
- **What** — a depth column of cards rolls forward through perspective; each landing beat tumbles the next card into the front slot with a solid colored wipe, the old front falls toward camera, dwells shrink card-to-card so the deck accelerates into a held final card.
|
||||
- **Reach for it when** — a stream of discrete onsets that **accelerate** (gaps shrinking / a build into a downbeat) and you want to flash a **sequence of items** — titles, projects, posters, tiles — one per hit, climaxing on a held card.
|
||||
- **Params** — `theme`, `bgColor`, `cards`, `landings`, `yaw`
|
||||
|
||||
### held-message-living-field · phrase_flow
|
||||
### held-message-living-field · phrase_flow
|
||||
|
||||
- **What** — a readable mark (logo / word / title) held dead still over a soft, color-shifting blurred field; only the field breathes.
|
||||
- **Reach for it when** — a **calm / sparse** stretch with an onset desert — energy present but few or no onsets (a held pad or riser); you have one word or mark to hold and let breathe.
|
||||
- **Params** — `markText`, `titleText`, `tagText`, `palette`, `flowSpeed`, `duration`
|
||||
|
||||
### held-text-strobe-burst
|
||||
|
||||
- **What** — a dead-still word whose letters flip through texture-filled frames (texture-clipped fill + per-frame tint + bg color) every ~3 frames, in short bursts pinned to a roll.
|
||||
- **Reach for it when** — a **dense, hard-hitting roll / fill** and a single word you want to strobe through textures for a few bars. (Ships texture-mask PNGs under `assets/`.)
|
||||
- **Params** — `markText`, `fontStyle`, `markScale`, `idleColor`, `idleInk`, `frames`, `strobePlan`, `decor`, `duration`
|
||||
|
||||
### intro-kinetic-cascade
|
||||
|
||||
- **What** — a line laid out as a sequence of big editorial **phrases** (each a stacked poster with one enlarged hero word), revealed word-by-word on its anchors, hard cut between phrases, climaxing on a phrase that slides in with a swappable ringing **icon** (bell / cursor / sparkle / emoji / SVG).
|
||||
- **Reach for it when** — an **intro / opening statement**: a short line to land word-by-word as big type, climaxing on one keyword + an icon. Medium-or-more energy, steady grid.
|
||||
- **Params** — `theme`, `icon`, `phrases`, `climax`
|
||||
|
||||
### logo-split-lockup-pulse
|
||||
|
||||
- **What** — a two-part mark joined at center splits left↔right to open a gap, grows a center word-lockup one word per onset (key word lands on the downbeat surge), snap-closes on a hit, then pulses with the beat.
|
||||
- **Reach for it when** — a short **logo / brand sting** (not a typed sentence): fast dense onsets + a sustained roll bed to pulse on, with a left/right bracketing mark.
|
||||
- **Params** — `bgColor`, `markColor`, `textColor`, `leftMark`, `rightMark`, `word1`, `word2`, `word3`, `word4`
|
||||
|
||||
### poster-tile-mosaic
|
||||
|
||||
- **What** — a packed **mosaic** of different-sized colored tiles that tessellate to fill the frame (no overlap), driven by interchangeable beat-synced operations: staggered enter/exit, locked global recolor, snake-fill + overlay.
|
||||
- **Reach for it when** — many discrete, individually-placeable onsets (a hit for every tile move) with distinct sub-phases you want articulated differently (accumulate → recolor-on-roll → fill-then-drop). A dense section best held as **one** rich tile program rather than split.
|
||||
- **Params** — `bgColor`, `tiles`, `bands`, `gap`, `showText`, `labels`, `program`
|
||||
|
||||
### roll-flipbook-word-cycle
|
||||
|
||||
- **What** — a hi-hat roll drives a centred word that flips every 16th-note through a word list; optionally the flicker resolves and locks into a final phrase.
|
||||
- **Reach for it when** — a **fast sustained-fill roll** (hundreds of hits/min, ~16th-note) with no single readable message — fill the roll with a rapidly-cycling word flipbook.
|
||||
- **Params** — `bgColor`, `textColor`, `accentColor`, `flipWords`, `resolveText`, `periodChar`
|
||||
|
||||
### split-anchor-word-slot
|
||||
|
||||
- **What** — a held left anchor column of fixed-word rows beside a torn-paper word-slot box on the right, driven by beat-synced operators: anchor lock-in, slot word-group cycle (in/out + per-line color), full-scene background flip, per-beat jitter, box-zoom exit wipe. Row count + number of flips are data.
|
||||
- **Reach for it when** — a short section with a **held idea** (a brand / name to anchor on the left) **and** a stream of onsets popping separate words on the right, plus a dense run to ride a shake on and a strong downbeat to wipe out into.
|
||||
- **Params** — `bgColor`, `anchors`, `theme`, `showText`, `program`
|
||||
|
||||
### typewriter-phrase-keyword-shuffle
|
||||
|
||||
- **What** — words type in one-per-onset to spell a phrase, then one keyword cycles typefaces on the beat while everything else holds dead still.
|
||||
- **Reach for it when** — a steady grid with a **continuous onset stream** (no desert): a phrase to type out, then a keyword to shuffle. The inverse of `held-message-living-field` (which wants an onset desert).
|
||||
- **Params** — `bgColor`, `textColor`, `accentColor`, `lead1`, `lead2`, `lead3`, `keyword`, `periodChar`
|
||||
|
||||
@@ -61,7 +61,9 @@ if (G.canvas) {
|
||||
if (Number.isFinite(c.w)) WIDTH = c.w;
|
||||
if (Number.isFinite(c.h)) HEIGHT = c.h;
|
||||
} catch {
|
||||
anomalies.push(`could not JSON.parse canvas frontmatter: ${G.canvas} — using ${WIDTH}×${HEIGHT}`);
|
||||
anomalies.push(
|
||||
`could not JSON.parse canvas frontmatter: ${G.canvas} — using ${WIDTH}×${HEIGHT}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +90,9 @@ for (const f of manifest.frames) {
|
||||
const compId = basename(f.src).replace(/\.html?$/i, "");
|
||||
const inner = readFileSync(compAbs, "utf8");
|
||||
if (!inner.trim() || !/<\w/.test(inner))
|
||||
die(`${label}: ${f.src} is empty/blank — the frame-worker wrote a partial file. Re-dispatch it.`);
|
||||
die(
|
||||
`${label}: ${f.src} is empty/blank — the frame-worker wrote a partial file. Re-dispatch it.`,
|
||||
);
|
||||
if (
|
||||
!inner.includes(`data-composition-id="${compId}"`) &&
|
||||
!inner.includes(`data-composition-id='${compId}'`)
|
||||
|
||||
@@ -25,7 +25,8 @@ function die(m) {
|
||||
const fromDir = flag("from", null);
|
||||
if (!fromDir) die("missing --from <srcDir>");
|
||||
const fromAbs = resolve(fromDir);
|
||||
if (!existsSync(fromAbs) || !statSync(fromAbs).isDirectory()) die(`--from is not a directory: ${fromAbs}`);
|
||||
if (!existsSync(fromAbs) || !statSync(fromAbs).isDirectory())
|
||||
die(`--from is not a directory: ${fromAbs}`);
|
||||
const hyperframesDir = resolve(flag("hyperframes", "."));
|
||||
const into = flag("into", "");
|
||||
const destDir = join(hyperframesDir, "assets", into);
|
||||
@@ -50,6 +51,8 @@ for (const name of readdirSync(fromAbs)) {
|
||||
landed.push(join("assets", into, basename(name)));
|
||||
}
|
||||
|
||||
console.log(`✓ stage-assets: ${staged} copied, ${skipped} already present → ${join("assets", into)}/`);
|
||||
console.log(
|
||||
`✓ stage-assets: ${staged} copied, ${skipped} already present → ${join("assets", into)}/`,
|
||||
);
|
||||
for (const l of landed) console.log(` ${l}`);
|
||||
if (staged === 0 && skipped === 0) console.log(` (no media files found in ${fromAbs})`);
|
||||
|
||||
@@ -73,7 +73,9 @@ for (const f of frames) {
|
||||
sum = r3(sum);
|
||||
const tileTarget = Number.isFinite(declaredDur) ? declaredDur : audioDur;
|
||||
if (tileTarget != null && Math.abs(sum - tileTarget) > 0.1)
|
||||
errors.push(`frame durations sum to ${sum}s but the track is ${tileTarget}s — frames must tile it gap-free`);
|
||||
errors.push(
|
||||
`frame durations sum to ${sum}s but the track is ${tileTarget}s — frames must tile it gap-free`,
|
||||
);
|
||||
|
||||
// ---------- group checks (warns) — parse RAW text ----------
|
||||
const FRAME_HEAD = /^##\s+(?:frame|scene|section)\b/i;
|
||||
@@ -116,7 +118,9 @@ const framesWithGroups = new Set(blocks.map((b) => b.frameLabel));
|
||||
for (const f of frames) {
|
||||
const lbl = `Frame ${f.number ?? ""} — ${f.title ?? f.index}`.replace(/\s+—\s+$/, "");
|
||||
if (![...framesWithGroups].some((s) => s.includes(String(f.title ?? "")))) {
|
||||
warns.push(`${lbl}: no parseable groups (expected \`- **gN** — template|free_design|asset …\`)`);
|
||||
warns.push(
|
||||
`${lbl}: no parseable groups (expected \`- **gN** — template|free_design|asset …\`)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +135,9 @@ for (const b of blocks) {
|
||||
warns.push(`${gid}: template \`${id}\` not found at templates/${id}/index.html`);
|
||||
}
|
||||
if (b.kind === "asset" && b.pacing === "phrase_flow" && /beat_cut/.test(blockText))
|
||||
warns.push(`${gid}: beat_cut asset treatment on a phrase_flow frame — use ken_burns/crossfade instead`);
|
||||
warns.push(
|
||||
`${gid}: beat_cut asset treatment on a phrase_flow frame — use ken_burns/crossfade instead`,
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- report ----------
|
||||
@@ -141,4 +147,6 @@ if (errors.length) {
|
||||
console.error(`\nvalidate-plan: ${errors.length} error(s), ${warns.length} warning(s)`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`✓ validate-plan: ${frames.length} frames tile ${sum}s; ${blocks.length} groups; ${warns.length} warning(s)`);
|
||||
console.log(
|
||||
`✓ validate-plan: ${frames.length} frames tile ${sum}s; ${blocks.length} groups; ${warns.length} warning(s)`,
|
||||
);
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
You build one frame's composition file: `compositions/frames/<frame_id>.html`. Siblings build
|
||||
the other frames in parallel. The generic HyperFrames law — sub-composition shape, timeline
|
||||
registration, determinism, layout — lives in `hyperframes-core` (`references/sub-compositions.md`
|
||||
+ `determinism-rules.md` + `data-attributes.md`); read it first. This file covers the
|
||||
music-specific part.
|
||||
|
||||
- `determinism-rules.md` + `data-attributes.md`); read it first. This file covers the
|
||||
music-specific part.
|
||||
|
||||
Your job: **follow the manual, fetch the materials, assemble.** The storyboard tells you WHAT
|
||||
(the frame's groups, each group's template / primitives, content, brand, real beat-anchor
|
||||
@@ -47,7 +48,7 @@ If your dispatch carries lint / validate feedback from a prior pass, address eac
|
||||
1. **Read** your `## Frame` block, `frame.md`, and the body of every template / primitive it
|
||||
cites. Reproduce those recipes.
|
||||
2. **Work in frame-local time.** Subtract the frame start from every anchor: `local_t = track_t
|
||||
− span_sec[0]`.
|
||||
− span_sec[0]`.
|
||||
3. **Author** `compositions/frames/<frame_id>.html`: a `<template>` wrapping `#stage`
|
||||
(`data-composition-id="<frame_id>"`), with all `<style>` / `<script>` inside it, and one
|
||||
paused `gsap.timeline({paused:true})` registered at `window.__timelines["<frame_id>"]`, built
|
||||
|
||||
Reference in New Issue
Block a user