refactor(skills): rebuild faceless-explainer + pr-to-video on the shot-sequence architecture (#1778)

* refactor(skills): rebuild faceless-explainer + pr-to-video on the shot-sequence architecture

Move both skills onto the current shot-sequence authoring architecture and
the latest shared engine, then re-narrate to each domain. The engine fixes
(pre-assembly frame guards + BGM loop-extend in assemble-index, dark-ground
caption contrast, brand-accent selection + mono role in tokens, dark-mode
polarity invert / weight-clamp / icon-font filter in build-frame) had only
landed in one copy; both skills were a generation behind.

- Authoring model: visual-design now writes a time-coded shot sequence (Scene
  windows paced to the voiceover) instead of the older effects-id phased note;
  motion-language carries the move vocabulary + the tightened motion doctrine
  (smooth over bouncy) + the seek-safe core (fromTo entrances, no CSS-transition
  motion); add cut-catalog (within-frame velocity-matched seams); frame-worker
  and SKILL Step 4/5 move to blueprint instantiation + shot-sequence fidelity.
- faceless-explainer: fold the standalone composition.md into visual-design
  (inventing-the-visual / portrait / caption geometry); keep the explainer
  story doctrine; graft cue-segmented VO + candidate-blueprint-from-Step-3.
- pr-to-video: keep the ingest pipeline (fetch-pr / ingest / fetch-people-avatars)
  and code-vocabulary; preserve the code-beat (code-* block as focal, Scenes
  choreograph the surround) and mechanism-beat treatments under the new model.
- Drop stage-assets from both (no captured assets to stage); remove derivation
  references so each skill reads standalone.

bun run scripts/lint-skills.ts passes; all scripts node --check clean.

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

* chore(skills): resync skills-manifest after format pass

The pre-commit skills-manifest hook hashed the skills before the format hook
reformatted cut-catalog.md, so the committed manifest lagged the on-disk
content and CI's "Skills: manifest in sync" check failed. Regenerated.

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-30 00:02:24 +08:00
committed by GitHub
co-authored by Claude Opus 4.8
parent c811a2750a
commit 7cb8386539
29 changed files with 2123 additions and 779 deletions
@@ -5,25 +5,25 @@
**INPUT** — your dispatch context provides:
- `PROJECT_DIR` — the project root; all paths are relative to it.
- `frame_id` — e.g. `03-mechanism`. Use it **verbatim** as the composition id, the `window.__timelines` key, and the file name (`compositions/frames/03-mechanism.html`) — that path **is** the frame's `src` in `STORYBOARD.md`, so writing there is how the assembler finds your frame.
- `frame_id` — e.g. `03-compounds`. Use it **verbatim** as the composition id, the `window.__timelines` key, and the file name (`compositions/frames/03-compounds.html`) — that path **is** the frame's `src` in `STORYBOARD.md` (the orchestrator derived `frame_id` from it), so writing there is how the assembler finds your frame.
- Your **`## Frame N` block** in `STORYBOARD.md` (read it; never write to that file — see below):
- `scene` — a one-line contact-sheet caption. **Design intent, never visible DOM text.**
- `voiceover` — the narration line. **Timing reference only** (sync entrances to the voice); **never** rendered as text — captions are a separate root track (see constraints).
- `duration` — your render length in seconds. **Fixed upstream; never change it or tween to fill a different length.**
- `transition_in` — informational. The injector stamps it at the root; **you do not author transitions.**
- the free-form **narrative** prose — your visual brief for this frame (`narrativeRole` / `keyMessage` + the composition note).
- `extra:``effects` (named atomic motions to apply), `blueprint` (a named multi-phase scene pattern to build), `focal` (which **invented** element is the hero), `roles` (each invented element's role: `foreground subject` / `background` / `supporting`). (`sfx` also rides in `extra` but is the orchestrator's — you mount no audio.)
- the **time-coded shot sequence** — your build spec. A sequence of Scene lines (`Scene 1 (0.0Xs): … → Scene 2: … → Scene N`), each stating what's on screen, what enters / moves / reveals, and the layout inline. Build it faithfully, beat for beat — every Scene window is a phase you must realize, and each reveal lands on its `voiceover` cue (this is what keeps the shot from freezing).
- `blueprint:` — an id (or the literal `compose`). The id points to `../hyperframes-animation/blueprints/<id>.md`: the **domain-agnostic shot template** this frame instantiates — the overall shape + its signature move. Read it for the shape; `compose` means there's no template, sequence the shot from the Scene lines directly.
- `focal:` — which **invented** element is the hero.
- `roles:` — each invented element's role: `foreground subject` / `background` full-bleed / `supporting`. Because the explainer is **faceless, these are elements you design** (a hero word, a diagram node, a chart series, a coined-term card), not captured assets. The **only** real media is a user-supplied image, when present: `public/<basename> — description` (a **`[video]`** tag marks a `.mp4` clip the user provided).
- `sfx:` — the orchestrator's; you mount no audio.
- `frame.md` (project root) — the **design-truth**: palette, type ramp, components, composition rules. The LOOK. Pull every visual token from here.
- `ANIM_DIR` — absolute path to the shared `hyperframes-animation/` skill. Resolve every cited id under it: `ANIM_DIR/rules/<id>.md` (effect recipe), `ANIM_DIR/blueprints/<id>.md` (blueprint recipe), `ANIM_DIR/examples/<id>.html` (a worked, runnable source for each blueprint).
- `RULES_DIR` — absolute path to this skill's local `../hyperframes-animation/rules/`. The **named motion verbs in the Scene lines** (and the moves the blueprint cites) resolve to rule recipes here: `RULES_DIR/<id>.md` is the mechanics for a motion. (A few rules link an optional runnable demo in the shared `../hyperframes-animation/examples/<id>.html` — open it only when a recipe is unclear.)
- `../references/cut-catalog.md` — the **cut catalog** (zoom-through / inverse / cut-the-curve / waterfall). When a Scene seam is a within-scene swap, a scene-to-scene cut, or a text-to-text line change, build it INSIDE your composition per this catalog (Z-scale + blur + opacity, or per-word x-staggers). You never author the between-frame transition — story's `transition_in` + the injector own that.
- Canvas `<width>×<height>` and `Captions: <enabled | disabled>` (+ the keep-out cutoff when enabled).
**Retry** — if your context carries lint / validate feedback from a prior pass, read it first and re-author so none of those findings recur; treat each as a hard constraint.
**OUTPUT**`compositions/frames/<frame_id>.html`, one self-contained sub-composition. Writing it (past the self-check) is your **terminal action** — you do not edit `STORYBOARD.md`, mint audio, assemble the index, or report back. The orchestrator picks up the file and marks the frame's `status`.
## Faceless — you INVENT the visual
This is a faceless explainer: there are **no captured assets, no screenshots, no product UI**. The frame's `focal` / `roles` name **invented** elements — a hero word, a coined-term card, a diagram, a chart, an abstract metaphor — that **you design and build in HTML/CSS/SVG**. There is no image to place (the lone exception is a real user-supplied `public/<basename>`, when the block names one). Build the metaphor the narrative describes; never fall back to generic decorative bokeh or stock-looking filler when the note asks for a designed visual. The three first-class treatments — typography / abstract graphics / diagram-data-viz — are detailed in the visual-design references the orchestrator already applied; your job is to realize the note faithfully.
**OUTPUT**`compositions/frames/<frame_id>.html`, one self-contained sub-composition. Writing it (past the self-check below) is your **terminal action** — you do not edit `STORYBOARD.md`, mint audio, assemble the index, run the CLI, or report back. The orchestrator picks up the file and marks the frame's `status`.
## You do NOT decide
@@ -31,42 +31,44 @@ These belong to other steps — touching them collides with a sibling or breaks
- **What is SAID** — narration is locked in `SCRIPT.md` / the `voiceover` line. You only show; you never write or restate narration text.
- **Duration** — fixed from real voice timing. Build your entrance to land within it; don't stretch or trim it.
- **Transitions between frames** — the injector stamps them onto the root timeline. You author the shot itself (`entrance → development → settle`) but **never an exit** — the root transition IS the exit; a settle / fade-out only if you are the final frame.
- **Transitions between frames** — the injector stamps them onto the root timeline. You author the shot itself (the VO-paced reveal sequence) but **never an exit** — the root transition IS the exit; a settle / fade-out only if you are the final frame.
- **Audio** (narration / BGM / SFX) — assembled at the root by the orchestrator. **No `<audio>` element in your composition.**
- **Design tokens** — palette / fonts / components come from `frame.md`. Don't invent them, and **never lift a word, label, or wordmark out of `frame.md` as your copy** — it is a style spec, not content. Visible text comes from your frame's `scene` / narrative.
- **Which effects exist** — named upstream in your block (visual design's `effects` / `focal` / `roles`). Implement them; don't invent new ones.
- **Design tokens** — palette / fonts / components come from `frame.md`. Don't invent them, and **never lift a word, label, or wordmark out of `frame.md` as your copy** — it is a style spec, not the video's content. On-screen text comes from your frame's `scene` / narrative.
- **Which motions exist** — named upstream in your block (the shot sequence's motion verbs + `blueprint:`). Implement them; don't invent new ones. You design the **invented visuals** the Scene lines describe (typography / abstract graphics / diagrams / data-viz), but you have **no asset-fetch tool** — never fabricate an image URL or reference a file that isn't the user-supplied `public/<basename>`.
- **The shared `STORYBOARD.md`** — read your block, never write it. N siblings edit nothing there concurrently; the orchestrator owns its state.
## Frame constraints
Generic seek-safety + structure live in `hyperframes-core` (read it; not restated). These are the **faceless-explainer deltas**, each load-bearing:
- **Caption keep-out — all content in the top ~83%.** A karaoke caption pill owns the bottom ~17% of the canvas. Keep every element (headline, cards, diagram, labels) above `y ≈ 0.83 × height` — compute the pixel cutoff from your canvas (e.g. `≤ 900` on a 1080-tall frame, `≤ 1600` on a 1920-tall portrait). Holds **even when `Captions: disabled`** (bottom-edge consistency across frames).
- **Fill the content area — especially portrait.** Compose the whole top-83% region; don't float one small cluster mid-frame. Anchor the hero high (~0.20.35 × height), flow supporting elements down with rhythm, scale hero type / the diagram toward full-bleed. (Landscape's region is short, so vertical centering near 0.42 × height is fine.)
- **Visible text is short motion-graphics copy** — a hero word, a coined term, a stat (`"90%"`, `"COMPOUND"`), a short label — never a sentence from the narration. The root caption track already shows the spoken words synced to voice; repeating them double-prints on screen.
- **Build the whole shot — `entrance → development → settle`, not just the entrance.** A frame that animates in over ~0.8s then freezes for the rest of its `duration` reads as a PowerPoint slide. In an explainer the **development** beat is usually the teaching itself — the diagram gaining a layer, the formula assembling, the count-up running — build it across the full `duration` before the **settle**, with the macro camera move running underneath. **Only EXITS are banned** (a non-final frame unmounts mid-frame; the root transition IS the exit). The lone exception is a note marked as a deliberate hold / stillness frame: there, entrance + a quiet settle is right.
- **Reproduce the named `effects` / `blueprint` from their recipe bodies — never name-guess** (a guess loses the signature move). Every id has a real recipe under `ANIM_DIR` (`rules/<id>.md` per effect, `blueprints/<id>.md` per blueprint, `examples/<id>.html` to watch it run). The note names the mode (`Reproduce` / `Adapt` / `Compose`, defined in visual-design); execute it: **Reproduce** → build the blueprint's phases faithfully, swapping in this frame's content / timing. **Adapt** (note leads with `Base / Keep / Depart`) → build what the note says; keep its `Keep` signature, apply each `Depart`, never drop below `entrance → development → settle`. **Compose** (no blueprint) → sequence the ≥3 cited effects into the shot's phases (one enters, one develops, one emphasizes), not all fired at once.
- **Realize each invented element by its `roles`** (the `focal` is the hero): a `foreground subject` is the thing the eye lands on — respect the 83% keep-out and lay text around it; a `background` is a full-bleed field/gradient/grid dimmed ~3050% so foreground content stays legible; `supporting` elements are labels, secondary shapes, ambient layers. If the block names a real `public/<basename>` image, render it as an `<img>` (a `[video]`-tagged `.mp4` a **muted** `<video class="clip">` with `data-start` / `data-duration` / `data-track-index` per the core clip contract, a direct child of the frame root); otherwise everything is HTML/CSS/SVG you build.
- **Caption keep-out — all content in the top ~83%.** A karaoke caption pill owns the bottom ~17% of the canvas. Keep every element (headline, diagram, coined-term card, stats) above `y ≈ 0.83 × height` — compute the pixel cutoff from your canvas (e.g. `≤ 900` on a 1080-tall frame, `≤ 1600` on a 1920-tall portrait). Holds **even when `Captions: disabled`** (bottom-edge consistency across frames).
- **Fill the content area — especially portrait.** Compose the whole top-83% region; don't float one small cluster mid-frame. Anchor the hero high (~0.20.35 × height), flow supporting elements down with rhythm, scale hero type toward full-bleed. (Landscape's region is short, so vertical centering near 0.42 × height is fine.)
- **Visible text is short motion-graphics copy** — a hero word / coined term / stat / one-word emphasis (`"COMPOUNDING"`, `"2×"`), never a sentence from the narration. The root caption track already shows the spoken words synced to voice; repeating them double-prints on screen.
- **Build the whole shot — reveal across the full `duration`, never front-load.** Dumping the whole canvas in the first ~25% then holding it is exactly what reads as a PowerPoint slide. Instead reveal each piece — a line, a layer, a node, a stat — **as the `voiceover` reaches it** (on a silent frame, on the beat), sequencing reveals across the shot and especially the back ~50%, with the macro camera move running underneath. **Only EXITS are banned** a non-final frame unmounts mid-frame, so an exit tween truncates and reads as a glitch (the root transition IS the exit); mid-shot reveals are free and seek-safe. The lone exception is a note marked as a deliberate hold / stillness frame: there, an entrance + a quiet settle is right (a held read beats bad motion).
- **Implement the shot sequence faithfully — every Scene is a timeline phase.** The Scene lines ARE the build: map each Scene onto a phase of the one timeline, each piece revealing as the `voiceover` reaches it. For each **named motion** in a Scene, open its rule recipe under `RULES_DIR/<id>.md` and reproduce its mechanics — **never name-guess** (a guess loses the signature move). The **`blueprint:` template** (`../hyperframes-animation/blueprints/<id>.md`) gives the overall shape; read it and keep its **signature move** recognizable, then instantiate it with this frame's invented content / timing. `compose`no template; sequence the shot straight from the Scene lines. Whichever, never front-load the whole sequence at `t=0` — pace the reveals to the voiceover.
- **Design each element by its `roles`** (the `focal` is the hero): a `foreground subject` is the thing the eye lands on — respect the 83% keep-out, lay text around it, not over it; a `background` is full-bleed and dimmed ~3050% so foreground content stays legible; `supporting` elements (labels, secondary shapes, ambient layers) stay quiet. These are **invented** — you build them in SVG / CSS / type, not from a file. **If the user supplied a real image** named in `roles:`/`focal:`, place it: a `[video]` candidate (`.mp4`) renders as a **muted** `<video class="clip">` (`data-start` / `data-duration` / `data-track-index` per the core clip contract), a **direct child of the frame root** — never nested in another timed element, or the renderer freezes it; an untagged image → `<img>`.
## Workflow
1. **Read**`hyperframes-core`'s composition contract (the structural law), then `frame.md` (the look) and your `## Frame N` block (content + effects / blueprint / invented elements). **Then open the recipe body of every id the block cites** `ANIM_DIR/rules/<id>.md` per effect and `ANIM_DIR/blueprints/<id>.md` for the blueprint (plus its linked `examples/<id>.html` when unclear): you reproduce these, not improvise them. Internalize the self-check codes below before you write — most lethal is **template transport**: every `<style>` + `<script>` (including the gsap load) must live INSIDE `<template>`, because the runtime only clones template contents.
2. **Design** — translate `scene` + the (sometimes shot-by-shot) narrative + the recipes you just read into a visual plan using `frame.md`'s components and type ramp. Honor the note's phases shot-by-shot per the whole-shot rule above, and find a visual idea that reinforces the beat, not a literal restyle of the words. Build the invented hero (diagram / type / metaphor).
3. **Author** — write the full sub-composition to `compositions/frames/<frame_id>.html`. `<template>`-wrapped root carrying `data-composition-id="<frame_id>"`, exactly one `gsap.timeline({ paused: true })` registered at `window.__timelines["<frame_id>"]`, built synchronously — per the core contract.
4. **Self-check, then finish** — run the checklist below and fix in place. Writing the passing file is your terminal action.
1. **Read**`hyperframes-core`'s composition contract (the structural law), then `frame.md` (the look) and your `## Frame N` block (the shot sequence + `blueprint:` / `focal:` / `roles:`). **Then read the blueprint template** `../hyperframes-animation/blueprints/<id>.md` (skip if `compose`) for the shot's shape and signature move, and **open the rule recipe `RULES_DIR/<id>.md` for every named motion** in the Scene lines (plus the shared `../hyperframes-animation/examples/<id>.html` when the recipe is unclear): you reproduce these mechanics, not improvise them. Internalize the self-check codes below before you write — most lethal is **template transport**: every `<style>` + `<script>` (including the gsap load) must live INSIDE `<template>`, because the runtime only clones template contents and `lint` / `validate` / `inspect` can miss the resulting blank sub-composition.
2. **Design** — turn the time-coded shot sequence into a timeline using `frame.md`'s components and type ramp: each Scene window becomes a phase revealed on its `voiceover` cue, each named motion built from the recipe you just read, the blueprint's signature move kept recognizable. Invent the visual elements the Scene lines describe (the diagram, the hero word, the metaphor), and find a visual idea that reinforces the beat, not a literal restyle of the words.
3. **Author** — write the full sub-composition to `compositions/frames/<frame_id>.html` (rewrite to iterate; last write wins). `<template>`-wrapped root carrying `data-composition-id="<frame_id>"` and styled via `#root` (not a class on that element — see the self-check below), exactly one `gsap.timeline({ paused: true })` registered at `window.__timelines["<frame_id>"]`, built synchronously — per the core contract.
4. **Self-check, then finish** — re-read your file against the checklist below and fix in place. Writing the file is your terminal action; you do **not** run the CLI.
## Self-check (fix before finishing)
## Self-check before finishing (you do NOT run the CLI)
The orchestrator runs `lint` / `validate` / `inspect`; catch these yourself first (codes are `hyperframes lint`'s; the rules behind them are in `hyperframes-core`):
You **can't** meaningfully run `hyperframes lint` / `validate` / `inspect` here: they operate on the **assembled project** (the `index.html` graph / bundle), and your frame isn't wired in yet — so they report on _other_ files, not yours (a false green). The **orchestrator** runs them at **Step 6, after assembly** (the correct unit), and **re-dispatches you with the finding** if your frame fails (see **Retry** above). So get it right on write: re-read your file against this checklist before finishing — the codes in parens are `hyperframes lint`'s and what the orchestrator may cite back (the rules behind them live in `hyperframes-core`):
- `missing_template_wrapper` / `missing_composition_id` — root is `<template>`-wrapped and carries `data-composition-id="<frame_id>"`.
- **Template transport** — every `<style>` and `<script>` block, including the GSAP load, lives inside `<template>`.
- `subcomposition_root_styled_by_class`**style the frame root via `#root`, never a class on the `data-composition-id` element**: at render a class on the root gets scoped to a descendant selector that can't match it, so the **whole scene renders unstyled** (Studio preview still looks right — trust this rule, not the preview). Descendants use plain selectors.
- `clip_missing_data_attrs` — every `class="clip"` element has `data-start` / `data-duration` / `data-track-index`.
- `timeline_not_paused` / `timeline_not_registered` — one paused timeline, registered at `window.__timelines["<frame_id>"]`.
- `css_transition_used` + repeat / yoyo / non-deterministic logic — none present (the renderer seeks frame-by-frame).
- `gsap_css_transform_conflict` — never put a CSS `transform` (e.g. `translateY(-50%)` centering) on an element you then GSAP-animate a transform prop on (`x` / `y` / `scale` / `rotation`): GSAP overwrites the whole `transform` and silently drops the CSS centering (the element jumps). Center with `margin` / `inset` (or `top`/`left` + offset), fold the offset into the tween via `xPercent` / `yPercent`, or use `fromTo` (the rule exempts it).
- **Hero visibility** — the main subject is visible by `t <= 0.5s`; entrance tweens use `fromTo` instead of CSS-hidden starting states.
- `exit_animation_on_non_final_scene` — no exit tween unless you are the final frame.
- **Shot develops (not a slide)** — a non-still frame carries a development beat between entrance and settle; cited effects are sequenced into phases, not all fired at `t=0`.
- **Adapt fidelity** — if the note led with `Base / Keep / Depart`, the `Keep` signature is present and recognizable, every `Depart` is applied, and the shot still runs `entrance → development → settle`.
- **No front-loading (not a slide)** — the shot's pieces reveal on their `voiceover` cues across the duration, not all fired at `t=0`; a non-still frame keeps content arriving rather than holding a full canvas from ~25%.
- **Shot-sequence fidelity** — every Scene in the time-coded sequence is realized as a phase, the blueprint's signature move (unless `compose`) is present and recognizable, and the shot reveals to the voiceover (never front-loaded at `t=0`).
- `font_family_without_font_face` — every font you name has a matching `@font-face` (or `@import`) **inside this file**. **Only use fonts that ship as files** with the project: the families declared in `frame.md` (their `.woff2` live in `assets/fonts/` or `capture/assets/fonts/` — point the `@font-face` `src` at the real file you find there). **Never name a font that has no file**, including system CJK / Japanese / Devanagari families (`Hiragino Sans`, `Yu Gothic`, `Noto Sans CJK`, `Noto Sans Devanagari`, …): the render machine is a clean headless Chrome with none of them installed, so the text silently falls back to a generic font and the typography is wrong in the MP4. For non-Latin or multilingual visible text, either use a shipped font that covers the script, or romanize / transliterate it (e.g. `日本語``Japanese`); if neither is possible it is out of scope for this frame — do not invent a font name.
- **Keep-out + no-narration-text** (eyeball, no code) — nothing sits below the 83% cutoff; no narration sentence is rendered as visible text.