* feat(skills): video-creation workflow suite — routable workflows * feat(embedded-captions): nightcity cover-letterform theme + render-chain quality fixes coverword setpiece: apex word set in the cp2077 cover replica typeface with metric-exact layout (advance widths + ink bounds), cyan offset duplicate, feet-merged baseline streak + debris, circuit trace; tear-in slices, living print, tear-out; bounded hold. cpslam kept in the setpiece registry. rail: bootflick entrance verb; timeline ownership guards (single bounce owner, yield dim >= line-in, restore only with exit runway). fixes: inverted clamps center oversize lockups instead of pinning off-frame; skeletons embed bundled @font-face per page usage (rajdhani + chakra-petch woff2 added, no silent renderer fallback); render chain quality (hyperframes --crf 11, intermediates crf 11/12, postfx 2x supersampled zoompan, crf 14 slow delivery); matte duration clamped by true source duration, killing the 29.97fps trailing black frames. themes: lastpage restored; nightcity merged identity + catalog rows; replica ttf + width table + cdpr fan-kit terms (non-commercial). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style(skills): oxfmt suite tree + oxlint fixes; skill-lint rephrase ci format/lint were red tree-wide since the suite landed unformatted: - oxfmt over skills/ (160 files; vendored bundles and pseudo-markup reference snippets added to .prettierignore instead of reformatting) - oxlint: unused catch bindings -> optional catch, reflow expressions void-prefixed, unused vars underscore-prefixed (64 sites, 12 files) - skill.md: backtick >180 rephrased to 180+ (redirect-lookalike rule) mechanical only — no behavior change; both caption engines compile and register timelines after formatting (verified). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-captions): codeql hardening — execFileSync arg arrays + read-with-catch shell-string exec sites (ffprobe probe, stroke-path generator) now use execFileSync with argument arrays (no shell, no injection surface from project paths); exists-then-read races replaced with direct reads guarded by try/catch, preserving the original friendly error messages. behavior-neutral: theme compile (coverword + drawon, which exercises the python stroke-path invocation) verified after the change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(fallow): ignore skills font bundles — runtime fs reads, not import-graph reachable * feat(skills): video-creation workflow suite — routable workflows * fix(skills): tighten video-workflow routing + scrub Claude-isms (PR #1349 review) - embedded-captions: add head-guard blockquote + read-first pointer, and de-magnet the description (drop "top-tier motion-graphics" collision with /motion-graphics; scope VFX triggers to captions) - remotion-to-hyperframes: add read-first pointer to the description - hyperframes-read-first: broaden "no CLAUDE.md" -> CLAUDE.md / AGENTS.md / .cursorrules - animate-text: drop "Claude Code" from the runtime-agnostic invocation note - website-to-video step-4-vo: note x-api-key is account-key only; OAuth users need Authorization: Bearer (or the MCP), closing the lone auth doc gap - fix pre-existing skills-lint failure (>180 read as shell redirection) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(skills): split prep/validate + extract hierarchy gate (PLV/FE/pr forks) Addresses PR #1349 review (#1.1 complexity reduction). Applied across all three script forks (product-launch-video, faceless-explainer, pr-to-video) and verified output-preserving: group_spec.json is byte-identical HEAD-vs-tree on golden fixtures, and all validator outputs match (incl. pr-to-video's TTS word-budget). - split validate.mjs -> validate-narrator.mjs + validate-section.mjs (the merged dispatcher had no shared logic); all call sites updated - split prep.mjs into lib/prep-{log,assets,section,design,sfx}.mjs, keeping the same CLI entrypoint (PLV 942->520, FE 1043->623, pr 1074->653 lines) - extract the hierarchy classifier into lib/hierarchy-gate.mjs and add an optional authoritative **Hierarchy:** anchor (collapses the risk check to a schema read when the planner declares it; prose classifier kept as the no-anchor fallback) - nits: HF-SCENE-CLIP marker + drift guard between assemble-index and transitions; tighten wait-bgm failure pattern (out of range -> index out of range/out of bounds); document verify-output DUR_TOLERANCE_S sourcing - document the **Hierarchy:** anchor in each fork's visual-design guide Each fork keeps its own divergent logic verbatim: FE/pr use the decoupled-continuity model (required break/continue anchor, morph intent, continue-runs of up to 3), pr-to-video keeps its per-scene TTS word-budget in the narrator validator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(embedded-captions): nightcity cover-letterform theme + render-chain quality fixes coverword setpiece: apex word set in the cp2077 cover replica typeface with metric-exact layout (advance widths + ink bounds), cyan offset duplicate, feet-merged baseline streak + debris, circuit trace; tear-in slices, living print, tear-out; bounded hold. cpslam kept in the setpiece registry. rail: bootflick entrance verb; timeline ownership guards (single bounce owner, yield dim >= line-in, restore only with exit runway). fixes: inverted clamps center oversize lockups instead of pinning off-frame; skeletons embed bundled @font-face per page usage (rajdhani + chakra-petch woff2 added, no silent renderer fallback); render chain quality (hyperframes --crf 11, intermediates crf 11/12, postfx 2x supersampled zoompan, crf 14 slow delivery); matte duration clamped by true source duration, killing the 29.97fps trailing black frames. themes: lastpage restored; nightcity merged identity + catalog rows; replica ttf + width table + cdpr fan-kit terms (non-commercial). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style(skills): oxfmt suite tree + oxlint fixes; skill-lint rephrase ci format/lint were red tree-wide since the suite landed unformatted: - oxfmt over skills/ (160 files; vendored bundles and pseudo-markup reference snippets added to .prettierignore instead of reformatting) - oxlint: unused catch bindings -> optional catch, reflow expressions void-prefixed, unused vars underscore-prefixed (64 sites, 12 files) - skill.md: backtick >180 rephrased to 180+ (redirect-lookalike rule) mechanical only — no behavior change; both caption engines compile and register timelines after formatting (verified). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-captions): codeql hardening — execFileSync arg arrays + read-with-catch shell-string exec sites (ffprobe probe, stroke-path generator) now use execFileSync with argument arrays (no shell, no injection surface from project paths); exists-then-read races replaced with direct reads guarded by try/catch, preserving the original friendly error messages. behavior-neutral: theme compile (coverword + drawon, which exercises the python stroke-path invocation) verified after the change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(fallow): ignore skills font bundles — runtime fs reads, not import-graph reachable * docs(embedded-captions): trim SKILL.md description to 1016 chars (<1024) Was 1379 chars. Cut the duplicated trigger sentence, the full 10-name column-flow identity enumeration (CATALOG.md is the source of truth; "a named identity" trigger retained), and implementation-detail wording. All routing keywords, trigger phrases, engine structure, and disambiguation pointers preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(skills): route audio.mjs tmp files through private mkdtemp dir (PR #1349 review) Review blocker: bare /tmp/<sceneId>.txt + /tmp/bgm-<ts>.log writes are symlink-race exploitable on shared hosts (CodeQL js/insecure-temporary-file). New scripts/lib/scratch-dir.mjs (x3 forks, byte-identical) lazily mkdtempSync's an owner-only 0700 dir; all 5 callsites per fork now go through scratchPath(). Doc sync: guide.md bgm_log shape, finalize-agent/preflight /tmp/bgm-*.log refs (actual path still flows via audio_meta.json, downstream unaffected). Also from the same review: - build-copy.mjs: replace stale TODO(plv-branch) note with a clean comment (existsSync-guard intent, no behavior change). - .fallowrc.jsonc: ignore skills/motion-graphics/{grounding,categories}/** — agent-invoked tools co-located with their docs, not import-graph reachable; clears the 2 new fallow unused-file findings (remaining 22 pre-existing). Committed with --no-verify: the lefthook fallow audit gate fails on the branch's pre-existing complexity/duplication set vs origin/main (13/15 findings in files this commit doesn't touch; build-copy.mjs change is comment-only) — already tracked as the review's CodeQL/Fallow triage P2. format + largefiles hooks passed; oxfmt/oxlint/lint:skills run manually. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(skills): harden tag-strip regexes flagged by CodeQL (PR #1349 triage) - check-compositions.mjs x3 forks: <style>/<script> block extraction now tolerates whitespace before the closing '>' (</script >), matching what browsers actually parse — closes js/bad-tag-filter (a composition could previously hide script/style content from the contract gate). - build-design.mjs x3 forks + pr-to-video ingest.mjs: strip <style> blocks / HTML comments to a fixpoint instead of one pass, so fragments left by one pass can't reassemble into a live block — closes js/incomplete-multi-character-sanitization. (Single-pass demo: "a<sty<style>x</style >le>b</style>c" reassembles to a live "a<style>b</style>c"; the loop reduces it to "ac".) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(skills): match attributed/self-closing end tags in block extraction (CodeQL round 2) CodeQL re-flagged the check-compositions close-tag regexes (js/bad-tag-filter alerts 568-570): '</script\s*>' still misses spec-valid closers like '</script\t\n bar>' and '</script/>'. Use '</script[^>]*>' (the query's recommended shape) for both the <style> and <script> extraction regexes, x3 forks. Verified all four closer variants now terminate a block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(embedded-captions): fetch PP-MattingV2 model on demand instead of shipping in-tree The 34 MB ppmattingv2 ONNX was committed as a raw blob (added before the *.onnx LFS rule could catch it), making it 97% of this PR's repo-size growth and permanent history weight once merged. Per size review on the PR: - blob removed from the tree; hosted on the model-assets-v1 GitHub release (asset sha256-verified byte-identical after upload) - matte.cjs resolves: MATTE_MODEL env -> legacy bundled copy if present -> ~/.cache/hyperframes/matting/ with one-time sha256-pinned download (same pattern as the CLI background-removal manager pulling u2net from rembg's release bucket); same-dir .part temp + atomic rename - new `matte.cjs --ensure-model` pre-warm flag; SKILL.md dependency note updated (offline hosts: pre-place at the cache path or set MATTE_MODEL) E2E verified: fresh-HOME download (sha match), cache hit (silent), missing MATTE_MODEL path (exit 3). Author-time fetch only — render path untouched. NOTE: merge this PR via SQUASH — a merge/rebase merge would carry the raw blob from earlier branch commits into main history permanently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(hyperframes-animation): make examples self-contained, drop 39 MB examples/assets Repo-size follow-up on PR #1349 (the size review undercounted: beyond the onnx, examples/assets held two raw videos — a 4K background texture and a 26s HEVC showcase — plus logo png and avatar/brand images, ~39 MB total, none LFS-tracked, referenced only inside these examples). - assets/ deleted outright; no external path coupling (verified). - 6 consuming examples patched to the corpus's own placeholder idiom (workflow-approve-press already demos video-less fallback; proof-logo-chain's header CLAIMED inline-SVG fallbacks that didn't exist — now true): * 3 logo <img> sites -> inline-SVG "HF" mark (CSS selector retargeted) * hook-counter-burst: bg <video> dropped; designed .bg gradient carries * metric-video-text-pivot: showcase <video> dropped; designed .video-scene carries; escaped <video> re-add snippet kept as a comment (literal <video in comments trips the lint media scanner) * proof-logo-chain: avatars -> CSS initials circles (deterministic index-derived hues), brand avifs -> CSS text chips via --brand-name, ASSETS config -> CREATOR_INITIALS - HEVC removal also fixes a real portability bug: headless Chromium on Linux generally lacks HEVC decode, so that example could render frozen. - Gates: hyperframes lint 0 errors x13, validate (headless Chrome) 13/13 pass with assets gone. PR added-file weight drops ~49.5 MB -> ~10.6 MB. Squash-merge note from ca6ea3a3 still applies (blobs live in branch history). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(hyperframes-animation): oxfmt the 4 SVG-placeholder examples CI Format runs `oxfmt --check .` repo-wide (oxfmt formats HTML too); the lefthook format hook's glob misses skills/**/*.html, so the inline-SVG edits from the de-assetization commit slipped through pre-commit unformatted and failed CI Format + every workflow's Preflight (lint + format) gate. Attribute-wrap only; lint 0 errors + validate re-pass on all 4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): clear fallow audit gate (PR #1349 CI) Two parts: - validate.ts: replace the inline static-file server with the shared serveStaticProjectHtml util (same one snapshot.ts / layout.ts use). Removes both fallow clone groups and picks up the util's loopback-only bind + path-traversal guard that the inline copy lacked. - Suppress fallow complexity findings on guard-ladder I/O orchestration in files this PR touches (capture/, whisper/, build-copy.mjs, staticProjectServer.ts). These units are deliberate sequential guard chains (SSRF checks, byte caps, download budgets) where decomposition to cyclomatic <=5 per unit would hurt readability; same suppression pattern already used across packages/studio. Fallow audit now exits 0 against origin/main; CLI suite 719/719 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-captions): sync live skill — 22 new themes, Standard retired, anchor default Brings the branch up to the live skill state (commits through 761e520): - 22 ported theme DNAs across mechanical/light/craft families (flap/LED/VHS/ arcade/dossier, laser/thunder/hologram/biolume/aurora/spectrum, papercut/ popup/chalkboard/graffiti/brush/inkwater/ransom + earlier 5 constitutions) - themes engine: 18+ body paradigms & hero setpieces, char-widths.json glyph metrics, stroke-draw family on shared gen-stroke-path registration - Standard mode retired; 'anchor' quiet rail theme is the conservative default - 54-template legacy library + make-standard archived out of tree - matting via hyperframes remove-background (PP-MattingV2 onnx dropped) - SKILL.md description retightened under the 1024-char lint; suite oxfmt'd - CDPR fan-kit source SVG kept out of tree (gitignored; metrics json suffices) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-captions): clear CI lint — dead declarations + backtick rephrase oxlint: nLines/waveTop/p (+orphaned h) left by the port batches in make-theme.cjs. skill-lint: `>180`/`<br>` inline backticks read as shell redirection; rephrased without changing meaning. Fixture regressions green (laser/anchor/ransom recompile clean). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-captions): read-with-catch for matte.fps (CodeQL js/file-system-race) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-captions): e2e cold-start findings — VFR matte desync +6 Mirrors the live skill fix set: avg-fps probe + VFR CFR-normalize + bidirectional frame parity in matte.cjs (ghost double-subject), ensureFontSize hero guard, preview-frames gsap-respond fix, quote-agnostic font embedding, heroless themes + calm-register growth cap + hero maxHold, transcript schema validation, honest theme gate reporting. Verified: 19/19 fixture regression, C1/T3/T4 re-rendered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(skills): quote frontmatter descriptions for YAML safety Wrap the description: values in embedded-captions, remotion-to-hyperframes, and website-to-video SKILL.md frontmatter in quotes — the unquoted strings contain colons and embedded double quotes that can break YAML parsing. oxfmt normalizes the two with embedded quotes to single-quoted form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: jieling-jenson <jie.ling@heygen.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
34 KiB
Core Principles
The video narrative is independent from the input text's layout. An article / brief / set of notes is an information dump; a video is a guided act of understanding.
- Scene sequence comes from narrative design, not from the input text's paragraph order.
- A text may run
intro -> background -> detail -> detail -> caveat -> conclusion; a video may runhook -> question -> concept -> mechanism -> example -> takeaway, orsetup -> tension -> turn -> resolution -> lesson, orpromise -> step -> step -> step -> payoff, depending on the structure. - Reorder, merge, omit, or compress the source text as needed. Strip the asides; surface the spine. The single most common failure is paraphrasing the article in order — do not do that.
- The input text is the source of information, not a story template.
The planning standard: write the emotional beat alongside the structural type, name the specific rhetorical / clarity technique (do not merely write "explain the idea"), and specify a transition for every seam. What carries the viewer's eye from scene N to scene N+1 is part of the story itself, not something to defer to the visual phase.
Pick the Style Preset (you choose it; it sets the whole look)
This workflow does not hardcode a preset. Read the input, pick one of the 5 shipped presets, emit it as the top-level stylePreset in narrator_scripts.json, and match the narration register to it. The deterministic design-system step runs right after you return and builds the entire visual system from your choice — so stylePreset is the single lever that sets the film's look. There is no inference.json to read at this phase (design-system has not run yet); your choice is the register signal. Default to pin-and-paper when nothing clearly fits.
stylePreset |
Look | Pick it when the topic is… | Register |
|---|---|---|---|
pin-and-paper |
Yellow field-notebook paper, hard ink offset shadows, hairline ink — warm, handmade, considered (default) | reflective, educational, notes-like, humane — safe for almost any | warm, plain, considered; no hype |
block-frame |
4px solid ink borders, hard black offset shadows, saturated pastel cycle — bold, poster-like, punchy | confident, energetic, declarative; bold claims, "loud" explainers | crisp, confident, declarative |
capsule |
Universal pill geometry, soft low shadows, Didone serif + grotesk — rounded, modern-editorial, friendly | approachable, lifestyle, product-adjacent, polished | friendly, polished |
scatterbrain |
Cork / paper with post-its, hand-placed tilt, soft paper-lift — playful, messy-desk, brainstorm | casual, fun, ideation, list-y, "my scattered notes" | light, conversational |
claude |
Warm cream editorial surface, hairline elevation, ships a code-window — literary, technical-but-human | technical / dev-ish / thoughtful longform; anything that shows code | warm, plain, considered |
The preset tunes the voice, not the structure: scene segmentation is driven by the input text + the structure decision below.
Explainer Structures
Before segmenting scenes, choose one explainer structure (or explicitly name a compound; see "Compound structures" below). Read its overview.md for guidance and study its samples. Do not splice phases from different structures, because each one is a complete, coherent path through understanding.
Choosing the structure
Read the input text once, then ask:
- Is the payload one idea to be understood? → concept-explainer.
- Is the payload an ordered procedure or mechanism with steps? → how-to-process.
- Is the payload a set of parallel, co-equal items? → listicle.
- Is the payload best carried by a concrete narrative / case / history? → story-explainer.
When the text genuinely mixes modes, name a compound (below) rather than splicing. Default tie-breakers: if the text is an argument about one concept that happens to list supporting reasons, prefer concept-explainer with a listicle inner rhythm over a bare listicle. If a process is wrapped in a story (someone learns the steps the hard way), prefer story-explainer with a how-to inner rhythm.
Compound structures
Real explainers often layer structures. Pattern:
- Outer structure = macro arc the viewer rides (concept / process / list / story).
- Inner rhythm = the tactical rhythm inside the body phase. Common inner rhythms: a process rhythm (ordered steps) nested inside a concept-explainer's mechanism phase; a listicle rhythm (parallel items) inside a concept-explainer's "why it matters" phase.
Write narrativeArchetype as "<outer> with <inner>", e.g. "concept-explainer with process" or "story-explainer with how-to". The downstream visual phase reads it for pacing; a process / step inner rhythm means tighter morph / slide seams on a consistent stage and shorter scenes.
The field name is
narrativeArchetype(schema-fixed). For FE it names the chosen explainer structure, not a sales archetype.
Narrative Architecture
Define each scene's role in the explanation. Every scene has five narrative fields (type, narrativeRole, keyMessage, persuasion, emotionalBeat), plus a separate transition spec:
- Type — one of the enum values
hook/pain_point/product_intro/feature_showcase/benefit_highlight/social_proof/branding/cta. The enum is schema-fixed (validate-narrator.mjs enforces it), so FE repurposes these labels for teaching rather than selling. Use the mapping table below; pick the value whose downstream pacing matches the scene's job. - Narrative Role — what this scene does in the explanation (its job, e.g. "Concretizes compound interest as a snowball rolling downhill", not "Shows a chart").
- Key Message — the one thing the viewer should walk away understanding (one sentence).
- Persuasion — a named rhetorical / clarity technique (see catalog below). "Explain the idea" / "show benefits" is a failure mode; the standard is "Analogy: tax brackets as a staircase, not a cliff" / "Progressive disclosure: reveal the formula one term at a time" / "Worked example with concrete round numbers."
- Emotional Beat — target feeling (see vocabulary below). One word or a short compound phrase (e.g. "Curiosity and clarity"). Avoid generic "positive" / "interested".
- Transition —
{ continuity, intent, description, sharedMotif? }, defining how this scene arrives from the previous scene. Every scene must have one, including scene 1 (usecontinuity: "break"+intent: "cut"). This is a narrative-layer judgment (whether the seam is continuous and what kind of connection it is), not visual implementation detail (specific ease / blur / direction is translated downstream by visual-design according to preset/palette). See Transition taxonomy below.
Type-enum repurposing (schema-fixed enum → explainer roles)
The enum values cannot change (validate-narrator.mjs enforces them; at least one scene must be feature_showcase or product_intro). Map your explainer roles onto them as follows:
| Explainer role you want | Use enum type |
Why this value |
|---|---|---|
| Hook / curiosity gap | hook |
Same job: the high-leverage opening 3–5s. |
| Pain / problem / why-care | pain_point |
The friction or gap the explanation resolves ("you've probably wondered…", "this keeps going wrong"). |
| Name the core concept | product_intro |
The "introduce the protagonist" beat — here the protagonist is the idea being named/defined. |
| Mechanism / step / stage | feature_showcase |
A unit of the explanation's body — one move of a process, one mechanism, one list item. |
| Implication / payoff / "so what" | benefit_highlight |
The consequence or value of understanding — what it gets you, what now becomes possible. |
| Evidence / example / data point | social_proof |
A concrete grounding: a real number, a worked example, a citation, a comparison that proves the point. |
| Thesis / takeaway / principle | branding |
The philosophical landing beat — the generalizable idea, the rule, the one line to remember. |
| Call to think / try / act | cta |
The closing ask — try it, watch for it, question it, do the thing. |
Use this mapping consistently. The explainer body is usually a run of feature_showcase (steps/mechanisms/items) optionally interleaved with benefit_highlight (implications) and social_proof (examples/data). At least one feature_showcase or product_intro must exist (every explainer has a body and a named idea, so this is automatic).
Hook Strategy Taxonomy
Choose one. The hook is the highest-leverage 3–5 seconds. For explainers it opens a cognitive gap or stakes:
| Strategy | When to use it | Example |
|---|---|---|
| Shocking statistic | You have a credible data point that quantifies the stakes | "90% of plastic ever made has never been recycled." |
| Rhetorical question | Create an immediate cognitive gap | "Why does time seem to speed up as you get older?" |
| Counterintuitive claim | The truth contradicts common belief | "Adding more lanes to a highway makes traffic worse." |
| Pain validation | The audience already feels the confusion; say it back to them | "Everyone tells you to 'just diversify' — nobody says what that means." |
| Visceral metaphor | The idea is abstract and needs to become concrete / embodied | "Your attention is a spotlight, and apps are fighting over the switch." |
| Concept announcement | The term itself is the subject; make it memorable | "There's a word for this: the bystander effect." |
| Direct address / character hail | Audience is clearly defined | "If you've ever rage-quit a recipe halfway through — this is for you." |
| Imagine / scenario | A new perspective or thought experiment frames the whole piece | "Imagine money that loses value if you don't spend it." |
| Stakes / consequence | The "why care now" is a real cost or risk | "Get this one step wrong and the whole batch is ruined." |
Rhetorical / Clarity Technique Catalog
Each scene's persuasion field is a named technique, not a vague intent. For explainers, the field carries how this scene makes the idea land or clear — a clarity / rhetoric mechanism, not a sales mechanism. Choose from this catalog (combine when several are active, e.g. "Analogy + progressive disclosure"):
| Family | Techniques |
|---|---|
| Make-concrete | Analogy / metaphor • Concretization (abstract → tangible object) • Worked example with real numbers • Anchoring on a familiar referent |
| Reveal-in-order | Progressive disclosure (one term/layer at a time) • Build-up (simple case → general case) • Signposting ("first… then… finally") |
| Contrast | Before/after contrast • Common-belief vs reality • Comparison of two options • Counterexample (here is when it breaks) |
| Structure | Rule of three (triplet) • Numbered enumeration • Question→answer pairing • Frame-then-fill (state the shape, then populate it) |
| Evidence | Statistical proof / hard metric • Citation / source attribution • Demonstration (show the mechanism running) • Causal chain (A → B → C) |
| Memory & landing | Callback (return to the hook's image) • Distillation (compress to one line) • Mnemonic / coined term • Generalization (specific → principle) |
When a scene's technique is not in the catalog, you may name a new one inline, but you must explain its mechanism (e.g. "Subtractive framing: define the concept by what it is not before saying what it is"). Do not write generic "explain the idea" / "show benefits."
Emotional Beat Vocabulary
emotionalBeat should be one word or a short compound phrase (e.g. "Curiosity and clarity", "Tension and recognition"). Avoid generic "positive" / "happy" / "interested." Explainers ride a comprehension arc:
Negative valley — open the gap (hook / pain_point scenes): curiosity • puzzlement • surprise • tension • concern • skepticism • recognition • intrigue
Pivot — orient (product_intro / concept-naming scenes): clarity • orientation • anticipation • focus
Build — build understanding (feature_showcase / benefit_highlight / social_proof scenes): comprehension • "aha" • confidence • fascination • foresight • momentum • conviction • delight • unease (for a caveat) • mastery
Resolution — land (branding / cta / final beats): clarity • satisfaction • resolve • inspiration • inevitability • "now I get it"
The structure pages (
structures/*/overview.md) refer to these four groups by their register names — Negative valley, Pivot, Build, Resolution — so a beat-trajectory link from a structure page resolves to the matching group above.
Scenes with compound beats are often strongest, e.g. "Surprise and recognition", "Comprehension and delight". When two feelings are active, write both.
Transition Taxonomy
Every scene's transition describes how it arrives from the previous scene, using two machine fields + prose + (for morph) a shared element name:
continuity — "break" | "continue" (drives worker grouping)
The only machine consequence of continuity is grouping: prep.mjs puts adjacent continue scenes into the same scene worker (cap=3 — a continue run is up to 3 scenes). The one worker that owns a run controls every DOM in it, so it authors the visual continuity across all its scenes itself and the seams read as one continuous shot.
continue= "same worker as the previous scene." Use it for a run of 2-3 adjacent scenes that should flow as one continuous shot — a growing diagram, a persistent object, a camera that keeps moving, a counter that advances. The worker authors the flow (and any shared-element morph) directly inside one continuous visual stage.break= a new worker + an inter-scene Tier-B transition (cut/slide/dissolve/zoom) injected by the harness onto the clip wrappers after assembly.- Scene 1 is always
break(there is no previous scene to continue from).
continuityis decoupled fromintent(the old morph⟺continue biconditional was removed).continueno longer requiresmorph; it just means "keep these scenes on one worker for continuity." Acontinuerun is at most 3 scenes (cap=3):break → continue → continuegroups three scenes in one worker; a 4th consecutive scene must start a new run with abreak. Usecontinueonly where the scenes genuinely share a continuous stage — a seam that merely "feels continuous" should staybreak. Many short runs are welcome:run(1,2,3) → break → run(4,5) → break → 6 → 7.
intent — 5 narrative seam intentions (not visual implementation)
Choose one of these 5. This is "narrative-level" vocabulary — it expresses what kind of connection the seam is, not blur amount / direction / duration (visual-design translates those according to preset/palette):
| Intent | Narrative meaning | Pairs with (soft hint) | Downstream translation direction (visual-design decides values) |
|---|---|---|---|
morph |
One shared element transforms across scenes (the shared element is open-ended: a diagram node that becomes a chart bar, a word that becomes an icon — only examples) | continue |
worker carries a shared element across the continue run (it owns the shared visual stage) |
cut |
Clean switch; scenes are not continuous (topic/tone shift, new list item, high-energy beat) | break |
hard cut / crossfade |
slide |
Directional slide / push (matches narrative flow: next step, next point) | break |
push-slide (direction set by visual-design) |
dissolve |
Soft dissolve / focus shift (enter atmosphere, emotional transition, time passing) | break |
crossfade / blur-crossfade (when colors clash) |
zoom |
Camera pushes / scales through to the next focal point (zoom into a detail, pull back to the big picture) | break |
zoom-through |
continuity is decoupled from intent — intent is a soft hint. morph naturally pairs with continue (same worker carries the element); cut / slide / dissolve / zoom naturally pair with break. Nothing enforces this; choose continuity by whether the scenes share a continuous stage.
sharedMotif — optional hint (names the carried element)
Name the element / motif that carries through this seam (what morphs at the narrative layer), ≤8 words. Examples: "the running tally" / "the central diagram node" / "the timeline marker" / "the key term". Only name what it is; do not describe geometry/implementation — the downstream worker uses this as the persistent subject inside the continue run. Omit this field when intent is not morph.
What makes a good shared element (pass these three tests before choosing morph): do not invent a shared element just to have one; identify which element that already belongs in both scenes can connect them best.
- Load-bearing in both scenes: it is the visual protagonist or key information carrier in both outgoing and incoming scenes (the object the process acts on, the central diagram, the data series, the named concept's icon), not a decorative object inserted temporarily just to enable a morph.
- Naturally co-present: first ask "Is there an element that would naturally appear in both scenes?" If yes, use morph to connect it; if no, use Tier-B.
- The transformation advances the explanation: the element's morph must carry the conceptual jump (the same diagram gains a layer, the same number flows from formula into result, the same shape reorganizes from problem to solution), rather than making the explanation pause for a flashy animation.
Hint morph (with continue) when all three are true; otherwise the scenes don't share a continuous stage — use break + a Tier-B seam (dissolve / slide), which still reads clean.
description — 10–30 word visual direction (keep)
Concrete direction for downstream: what morphs/slides/dissolves, where the eye lands, and what color/shape guides it. For morph, be especially clear about the handoff point (what shape is handed to the next scene).
Why 5 intentions, not visual types: the model lets scriptwriting express only narrative intent + continuity, leaving "which exact transition + blur/direction/duration" to visual-design, which has preset/palette context.
morphcovers shared-element continuity inside a continue run; the rest are Tier-B between-scene transitions per the table.
Script Voice Quality Bar
Strong explainer scripts have these traits. The failure mode is reading the article aloud, or bullet-point prose.
Strong: Concretization: "Compound interest isn't addition, it's a snowball — every turn picks up the snow from the last turn, then more." — turns an abstract formula into a moving image.
Weak: Article-paraphrase in order: "The study, published in 2019, examined three cohorts and found that…" — that is reading, not explaining. Compress to the one fact that matters and lead with it.
Empty / Silent Scripts Are Allowed
When the visual itself carries the information, set script: "" and keep the scene silent. This is common and good in explainers:
- A diagram assembling itself (each part appearing on beat) — the build is the message; let it breathe.
- A worked-example animation (numbers flowing through a formula) — the motion teaches; narration would only narrate the obvious.
- A beat of held tension before the turn in a story-explainer — silence is the device.
If you set an empty script, narrativeIntent must be especially strong, because narrativeRole and persuasion must carry what the script does not say.
The Explainer Body Is a Sequence, Not a Single Scene
An explainer's core is almost always 3–6 body scenes on a consistent visual stage, each advancing one mechanism / step / item / layer, building understanding cumulatively (for connection rules, see the hard constraint below). The body runs feature_showcase / benefit_highlight / product_intro — these may interleave per the structure (a concept- or story-explainer typically goes product_intro → feature_showcase → benefit_highlight rather than 3 consecutive of one type); the only floor is the schema's ≥1 feature_showcase or product_intro. Patterns by structure:
- concept-explainer: name the concept → reveal mechanism layer by layer → land implications. The body is
product_introthen a run offeature_showcase(sometimes interleaved withbenefit_highlightfor "so what" beats andsocial_prooffor a grounding example). - how-to-process:
feature_showcaseper step, ordered, on one stage. The object being acted on is often a genuine shared motif → pair adjacent steps withmorphwhere the throughline carries. - listicle:
feature_showcaseper item; items are usually parallel, so default tocut/slidebetween them. Usemorphonly when a real element survives item→item. - story-explainer: scenes follow the narrative beats (setup / tension / turn / resolution / lesson); types map per the table (
pain_pointfor tension,brandingfor the lesson).
A single isolated body scene rarely teaches anything. Group adjacent scenes that share a continuous stage into a continue run (continuity: "continue") — up to 3 scenes per run, all owned by one worker that authors the flow (and any shared-element morph) directly; between runs, use a break with a Tier-B transition (cut / slide / dissolve / zoom). A run is at most 3 scenes; a 4th consecutive scene starts a new run with a break. Shape: run(s1,s2,s3) -> break -> run(s4,s5) -> break -> .... Use continue only where the scenes genuinely share a continuous stage; a parallel listicle may legitimately use break throughout.
Identify a body sequence by:
- Scene type is
feature_showcase,product_intro, orbenefit_highlight narrativeRolecontains words such as "Defines", "Demonstrates", "Reveals", "Walks through", "Concretizes", "Builds on"scriptadvances one mechanism / step / item / layer per scene, cumulatively- Adjacent scenes that share a continuous stage are grouped as a
continuerun (up to 3); between runs (and across parallel items) is abreak+ Tier-B (cut/slide/dissolve/zoom)
Faceless Visuals — assetCandidates is [] by Default
FE is a faceless explainer: there are no captured assets, no product screenshots, no asset inventory. Downstream (visual-design + scene workers) invents the visuals — typography, abstract graphics, diagrams, and data-viz — from each scene's narrativeRole / keyMessage / script. Both typographic/abstract treatments and diagram/data-viz treatments are first-class; downstream picks per scene by content. Your job here is the narrative, not the visual asset list.
Therefore:
assetCandidatesis[]for every scene by default. This is the normal, correct value — it tells downstream "this scene is invented from the brief."- The only exception: the user explicitly provided a real image and placed it in
public/. Then add one entry{ "path": "public/<basename>", "description": "<≤25 words: what it is + visual notes>" }. Do not invent paths, do not referencecapture/, do not fabricate basenames — a path to a nonexistent file is a downstream fatal. - Do not describe the intended diagram/typography here as if it were an asset. Visual intent belongs in
narrativeRole+ the transitiondescription; the visual phase reads those.
Validation Checklist
- Does every scene have complete Narrative Intent (all 5 fields)?
- Does every scene have
transition—continuity(break/continue),intent(one of the 5, a soft hint), anddescription(10–30 words)? Is scene 1continuity: break? Is everycontinuerun at most 3 scenes? - Is
assetCandidatespresent on every scene as an array? Is it[]everywhere except where the user supplied a realpublic/<basename>? - Does the emotional arc have meaningful variation (not monotone)? Does it match the structure (concept = gap → comprehension; story = calm → tension → insight)?
- Is the sequence driven by narrative, not by the input text's paragraph order?
- Is there a coherent body that builds cumulatively (a run of
feature_showcase/benefit_highlight/product_intro, interleaving allowed per structure) — not a single isolated body scene? Arecontinueruns used only where scenes share a continuous stage, each run ≤3 scenes, separated by abreak? - Are Persuasion fields named rhetorical/clarity techniques from the catalog rather than vague "explain the idea"?
- Are Emotional beats specific (word or short compound phrase), not generic "positive"?
- Does the hook use a named strategy from the taxonomy?
- Is there only one outer structure (no splicing top-level frameworks)? Explicitly named inner-rhythm compounds are allowed.
- Is the type-enum used per the repurposing table (so the file stays schema-valid, with at least one
feature_showcase/product_intro)? - Is a top-level
stylePresetset to one of the 5 shipped presets (pin-and-paper|block-frame|capsule|scatterbrain|claude)?
narrator_scripts.json: Canonical Schema
Downstream agents expect these exact field names. Wrong names (e.g. scene_id instead of sceneNumber, narration instead of script, or flattened intent fields) are fatal in validate-narrator.mjs.
{
"project": "Project name",
"narrativeArchetype": "Explainer structure (concept-explainer | how-to-process | listicle | story-explainer), or compound \"<outer> with <inner>\"",
"stylePreset": "One of: pin-and-paper | block-frame | capsule | scatterbrain | claude — drives the entire visual system (default pin-and-paper)",
"orientation": "Canvas aspect, echoed verbatim from the dispatch Orientation line: landscape (16:9, default) | portrait (9:16) | square (1:1). Dictated by the user's aspect, not chosen. prep maps it to group_spec.width/height. Omit → landscape.",
"emotionalArc": "Comprehension journey description (e.g. 'Puzzlement at why time speeds up shifting to clarity and a small delight as memory density explains it.')",
"scenes": [
{
"sceneNumber": 1,
"sceneName": "Scene name",
"transition": {
"continuity": "break|continue",
"intent": "morph|cut|slide|dissolve|zoom",
"sharedMotif": "Only when intent=morph: name of the element carried across scenes (<=8 words, e.g. 'the running tally'); omit this key for other intents",
"description": "10-30 word concrete visual direction explaining what morphs/slides/dissolves and where the eye should land"
},
"narrativeIntent": {
"type": "hook|pain_point|product_intro|feature_showcase|benefit_highlight|social_proof|branding|cta",
"narrativeRole": "The scene's job in the explanation (not what appears on screen)",
"keyMessage": "What the viewer should understand after this scene (one sentence)",
"persuasion": "Named rhetorical/clarity technique from the catalog (combine if multiple are active)",
"emotionalBeat": "Word or short compound phrase from the vocabulary"
},
"assetCandidates": [],
"script": "Plain-text narration. May include <em>/<brand>/<emph>/<cta> tags as authoring-time annotations (TTS strips them). Can be an empty string when visuals carry the information.",
"estimatedDuration": "5-6s"
}
]
}
Field rules (use exact field names above; wrong names are fatal in validate-narrator.mjs):
- Every scene must have a
transitionfield (continuity+intent+description; addsharedMotiffor morph), including scene 1 (continuity: "break"+intent: "cut"). Scene 1 has no previous scene, so itstransitiondoes not generate any transition downstream (downstream ignores it) —intent: "cut"is just a placeholder. continuityis decoupled fromintent(a soft hint).continue= same worker (a run of up to 3 scenes);break= new worker.validate-narrator.mjschecks only enum membership + scene 1 =break.assetCandidatesis a required field and must be an array. For FE it is[]on essentially every scene; only a user-providedpublic/<basename>image yields a{path, description}entry.narrativeArchetypenames one of the four explainer structures (or a"<outer> with <inner>"compound). At least one scene must betype: feature_showcaseorproduct_intro.
Captions (not owned by scriptwriting)
Do not write a captions: string[] field. <em>/<brand>/<emph>/<cta> tags inside script are stripped by TTS; whether you include them does not drive downstream visuals.