refactor(skills): move product-launch / pr-to-video / faceless-explainer onto the script-driven architecture (#1635)

* refactor(product-launch-video): restructure onto script-driven architecture

Move product-launch-video onto the shared script-driven authoring flow:
build-frame remixes a hyperframes-creative preset onto brand tokens, audio
routes through the shared hyperframes-media engine, per-preset caption skins,
and every frame is authored as a directed shot. Removes the old bespoke
scripts (captions/validate/prep/hoist/…) in favour of the shared lib.

assemble-index.mjs keeps upstream #1629's blank/partial scene-file guard
(reject an empty or markup-less scene file at assembly, before emitting
data-composition-src, and re-dispatch) carried onto the restructured reader.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pr-to-video): restructure onto script-driven architecture

Move pr-to-video onto the shared script-driven authoring flow: ingest.mjs
folds the gh PR artifacts into the synthetic capture package the shared
backend (build-frame / captions / assemble-index) reads, add the mechanism
beat, route audio through hyperframes-media, and remix a hyperframes-creative
preset onto brand tokens via the shared lib.

- Fix skill name: pr-to-video-refactor -> pr-to-video (match directory).
- Drop a stale faceless-explainer-refactor reference in an ingest.mjs comment.
- assemble-index.mjs keeps upstream #1629's blank/partial scene-file guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(faceless-explainer): restructure onto script-driven architecture

Move faceless-explainer onto the shared script-driven authoring flow:
every visual is invented (typography / abstract graphics / diagram / data-viz)
and authored through the shared backend (build-frame remixes a
hyperframes-creative preset onto tokens, audio via hyperframes-media,
assemble-index builds the standalone index.html) using the shared lib.

- Fix skill name: faceless-explainer-refactor -> faceless-explainer (match directory).
- assemble-index.mjs keeps upstream #1629's blank/partial scene-file guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(skills): refresh test-skills-fresh.sh workflow roster

Update the install-and-verify harness to the current surface: 10 workflows
(adds website-to-video, embedded-captions, graphic-overlays, slideshow;
drops the removed footage-recut) and refreshed example prompts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style(product-launch-video): oxfmt storyboard.mjs

Run oxfmt over lib/storyboard.mjs — formatting only, no logic change.
Fixes the Format / Preflight CI check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(studio): import commitGsapPositionFromDrag from its actual module

The function was split out into gsapDragPositionCommit.ts in #1605, but the
test kept importing it from ./gsapDragCommit, which no longer exports it —
yielding 'is not a function' at runtime. Import from the correct module.

Inherited main breakage (same fix as #1631); fixes the Test CI check on this
branch independently of merge order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(hyperframes): refine router skill metadata tags

Update the entry router's metadata tags (video / animation / router focus);
oxfmt collapses the now-shorter metadata to a single line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(skills): tighten caption comment-strip + document audio --only merge

Review follow-ups (#1635):

- captions.mjs (x3): the HTML-comment strip used a single global replace, which
  CodeQL flags as incomplete multi-character sanitization (a nested/partial pair
  can re-form a marker the single pass misses). Strip in a fixpoint loop instead.
  Input is preset-library content, not user-controlled, so this is lint-
  cleanliness, not XSS defense.
- audio.mjs (x3): document that fetch-sfx (--only sfx) MERGES into the neutral
  audio_engine_meta.json sidecar — the engine reads prev and recomputes only the
  sfx section, so voices/bgm from the generate pass are preserved (review Q).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(skills): remove existsSync->write TOCTOU in workflow scripts

Clears the 9 js/file-system-race CodeQL alerts (captions/audio/transitions x3).
Each was an existsSync precheck followed by a later write of the same path:

- captions.mjs: caption-overrides shim -> atomic writeFileSync({ flag: 'wx' }).
- audio.mjs (sync-durations) + transitions.mjs (inject): drop the existsSync
  precheck and read directly, surfacing the same friendly error from a try/catch
  on readFileSync — no check->write gap.

Behavior is unchanged (same error messages); these are local single-process
deterministic scripts so the race was never a real risk, but this clears the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(skills): paint root composition ground color in assemble-index

Per-frame roots carry data-start/data-duration and get clip-gated against the
global timeline at render, so only the first frame's window overlaps global 0 —
a frame's own full-bleed background can't serve as the video ground, and every
frame after the first renders on the bare body color (black). Paint the ground
on the always-present root composition using the project's frame.md canvas color
(the same role the caption skin maps to --cap-canvas); fall back to the body
letterbox color when frame.md is absent or has no resolvable ground.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(hyperframes): drop router-tag edit (moved to the foundation PR)

The entry SKILL.md is rewritten wholesale by the frame-presets/media foundation
PR (#1632); editing it here too guaranteed a merge conflict. Restore this file
to main and let the router-tag tweak live with the rewrite in #1632, so the two
PRs no longer both touch it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
WaterrrForever
2026-06-22 22:49:42 +08:00
committed by GitHub
co-authored by Claude Opus 4.8
parent d0f0ec29e7
commit 1967901b57
630 changed files with 8846 additions and 87665 deletions
@@ -0,0 +1,119 @@
# Composition — product-launch visual-design judgment
> The composition-judgment layer for **Step 4 (Visual design)**. You read it while enriching `STORYBOARD.md` frames: which layout, how much frame the hero fills, how many depth layers — **director decisions**. Concrete px (safe margins 96-150), scale (1.05 / 0.92), three-layer `box-shadow`, `perspective` values are the **frame worker's** job; you name the intent in the frame's composition note. Video composition is closer to film / poster design than webpage layout — no scrolling, no reflow; every frame is a fixed canvas, every pixel matters. Default canvas **1920×1080**; portrait `1080×1920` / square `1080×1080` per the storyboard `format`.
## Squint test
Squint (or blur the frame). Can you still pick out the most important element, the second, and clear spatial groups? If everything has equal weight after blur, hierarchy is broken — redesign before writing the note. The strongest frames pass this: one dominant block + one supporting structural element, everything else demoted.
## Canvas zones (conceptual)
```
+--------------------------------------------------+
| Optional top chrome |
| +----------------------------------------------+ |
| | Safe margin | |
| | +----------------------------------------+ | |
| | | Primary content area | | |
| | | (center 65-75% of frame) | | |
| | +----------------------------------------+ | |
| | | Caption band (bottom ~17%, HARD w/ captions) | |
| | +----------------------------------------+ | |
| +----------------------------------------------+ |
+--------------------------------------------------+
```
- **Top chrome** — only for workspace-mockup frames; otherwise skip.
- **Safe margin** — key content stays off the edges; hero / editorial frames need more air.
- **Primary content area** — the center 65-75% is where the eye rests; body text never presses the edge.
- **Caption band (bottom ~17%, HARD-reserved when captions are on)** — when the film has captions enabled (the frame's `Captions:` flag), the bottom ~17% of **canvas height** is reserved (landscape 1080h → bottom 180px, y 900-1080; portrait 1920h → bottom 320px, y 1600-1920): primary content and key visuals **cap at the band top**, and a centered hero anchors at **y ≈ 0.42 × height** (landscape ≈454, portrait ≈806), not the canvas midpoint. Background / ambient / surface layers are exempt and may stay full-bleed. Captions disabled → the zone is usable, but keep it clear anyway for bottom-edge consistency across frames.
You write "hero word centered with generous safe margins"; you do not write `padding: 150px 120px 92px`.
## Portrait & square (non-16:9 canvases)
The zones, density, hierarchy, and depth principles all still apply; the **aspect ratio** changes, and a wide-frame layout does not transplant into a tall one. Design for the storyboard's `format` from the start — never plan landscape and "crop."
- **Stack vertically, not side-by-side.** Portrait has little horizontal room: split-screen / triptych / 60-40 asymmetry become **top/bottom stacks**, vertical step lists, stacked bands. Square tolerates side-by-side only for two compact items.
- **Vertical center moves with the canvas** — anchor a centered hero around **y ≈ 0.42 × height** (portrait ≈806, square ≈454), not a fixed 540.
- **Type runs larger, fewer words per line** — narrow frames wrap long headlines badly; prefer short kinetic lines, bigger type, more vertical rhythm.
- **Travels well to portrait:** Centered, Layered Depth, Full-Width Strip (now a stacked band), vertical Rule-of-Thirds. **Avoid** wide Split Screen and Triptych — use stacked equivalents.
- **Density still rules** — primary visual ≥ 40% of canvas, ≥ 3 depth layers, measured against the tall frame; an empty top or bottom third reads as placeholder.
- **Real 16:9 screenshots don't fit a tall frame** — never letterbox with dead bars, never stretch-distort. Instead: crop to the salient region; seat it as a top/bottom band with kinetic type filling the rest; or scale it inside a device / browser-frame mock.
## 7 composition templates
Use ≥3 different templates per video (5 frames → 3+, 9 frames → 4+). **Don't default every frame to centered**; never use the same layout class twice in a row.
1. **Centered (hero / climax)** — one dominant element, generous breathing room. Brand reveal, key metric, CTA, climax.
2. **Rule of thirds** — anchor on a thirds intersection; remaining space carries support or negative space. Feature showcase, demo + description.
3. **Split screen (comparison / dual focus)** — left/right halves carry separate elements. Before/after, comparison, problem/solution.
4. **Layered depth (immersive)** — foreground / midground / background differ in scale + opacity. Opening hooks, atmosphere, workbench beats.
5. **Asymmetric (editorial)** — primary content pushed to one side (60/40, 70/30); intentional imbalance → tension + sophistication. Feature focus, dense info, editorial proof.
6. **Triptych (three-panel)** — three equal zones for three capabilities / beats at once.
7. **Full-width strip** — one horizontal band (ticker, logo chain, marquee), usually ~20% of canvas height.
## Frame density — avoid empty frames
Common failure: small elements floating in the center with empty space around them. Every frame must feel **intentionally filled**.
- **Primary visual occupies ≥ 40% of canvas** — hero text 50-75% height × 60-80% width; a centered card 30-50% × 50-70%.
- **≥ 3 visual layers** — background (gradient / particles / grid) + midground (main content) + foreground (emphasis / decoration).
- **Openings and closings** are prone to emptiness — black background + a lonely line of text reads as placeholder. Add environmental layers: dual-radial swell, floating particles, brand-color ambient texture, low-opacity scanlines.
- **Text-only frames still need visual elements** — logo, an asset, icon decoration, halftone field, brand-derived geometry.
- **Feature the frame's `asset_candidates`** — story already chose the real assets for this frame (they're the highest-value material); a product screenshot at 60% + supporting text + an ambient layer feels full; the text alone feels empty.
**Fullness test:** could this frame stand as a poster or social graphic? If it looks like a sparse slide → add layers.
## Negative space
Whitespace directs attention, it isn't waste. Tight grouping (icon + label) → small spacing; unrelated groups → large separation; asymmetric outer margins feel more designed than equal padding; a hero word keeps large side whitespace so one word carries the weight. **Failure modes:** everything equidistant (no grouping); unintended overlap; text tight against an edge; captions colliding with bottom visuals; the framework's default padding everywhere.
## In-frame visual hierarchy
Visual weight, strong → weak: **large image** **motion** (moving beats static) **high contrast** **type scale** **position** (center + upper third are golden). Combine **at least two** — an element that is large, moving, and upper-third is unquestionably primary.
A title that is only _larger_ (sharing weight/color/spacing with body) reads weak. Stack dimensions:
| Dimension | Strong contrast |
| --------- | ------------------------------------------- |
| Size | 3:1 ratio or larger |
| Weight | 800-900 vs 400 |
| Color | high contrast against background |
| Motion | one element moving vs all else static |
| Position | top / left = primary |
| Space | large surrounding whitespace vs equidistant |
## Cards and grouping
Spacing + alignment can group without a card container. **Use cards** when content is genuinely distinct, when a UI-demo group is independently actionable (command row, feature card), or when shadow-stacking communicates "lifted." **Don't** card for mere separation (use whitespace) or for a continuous list. **Never nest cards** — claustrophobic, muddy hierarchy. You write "comparison-split: left/right dual cards, three-layer shadow stack"; the worker writes the `box-shadow`.
## Asset prominence
The frame's assets (logos, product images, screenshots) are real and strongly brand-related — feature them, don't shrink them into decoration. A focal product screenshot fills **40-60% of the frame**; a logo stays recognizable at playback size; use the highest-quality version. **Never** replace a real asset with a generic decorative graphic when a real one exists.
## Depth on a 2D canvas
Layer **2-3 depth techniques** per frame to avoid a flat poster (concrete perspective / rotate / scale values are the worker's):
| Technique | Effect |
| ---------------- | ------------------------------------------------------------------------------------------ |
| Size difference | larger = nearer, smaller = farther |
| Blur | blurred = background, sharp = foreground |
| Opacity gradient | low = receding, full = primary |
| Overlap | foreground partially covers background |
| Shadow stacking | three-layer shadow = lift + brand feel |
| Motion speed | faster parallax = closer |
| Counter-scale | camera pushes toward focus → background appears larger, focal CSS scale <1 but fills frame |
You write "3 depth layers: background swell + midground product + foreground CTA glow; background counter-scales for the push"; the worker writes the scale values.
## What should not appear
Nav bars, footers, cookie banners, scrollbars, cursor arrows, browser chrome, unclickable buttons, generic decorative shapes replacing real assets, floating bokeh / purple-to-blue AI gradients (the "default AI cliché," banned). **Exception:** an intentional product-interface reconstruction for a UI-demo frame — nav bars, command panels, timeline tracks, CTA buttons give real context, making it read as a real workflow.
## Composition note example
> "Composition: asymmetric 60/40 — product screenshot occupies left 60%, copy + CTA right 40%. Generous safe margin; text capped inside the primary content area. 3 depth layers: background swell + midground screenshot + foreground CTA glow. Density: primary visual ~55%, ambient adds 5% scanline + architectural grid."
One line per frame; never concrete px / scale / shadow recipes (the worker writes those).
@@ -0,0 +1,139 @@
# Motion language — product-launch visual-design judgment
> The motion-judgment layer for **Step 4 (Visual design)**. You name **each shot's choreography, spring intent, beat rhythm, holds, stillness, and the idle-motion budget** while enriching `STORYBOARD.md` frames; the **frame worker** maps intent to concrete GSAP eases / ms / stagger / code (via `hyperframes-animation`). A good promo feels like one continuous whole — one camera, one spring feel, **every shot directed across its full length** — not a pile of slides that animate once and freeze. You reference motion by **role**, never by curve: eases / durations resolve from `frame.md`'s motion tokens, named `entry` / `emphasis` / `exit` / `drift` (the pack's exact keys may differ); the worker maps the curve. Between-frame **transitions are not yours** — story names `transition_in`, the harness injects it.
## A frame is a shot, not a slide
The single failure that makes a promo read as PowerPoint: a frame whose content **animates in over the first ~0.8s, then freezes** for the rest of its duration while a slow drift plays underneath. The entrance is not the shot — it's the **first beat** of it. You direct the **whole duration**.
Three layers fill a shot, each governed by a different rule:
| Layer | What | Rule |
| ------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| **Camera** (macro) | ONE correlated move on the frame root — slow drift / dolly / push / parallax pan | **always on, the whole shot** — this is the "someone is filming this" layer |
| **Choreography** (action) | the beat develops: entrance → mid-shot move (reveal / rearrange / morph / emphasis hit) → settle | **fill the duration** — a shot animated only at entry is a slide |
| **Idle life** (texture) | ambient continuation on the 1-2 elements that hold a live slot — breathing, glow, float | **budgeted** — this is where screensaver lives; cap it |
The reconciliation that matters: **mandate choreography, budget idle life.** Purposeful, sequential motion that carries information should fill the shot; ambient, simultaneous motion that carries none should be capped. Many elements each floating independently reads as _screensaver / UI animation_; a shot that only enters then freezes reads as _slideshow_. Avoid both — **one camera move + a directed multi-phase action + 1-2 living elements**, nothing scattered.
## Multi-phase choreography — direct the full shot
Every non-still frame's timeline is choreographed across its length, not front-loaded into the entrance:
```
entrance → development → settle
```
- **entrance** — the beat's primary content arrives (hero `entry` / `heavy`; groups staggered).
- **development (the phase that's usually missing → PPT)** — mid-shot, the content _does something_: a second element reveals, elements rearrange to a new layout, a card morphs to its next state, a count-up runs, an emphasis hit lands on the keyword. This is the motion that separates video from slides.
- **settle** — the shot resolves and holds for its read; the camera + idle life continue underneath (never a hard freeze).
**Architecture:** in hyperframes only the **exit** is forbidden mid-video (the frame unmounts; the harness transition _is_ the exit). Everything _before_ the settle — including rich mid-shot development — is free and seek-safe. Build the development phase; skip only the exit (unless you are the final frame).
When you name a **`blueprint`**, the development phases come from its recipe — write the composition note **shot-by-shot** to match. When you name **no blueprint**, the **≥3 cited effects ARE the phases** — sequence them (one enters, one develops, one emphasizes); **don't fire them all at t=0** (simultaneous entry collapses three phases into one slide).
## Spring intent (by role, not curve)
| Intent | Feel | Use |
| ---------- | ------------------------------------------- | ------------------------------------------------- |
| **entry** | confident slight overshoot, settles quickly | primary element entry (default) |
| **gentle** | soft slide-in, no overshoot | background elements, subtle motion |
| **snappy** | tight overshoot, nearly instant | UI elements, small icons, buttons |
| **heavy** | weighted deceleration | large images, prototype screenshots, hero visuals |
| **slam** | bouncy overshoot, intentionally loud | logo / bell / impact moments |
**Consistency:** similar elements share one intent (all icons `snappy`, all hero images `heavy`). Don't invent a unique ease + duration per element.
**Forbidden:** `bounce.out` / `elastic.out` (dated; real objects decelerate, they don't bounce — low overshoot for `entry` is fine, high overshoot only for clearly playful moments); a unique ease+duration per element (visual noise).
## Duration intent
Reference by **tier** ("instant feedback" / "state change" / "layout change" / "entry animation"); the worker maps concrete ms / frames at 30fps. **A single entry should not exceed ~800ms** — for a longer buildup, use multi-element stagger or a development phase, **not** one long tween.
**Phase-to-phase within a shot is swift** — when one element makes way for the next (development), the outgoing move runs ~75% of an entry; arrival is deliberate, hand-off is quick. (The between-frame **exit** is the harness's transition, never your within-shot motion.)
## Stagger cap
When staggering N elements, **total ≤ 500ms** (longer feels dragged):
- **3-7 elements** — normal stagger, total 300-700ms.
- **8+ elements** — tighten per-item delay, or stagger only the first few and enter the rest with the last.
- Never let stagger run past 500ms.
## Beat structure across frames (the cross-frame rhythm)
Rhythmic videos breathe: tension → release → tension → release. A clean reference shape for a ~46s video:
| Phase | Duration | Rhythm | Frame type |
| -------------- | -------- | ------------------ | ---------------------------------- |
| Slow setup | 6-10s | slow build | hero establish, VO not yet present |
| Fast montage | 6-10s | ~2s each | quick cuts every 1.5-2s |
| Process reveal | 12-18s | continuous, no cut | screen recording, real workflow |
| Closure | 3-5s | still, breathable | logo, URL, CTA |
Allocate motion by a frame's energy: **high-energy** (hook, CTA) → faster entry, tighter stagger, `snappy`, busier development; **breathable** (brand reveal, emotional beat) → slower entry, `gentle`, longer hold, minimal development; **data** (stat, feature) → medium rhythm, clean stagger, count-up as the development phase.
## Hold time — read time, not freeze time
After an element enters it must stay long enough to read (the worker maps concrete frames). "Hold" means **don't cut early** — the camera + idle life keep playing underneath; it is never a hard freeze.
| Content | Minimum hold |
| ------------------------------------ | ------------ |
| display text (1-3 words) | ~1s |
| short sentence | ~1.5s |
| data / statistic | ~1.5s |
| product screenshot | ~2s |
| complex visual (diagram, comparison) | ~2.5s |
| hero / climax word | ~1-1.4s |
Narration shorter than the needed hold → the frame's `duration` should still give the visual its read time.
## Stillness before climax — the marked exception
Archive signature: a **0.3-0.75s pause** between the major action and its confirmation / result — the silence builds tension before the landing. It lands **because the rest of the video is choreographed** — stillness is a contrast against motion, so it only reads when motion is the baseline (if every frame is already frozen, a pause carries nothing). **Allocate it to only 2-3 frames per video, named in the `## Video direction` block**, where the narration lands a payoff. Stamped on every frame it becomes a tic and flattens the rhythm. Name `stillness-before-climax` in that frame's motion note; even then the camera move continues (still ≠ frozen).
## The idle-life budget — what may move during the hold
The 1-2 elements that keep moving _after_ the development settles. This is the layer that, overdone, becomes screensaver — so it is **capped**, not mandated:
1. **Camera move** — always present (the macro layer above); it alone keeps everything coherently alive.
2. **At most 1-2 secondary live elements** — the ones carrying the beat (hero, CTA). Everything else holds.
3. Prefer **macro move + depth parallax** over many independent floats.
Secondary-slot menu (formulas are the worker's): **multiplicative breathing** (hero — small ±2-5% on final scale) · **glow pulse** (CTA / click target) · **sine float** (one decorative cluster at most) · **rotational drift** (3D cards, hero logo) · **orbit** (surrounding icons; counts as the one decorative cluster) · **halftone breathing** (atmospheric frames).
Multiplicative breathing is the signature for a hero **that holds a live slot** — not stamped on every hero. **Minimum amplitude ±6px or ±2-5% scale** — a 3px micro-float doesn't count; the budget caps how many things idle, it does not license invisible motion.
## Seek-safe motion — intents that don't survive the renderer
The frame is a **paused GSAP timeline seeked frame-by-frame**, so some "continuous" intents from a real-time engine cannot render — **don't name them**:
- **No infinite / forever motion** — "particles loop endlessly," "logo rotates forever," "marquee scrolls on repeat." Idle life is a **finite tween over the hold** (breathe up then back), never `repeat`/`yoyo`.
- **No randomness or wall-clock** — `Math.random` particle fields, `Date.now` drift. Every motion is the same on every render; name deterministic motion only.
- **Entrance + development only** (exit = final frame only) — the cross-frame exit is the harness's transition.
- Express oscillation/breathing as a **bounded finite move**, not a loop; the worker writes it, but don't ask for a loop the timeline can't hold.
## Forbidden — both failure modes
**Slideshow (under-motion):**
- Content animates in, then **freezes** for the rest of the shot (the PPT tell).
- Only the entrance is animated; the remaining duration is a frozen hold under a drift.
- The ≥3 cited effects **all fire at t=0** instead of sequencing into entrance / development / emphasis.
- No mid-shot development on a non-still frame.
**Screensaver (over-motion):**
- **Every element** floating independently; idle motion with no information.
- More than 1-2 elements idling at once; scattered sine floats as the "aliveness."
- A 3px micro-float standing in for real motion.
**Always:**
- `bounce.out` / `elastic.out`; a bespoke ease+duration per element; `repeat` / `yoyo`; all elements entering simultaneously (must stagger or sequence).
## Motion note example
> "Macro: slow dolly-in on the frame root across the whole beat. **Entrance** — hero enters `EASE.entry` (heavy); icons snappy-stagger (5 items, ~400ms). **Development** — icons rearrange into a row, then the result panel reveals beneath them. **Stillness-before-climax 0.6s** (allocated frame; only the dolly continues). **Settle** — result emphasis: text gentle entry + double-layer glow; idle hold with hero breathing ±3% as the one live element."
One line for a single-shot frame; **shot-by-shot when the beat is multi-phase** (always, when you named a `blueprint`). Never concrete ease curves / ms / stagger formulas / JS — the worker writes those.
@@ -0,0 +1,267 @@
# Story design — product launch video
Use this reference in Step 3 to write `STORYBOARD.md` and `SCRIPT.md` for a product launch, promo, feature reveal, or marketing video.
This file defines the story: what the video says, in what order, and why each frame exists. It does not define layout, visual effects, animation, or final markdown schemas. For exact file syntax, follow `../hyperframes-core/references/storyboard-format.md` and `../hyperframes-core/references/script-format.md`.
## Read first
Read these inputs before writing:
1. `hyperframes.json` — locked brief: angle, length, aspect ratio, language.
2. `frame.md` — tone, mood, design system, and brand register.
3. `capture/extracted/visible-text.txt` — product facts, page copy, positioning, proof, CTA.
4. `capture/extracted/asset-descriptions.md` — the only source for captured asset inventory.
5. `user_script.txt` and `VO_MODE`, when present.
Do not inspect `capture/assets/`, contact sheets, screenshots, or raw captured files during Step 3. Treat `asset-descriptions.md` as the canonical asset list. Do not invent asset filenames.
## Output
Create two files:
- `STORYBOARD.md` — the narrative plan, one frame per beat.
- `SCRIPT.md` — the locked narration, only for spoken frames.
Every storyboard frame must include the required fields from the storyboard format reference, plus the narrative metadata below.
## Core rule
A website is an information layout. A video is an emotional sequence.
Do not follow page order. Reorder, merge, omit, and reshape captured content into a clear launch story.
## Step 3 method
### 1. Extract the product truth
From the brief and captured text, identify:
- Audience — who the video is speaking to.
- Pain or desire — what the viewer already wants fixed or achieved.
- Promise — the one-line thesis of the video.
- Product role — what the product does in the story.
- Proof — features, UI moments, metrics, logos, examples, or demos.
- CTA — what the viewer should do next.
Write the storyboard around the promise, not around a list of features.
### 2. Match the register to `frame.md`
Use `frame.md` as a soft guide:
| `frame.md` signal | Story effect |
| ----------------------------- | ----------------------------------------- |
| restrained, editorial, B2B | plain, confident, low-hype script |
| bold, kinetic, launch-like | short hooks, punchier beats |
| warm, human, playful | friendly direct address, lighter phrasing |
| premium, cinematic, spectacle | aspirational framing, fewer words |
The product truth decides the arc. The visual system tunes the voice.
### 3. Choose one outer arc
Pick one primary arc. Use a compound only when useful, e.g. `PAS with feature-benefit progression`.
| Arc | Use when | Beat order | Reveal timing |
| ------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------- |
| `PAS` | The pain is known and urgent. Best for broken B2B workflows. | hook → pain → agitation → solution tease → product intro → proof/demo → CTA | late |
| `Future Pacing` | The product sells a new future, category, or paradigm. | imagine → name product → remove pain → show mechanism → show outcome → CTA | early |
| `Demo Loop` | The UI is self-explanatory and the product is best shown working. | question → product intro → demo cycle 1 → demo cycle 2 → trust/benefit → CTA | early |
| `BAB` | The product bridges an old workflow to a better one. | before → after tease → bridge/product → step 1 → step 2 → step 3/wow → CTA | early-mid |
| `Feature-Benefit Cascade` | The product is feature-rich or desire/status-driven. | product/category hook → feature → benefit → feature → benefit → climax → CTA | frame 1 |
Use feature-benefit rhythm inside any arc when the video has many capabilities. Do not stack several features without translating them into viewer value.
### 4. Build the frame sequence
Each frame needs one clear job. Avoid frames that only say “more benefits” or “another feature.”
Use these frame types:
`hook | pain_point | product_intro | feature_showcase | benefit_highlight | social_proof | branding | cta`
For every frame, define:
- `type` — one of the allowed frame types.
- `persuasion` — the specific persuasion move.
- `beat` — the viewer emotion.
- `scene` — a one-line visual idea, not detailed composition.
- `voiceover` — spoken guide text, or empty for silent frames.
- `asset_candidates` — real captured assets that could support the frame.
The prose under each frame should state:
- `narrativeRole` — why this frame exists in the story.
- `keyMessage` — the one idea the viewer should remember.
## Hook strategy
Pick one opening strategy for the first 3-5 seconds:
| Strategy | Use when |
| ----------------------- | ----------------------------------------------- |
| Shocking statistic | A credible number proves the pain. |
| Pain validation | The audience already knows the problem. |
| Rhetorical question | You need curiosity and speed. |
| Direct address | The audience is clear and specific. |
| Imagine / future pacing | The product introduces a new future. |
| Category announcement | The product is the category or launch headline. |
| Visual spectacle | The aesthetic is part of the pitch. |
| Trend positioning | The product rides a timely market shift. |
The hook must create tension, curiosity, or desire. Do not open with generic company description.
## Persuasion labels
Use concrete labels. Do not write generic labels like “show benefit.”
Good labels include:
- Pain agitation
- Cognitive overload
- Negative contrast
- Friction reduction
- Simplification
- Show-dont-tell proof
- Demonstration of capability
- Feature-to-benefit translation
- Statistical proof
- Authority by association
- Social proof
- Risk reversal
- Future pacing
- Value stacking
- Empowerment and control
- Rule of three
- Scarcity / urgency
- Status seeking
When no label fits, create one and explain the mechanism in the frame prose.
## Emotional beats
Use specific emotions:
- Negative: `anxiety`, `frustration`, `overwhelm`, `tension`, `urgency`, `skepticism`, `FOMO`
- Pivot: `relief`, `curiosity`, `clarity`, `intrigue`, `aspiration`
- Build: `trust`, `confidence`, `control`, `ease`, `power`, `awe`, `excitement`, `belonging`, `reassurance`
- Close: `triumph`, `motivation`, `urgency-to-act`, `peace of mind`, `inevitability`
Compound beats are allowed, e.g. `relief + control`.
## Asset candidates
`asset_candidates` is the handoff from Step 3 to visual design and frame building.
Rules:
1. Read only `capture/extracted/asset-descriptions.md` to know what assets exist.
2. Use only filenames listed there.
3. Write candidates as `assets/<basename>`, where `<basename>` is the captured file basename.
4. Put all candidates on one line.
5. Separate candidates with semicolons.
6. Add a short description after `—`.
7. Prefer `[video]` assets when motion proves the product better than a still.
8. Use content assets: UI, screenshots, product photos, charts, diagrams, demos.
9. Skip tiny icons, favicons, badges, decorative chrome, and repeated logo variants unless the frame needs them.
10. Pure typography frames may use an empty asset list.
Example:
```md
- asset_candidates: assets/dashboard-hero.png — main analytics UI, dark dashboard, wide screenshot; assets/demo-loop.mp4 — product interaction clip, query to result flow
```
Do not use nested lists for assets. The parser expects a single metadata line.
## UI demos
A UI demo should usually be a sequence, not one isolated frame.
Use 3 or more consecutive `feature_showcase` / `benefit_highlight` frames on the same product surface when the product value depends on workflow or interaction.
Good demo rhythm:
1. Input or user action.
2. Product response.
3. Result, insight, automation, or saved effort.
4. Benefit or trust proof.
Use consistent transitions across the sequence so it feels like one flow.
## Script rules
### If there is no pasted script
Write tight per-frame narration:
- 1-2 sentences per spoken frame.
- Usually 6-20 words per frame.
- Concrete and human.
- Say what the product does for a person.
- Prefer active verbs.
Avoid:
- “Seamless experience.”
- “Unlock the power of...”
- “Streamline your workflow.”
- Long noun-phrase lists.
- A whole frame with only “Or...” or another filler bridge.
Silent frames are allowed when the visual proves the point. Leave them out of `SCRIPT.md`.
### If `VO_MODE = restructure`
Treat `user_script.txt` as source material. Rewrite, reorder, merge, or omit to fit the chosen arc and target length.
### If `VO_MODE = verbatim`
Do not rewrite the users words. Segment the script into frame-sized chunks at sentence or paragraph boundaries. You may split a long sentence at a natural clause boundary, but do not change words. The final duration follows the provided script.
## Transitions
Use only registry transition names:
`cut | crossfade | blur-crossfade | push-slide LEFT | push-slide RIGHT | push-slide UP | push-slide DOWN | zoom-through | squeeze`
Pick 2-3 transition types for the whole video and repeat them. Frame 1 can use `cut` as a placeholder.
## Frame template
Use the exact fields required by the core storyboard format. This is the narrative shape each frame should satisfy:
```md
## Frame N — Short name
- scene: one clear visual idea
- voiceover: "spoken guide text, or empty"
- duration: rough estimate in seconds
- transition_in: cut
- status: outline
- src: compositions/frames/NN-short-name.html
- type: hook
- persuasion: Pain validation
- beat: urgency
- asset_candidates: assets/example.png — short asset description
narrativeRole: What this frame does in the viewer journey.
keyMessage: The one idea the viewer should remember.
```
## Final checklist
Before asking for user approval, verify:
- The arc is named and consistent.
- The sequence is narrative-driven, not page-order-driven.
- The opening uses a clear hook strategy.
- Each frame has one job.
- Every frame has `type`, `persuasion`, and `beat`.
- Every visual frame has suitable `asset_candidates`, unless intentionally typography-only.
- Asset filenames come only from `capture/extracted/asset-descriptions.md`.
- UI/product demos use a multi-frame sequence when needed.
- Transitions use only registry names and repeat 2-3 types.
- `SCRIPT.md` contains only locked spoken narration.
- Silent frames are intentional and omitted from `SCRIPT.md`.
@@ -0,0 +1,83 @@
# Visual design — product-launch per-frame enrichment method
> The method behind **Step 4 (Frame visual design)**. You (the orchestrator) read it to **enrich `STORYBOARD.md` frames in place** — story-design wrote the skeleton (each frame's `scene`, `voiceover`, `transition_in`, the five narrative fields, and its `asset_candidates`); you add how each frame **looks and moves**. Each frame is a **directed shot, not a static slide** — you choreograph it across its whole duration, not just its entrance. You write **no HTML** (that's the frame workers), you **never read `capture/`** (story already chose the assets), and you do **not** select assets or name transitions (story owns both). `frame.md` is your palette/type truth. Composition / motion detail lives in `composition.md` + `motion-language.md`; effect & blueprint **bodies** live in `hyperframes-animation`. Adding palette theory or a generic font rule here? Wrong home — `frame.md` + `hyperframes-creative`.
## Every frame is a directed shot
A frame's visual layer is choreographed across its **full duration**, not front-loaded into an entrance. The failure that reads as PowerPoint: content animates in over the first ~0.8s, then **freezes** while a slow drift plays under it. So every frame's metadata + note describe a **shot with phases**`entrance → development → settle` — where _development_ (a reveal, a rearrange, a morph, an emphasis hit, a count-up) is the mid-shot motion that separates video from slides. The shot model and the choreography-vs-idle budget live in `motion-language.md`; here you **encode it into the frame**: the `effects` / `blueprint` ids are the motion vocabulary, and the **composition note sequences them into phases**.
Deliberate **stillness** is the marked exception — the 2-3 climax/breather frames you allocate in `## Video direction`. Every other frame develops; a held frame outside that allocation is just a slide.
## What you add to each frame
Story-design's `## Frame N` block already carries the narrative. You append the visual layer as frame metadata + one composition note (story's role/message prose stays):
```
## Frame 3 — The problem
- scene: a 20-minute timer spins on a stack of rejected takes ← refine only if it could read sharper
- voiceover: "…" ← story's; leave it
- transition_in: crossfade ← story's; leave it
- type: pain_point ← story's
- persuasion: Pain agitation
- beat: frustration
- effects: slow-push, count-up, vignette-pulse ← you add: cite effect ids (≥3, sequenced into the phases below)
- blueprint: messaging-multi-phase ← you add (optional): one multi-phase blueprint id
- focal: assets/timer-stack.png ← you add: which existing candidate is the hero
- roles: timer-stack = background (dim ~40%) ← you add: cutout / background / supporting per candidate
- sfx: impact-soft, riser ← you add: the sound the beat wants (fetched + mounted at root; never yours to embed)
Entrance: timer drops in upper-left (heavy), rejected-takes stack seated low. Development: the stack grows beat-by-beat toward the band as the reject count ticks up (count-up). Settle: vignette pulses in and holds; only the slow-push continues. A dense, edge-anchored frame.
```
- **`effects`** — name atomic effect **ids** from `hyperframes-animation`'s rules index. **Cite ≥3 when you name no `blueprint`** (the worker composes them into the beat; fewer than 3 reads as generic motion); 1+ as accents when a blueprint already carries the choreography. With no blueprint, those **≥3 effects are the shot's phases** — your note must **sequence them** (one enters, one develops, one emphasizes), not list them as a flat set that all fires at entry (that collapses three phases into one slide). The names are a shared vocabulary; the recipe lives there — you cite, the worker **reads the body and reproduces it** (not a name-guess).
- **`blueprint`** — name **one** multi-phase blueprint id from `hyperframes-animation/blueprints-index.md` when a frame's beat wants a proven multi-phase shape (multi-phase reveal, orbit-collapse, …). Two postures — **both require the worker to read the recipe body (and run its `examples/<id>.html` to see the signature move) first**; deviating from a name-guess instead of from understanding is the one banned failure:
- **Reproduce** — the blueprint fits the beat cleanly and the frame's content maps onto its slots; the worker reproduces its phases faithfully. Write the composition note shot-by-shot to match.
- **Adapt** — the blueprint is the right _structure_ but the content / beat / asset-count doesn't fit its exact form, or you want the proven skeleton with a fresher surface (anti-templating), or you're grafting a second blueprint's phase. Lead the note with a **`Base / Keep / Depart`** line — `Base:` the blueprint id · `Keep:` its **signature** (the move that makes it itself — the SVG ring, the orbit→collapse; never drop this, or you named the wrong blueprint) · `Depart:` what you change and why. Adapt may **extend or vary, never reduce below the shot model** — never flatten a multi-phase blueprint into a single entrance. E.g. `Base: avatar-cloud-network · Keep: the SVG-connected elliptical ring · Depart: 7 logos not 12, + a hub count-up as the development beat`.
Choose **Reproduce** when the shape fits as-is, **Adapt** when the structure fits but the form doesn't (or to avoid templating); **omit** `blueprint` entirely when no blueprint's structure fits — then the cited `effects` (≥3) carry the phases (**Compose**).
- **`focal` / `roles`** — story listed `asset_candidates`; you pick the **focal** hero and each candidate's role (`cutout` = foreground subject, lay text around it; `background` = full-bleed, dim 30-50%; supporting = secondary). You **consume** the candidates — never add, swap, or drop one (coverage is story's call; if a frame truly has the wrong candidates, flag it back, don't reach into `capture/`).
- **`sfx`** — name the sound the beat wants (an impact for a slam, a whoosh for a push). The audio script's `fetch-sfx` pass retrieves it from HeyGen and the assembler mounts it at the root — you only **name** it, never embed an `<audio>` element.
- **composition note** — the frame's visual brief: layout, hero, depth layers, the macro move, **and the shot's phases**. **Default to a phased note**`entrance: … → development: … → settle: …` (shot-by-shot; mandatory when you named a `blueprint`) — naming what's on screen and what moves in each phase, so the worker builds the development instead of freezing after entry. A **single still line** is correct only for a deliberately held climax or an allocated stillness frame. Full method → `composition.md` (layout) + `motion-language.md` (phases).
## Video direction — write the invariants ONCE
The whole video shares one look and one motion grammar. State it **once**, at the top of `STORYBOARD.md` (a `## Video direction` block), so every frame inherits it and per-frame metadata carries only the **delta**:
- **palette system** — from `frame.md`: which roles map to which hues. Never invent.
- **motion defaults + shot model** — default eases + the **choreography baseline** (every frame a directed shot: entrance → development → settle) + the **idle-life budget** (what may keep moving during the hold) (→ `motion-language.md`).
- **negative list** — what never appears: off-brand textures and effects the pack forbids, **plus both motion failure modes** — slideshow (enter-then-freeze) and screensaver (everything floating independently) (→ `motion-language.md`).
- **stillness allocation** — name the 2-3 frames that hold still before a climax; every other frame develops (the anti-repetition discipline; → `motion-language.md`).
Do **not** repeat these in every frame — restating video-level rules per frame is exactly the bloat this layer prevents. Each frame's metadata is the delta on top of Video direction.
## Palette & type — from `frame.md`, never invented
- **Palette** — `frame.md` (the adopted pack) is the color truth; apply its roles per frame. Generic basics (one accent, tint neutrals, avoid pure `#000`/`#fff`) → `hyperframes-creative/references/house-style.md`.
- **Type** — fonts resolve via `frame.md`'s type tokens; reference them **by role** (display / body / mono / the pack's ramp), never by raw family or px. Generic typography craft (embedded fonts, dark-bg optical compensation, `tabular-nums`) → `hyperframes-creative/references/typography.md`.
## Caption-band keep-out (plan side)
The bottom ~17% of the canvas is reserved for the caption pill. Plan every frame's content into the **top ~83%** so nothing important lands in the band (the worker enforces the pixel cutoff; you plan the layout). Holds even when captions are disabled — bottom-edge consistency. Geometry detail → `composition.md`.
## Where the detail lives
| For… | Read |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| composition — zones, density, templates, asset prominence, caption geometry | `composition.md` (local) |
| motion — the shot model, phases, idle budget, beat structure, stillness | `motion-language.md` (local) |
| effect ids + blueprint ids (vocabulary + recipes) | `../hyperframes-animation/blueprints-index.md` + `../hyperframes-animation/rules-index.md` |
| palette + type tokens | the project's `frame.md`; basics → `hyperframes-creative` `house-style.md` / `typography.md` |
| "produced, not generated" foreground density | `hyperframes-creative/references/video-composition.md` |
| transitions | story-design owns `transition_in`; you don't touch it |
## Before you finish — checklist
- Every frame has `effects` (≥1 cited id; **≥3 when no `blueprint`** is named); a `blueprint` where the frame matches one, with a shot-by-shot composition note.
- **Every frame's composition note is phased** (entrance → development → settle / shot-by-shot) — not a single entry that then freezes; the ≥3 effects are **sequenced across phases**, not all fired at t=0.
- **Stillness is only the 2-3 frames allocated in Video direction**; every other frame develops mid-shot.
- Each visual frame's `asset_candidates` have a `focal` + per-candidate `roles`; none added or dropped.
- **Video direction** stated once at the top (palette · shot model + idle budget · negative list incl. both failure modes · stillness allocation); per-frame entries are deltas, not restatements.
- Content planned into the top ~83% (caption band clear).
- Palette / type pulled from `frame.md` by role — nothing invented.
- You wrote no HTML and never read `capture/`.