* fix(skills): preserve caption skin contrast states * chore(skills): refresh caption contrast manifest * fix(skills): keep caption skin ownership aligned * style(skills): format shared caption builders
17 KiB
Frame worker — product-launch 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 product-launch 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.03-feature. Use it verbatim as the composition id, thewindow.__timelineskey, and the file name (compositions/frames/03-feature.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 product-agnostic shot template this frame instantiates — the overall shape + its signature move. Read it for the shape;composemeans there's no template, sequence the shot from the Scene lines directly.focal:— which candidate is the hero.roles:— each candidate's role:cutoutforeground /backgroundfull-bleed / supporting — plus the real media available (eachpublic/<basename> — description; a[video]tag marks a.mp4motion source that cannot be mounted by this sub-composition worker).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.- Asset paths are project-root relative everywhere. This includes HTML attributes and CSS
url(...)values such as@font-face src: useassets/...(or the suppliedcapture/assets/...path), never../or../../. Stage shared fonts before dispatch so every parallel frame resolves the same local files; never fall back to a network@import. 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. (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'stransition_in+ the injector own that.- Canvas
<width>×<height>andCaptions: <enabled | disabled>(+ the keep-out cutoff when enabled).
Retry — if your context carries lint / check 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.
When a confirmed sketch exists
In collaborative runs the orchestrator wireframes the board first, so your target file may already exist as the frame's user-confirmed wireframe — your dispatch says whether it does (a file found on a retry is your own prior output, not a sketch). Read it first and keep its composition: the placement, hierarchy, and copy were approved — don't move or drop them. Everything else is yours to finish: the full frame.md treatment (the sketch is deliberately unstyled), the real assets where the sketch used stand-in blocks, and the motion — map each Scene onto a timeline phase, reveal each piece on its voiceover cue with fromTo entrances, adding DOM only where a phase needs it. The frame's landed state must still read as the approved wireframe, fully dressed.
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 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 (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 the product's content. Brand text comes from your frame'sscene/ narrative. - Which motions / assets exist — named upstream in your block (the shot sequence's motion verbs +
blueprint:, the candidate media inroles:/focal:). Implement them; don't fetch or invent new ones (you have no asset-fetch tool — never fabricate an image URL). - 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 product-launch 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, CTA, stats, brand mark) 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 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 — headline / stat / one-word emphasis (
"$83K","INSTANT"), 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 card, a stat, an icon — 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 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
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, then instantiate it with this frame's content / assets / timing.compose→ no template; sequence the shot straight from the Scene lines. Whichever, never front-load the whole sequence att=0— pace the reveals to the voiceover. - Place each candidate by its
roles(thefocalis the hero): acutoutis a foreground subject — respect the 83% keep-out, lay text around it, not over its face; abackgroundis full-bleed and dimmed ~30–50% so foreground content stays legible. Frame files are sub-compositions. Audio remains orchestrator-owned: never author<audio>in a frame. An approved[video]candidate may be declared as a frame-local<video data-frame-video="approved" data-start="..." data-duration="..." data-track-index="...">;assemble-index.mjshoists it to the host root and translates its timing. Give every approved video explicit host geometry with numericdata-frame-video-x,data-frame-video-y,data-frame-video-width, anddata-frame-video-height; optionally setdata-frame-video-fit="cover|contain|fill|none|scale-down"(defaultcover). The assembler converts only those values to host CSS: frame-local classes and inline styles do not cross the hoist boundary. Do not use this declaration for audio, unapproved URLs, or videos without explicit timing and geometry. If no approved video is supplied, use an explicitly supplied static still/key art ([video-still]or another image candidate) as<img>; do not extract a frame, fabricate a URL, or silently embed an unapproved clip.
Workflow
- Read —
hyperframes-core's composition contract (the structural law), thenframe.md(the look) and your## Frame Nblock (the shot sequence +blueprint:/focal:/roles:/ assets). Then read the blueprint template../hyperframes-animation/blueprints/<id>.md(skip ifcompose) for the shot's shape and signature move, and open the rule recipeRULES_DIR/<id>.mdfor every named motion in the Scene lines (plus the shared../hyperframes-animation/examples/<id>.htmlwhen 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 the assembled-projectlint/checkgate can miss an unwired blank sub-composition. - 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. Place the named assets, and find a visual idea that reinforces the beat, not a literal restyle of the words. - 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. Prefix authored ids and globally reusable class names with<frame_id>-so sibling frames assembled from parallel workers cannot collide. Contract selectors such as#rootand.clipare the only exceptions. - 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 / check 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). 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.