* fix(skills): storyboard review — bg-on-clip rule, slideshow output, parser parity guard Addresses the storyboard-angle review (jrusso1020): - M1 (invisible text): frame-worker.md (x3) + SKILL.md Step 5 (x3) now require a frame's full-bleed background on a class=clip layer, never the #root / data-composition-id element (the root is clip-gated to its scene window, so a background on it is not a dependable ground and dark text can land on the black host body). The assembler already paints frame.md's canvas onto index #root as the base ground; the per-frame clip rides on top. - B3 (slideshow truncates to slide 1): slideshow/SKILL.md gains an Output section (decks render via 'present'; 'render index.html' captures only the first composition; linear main-line MP4 export is deferred). - Parser drift: vendoredParity.test.ts guards the three vendored storyboard.mjs copies (byte-identical + parse-parity with @hyperframes/core). - skills-manifest.json regenerated for the edited SKILL.md files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): storyboard review — lint, validate help, snapshot, inspect, capture, render Addresses the CLI findings from the storyboard-angle review (jrusso1020): - lint (@hyperframes/lint): accept vendor-prefixed system-font keywords -apple-system / BlinkMacSystemFont so a system stack with a generic fallback no longer trips font_family_without_font_face (+ test). - help: list 'validate' under Project in 'hyperframes --help' (was runnable but undocumented). - snapshot: honor -o/--output (the flag did not exist; output was hardcoded to snapshots/). The dir is resolved once and threaded through capture + contact sheet + Gemini. - snapshot: split font status into loaded / error / unused with a one-line summary; only a real 'error' is reported as FAILED (an unrequested @font-face is 'unused', not a contradiction with 'loaded'). - inspect: suppress text_occluded across a scene-to-scene crossfade (occluder in a different data-composition-id mount while a scene is mid-fade); a same-scene or two-settled-scenes overlap still flags. - inspect: suppress content_overlap between in-flow siblings governed by the same flex/grid container (tight stacks / number lockups are layout slop). - capture: record source resolution (videoWidth/Height) in video-manifest.json alongside the DOM display box; consumers size off the source dims. - render: warn when the target carries a slideshow island (render captures only the first scene, so the MP4 is truncated to slide 1; use 'present'). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
20 KiB
Frame worker — PR-to-video per-frame composition author
You build one frame's composition HTML and nothing else. You run N-up, one frame each — siblings build the others. The structural composition contract (sub-composition shape, timeline registration, clip attrs, transform-only motion, determinism, root sizing) lives in
hyperframes-coreand is not restated here — read it first. This file carries only what's specific to a PR-to-video frame. Tempted to add a generic GSAP / timeline rule here? Wrong home — it belongs inhyperframes-core.
INPUT — your dispatch context provides:
PROJECT_DIR— the project root; all paths are relative to it.frame_id— e.g.04-the-fix. Use it verbatim as the composition id, thewindow.__timelineskey, and the file name (compositions/frames/04-the-fix.html) — that path is the frame'ssrcinSTORYBOARD.md(the orchestrator derivedframe_idfrom it), so writing there is how the assembler finds your frame.- Your
## Frame Nblock inSTORYBOARD.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 time-coded shot sequence — your build spec. A sequence of Scene lines (
Scene 1 (0.0–Xs): … → 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 itsvoiceovercue (this is what keeps the shot from freezing). blueprint:— an id (or the literalcompose). 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;composemeans there's no template (common for a code beat — thecode-*block is the shape), sequence the shot from the Scene lines directly.focal:— for a concept/mechanism beat, which invented element is the hero; for a code beat, the namedcode-*block (+ the hunk); for the credits close, the avatar row.roles:— each element's role:foreground subject/backgroundfull-bleed /supporting. Most are invented elements you design; the only real assets are the creditsassets/<login>.pngavatars.sfx:— the orchestrator's; you mount no audio.
frame.md(project root) — the design-truth: palette (claude), type ramp, components, composition rules. The LOOK. Pull every visual token from here.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>.mdis the mechanics for a motion. The blueprint templates are the sibling../hyperframes-animation/blueprints/<id>.md; an optional runnable demo is../hyperframes-animation/examples/<id>.html.code-vocabulary.md— absolute path provided in your dispatch. For a code beat, read it for the namedcode-*block's exact inputs (window.__TOKENS,window.__BLOCK, line indexing).../references/cut-catalog.md— the cut catalog (zoom-through / inverse / cut-the-curve / waterfall) for a within-frame seam. You never author the between-frame transition — story'stransition_in+ the injector own that.- Canvas
<width>×<height>andCaptions: <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 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.
Mostly invented — you build the visual (except code blocks + the credits avatars)
A PR video is mostly invented: there are no screenshots and no captured UI. For hook / change / mechanism / impact / cta frames the focal / roles name invented elements — a hero line, a coined-term card, a number-lockup stat, a coral callout, a mechanism animated diagram of the behavior — that you design and build in HTML/CSS/SVG from frame.md. Build the idea the narrative describes; never fall back to generic decorative bokeh or stock filler. Two beats are NOT invented from scratch — see the next section: code beats use a ready-made code-* block, and the credits close uses the real contributor avatars.
PR code beats, mechanism beats + the credits close
- Code beats (
diff/before_after/ a new-code reveal) — use the namedcode-*block, don't hand-build code motion. Your## Frame Nscene/focalnames which block (e.g.code-diff,code-morph,code-typing); the orchestrator has already installed it (Step 5 pre-install). Readcode-vocabulary.md(path in your dispatch) for that block's exact inputs, then:- Pull the real before/after hunk or snippet from
capture/diff.patch(or the brief's "Representative diff" incapture/extracted/visible-text.txt). - Fill the block's
window.__TOKENSwith that real code (the baked Shiki tokens) and setwindow.__BLOCK(effect,line,duration) so the full block completes within the frame'sdata-duration— a long snippet at the block's default per-character cadence overruns a short frame (the code never finishes typing).code-diff/code-morphneed 2 states (before, after); the others take one. Line indexing differs —code-highlightis 0-based,code-scroll1-based — don't off-by-one. - Integrate the filled block as this frame's composition per
hyperframes-core's sub-composition contract: itsdata-composition-idand itswindow.__timelines[...]key must both be your<frame_id>(the block ships its own id + paused timeline; rename both to match the frame contract). The block already renders an editor window (titlebar / filename) reading as claude's navy Code Surface — set the filename + any+N/−Mchrome from thescene. - The block owns the code animation; your Scene windows choreograph the surrounding Code Surface — the navy window seating in, the file header typing on, the camera settling onto the hunk, a coral underline on the landed line. Do not re-specify the code motion (the block is the development beat). A code beat is usually
blueprint: compose. - The block has no caption-safe band. When
Captions: enabled, inset/scale the code panel into the top ~83% so it clears the keep-out band; never let code run under the caption pill.
- Pull the real before/after hunk or snippet from
- Mechanism beats (
mechanism) — build an invented animated diagram of the behavior; the build is the shot. This is the "show what the change does at runtime" frame (the request retrying, the cache filling, serial→parallel, the race resolved) — read itsscenefor the behavior to animate. Unlike a code beat, the motion is yours to author (no block owns it):- If the
scenenames aflowchart/flowchart-vertical/data-chartblock, the orchestrator pre-installed it — fill + mount it like a code block (itsdata-composition-idandwindow.__timelines[...]key both become your<frame_id>). Otherwise hand-build the diagram in SVG / HTML / GSAP fromframe.md's atoms. - Claude register: hairline-ink nodes / edges / lanes on the cream ground, one coral marker on the active / changed element, mono labels — not the navy code surface (that's for code), no heavy shapes / bokeh.
- Choreograph the Scene windows: the nodes / lanes draw on (Scene 1); the flow runs as the VO names each step (middle Scenes — the request hops, the lane splits, the front advances, the bars race) — this is the teaching, so it must play across the shot, never enter-then-freeze; the resolved state + the one coral emphasis lands (final Scene). Keep it in the top ~83% (caption keep-out).
- If the
- The
creditsclose — the one frame with real assets. Itsasset_candidatesnames 2–6assets/<login>.pngavatars (downloaded in Step 1). Render them as<img>in hairline-ringed chips — an avatar row with each contributor's name + role in mono (an "approved" mark if the close calls for it), staggered in across the Scene windows. Avatars appear only here, never decorating a code frame.
You do NOT decide
These belong to other steps — touching them collides with a sibling or breaks an upstream contract:
- What is SAID — narration is locked in
SCRIPT.md/ thevoiceoverline. You only show; you never write or restate narration text. - Duration — fixed from real voice timing. Build your shot 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 (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 offrame.mdas your copy — it is a style spec, not content. Visible text comes from your frame'sscene/ narrative (and the real code, for a code beat). - Which motions exist — named upstream in your block (the shot sequence's motion verbs +
blueprint:+ thecode-*block). Implement them; don't invent new ones. - 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 PR-to-video 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, code panel, diagram, labels) above
y ≈ 0.83 × height— compute the pixel cutoff from your canvas (e.g.≤ 900on a 1080-tall frame,≤ 1600on a 1920-tall portrait). Holds even whenCaptions: 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.2–0.35 × height), flow supporting elements down with rhythm, scale the hero / code panel / 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 stat (
"+1,204","2× faster"), a file/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. (Real code inside acode-*block is the exception — that is the content, not narration.) - 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 file chip, the hunk, a node, a stat — as thevoiceoverreaches it, 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 (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. - 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
voiceoverreaches it. For each named motion in a Scene, open its rule recipe underRULES_DIR/<id>.mdand reproduce its mechanics — never name-guess (a guess loses the signature move). Theblueprint:template (../hyperframes-animation/blueprints/<id>.md) gives the overall shape; read it and keep its signature move recognizable.compose→ no template; sequence the shot straight from the Scene lines (a code beat composes the surround around the block). Whichever, never front-load the whole sequence att=0. - Realize each element by its
roles(thefocalis the hero): aforeground subjectis the thing the eye lands on — respect the 83% keep-out and lay text around it; abackgroundis a full-bleed field / gradient / grid dimmed ~30–50% so foreground content stays legible;supportingelements are labels, secondary shapes, ambient layers. Invented elements are HTML/CSS/SVG you build; acode-*block /flowchart/data-chartis filled + mounted per the section above; the credits avatars render as<img>.
Workflow
- Read —
hyperframes-core's composition contract (the structural law), thenframe.md(the look) and your## Frame Nblock (the shot sequence +blueprint:/focal:/roles:). Then read the blueprint template../hyperframes-animation/blueprints/<id>.md(skip ifcompose) and open the rule recipeRULES_DIR/<id>.mdfor every named motion in the Scene lines; for a code beat, readcode-vocabulary.mdfor the block's inputs. 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>. - 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 itsvoiceovercue, each named motion built from the recipe you just read, the blueprint's signature move kept recognizable. Build the invented hero (diagram / type / number-lockup), or fill + mount thecode-*block and choreograph the surround. - Author — write the full sub-composition to
compositions/frames/<frame_id>.html(rewrite to iterate; last write wins).<template>-wrapped root carryingdata-composition-id="<frame_id>"and styled via#root(not a class on that element — see the self-check below), exactly onegsap.timeline({ paused: true })registered atwindow.__timelines["<frame_id>"], built synchronously — per the core contract. - 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 before finishing (you do NOT run the CLI)
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 carriesdata-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 thedata-composition-idelement: 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.- Full-bleed background on a
class="clip"layer, never#root— author a frame's full-bleed ground (color field / gradient / grid) as a dedicated full-durationclass="clip"background element on the lowest content track, not as abackgroundon the#root/data-composition-idelement. At assembly the frame root is clip-gated to its scene window, so a background painted on the root is not a dependable full-frame ground — dark content can end up over the hostbody(black) and render invisible. The video's base ground is painted separately by the assembler fromframe.md'scanvascolor onto the index#root; your full-bleed clip rides on top of it. clip_missing_data_attrs— everyclass="clip"element hasdata-start/data-duration/data-track-index.timeline_not_paused/timeline_not_registered— one paused timeline, registered atwindow.__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 CSStransform(e.g.translateY(-50%)centering) on an element you then GSAP-animate a transform prop on (x/y/scale/rotation): GSAP overwrites the wholetransformand silently drops the CSS centering (the element jumps). Center withmargin/inset(ortop/left+ offset), fold the offset into the tween viaxPercent/yPercent, or usefromTo(the rule exempts it).- Hero visibility — the main subject is visible by
t <= 0.5s; entrance tweens usefromToinstead of CSS-hidden starting states. exit_animation_on_non_final_scene— no exit tween unless you are the final frame.- No front-loading (not a slide) — the shot's pieces reveal on their
voiceovercues across the duration, not all fired att=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 att=0). - Code-block cadence fits
data-duration— for a code beat, thecode-*block's internal cadence is set so the full block completes within the frame'sdata-duration(a long snippet at the default per-character speed overruns — the code never finishes and the chrome beats never play; seecode-vocabulary.md). 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 inframe.md(their.woff2live inassets/fonts/orcapture/assets/fonts/— point the@font-facesrcat 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.