Add HyperFrames skills (from heygen-com/hyperframes, Apache-2.0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXVsD1DgdzZLiPuwtKzMSY
This commit is contained in:
Claude committed 2026-10-21 23:18:32 +00:00
1 parent 3e3f41ee5f
commit daac216bb9
300 files changed
+49175

No files matched your search

@@ -0,0 +1,46 @@
# Captions
## Words and timings
- `npx hyperframes transcribe <file> --json` and a HeyGen voiceover (`/media-use`) give every word's start and end. Work from those as `[{ "id": "w0", "text": "Hello", "start": 0.0, "end": 0.5 }, …]`.
- Local transcription defaults to `small.en`, which **translates** non-English speech into English. Pass the model: `npx hyperframes transcribe <file> --model small.en` for English, `--model small --language <code>` otherwise, `--model medium.en` for speech over music. It writes `assets/transcript.json` (its JSON names the path) and overwrites it on the next run; `--to srt` or `--to vtt` writes a sidecar.
- For Chinese, Japanese and other unspaced scripts, local transcription returns a whole sentence as one "word", sometimes in the other script (Traditional for Simplified speech). Split each segment's text into short phrases and spread its time across them by character count; check the characters against what is said.
- Whisper invents "Thank you." over silence and repeats lines over a silent tail: drop words that start inside a final silence (`silencedetect=noise=-35dB:d=0.6`; real speech sits around −16 to −26 dB).
- Never put two words in one timed entry, and match transcript words to caption words by position, not text, or a repeated word takes the wrong time.
- Read the transcript before using it. More than 20% `♪` or `�` tokens, or nonsense words, means it failed: retry with a larger model, or take lyrics from the person. Drop non-word tokens before building groups.
## Structure
- One sub-composition host with `data-track-kind="captions"` holds every caption group, so Studio shows them on one captions track.
- Ready-made styles: `npx hyperframes catalog --tag caption-style --json`. An installed one is source you can restyle.
## Groups
- 2–3 words for high energy, 3–5 conversational, 4–6 calm. Break at a sentence end, a pause of 150 ms or more, or the word limit.
- One group visible at a time. A group opens at or before its first word's start and closes at or after its last word's end. A caption at exactly 0 s looks pre-existing: start 0.1–0.3 s in.
- Every group ends with a hard kill after its exit, or a stale group can stay on screen:
```js
tl.to(group, { opacity: 0, duration: 0.12, ease: "power2.in" }, end - 0.12);
tl.set(group, { opacity: 0, visibility: "hidden" }, end);
```
`tl.set` is an instant flip, so setting `visibility` on the group is fine; never tween it on a `.clip`.
## Fit and place
- Size each group to fit: `window.__hyperframes.fitTextFontSize(text, { fontFamily, fontWeight, maxWidth })` returns `{ fontSize }`. Defaults: `maxWidth` 1600 landscape, 900 portrait; base 78, minimum 42, step 2. When a word scales up for emphasis, divide `maxWidth` by that scale.
- A full-width absolute container, centred with text alignment. Never `left: 50%; transform: translateX(-50%)`, which clips at the frame edges.
- `overflow: visible`, not `hidden`, which cuts off scaled words and glows.
- Landscape: 80–120 px from the bottom. Portrait: about 600–700 px from the bottom. Never over the speaker's face; inside the 80% title-safe box.
- Position each group absolutely. In a flex column a group hidden with `visibility: hidden` still takes space, so the next one lands lower. In a stack that accumulates, groups in one block share one exit time, or the stack reflows.
- Fade the group or its words, not both: the opacities multiply and read as a pop. A gradient or `background-clip: text` fill goes on the inner text span, not the transformed container.
- Over footage, choose the blend by the brightness (0–255) behind the text: under 60 `screen`, 60–180 `overlay`, over 180 `normal` with opaque colour. Plain subtitles above brightness 60 get a scrim sized to the text (30–40%), never a full-width bar.
- Footage may already carry burned-in subtitles only mid-clip: scan a 1 fps contact sheet before placing captions.
## Text behind the speaker
- Stack the video, then the captions, then a cutout of the person from `npx hyperframes remove-background` (`/media-use`), ordered by `z-index`. Shake or punch in the video and the cutout together in one wrapper.
- `check` treats a `<video>` as opaque across its whole box, so a WebM cutout fails every caption behind it with `text_occluded`: put `data-layout-allow-occlusion` on the cutout and judge from `snapshot`.
- A big word behind the head reads best 30–55% covered: under about 15% it floats, over about 65% it is eaten. Re-lay out when a content word is 80% covered, or two adjacent words 50% each. Measure the rendered word boxes against the matte over each caption's own time window (heads move: one clip went from y=224 to y=96 in 8 s); estimates from character widths were wrong both ways.
- The matte model drops mic arms, lets large rigs near the body leak in, and flickers on held objects. Its first run downloads 168 MB, and it runs at about 2 fps at 1080p on CPU.
@@ -0,0 +1,47 @@
# Films from a source: measured facts
What is specific to HyperFrames, or was measured on shipped films, when one video is made from a website, a script, a pull request, a music track, a real place or a short motion brief. The story comes first, from `story.md`; the shape of each kind of film is in `types/`. Motion craft is in `motion.md`, look in `look.md`, sound in `sound.md`, captions in `captions.md`.
## Script and pacing
- TTS reads about 2.2 words a second. Keep a scene to about 19 words (9 s); at most two scenes may reach 26 words (12 s). 30–90 s is about 155 words or fewer; a whole film about 400 at most.
- A voiceover longer than its visuals makes the visuals pad with idle drift. Trim the script; never stretch the motion.
- Everything on screen in the first quarter of a scene, then frozen, reads as a slideshow. Reveal each piece when the voiceover names it, across the back half of the scene; write the voiceover as separate phrases so it has cue points.
- Up to about six short scenes, build them yourself in one context (about 9 min for five, against about 21 min split across sub-agents). Beyond that, give each worker two or three scenes and start them all at once; a second wave nearly doubles the time.
## From a website
- `npx hyperframes capture <url> --json` writes `screenshots/full-page.png`: a 1x plate of the whole page, taken after the scroll pass with sticky and fixed elements made static, pixel-exact for a 1920-wide viewport travelling down it. It is skipped above 16384 px tall (use `scroll-*.png`); pushing in past 1:1 needs its own 2x capture of that region.
- To show the site as it is, use its screenshots. Rebuilding the page in HTML drifts from the real layout; rebuild only the part that moves, or overlay captured assets at measured positions.
- `--capture-budget` (default 120000 ms) bounds work after navigation; `--timeout` bounds navigation only. Videos are not downloaded: `capture --video <dir> --list`, then `--index N`. `asset-descriptions.md` has vision captions only when `OPENROUTER_API_KEY`, `GEMINI_API_KEY` or `GOOGLE_API_KEY` is set.
- "very little text content" with an empty asset catalog means the capture is unusable even when it says `ok: true`. A `logo-*.svg` name is a guess from page structure, not proof.
- Brand tokens: link blue `#0000EE` and status reds and greens are never the accent; the accent is the colour that recurs across the most roles. Icon fonts (Font Awesome, icomoon, webflow-icons) are never the display or body face; a captured monospace goes to the mono role. Use only weights the brand font ships, or 600/700 render as fake bold. Every preset has a light ground: for a dark brand, swap ground and ink.
- Capture keeps each `@font-face` rule, `unicode-range` subsets included, in `extracted/page.html`; copying the font files alone loses which characters each covers. Name copied fonts by family, weight and style, or an upright and an italic "Regular" collide.
## From a pull request
- `gh pr view --json files` stops at about 100 files: page `gh api repos/{owner}/{repo}/pulls/N/files`.
- The PR author only opened it: credit commit authors, by commit count (`commits[].authors[]`); skip bots, lockfiles and `dist/`. Reviewers come as logins; names via `gh api users/<login> --jq .name`. TTS reads `@handles` aloud, so speak names.
- Never invent a version. Use the first non-draft release published at or after the merge; else the default branch's `package.json` version as "unreleased"; else only the PR URL.
- Length from size (additions + deletions), as a ceiling: ≲50 lines 20–40 s, 50–200 40–70 s, 200–600 70–110 s, ≳600 lines or 25+ files 110–180 s.
- Show 2–4 hunks of 4–12 lines; counts like +N/−M go on a stat card, not into the voiceover.
- The `code-*` registry blocks (`npx hyperframes catalog --query code --json`) are 1920×1080 scenes driven by `window.__TOKENS` (Shiki tokens, `github-dark`; `fontStyle` bit 1 italic, bit 2 bold) and `window.__BLOCK` (`{id, effect, seq, line?, duration}`). `code-diff` and `code-morph` need at least two states; `code-highlight` counts lines from 0 and `code-scroll` from 1; `code-typing` types at a fixed speed, so check characters × speed against the scene's duration. They reserve no caption band: inset the panel when captions are on.
## On music
- Library music, HeyGen's included, comes as 15–30 s loops: loop the bed to the film with a 0.4 s fade-in and a 1.5 s fade-out. A track whose opening is a quiet build drains a short film: start from a stronger later section.
- Trust beat times only on rhythmic stretches; on calm music the tracker invents a metronome, often at double tempo (`npx hyperframes beats` reports `bpmConfidence`). Cut there on phrase and energy changes; `npx hyperframes audio --only sections,onsets,moments --json` finds them.
- A move that must land on a hit starts 40–190 ms early; a reaction starts 0–45 ms after. The hit itself is a 0 ms `tl.set`: easing a hit kills it. Scenes tile the track with no gaps.
- A word meant to be read holds at least one beat. Any single treatment goes stale after about 6 s: lengthen a section with a hold or a new treatment, never by slowing tweens. On rhythmic sections give footage one clip per strong beat; on calm ones one slow push from 1.0 to 1.08.
## Maps and real places
- A real place (a city, a building, a route, a zoom from the globe to a street) is shown on real imagery. Vector outlines from Natural Earth or GeoJSON read as a diagram and lose the place; keep them for borders and labels drawn on top.
- Live tiles change between renders, so bake the camera move to video with `scripts/bake-basemap.mjs` (MapLibre over Esri satellite or Carto tiles; writes an all-intra MP4 and `<NAME>-coords.json`). From space down to a street: `PROJECTION=globe FROM="lng,lat" CENTER="lng,lat" ZSTART=1.85 ZEND=16` (Earth in a starfield turns from FROM to CENTER, then dives); `--help` lists the rest. It needs network for the tiles and takes well under a second a frame, so bake once, keep the MP4, and run one bake at a time. Draw borders, pins and labels as a live SVG from those coordinates, animating them only while the camera holds.
- Credit the imagery on screen ("Esri, Maxar, Earthstar Geographics" or "© CARTO, © OpenStreetMap") in small type on a soft dark backing, so it holds contrast over bright tiles; mark it `data-layout-ignore` so the type floor in `check` skips it.
## Short motion pieces
- Kinetic type takes 3–7 English words or 4–12 Chinese characters per scene. A count-up runs 1.2–1.6 s, then holds, landing on a downbeat or a hard stop.
- A highlighter that wraps across lines: tween a custom property on a `background-size` band with `box-decoration-break: clone`. `getTotalLength()` can return 0 before layout; give line draw-ons a fallback.
- A point in an image guessed directly misses by 16–24% of the frame. `scripts/locate.mjs` narrows it with a numbered 9×9 grid, then 6×6 on the crop, then a check render (about 2% error).
@@ -0,0 +1,157 @@
# Composition format
The detail behind the contract in `SKILL.md`. Full references: `npx hyperframes docs compositions`, `docs data-attributes`, `docs troubleshooting`.
## Shape of a project
```html
<!-- index.html: a normal document; declares variables, mounts scenes -->
<html
data-composition-variables='[{"id":"headline","type":"string","label":"Headline","default":"…"}]'
>
<body>
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="12"
data-width="1920"
data-height="1080"
>
<div
id="intro"
data-composition-id="intro"
data-composition-src="compositions/intro.html"
data-start="0"
data-duration="4"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
</div>
<script>
window.__timelines["main"] = gsap.timeline({ paused: true });
</script>
</body>
</html>
```
```html
<!-- compositions/intro.html: everything inside <template> -->
<template>
<style>
#root {
position: absolute;
inset: 0;
}
</style>
<div id="root" data-composition-id="intro" data-width="1920" data-height="1080">
<h1 id="headline" class="clip" data-start="0.4" data-duration="3" data-var-text="headline">
…
</h1>
</div>
<script>
const tl = gsap.timeline({ paused: true });
tl.fromTo(
"#headline",
{ opacity: 0, filter: "blur(8px)" },
{ opacity: 1, filter: "blur(0px)", duration: 0.18, ease: "power4.out" },
0.4,
);
window.__timelines["intro"] = tl;
</script>
</template>
```
- Give the host, the scene root and the timeline key the same id. A host id that differs from its scene's makes each render wait 45 s per scene, then show static frames. Styles and scripts outside `<template>` are dropped.
- Size the scene root with `inset: 0`, never hard-coded pixels; the canvas is `data-width` × `data-height`.
- Keep ids unique across the assembled page (prefix them: `#intro-title`). A scene's timeline can't reach elements outside its file.
- Use `fromTo`, not `from`, inside a scene: the host re-seeks the scene each time it shows, and `from` records its start state once, so it drifts on seek-back.
- Never `add()` a scene's timeline to the root's; the runtime drives each scene itself, and nesting seeks it twice.
## Attributes you'll use most
| Attribute | Meaning |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `data-start`, `data-duration` | Seconds, local to the file that declares the element |
| `data-track-index` | Studio lane only; paint order is CSS `z-index` |
| `data-track-kind` | `graphics` or `captions` on scene hosts (video and audio come from the tag) |
| `data-composition-src`, `data-variable-values` | Mount a scene file; per-mount variable values (a JSON object) |
| `data-media-start` | Offset into the source media (a trim) |
| `data-playback-rate` | Constant speed, 0.1–10 |
| `data-has-audio="true"` | A `<video>` keeps its own sound; a timed `<video>` with neither this nor `muted` fails lint |
| `data-volume`, `data-fade-in`, `data-fade-out` | Gain (1 = 0 dB, up to 3.98 = +12 dB) and edge fades; envelopes use `data-automation` (`/media-use`) |
| `data-link`, `data-sync-origin` | A video and audio edited as one (see Linked clips) |
| `data-hidden` | Hidden in preview and render; reversible |
| `data-no-timeline` | On the root of a still composition: no timeline to wait for |
## Variables
- Declare an array of `{id, type, label, default}`: the video's own on `index.html`'s `<html>`, a scene or block file's on that file's `<html>` (or root element), where they stay inside each mount. Types: `string` (`placeholder`, `maxLength`), `number` (`min`, `max`, `step`, `unit`), `color`, `boolean`, `enum` (`options: [{value, label}]`).
- Bind without script: `data-var-text="id"`, `data-var-src="id"` (keep a real fallback `src` on media with audio), `var(--id)` in CSS; for logic, `window.__hyperframes.getVariables()` once at init.
- Values are an object keyed by id. A host's `data-variable-values` sets one mount; Studio's Variables panel and batch renders set only the root's variables, so to vary a block, change its host's values. Variables can't change the root's `data-duration`.
## Fonts
- Bundled, so they resolve by name: Inter, Montserrat, Outfit, Nunito, Oswald, League Gothic, Archivo Black, Space Mono, IBM Plex Mono, JetBrains Mono, EB Garamond, Playfair Display, Source Code Pro, Noto Sans JP, Roboto, Open Sans, Lato, Poppins. Common system fonts (Helvetica, Arial, Times, Menlo and the like) are swapped for the nearest of these.
- Any other family needs `@font-face` in the file that uses it, or lint fails `font_family_without_font_face`: `src: url('assets/fonts/x.woff2')` for a file in the project (a capture's own fonts, or one you download there), or `local('Exact Name')` for a font installed on this machine.
## Timing and the timeline
- A clip shows over `[start, start + duration)`: an end state landing exactly on its end is never rendered.
- Only a composition's root timeline is paused; timelines nested inside it are not. Never `tl.play()`.
- A scene's clock starts at 0 at its host's `data-start`; media `data-start` is local to its composition. `<video>` / `<audio>` without `data-duration` play their full length; an `<img>` without one lasts 3 s.
- A host's `data-duration` is the scene's slot: a shorter scene timeline holds its last frame, and a slot shorter than the video goes blank when it ends.
- Root children with `data-start` are laid out full-frame by the runtime; an untimed full-bleed background needs `position: absolute; inset: 0` or it collapses to nothing.
- A fill on the composition root is dropped when the video has shader transitions or HDR media; put it on a full-bleed child.
- `data-start="intro + 0.5"` starts after `intro` ends (spaces required). A typo, a missing id or a cycle silently resolves to 0, so snapshot what you time this way.
- A clip nested inside another timed element counts its `data-start` from the composition, not from that element, and shows only while that element shows. `timeline` lists it under its parent and warns `never visible` when the windows miss (`lint`: `nested_clip_never_visible`).
- `npx hyperframes timeline --json` lists every clip by kind with `absStart`, `absEnd`, `file`, `src`, `playbackRate`, `nested`, `host` and `durationSource`: read it instead of opening every file. It reads one level of nesting (a deeper scene shows `children=unread`); `laneError` means a `data-automation` or `data-fx-chain` that doesn't parse.
## Adding media
Write what a Studio drop writes, so the clip behaves like one the person dropped:
- `id`, `class="clip"`, `data-start` (the playhead or the requested time, never a silent 0) and `data-track-index`. Video with sound: `playsinline data-has-audio="true"`; silent footage: `muted playsinline`. Audio: `data-volume="1"`.
- Images and video fill the frame: `position: absolute; left: 0; top: 0`, width and height equal to the canvas, `object-fit: contain`.
- `z-index` is the number of top-level clips already in that file plus one.
- Raise the root's `data-duration` to cover the new clip's end, or it never plays.
## Linked clips
Studio's Detach audio writes a muted `<video>` and an `<audio>` of the same file sharing `data-link` and `data-sync-origin`; Studio moves, trims, splits and deletes them as one.
- Prefer one `<video data-has-audio="true">`; link only when the sound needs its own track, volume or effects.
- Every member keeps the same `data-start`, `data-duration`, `data-media-start` and `data-playback-rate`; retime them all together.
- Unlink by removing `data-link` from every member. Leave `data-sync-origin` alone while they are still one source.
## Layout audit opt-outs
`check` measures boxes at sampled times; `overflow: hidden` does not silence a finding. Use the narrowest opt-out:
| Attribute | Silences |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `data-layout-allow-overflow` | Overflow on the element and every descendant, including text-clipping checks: keep it on the smallest decorative wrapper |
| `data-layout-bleed="true"` | One intentional crop of primary text |
| `data-layout-allow-overlap` | Deliberate text layering, on that block only (not inherited) |
| `data-layout-allow-occlusion` | An element meant to cover text |
| `data-layout-allow-caption-zone` | Intentional copy in the caption band |
| `data-layout-ignore` | A decorative element never audited |
## Lint errors you'll meet first
| Rule | Fix |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `video_missing_muted` | A timed `<video>` gets `data-has-audio="true"` or `muted` |
| `gsap_css_transform_conflict` | Set the start inside the tween (`gsap.fromTo`) instead of a CSS `transform` |
| `gsap_animates_clip_element` | Animate a child of the `.clip`, or its `opacity` |
| `video_nested_in_timed_element` | Time the `<video>` or its wrapper, not both |
| `media_missing_id`, `media_crossorigin_breaks_preview` | Give every `<audio>` an `id`; drop `crossorigin` |
| `gsap_timeline_registered_before_async_build` | Register the timeline at the end of the async callback |
| `standalone_composition_wrapped_in_template` | Only scene files use `<template>` |
| `subcomposition_root_styled_by_class` | Style a scene's root as `#root` |
| `subcomposition_blanks_before_host` | Make the scene's slot span the time it should show |
| `clip_ends_past_root_duration` | Raise the root's `data-duration` |
| `linked_clips_out_of_sync`, `linked_clip_orphan` | Retime every linked member together; unlink from all of them |
| `sweep_static` | A 3 s+ video with no visible change: spread the motion, or mark a still composition `data-no-timeline` |
| `missing_data_no_timeline` | A root or host with no timeline: add `data-no-timeline`, or every render waits 45 s |
@@ -0,0 +1,69 @@
# The look
Left alone, the same brief comes out with the same look run after run, and banning that look only moves it to the next default. Outside input breaks it, in this order: the project's design spec, a real brand, a reference image or film the person gave you, the subject's own world. Settle it before the first scene, as numbers in the LOOK line (below).
## The project's design spec
Read the first of `frame.md`, `design.md`, `DESIGN.md` at the project root that exists; ignore the rest.
- **Frontmatter is normative**: `colors`, `typography`, `spacing`, `components`. Quote hex values, families and weights exactly; never round or invent them.
- **The prose is intent**: what the look is for, Do's and Don'ts. Follow it for judgment, not values.
- **Brand, not layout**: hex, fonts, weight relationships and Do's and Don'ts are strict; type sizes, spacing, border weight and decoration are yours to scale for video. A 1px border or a 6% shadow from a web page vanishes on screen.
## From a brand
- A website: `npx hyperframes capture <url> --json` writes screenshots, the copy (`extracted/visible-text.txt`), images, logos, `extracted/tokens.json` (colours, fonts) and `extracted/design-styles.json` under its output folder. Look at the screenshots before choosing anything. A capture that exits non-zero, prints `ok: false` or writes `BLOCKED.md` failed: take no brand data from its partial files.
- Figma: `npx hyperframes figma tokens <fileKey>` (`/figma`).
- Images the person attached: take palette and type from what you see.
Use the brand's own fonts when the capture shipped their files or they are bundled (`format.md`); otherwise download the family's `.woff2` into `assets/fonts/` with an `@font-face`, or pick the closest bundled family and say so.
## From a reference
A reference film or image gives its grammar, never its content: shot lengths, how titles enter and leave, the transitions, the palette roles, the type's scale and weight. Snapshot it next to yours (`snapshot --at <t,…> --against ref.mp4`) and write the grammar down, marking what you measured apart from what you guessed.
## Presets
The presets are slide-deck looks: use one when the person names it, or for a deck (`/slideshow`), never as the fallback for a film without a brand. Each folder in `../assets/frame-presets/` holds a `FRAME.md` spec, a `caption-skin.html` and a `frame-showcase.html` contact sheet. To use one, copy its `FRAME.md` to the project root as `frame.md`, then read it as above; its fonts that aren't bundled need `@font-face` files. The two HTML files are reference code: read them for treatments, never mount them.
| Preset | Look |
| ------------------- | ---------------------------------------------------------------------------------------------- |
| `biennale-yellow` | Museum catalogue: parchment, deep indigo ink, solar-yellow blooms, Instrument Serif, hairlines |
| `blockframe` | Loud neobrutalist: 4px black borders, hard shadows, candy pastels, Inter 900 caps |
| `blue-professional` | Consulting restraint: cream canvas, one cobalt accent, Space Grotesk + Inter, tinted cards |
| `bold-poster` | Vintage editorial poster: Shrikhand tilted display, Baskerville, tomato red, double borders |
| `broadside` | Protest poster: ink-black and fire-orange planes, giant lowercase Barlow 900, mono chrome |
| `capsule` | Friendly editorial: every container a pill, cream, nine candy accents, Bodoni Moda |
| `cartesian` | Quiet catalogue: warm-stone palette, Playfair + Inter, taupe hairline grid, compass rings |
| `cobalt-grid` | Two-colour risograph: cream paper, cobalt ink, graph-paper grid, Newsreader serif |
| `code-editorial` | Developer brand book: warm paper, scarce terracotta, EB Garamond, navy code surface |
| `coral` | Bold magazine: coral, ink and cream planes at hard edges, Bebas Neue, giant numerals |
| `creative-mode` | Sparse neobrutalist: cream, 4px ink borders, Archivo Black caps, rationed accents |
| `daisy-days` | Picture book: charcoal outlines, sunny pastels, Fredoka One, hand-drawn ornaments |
| `editorial-forest` | Literary: green, pink and cream, Source Serif display, mono chrome, flat paper |
## The LOOK line
With no design spec, the look is the LOOK line of `SPEC.md` (`spec.md`), taken from the brand, the reference or the subject itself: its era, its materials, where it would be shown. Every part is a number (hex, px, %, a named face and weight), the ground and the hero come from a medium rather than from effects, and the BANNED line keeps it off the defaults. Inter, Space Grotesk, Instrument Serif, Geist and JetBrains Mono are where generated design lands; use them when the brand does, not by default. Copy the palette and the faces into the root variables below.
## Video facts
- Full-screen linear gradients on dark grounds band under H.264; use a radial gradient, a solid fill or a solid plus a local glow.
- An accent at 5% opacity disappears in compression: 15–25% for atmosphere, full saturation for the focal element.
## Make it re-brandable
Declare palette and fonts as variables on the root `<html>` and use them everywhere, so the person can re-brand from Studio's Variables panel:
```html
<html
data-composition-variables='[
{"id":"color-ground","type":"color","label":"Background","default":"<ground from the plan>"},
{"id":"color-ink","type":"color","label":"Text","default":"<ink from the plan>"},
{"id":"color-accent","type":"color","label":"Accent","default":"<accent from the plan>"},
{"id":"font-display","type":"string","label":"Display font","default":"<display face from the plan>"}
]'
></html>
```
Reference them as `var(--color-accent)`; point a registry item's colour and font variables at the same values.
@@ -0,0 +1,94 @@
# Motion
## Motion craft
Measured frame by frame from the official HyperFrames launch films. These are defaults: a reference the person gave, or a Moment that does otherwise on purpose, wins for its own beat.
- **Moves**: a fast move takes 0.3–0.6 s and carries motion blur on its own ease, peaking at its fastest frame: 6–12 px on text-sized elements, 15–20 px full-frame, horizontal-only on a horizontal move. One move is one tween.
- **Arrive slow, leave clean**: long-tailed ease-outs on arrivals; landing takes longer than leaving. An exit is one smooth motion, never slide, stop, then leave. Entrances that are too snappy read as glitches; slowing the whole video by about a second often fixes it.
- **Ease by role**: an arrival lands on a long tail (`expo.out`, `power4.out`), a camera move or a colour flood eases both ways (`power2.inOut`), a zoom through or an exit accelerates (`power3.in`). The same `power2.out` at 0.5 s on everything is the flat default that makes a film feel generated. Linear only for rotation, progress and constant speed that is the point.
- **Springs** are named by damping ratio: 1.0 never bounces, 0.7-0.85 reads premium, 0.4-0.6 playful, under 0.3 a cartoon. Position and size may bounce; opacity and colour never do. GSAP's `elastic.out(1, 0.5)` and `back.out(1.4)` are the nearest built-ins; a closed-form spring of `t` keeps any frame renderable on its own.
- **Exits** are 40-50% shorter than their entrances. **Overlap**: the next element starts when the previous is 30-50% done; fully sequential looks robotic, all at once looks cheap. **Stagger**: letters a frame apart, words 60-120 ms, list items 40-80 ms.
- **Beat grid**: frames per beat = fps × 60 / BPM; compute each beat from its index and round it, never add a rounded beat repeatedly (at 128 BPM and 30 fps that drifts 2 frames in 32 beats). Cut on the beat, change the layout on the bar.
- **Cuts**: cut at the fastest frame of a move, with matching direction and speed on both sides. A click into a new screen is a match cut on the click's rebound; the new screen comes in slightly large and settles.
- **Titles**: a landed title is dead still; the motion moves to the next element, the camera or the world behind. Vary the entrance within a film, never a fade and slide-up for each:
- per word: `blur(6px)` → 0 and opacity 0 → 1 in 0.13 s, 0.067 s apart, no translate;
- per word from light grey to ink, 0.07–0.1 s apart, no position change;
- the whole line blurring in, 8 px → 0 in 0.13–0.2 s;
- per letter for a name, 0.1 s apart, opacity 0.3 → 1 with a slight blur;
- behind the subject: the title between the plate and a matte of the person;
- one word of the sentence rolling through options like a slot machine, about 0.1 s a roll.
- **Holds**: a line the viewer must read holds about 0.5 s plus 0.3 s a word, the film's last line at least 2 s; a one- or two-word chapter line may hold 0.4 s. Then exit with a blur-out and fade of 0.13–0.25 s, word by word, or with the scene's move.
- **Title size**: 4–9% of the frame height, one or two lines; quiet lines at 2.5–4% against hero words that fill 60–90% of the width. One highlighted word in the accent colour, which carries meaning.
- **Rhythm**: contrast speeds. Montages cut every 0.13–0.8 s; a demo holds one framing for 7–10 s while the UI acts; shots average 1.8–3 s with motion inside them. Hold 0.3–0.75 s before the payoff, which plays at normal speed.
- **Continuous motion that works**: the product's UI working, footage playing, a character acting, a 3D world turning, a slow orbit of a background stage (3–5° a second).
- **Defaults that look generated**: breathing, floating or pulsing loops standing in for motion on titles and UI; titles that keep sliding or scaling after they land; a slow creeping scale on a title or a slow push late in a scene; bounce on UI; click rings; every scene entering the same way; every element fading and sliding up. A character's idle and overshoot are acting, not these (`types/character.md`).
## Transitions with a cause
Use 3–5 kinds in a film, each caused by something on screen. Hard cuts belong inside a scene (punch-ins); at most a third of the seams between beats are hard cuts, and worlds never crossfade into each other. Measured on the official films:
| Transition | How it is built |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Zoom through a button | the camera tracks to the button with blur (0.6 s), then the button scales from ~8% to ~250% of the frame in 0.35 s, strong ease-in; its fill becomes the next frame |
| Object becomes the world | a pill or card grows past the frame in 0.65 s, ease-in-out, its radius shrinking; what was around it blurs ~15 px and fades in 0.3 s; the new content fades in while it still grows |
| Morph | one container widens into the next (icon card → prompt box, ~110 → ~880 px in 0.2 s `power3.out`) with a horizontal blur 12 → 0 |
| Carry across | the result shrinks 1 → 0.6 with blur peaking at 6 px mid-move and lands inside the next app as it builds around it |
| Colour flood | the hero object stays while the whole ground changes colour in ~0.4 s `power2.inOut` |
| Whip | a horizontal or vertical smear of 0.13–0.3 s; the arriving element lands blurred and sharpens in ~0.13 s |
| Iris from the click | `clip-path: circle()` opens from the clicked point onto the next world in 0.15 s |
| Dip cut | the outgoing frame darkens ~40% for 3 frames before a hard cut, so the cut reads as meant |
| Blur swap on one stage | out: opacity → 0 with ~8 px blur in 0.13–0.2 s; in: blur → 0 in 0.13 s; the ground never cuts |
Registry versions: `npx hyperframes catalog --tag transition --json`.
| The person says | Change |
| ---------------------- | ------------------------------------------------------------------------------ |
| "too fast" | Slow every zoom and pan to 0.7× of its speed, nothing else |
| "speed this up" | Speed the content and shorten the beat by the same amount; later beats move up |
| "the titles move" | Land them, then hold them dead still until they leave |
| "it feels like slides" | Enter the next scene through an object on screen; cut a move mid-flight |
## The right mechanism
| Request | Mechanism |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Punch-in, zoom, pan, Ken Burns, reframe | Tween `scale` and `x`/`y` on a wrapper inside the clip, never on the timed media element; chain the states on one timeline |
| Crop or mask reveal | Tween `clip-path` or a mask on an inner wrapper; source time is untouched |
| Constant speed change | `data-playback-rate` on the media, 0.1–10 (picture and pitch-preserved sound) |
| Speed ramp | A `rate` lane in `data-automation` on the media (`t` in clip seconds, `v` 0.1–10); it overrides the constant rate |
| Hold or freeze | Hold a final frame or a finished scene; a freeze mid-source needs a still exported first and placed as its own clip |
Ready-made moves: `npx hyperframes catalog --tag camera --json`, or `catalog --query "<the move>" --tag motion-primitive --json`.
## Seeing motion
The whole-film pass (beat sheet, cuts, phone width, the craft warnings) is `review.md`. For one move:
- `npx hyperframes keyframes --json [--selector '#id'] [--runtime all]` lists keyframes and motion paths (GSAP, CSS, anime.js): property, time, value, ease.
- `npx hyperframes keyframes --shot motion.png --selector '#id'` draws the real element's samples over time with its path (9 samples; `--samples N`). `--layout strip` for motion in place, `--from`/`--to` for one stretch, `--angle iso|top|side|'yaw,pitch'` for 3D, `--ghost` for a `<canvas>`.
- `npx hyperframes snapshot --range 2,4 --frames 8` samples one stretch; `--composition <id>` samples one scene; `--at <t,…>` exact instants; `--zoom '#id'` a sharp crop; `--angle iso` depth and overlap.
- Against a reference video: `snapshot --at <t,…> --against ref.mp4` writes a side-by-side sheet.
- Assert intent instead of eyeballing it: a `<name>.motion.json` next to the composition makes `check` verify it on the seeked timeline, as errors (`motion_appears_late`, `motion_out_of_order`, `motion_off_frame`, `motion_frozen`):
```json
{
"assertions": [
{ "kind": "appearsBy", "selector": "#headline", "bySec": 0.5 },
{ "kind": "before", "a": "#headline", "b": "#cta" },
{ "kind": "staysInFrame", "selector": ".card" },
{ "kind": "keepsMoving", "withinSelector": ".scene" }
]
}
```
## Common faults
| Symptom | Usual cause |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Blank or frozen scene | Timeline registered before an async build finished, `visibility` tweened on a `.clip`, or a host id that differs from its scene's timeline key |
| Stutter or drift between renders | Layout measured in `onUpdate`, `requestAnimationFrame` or clock time; two timelines on one property |
| Jump at a cut | Outgoing and incoming states differ at the seam: snapshot just before and after it |
| Ease looks off at a handoff | Speeds don't match across the seam: mirror the ease (`power3.in` out, `power3.out` in) |
Studio's keyframe diamonds and motion-path handles rewrite the GSAP script in the composition file, so the person may have changed motion since you wrote it.
@@ -0,0 +1,39 @@
# Cards over talking-head footage
Designed cards, lower thirds and split layouts over a person speaking. Placement numbers are for 1920×1080 and 1080×1920; scale others.
## Where the video sits
| Layout | Landscape (x, y, w×h) | Portrait (x, y, w×h) |
| ---------------------------- | --------------------- | -------------------- |
| Split, video beside the card | 960, 0, 960×1080 | 0, 960, 1080×960 |
| Stack, video above | 14, 14, 1892×548 | 0, 0, 1080×844 |
| Overlay | full frame | full frame |
Picture-in-picture, 28 px from the edges (bottom-right: y = H − h − 28):
| Canvas | Source | Rectangle |
| --------- | ------ | ----------------- |
| Landscape | 16:9 | 1432, 28, 460×258 |
| Landscape | 9:16 | 1644, 28, 248×440 |
| Portrait | 16:9 | 690, 28, 360×203 |
| Portrait | 9:16 | 738, 28, 312×555 |
- Card zones: a lower third is the bottom 30%; a side panel the right 42% (landscape) or bottom 40% (portrait). Glass card slots: 48, 460, 660×540 landscape; 24, 1280, 1032×564 portrait.
- Portrait type is landscape × 1.3 (titles × 1.35, stats × 1.4); padding drops from 40–64 px to 24–36 px.
- Size a rectangle to its source's aspect: `object-fit: cover` crops a portrait source in a landscape box.
## Timing
- Cards fade in over 0.4 s (`power2.out`) and out over 0.35 s (`power2.in`). Re-frame the video (0.5–0.7 s) only in a gap between cards; move it with transforms on a wrapper, not `left`/`top`/`width`/`height`.
- A static card goes dead after about 8 s; past about 15 s it needs a staged reveal. As a starting density: one card per 6–8 s under a minute, 8–12 s up to 3 min, 12–20 s up to 10 min.
- A transcript's last word can end past the media: clamp card ends and the root `data-duration` to the file's ffprobe length, or the render ends in black.
## Pitfalls
- Lower-third, overlay and picture-in-picture scenes need a transparent root, or they hide the speaker.
- Glass over bright footage goes white on white: put a dark scrim behind it (about `rgba(0,0,0,.55)` fading out from the top, `.72` at the bottom) and a dark glass base such as `rgba(15,16,22,.78)` with `backdrop-filter: blur(22px)`.
- More than 3 timed elements on one track warns `timeline_track_too_dense`: give each chapter its own sub-composition.
- Footage with keyframes more than about 1 s apart freezes on seek: re-encode with `-g <fps>`.
Text behind the speaker (a caption between the footage and a cutout of the person): `captions.md`.
@@ -0,0 +1,43 @@
# Running from an installed plugin
Check the directory two levels above the loaded `SKILL.md` for `plugin.json`,
`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, or
`.cursor-plugin/plugin.json` identifying `hyperframes`, or a Gemini extension manifest.
That directory is `<PLUGIN_ROOT>`. If none exists, this is a standalone skill;
follow the normal installation and freshness instructions.
For a plugin installation, these rules replace the standalone update commands
in every skill and reference:
- Do not run `hyperframes skills update`, `skills check`, or `npx skills add`.
The agent's plugin manager owns installation and updates. Read the skill at
`<PLUGIN_ROOT>/skills/<name>/SKILL.md`; report a missing bundled skill instead
of downloading a different release. Resolve all skill references inside this
bundle, even if a standalone copy is also installed. Claude exposes the router
as `/hyperframes:hyperframes`; other clients may expose `/hyperframes`.
- Replace `npx hyperframes` (or bare `hyperframes`) in command examples with:
```bash
node "<PLUGIN_ROOT>/skills/hyperframes/scripts/plugin-cli.mjs" <command> <args...>
```
Run in the user's project directory, never in the plugin directory. The launcher
selects the manifest's CLI version and disables automatic standalone skill
installation, including during `init`. The first call may download that exact
CLI version through npm. A download failure is an error, not permission to use
latest. Existing project dependency files and lockfiles must not be overwritten
to match the plugin; surface a runtime incompatibility before changing them.
- For bundled Node helpers, use the same
launcher with `--script <absolute-script-path> <args...>`. This passes the
release version to their dependency loader. External providers, registry
downloads, and existing project dependencies have their own versions; a plugin
version alone does not freeze those inputs.
- Treat the plugin directory as read-only. Put outputs and temporary work in the
project or a temporary directory. Pass this plugin root and launcher convention
to any delegated agent so it does not fall back to global skills.
To get newer skills, use the client's plugin update flow and reload the session.
Do not silently update an installed plugin during a video task.
The launcher suppresses CLI update notices as well as standalone skill refreshes, including when running an older CLI with a stale notice cache. It passes the release as `HYPERFRAMES_PLUGIN_VERSION` for plugin-aware helpers and as `HYPERFRAMES_SKILL_PKG_VERSION` for the existing helper-package bootstrap. Our release process versions those packages together.
@@ -0,0 +1,51 @@
# Review
You wrote the timeline, so you know what happens; the viewer sees it once, at full speed, often on a phone. Review is how you see the film as they will: as a harsh motion director, not a proud author. `check` catches what is broken; this pass catches what is weak. Run it after `check` passes, on every new film and on any edit that changes timing or layout, before you reply or render.
## 1. Measure, then look
1. `npx hyperframes check --json`. Besides errors it warns on craft it can measure: a line on screen too briefly to read (`line_unread`), more than 3 s with nothing changing (`dead_stretch`), text under 2.6% of the frame's short side (`text_below_floor`), an empty first frame (`first_frame_empty`, the thumbnail on social players), a film over 15 s with no sound (`film_silent`). A warning is a question, not a rule: fix it, or keep it on purpose and say why in the note (a deliberate hold carries `data-hold`).
2. `npx hyperframes snapshot --review` writes three sheets: each scene just after it starts and at its middle, plus each `data-beat`, a strip across every cut (−0.2 s to +0.2 s), and the beat frames at phone width (360 px). Open all three.
3. Motion between the beats: `snapshot --range START,END --frames 8` around the hero move and the payoff; `keyframes --shot` for a path that must arc or land.
4. The rendered file: `npx hyperframes audio <video> --json` shows whether sound is there, where it hits and how loud it is. You cannot hear it; say so.
Trust the numbers over your eye where they disagree: a clipped label or a 0.6 s line looks fine on a contact sheet.
## 2. What to look for
First, describe each beat frame in one sentence as a stranger would, naming every object; an object you would have to explain ("those bars are mirrors") is a P0. Then read the beat frames in order: do they tell a story a stranger follows, each one causing the next? Compare each with its shot in `SPEC.md`; what does not match is a note. Then name every default the frames show (`spec.md` "BANNED by default", or one not on the list yet), add each to the BANNED line, and fix them before anything else.
Then go through the sheets once per question:
| Question | What it catches |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Does it read as what it is? | A shape that reads as another object, a material that reads as plastic, a product screen that reads as a mockup, a face or mascot that reads wrong |
| Does the subject separate? | Subject and ground too close in value, two focal points in one frame, the eye with nowhere to go |
| Framing and contact | Crops, text at the edge or under platform UI, collisions at a seam, things floating that should touch |
| Timing as a first-time viewer | A read shorter than its words (`story.md` §6), something that pops on with no move, a jump at a cut, everything moving at once on one curve |
| Tells | Which items of `story.md` §8 and `motion.md` "Defaults that look generated" are on screen |
| The pictures alone | With the words covered, do the beat frames still tell the storyline? |
| The phone sheet | Can every line meant to be read be read at 360 px? |
## 3. Notes and fixes
Write each problem as one line: `t=4.2s · where · problem → exact change · what stays`. Rank them:
- **P0** breaks the read or the contract: unreadable, wrong, broken, a tell in the hero shot.
- **P1** weakens the film: a dead stretch, a soft payoff, a seam that jumps.
- **P2** polish.
Fix every P0 and the P1s worth their time: text first, then timing, then detail. Then snapshot only what you changed, once. A second pass only when a P0 is still open. Never score the film out of 10: a score finds nothing to fix. When a fix does not hold, snapshot the frames on either side of the problem, list every difference, and change only the line that causes it. A film that feels busy: remove a third of the motion and keep the moves that lead the eye.
A fresh pair of eyes, when your host can start a sub-agent: give it only `STORYBOARD.md` and the three sheets, ask for P0 and P1 lines in the format above, and keep editing to yourself. Commit or copy the project before you fix, so a fix that makes things worse can be rolled back.
## 4. The delivery note
Write `REVIEW.md` at the project root and put its short form in your reply:
- the creative calls you made, and the storylines you did not build;
- what you measured and what you only looked at; what nobody checked (the sound was measured, not heard);
- the weakest shot, and what you would change with more time;
- warnings you kept on purpose, and why.
The next turn reads it before changing anything.
@@ -0,0 +1,43 @@
# Animation runtimes
HyperFrames seeks each frame on its own, so motion must be a function of time, never of the previous frame. Full GSAP reference: `npx hyperframes docs gsap`.
## GSAP facts that bite
- Register the timeline last. Building inside `document.fonts.ready.then(…)` or another async callback is fine if you register at the end of it; registering first renders blank (`gsap_timeline_registered_before_async_build`).
- Place tweens at absolute seconds (`tl.to(el, {…}, 2.4)`). Set start states in the tween (`fromTo`), not in CSS.
- Measure layout once before building tweens, never inside `onUpdate`.
- Finite repeats only: `repeat: Math.max(0, Math.floor(duration / cycle) - 1)`.
- Seed randomness with a small PRNG (mulberry32).
- A scene's timeline can't reach elements outside its file, and two timelines must not animate the same property of one element.
- Transforms on a `<span>` need `display: inline-block` or `block`.
- Text that must fit a width: `window.__hyperframes.fitTextFontSize(text, { fontFamily, fontWeight, maxWidth })` returns `{ fontSize }`; `window.__hyperframes.pretext` measures text without a reflow.
## Other runtimes
| Runtime | Use for | How HyperFrames drives it |
| ---------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------ |
| CSS `@keyframes` | Simple loops and decoration | Found automatically; finite iterations, `animation-fill-mode: both`, `data-start` on the element |
| WAAPI | Native keyframes | `element.animate(…, { fill: "both" })` then `pause()`; times are document time, so use `delay` |
| Lottie / dotLottie | After Effects exports, characters | `autoplay: false`, always set `loop`, push the instance to `window.__hfLottie` |
| anime.js v4 | Existing anime code | `autoplay: false`, push to `window.__hfAnime`, `anime.createSeededRandom` |
| Three.js, WebGPU, p5, canvas | 3D and generative | Draw from `e.detail.time` in an `hf-seek` listener; set `data-duration` on the root |
Charts and map overlays: drive them from a GSAP tween's `onUpdate`, not the library's own transitions. A real place is shown on baked imagery, not drawn (`film.md` "Maps and real places"). Heavy setup (big meshes, shader compiles) registers a `window.__hf.buildReady["<key>"]` promise.
## The seek protocol
```html
<script>
function renderAt(t) {
/* draw the frame for t seconds from state derived from t alone */
}
window.addEventListener("hf-seek", (e) => {
renderAt(e.detail.time);
// async work (GPU submit, image decode): e.detail.waitUntil(promise), called synchronously
});
renderAt(0);
</script>
```
No `requestAnimationFrame` loop or timer as the clock.
@@ -0,0 +1,45 @@
# Skill installation and freshness
Read this reference when installing or updating skills, diagnosing unexpected skill behavior, or running HyperFrames setup in CI.
For a plugin installation, follow [plugin execution rules](plugin-installation.md); the plugin manager owns updates and every skill is bundled. The commands below apply only to standalone skills.
- **Core set, installed eagerly:** `/hyperframes`, `/hyperframes-cli`, `/hyperframes-registry` and `/media-use`.
- **On demand:** `/figma`, `/slideshow` and `/remotion-to-hyperframes`, installed with `npx hyperframes skills update <name>`.
## What `init` does
`npx hyperframes init` checks GitHub and refreshes the core set plus other skills already installed. A current install is a no-op. Offline or rate-limited checks degrade gracefully and do not fail project scaffolding.
The `--skip-skills` CLI flag is temporarily ignored. CI and tests may opt out with `HYPERFRAMES_SKIP_SKILLS=1`.
`HYPERFRAMES_SKIP_SKILLS=1` also makes `skills update` (bare or named) and bare `skills` exit 0 without installing or pruning, so an install pinned to one commit stays pinned.
## Diagnose and update
```bash
npx hyperframes skills check
npx hyperframes skills check --json
npx hyperframes skills update
npx hyperframes skills update <name>
npx hyperframes skills
```
- `skills check` exits non-zero when an installed skill is stale or the core set is incomplete. Skills available on demand but not installed are not failures.
- Bare `skills update` refreshes the core set and everything already installed, and removes skills that are no longer published.
- Named `skills update <name...>` also installs those named skills.
- Bare `skills` installs the full published set explicitly.
If the HyperFrames CLI is unavailable, use `npx skills add heygen-com/hyperframes --skill <name>` for one skill or `npx skills add heygen-com/hyperframes --all` for the full published set.
The CLI may print a one-line stale-skill reminder during `render`, `lint`, or `check`. Treat a failed update as a visible tool failure; do not continue from a remembered skill.
## Keep the project's CLI current
A scaffolded project pins `hyperframes@<version>` in its `package.json` scripts so renders stay reproducible; the pin never advances on its own, and a pinned run of an older CLI prints no warning about it. When resuming a project whose scripts carry a pin, probe once before the first render-affecting command:
```bash
npx hyperframes@latest upgrade --project . --check
```
The probe is read-only and reports the pin against the latest release; keep the explicit `.` — on older CLI releases a bare `--project` followed by another flag consumes that flag as its directory value. When it reports the project behind — or any CLI output already shows it (the stderr notice `This project pins hyperframes@… (latest …)`, or `_meta.updateAvailable: true` in a `--json` result from a pinned script) — apply with `npx hyperframes@latest upgrade --project .`, then verify with `npx hyperframes check`. A passing check confirms the project's compositions still validate on the new version — not that rendered output is frame-identical to the old pin — so a successful bump is never silent: name the old and new version in the run's summary. A project with no composition yet needs no verification. If the check fails, revert the `package.json` change, continue on the pinned version, and report which version the project stays on and why. Act on the signal rather than relaying it to the user; never leave a bumped pin unverified.
@@ -0,0 +1,43 @@
# Sound
Sound is half of how a film feels, and the viewer notices a silent one before any frame. Every judge of frames misses it, so plan it on purpose.
## 1. The default
- **A film over about 15 s that tells something** (launch, explainer, recap, PR video) gets a music bed and sounds on its visible actions, unless the person asked for silence or it is made to play muted (a looping background, a feed that autoplays without sound: then the type and the pictures carry it alone).
- **A voiceover** only when the request or the source calls for one (`SKILL.md`).
- **Each storyline says its sound** in one line (`story.md` §2): the bed's feel and tempo, the one moment the music changes, which actions get effects, or silence and why.
- **Short pieces** (a title card, a sting under 10 s, captions or overlays on footage that has its own sound) follow the request and the footage.
## 2. The clock
- **Narrated**: the voice is the clock. Time each beat's start to its anchor word; text lands on the word that says it.
- **Music only**: the music is the clock. Land the payoff and the mark on its strong beats (`npx hyperframes beats`, then `film.md` "On music"); cuts follow the motion, not the drums.
- Let the sound act out the story when it fits: the old way sits near silence, the music opens when the product appears; the music holds back until the payoff.
## 3. Music
The person's own track first, as given. Else search the catalog (`/media-use`, `bgm`) with the film's feel and tempo, not its subject ("tense minimal pulse building to a bright drop, 120 bpm"), and read a few results before keeping one. Enter at the track's strongest bar, not a quiet intro; end on a bar line with a short fade under the end card.
With no music source (no `heygen` sign-in and no local model), deliver silent and say so in one line with how to add one. Do not spend the run building an audio chain or writing music in code: nobody can hear whether it is good.
## 4. Effects
Few and exact: every effect has a cause on screen, and too many read as a template.
| Sound | Only on |
| ----------------- | ---------------------------------------------- |
| click | a real cursor click on screen |
| whoosh | a scene cut, or a large card moving fast |
| soft key tick | text arriving letter by letter or line by line |
| pop | a badge, chip or number landing |
| riser into impact | the payoff or the mark, once in the film |
- Place a clip so its peak lands one frame after its picture (light arrives before sound; a sound early feels wrong sooner than one late): `npx hyperframes audio <file> --json` reports where it hits, and most files peak after their start.
- Each hit sits 3-6 dB over the bed; a bed with most of its energy under 150 Hz plays as nothing on a phone speaker.
- Rotate two or three takes of a repeated sound; the same whoosh on every cut is the most common tell.
- Over strong music, a typing-heavy film can drop the clicks and keys. No synthesised tones, chimes or dings.
## 5. Levels
Music under a voice sits around 0.14 volume; nothing clips (AI audio often does). Keep each file's true peak at −1 dBTP or lower and the mix around −16 to −14 LUFS for social, `npx hyperframes normalize-audio` to match levels (`/media-use`). Check the rendered file with `npx hyperframes audio <video> --json`: sound present, hits where the picture hits, loudness in range. Say in the delivery note that it was measured, not heard (`review.md` §4).
@@ -0,0 +1,76 @@
# The spec
Adjectives are where the default look lives. Given "premium, modern, clean", the model fills every gap with its own defaults, and they look the same in every run: a dark void, a glow, small type floating in the middle, everything fading in. Every adjective you turn into a number is a decision taken back from those defaults.
So before any scene, write `SPEC.md` at the project root: the film as a director's brief to yourself, with a number wherever you would have written an adjective. Build from it, check against it, and change it when a review changes the film.
The spec serves the story, never the other way round (`story.md`). A look that turns the story's things into abstract shapes has failed, however striking the frame.
## The template
```text
GOAL: what the viewer feels or does at the end, one sentence
OUTPUT: HyperFrames, W x H, fps
TIME: seconds x fps = frames; BPM and frames per beat when there is music
LOOK: ground (hex, texture and its amount), ink, one accent and where it may appear;
display face + weight + size in px + tracking, body face the same;
the medium (paper and ink, photographs, product UI, glass, clay, 3D studio) with its numbers;
light and depth: shadow offset, blur and opacity, grain %, vignette %
OBJECTS: every thing the story needs the viewer to recognise (a mirror, a clock, a phone, a person),
drawn with the material and details that make it read without a label: a mirror is
silvered glass with a bright edge and a mount, not a bar
MOTION: eases by role as named curves (enter, move, exit, camera); springs as damping ratio;
durations and staggers in frames
SHOTS: one line per shot: frame range, the one hero move, what the viewer reads (with its window),
the sound, and how it hands off to the next shot
BANNED: the defaults below, plus anything this film must not become
QA: the frames to check, and what each must show
```
## Turning words into numbers
| The word | Becomes |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| premium | a material with numbers: satin paper with 2% grain, a product on a seamless sweep with a 0.55 contact shadow, chrome at roughness 0.05 |
| modern | a camera and a type system: a 30-frame push on one ease, a display face at 9% of the frame height with -2% tracking |
| clean | a grid and a ration: 12 columns, 96 px margins, the accent on one word per shot |
| dynamic | cuts on the beat, a layout change on the bar (every 4 beats), one hero move per shot |
| playful | springs at damping 0.45-0.6 on position and scale (never on opacity or colour), 10-30% exaggerated poses |
| cinematic | a lens and a light: a 35 mm framing, a key from upper left, a rim from behind, 6% vignette |
## Composition numbers
- The hero of a shot fills at least 40% of the frame's height or width. A small object in the middle of an empty frame is never a shot; push in, crop, or give it a world.
- One hero move per shot. Secondary moves travel 50-70% as far, start 100-200 ms after it, and everything else holds still.
- In a story film every shot moves the story: something happens that causes the next shot. Lines read as cause and effect ("so…", "which means…"), not slogans.
- An information card (B-roll over a talking head, a data beat, a vertical explainer card) is different: its information is on screen within about 2.5 s, then it holds, so any paused frame reads as a finished poster.
- Something new every 2-4 s across the film; a new layout every bar when there is music.
- Text meant to be read: at least 28 px at 1080 (2.6% of the short side), held 0.5 s + 0.3 s a word. Too many words: cut words, never shrink type.
## BANNED by default
These are where generated video lands when nothing else is decided. Each is banned unless the brand's own material or the person's reference uses it, and then only as they use it:
- a black or dark navy void as the ground, with small elements floating in its middle;
- glow, bloom, neon, lens flares, glowing orbs and rings; floating particles as decoration;
- purple or blue gradients, mesh gradients, glass cards;
- a cream or off-white paper ground with a serif display, italic accent words, numbered "01 / 02" labels, small monospace caps labels, pill-shaped chips and buttons;
- everything centred; everything fading in at once; every element fading and sliding up;
- text that moves while it is read; titles that keep drifting after they land;
- corner labels, frame borders, corner brackets, HUD readouts as decoration;
- three cards in a row, KPI grids, a title over a dimmed screenshot;
- the logo docked in a corner.
After the first render, name every default it used anyway, from this list or not, add each to the film's BANNED line, and fix them. The list grows with every film.
## A look built from a medium
A look holds together when it is the rules of one medium, not a palette. A risograph look, for example, is five parts and nothing else:
- **paper**: one flat colour with a fibre noise, no gradients;
- **ink A**: every word that must be read and every structural line;
- **ink B**: one fluorescent accent, one focus per frame (the number, the bar, the stamp);
- **overprint**: where the inks overlap a third colour appears, and the B plate sits 3 px off register;
- **halftone**: every gradient is dots.
Motion comes from the same medium: the frame is printed, plate by plate, not faded in. Write any look this way: its parts, what each may carry, and how the medium itself moves (paper is cut and lifted, ink is printed, glass refracts, clay is pressed).
@@ -0,0 +1,103 @@
# Story
Read before building any video that tells something over about 15 s: a launch or promo, an explainer, a pull-request or changelog video, a recap, a music video with a narrative. Most weak films are good motion around no story: a list of features, three cards in a row, a logo, a URL.
Skip it for an edit to an existing project, captions or overlays on footage, and a short single-idea piece (one kinetic line, a map move, a highlight on an image), where the request is the story. When the person gave a script, a storyboard or a reference film, follow theirs. The shape of each kind of film (launch, explainer, music, data, vertical, 3D, character) is in `types/`.
## 1. The one thing
- **What it is**, in one line: "You can now …" for a launch, the one idea the viewer must understand for an explainer, what changed and why it matters for a pull request. A whole release ("2.0", "what's new") is not one thing: take its headline.
- **The one thing to remember** after the film ends, in the viewer's words, not the brand slogan.
- **One viewer doing one real task**, from the moment they need it to the result. That task is the spine, and one real piece of data (a query, a file, a prompt, a number) travels through it from the hook to the payoff. An explainer's spine is one concrete example worked through.
- Keep facts (what it does, real numbers, real labels) apart from creative choices (angle, tone, look). Every claim on screen traces to the source: the site, the PR, the docs or the person's words. Use the person's own copy verbatim.
- What the source does not say stays unsaid: never derive a claim it did not make ("40 min to 2 min" is not "20× faster"), mark an example number as illustrative, and show a screen you cannot get as a labelled placeholder, not a drawn one.
## 2. Three storylines, then one
Write three. They are three different films, not three titles on one film: each has its own **world** (where it happens and what it looks like), its own **device** (what the story is) and its own **form** (how the camera and the cuts carry it). Sketch five or more ideas first, a line each pairing a device with a form (tables below); drop those the subject cannot carry well, and of the rest keep the three that differ most. Make one of the three the bold one you would not have picked first. Each says:
- its name, its device and form in a few words, and in two sentences what the viewer sees and feels;
- the first two seconds, exactly;
- its beats in one line (`hook → … → end card`);
- its look in one line: ground, type, the one material or treatment, taken from the subject or the brand (`look.md`);
- its sound in one line (`sound.md` §1).
The storylines are words: build nothing for any of them before one is picked. Pick a storyline for its story: the one a stranger would follow and remember, with people or a situation they know, ahead of the most striking picture. Picking a storyline picks its look and its sound. Then write the picked one as `SPEC.md`.
Put your recommendation first, with the reason in one clause, and save all three in the project as `STORYLINES.md` so a later turn can switch.
- **The person is there to answer:** show the three and stop once until they pick. A mix ("A's opening with C's middle") is one storyline. Ask nothing else unless only they can answer it (real screens you cannot capture, narrated or music only). State everything else in one assumption line: the length as a range, the aspect, music or voice, and every paid item (a HeyGen voice, an avatar, catalog music) with the free option beside it.
- **They asked for the file or for no stops:** take your recommendation, build it, and name the other two in one line each in your reply.
- With a storyboard (`storyboard.md`), the picked storyline becomes the board's frames.
| Device | What the viewer sees |
| ----------------------------- | --------------------------------------------------------------------------------------- |
| A moment in someone's day | a person, a time stamp, the message that creates the need ("launch is tomorrow") |
| A character does the work | a mascot or a cursor carries, drags, draws and reacts; every cut has a cause |
| The feature as the hook | the feature happening to a recognisable image in the first half second |
| The result first | the finished output plays, then the camera pulls back to show where it came from |
| One object across apps | the thing being made travels from one app into the product, which keeps working on it |
| Life before and after | the old way as a scene (one person drowning in tabs), then the new way |
| Type carries the argument | giant kinetic type, colour fields and grids that cut on the beat |
| A number lands | one real number earned by a scene, then made physical |
| A question with the input | "What if you could make a video from your ⟨website⟩?", the blank filled by a real input |
| The object is the explanation | a machine or model whose working is the idea; the camera tours through it |
| Form | What the viewer sees |
| -------------------------- | -------------------------------------------------------------------------------------------- |
| One continuous take | the camera never cuts; it pushes, pulls and travels from one world into the next |
| Object-to-object chain | every shot ends on the thing that opens the next: a button becomes a card becomes a phone |
| An endless zoom | the camera dives into a detail that turns out to be the next world, again and again |
| A flight through one world | a 3D set, a map or a desk the camera travels across; each idea is a stop on the route |
| Cut on the beat | hard cuts on the music, type and colour fields changing every half beat |
| Match cuts | one shape holds while everything around it changes: a circle becomes a lens, a wheel, a logo |
| Split screen to one | several panes play at once, then collapse into the one that matters |
| One frame that rebuilds | a single composition keeps rearranging itself and never leaves the frame |
A pitch where all three are plain cuts between scenes has not tried the forms. Before and after: the new side moves calmly while the old side churns; never mock a named competitor. A recap asks one question first so each piece answers it.
## 3. The hook
The first two seconds are the densest of the film, and frame 0 is the thumbnail: never black, never a logo alone. Open on the feature working on something recognisable, a real moment that creates the need, a question with the input inside it, a sentence whose last word becomes the picture, or the bug itself. Then let the old way fail on screen (the wait, the wall of text, the wrong result) before the product brings relief; the proof is often the same shot improved in front of the viewer. Typing into a prompt box as the opener, with nothing else on screen, is the tell of a generated film; as a story step a few seconds in, it is fine.
## 4. Beats
A common shape for a launch of about 35 s; scale it, let the device bend it, and take another type's shape from its file in `types/`:
| Beat | Share | Job |
| -------- | ----- | ----------------------------------------------------------------------------------------- |
| Hook | ~8% | the world and the need, or the feature already working |
| Tension | ~12% | the old way failing on screen, or a gag |
| Reveal | ~8% | the product or idea named as a title, about 20–30% in |
| Proof | ~40% | the real task in the real UI as 3–6 verbs (_paste, pick, watch it build, ship_), one each |
| Payoff | ~20% | the output arrives, already moving: the biggest visual moment, once, 60–80% in |
| Claim | ~8% | the one thing, in one line |
| End card | ~8% | the mark carried in, the name and the call to action land and hold 2–3 s |
In each verb beat keep only what the cursor touches and what proves the line; delete the rest of the screen. The process runs fast; the payoff plays at normal speed. Length follows the story (a feature launch is usually 20–45 s); a length in the request is a range, never padded.
## 5. Worlds
A film visits several worlds, each with its own background, light and scale (a desktop, the product on a brand gradient, a white void with a character, live action), never one dark canvas with small UI floating in it. Enter each world through an object already on screen: the thing clicked becomes the next shot. Keep one home stage to return to, one carrier the eye follows, and the palette and type constant across worlds.
## 6. Copy and voice
- Titles take at most 6 words a line, one line on screen at a time, no trailing periods; an explainer's lines may run to about 10. A line holds about 0.3 s a word plus 0.5 s, and the last line at least 2 s.
- Show, don't list: no bullet slides or three-card rows. When similar things repeat, each gets its own idea and layout.
- Real numbers only, from the source. The first line is never "Introducing", "Meet" or the logo; after the pain, "Introducing X" is fine. Never "unlock", "seamless", "supercharge", "game-changer", "revolutionize", "effortless", or "in seconds" unless measured.
- Read the lines alone: a stranger gets the pitch. Watch the pictures alone: they tell the same story.
- No voiceover unless the request or the source calls for one; then the screen never repeats the spoken sentence word for word. Without a voice, large titles and the product's own text carry the words, over music and effects (`sound.md`).
## 7. The brand
The mark lands once, with weight, usually on the end card; before it the brand shows only inside the product and the story, never docked small in a corner. The end card is built, not a still: the mark is carried in, typed or wiped in. The accent colour is rationed: one highlighted word, the payoff, the end card.
## 8. Tells of a generated film
The defaults a film falls into when nothing is decided are banned by name in `spec.md` ("BANNED by default"): a dark void with small things floating in it, glow and particles, the cream-paper-serif-mono-label look, everything fading and sliding up, three cards in a row, the docked logo. Beyond those, for a story:
- every scene entering the same way, or the same move beat after beat;
- side-by-side panels and floating cards standing in for a scene;
- titles that keep sliding or shrinking after they land; breathing or floating loops standing in for motion on titles and UI (a character's idle breath is acting, `types/character.md`).
When a first render shows any of them, name which, take the replacement from the subject, the brand or the medium, and add it to the BANNED line of `SPEC.md`.
@@ -0,0 +1,107 @@
# Storyboard format — `STORYBOARD.md` + parsed manifest
Defines the storyboard's **base data format** only: the `STORYBOARD.md` file shape and the `StoryboardManifest` it parses into. Planning the film itself is in [story.md](story.md); the optional narration/TTS file (`SCRIPT.md`) is a separate concern, not here.
A storyboard is the **plan layer** for a video — an ordered set of **frames** (key moments) in one markdown file. Parser: `@hyperframes/core/storyboard` → `StoryboardManifest`.
## Frontmatter (global direction)
YAML block at the top. Unknown keys are kept under `globals.extra`.
| Key | Meaning | Example |
| ---------- | ------------------------------------------------------------ | ----------------------------------------- |
| `format` | Canvas size | `1920x1080` |
| `duration` | The length the person asked for (advisory, not a hard limit) | `22s` |
| `message` | One-line thesis | `Ship a launch video in an afternoon` |
| `arc` | Narrative arc | `Hook → Problem → Solution → Proof → CTA` |
| `audience` | Who it's for | `indie devs on X` |
| `mode` | Interaction mode (default collaborative) | `autonomous` |
Set `duration` when the storyboard is first written. It is an expectation, not a gate: compare where the cut actually lands against it — judge whether the drift serves the piece, and update the value when the intended length genuinely changes.
## Per-frame sections
One `## Frame N — Title` heading per frame (`Frame` / `Beat` / `Scene` accepted at H2/H3). Metadata as `- key: value` bullets; everything below them until the next heading is the free-form **narrative**.
| Key | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------------------------- |
| `status` | `outline` → `built` → `animated` (defaults `outline`) |
| `src` | project-relative path to the frame's HTML sub-composition (the tile poster renders from it) |
| `duration` | e.g. `4s` |
| `transition_in` | `crossfade` / `cut` / `wipe` … (alias `transition`); a note only, build it yourself (overlap clips for a crossfade) |
| `scene` | one-line contact-sheet caption (aliases `description` / `summary` / `caption`) |
| `voiceover` | the frame's narration _guide_ (aliases `vo` / `voice_over` / `narration`) |
| `poster` | seconds to seek for the tile poster (past the intro animation) |
| _any other key_ | kept verbatim under the frame's `extra` — carry your own per-frame data (effects, assets, …) here |
Two `extra` keys every film plan carries, so the build and the review check the same thing:
| Key | Meaning |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reads` | what the viewer must understand in this frame, in order, each with its window: `the cursor finds the button (0.4-1.2s); "Export" lands (1.2-2.6s)`. Windows never overlap, and each is long enough for its words (`story.md` §6) |
| `sound` | the bed, the effects on visible actions, or silence: `bed enters strong; click on Export; whoosh on the cut` |
## Parsed manifest
The parser is **lenient**: it never throws and records anything surprising as a `warning`.
```
StoryboardManifest {
globals: { format?, message?, arc?, audience?, extra: {…} }
frames: Array<{
index, number?, title?,
status, // "outline" | "built" | "animated"
src?, duration? / durationSeconds?, transitionIn?,
scene?, voiceover?, poster?,
narrative, // markdown below the metadata
extra: {…} // unknown keys, preserved
}>
warnings: Array<{ message, line?, frameIndex? }>
}
```
## `SCRIPT.md` (out of scope here)
Optional, free-form, **not parsed into the manifest** — the locked-narration file that drives TTS, absent for videos with no narration/TTS. The per-frame `voiceover` above is the storyboard's own narration guide.
## Example
```markdown
---
format: 1920x1080
message: "Ship a launch video in an afternoon"
arc: Hook → Problem → Solution → Proof → CTA
audience: indie devs on X
---
## Frame 1 — Hook
- scene: Big type punches in on the beat
- duration: 3s
- poster: 2s
- transition_in: cut
- status: animated
- voiceover: "Ship a launch video in an afternoon."
- reads: "Ship a launch video" lands (0.3-1.6s); "in an afternoon" (1.6-3.0s)
- sound: bed enters on its strongest bar; impact on "afternoon"
- src: compositions/frames/01-hook.html
Open cold on the promise. This is the thesis — everything after pays it off.
## Frame 2 — The problem
- scene: A 20-minute timer spins on a stack of rejected takes
- duration: 4s
- transition_in: crossfade
- status: built
- voiceover: "The old way? Prompt, wait twenty minutes, get something that misses."
- src: compositions/frames/02-problem.html
The old way: prompt, wait, get something that misses. Establish the pain we remove.
```
## Notes
- A frame with `status: outline` and no built `src` renders as an outline placeholder.
- `built` is the middle rung: the frame's HTML exists and its **layout is confirmed** (a wireframe sketch or better) — motion not yet added. Nothing is drawn yet beyond the approved layout.
- Multi-line `voiceover` values collapse to one line on save.
@@ -0,0 +1,36 @@
# Storyboard recipe — plan a video as stills the person approves
Use it whenever a run makes or changes a storyboard: when the person asks for one, or when you want their go on the shots before animating. This file owns **how to make the board**; `storyboard-format.md` owns the `STORYBOARD.md` file the tools parse.
A film's shots are planned in the SHOTS line of `SPEC.md` (`spec.md`); a storyboard turns those shots into stills when the person wants to approve them first, one frame per shot, carrying its `reads` and `sound` (`storyboard-format.md`).
The person sees the plan as stills on the board, comments or draws on a frame, and says go. The board is `boards/storyboard.html`, and tools that show it find its frames by the classes in § 2; boxes marked any other way (`cell` boxes, `id="frame-NN"` markers) are not found.
## 1. The run
1. **Pose each shot** in the composition the project renders, the root `index.html`, at its key moment: real words, real fonts, real colors, the focal point big enough to read on a phone. A shot posed in its own file (`compositions/frames/NN-*.html`) shows only once it is mounted in `index.html` (`data-composition-src`) at its planned start; `snapshot` renders `index.html` and nothing else. Before the first pose, copy `index.html` to `boards/.film.html`: until the go, the preview plays that film, never the stills (step 4).
2. **Snapshot every still in one run** with your host's snapshot tool when one is offered (each shot's seconds, out `boards/stills`); otherwise `npx hyperframes snapshot --at <every shot's seconds, in film order> --no-end --describe false -o boards/stills`. Each still is named `frame-NN-at-<seconds>s.png`, NN its place in the film. Every `snapshot` run clears its `-o` folder and numbers from `00`, so never snapshot a single moment into `boards/stills/` itself (step 5). Look at every still yourself; fix and snapshot again until each one holds.
3. **Write `boards/storyboard.html`** (§ 2), styled as a finished board: Inter or the system sans-serif, a light page, each frame a rounded card with its still above its words, even gaps.
4. **Put the film back.** Copy the posed `index.html` to `boards/.posed.html`, then `boards/.film.html` back over `index.html`. The board stays on its own page: held stills in the preview read as a slideshow.
5. **Stop.** Hand the person the board and wait for their go. Notes on frames change only those frames. For each one: copy `boards/.posed.html` over `index.html`, re-pose it, snapshot just its moment into `boards/stills/redo/` (`--at <its seconds>`), move that one file over the frame's old still under the old name (its new time in the name if the time changed, and the page's `src` with it), then put the film back (step 4), rewrite the page and stop again. The other stills stay as approved.
6. **Once they say go**, copy `boards/.posed.html` over `index.html` and animate from the approved stills.
## 2. The page
One `class="frame"` box per still, in film order, holding its image, a `class="title"` line "<two-digit number>: <name>" and a `class="time"` range in seconds. Optional: a `class="words"` line under it, and a `class="act"` heading before the first frame of each act.
```html
<section class="act">Act 1: The hook</section>
<div class="frame">
<img src="stills/frame-00-at-1.5s.png" alt="" />
<div class="title">01: The hook</div>
<div class="time">0.0 to 3.0s</div>
<p class="words">The cursor drops the photo on the timeline; the title lands on the drop.</p>
</div>
```
Stills are written next to the page (`boards/stills/`), so `src` is relative to `boards/` and names the file `snapshot` wrote for that shot. A still written after the page shows by itself; never reopen or rename the page for it.
## 3. The plan beside it
`STORYBOARD.md` at the project root (format: `storyboard-format.md`) names the same frames in the same order, and its words win over the page's: one heading per frame like `## Frame 1: The hook (0-3s)`, a `- act:` bullet for its act, and `- status: outline` until its still is posed.
@@ -0,0 +1,25 @@
# 3D, simulations and generative pieces
Use 3D when the film earns it: an object or a set as the payoff, a product, a simulation that explains. Loading Three.js, WebGPU, p5 or canvas and the seek protocol are in `../runtimes.md`.
## Deterministic by construction
- Draw every frame from timeline time in the `hf-seek` listener; frames render out of order.
- Simulate once at load (physics, agents, particles) into arrays, then look up the state at `t`: `render(t)` stays a pure function. Heavy setup (meshes, shader compiles, the simulation) registers a `window.__hf.buildReady` promise.
- Seed every random choice; a seeded hash of the index, never `Math.random()`.
## Camera and story
- One hero object beats a crowd. Fly through or into it rather than orbiting it: one dive is worth ten turns.
- Cutaways, exploded views and chapters with one caption line each, plus labels with real numbers, turn a simulation into an explanation instead of eye candy.
- Product UI is never tilted into 3D; only generated content tilts.
## Light and materials
- Light it with a warm key, a cool rim and a dark environment with two or three soft strips; turn the environment, not the object, so highlights sweep across it; a contact shadow under the object.
- Bloom eats detail: keep it to the brightest points. Glossy metal under a low light gives white streaks: roughness about 0.45 or more, environment intensity about 0.5.
- One NaN pixel turns a whole frame black through bloom: clamp values that can divide by zero.
## Check it
Snapshot the dive and the payoff (`../review.md`); a frame that takes too long to draw slows every render, so profile the heaviest moment once.
@@ -0,0 +1,20 @@
# Characters, mascots and story shorts
A character gives every transition a cause and gives the film its jokes: it carries the result into the player, rejects its own first try, reads the manual. After Effects characters come in through Lottie (`../runtimes.md`).
## Acting
- Mood changes go through a take: anticipation, a squash, the new face, a settle. A face never snaps from one state to the next.
- Squash and stretch, anticipation, overshoot and follow-through belong to the character. Here an idle breath or blink is life, not a tell; the tells about breathing and bounce in `../motion.md` are about titles and UI.
- Code moves every part at once, on one curve, by the same amount. Offset the parts (head leads, arms follow), vary the amounts, and never move both arms, both eyes or a crowd in unison.
- Contact sells it: feet on the ground, props held by the hand, eyes on what they look at.
## Reads
Each action is one read. Lead the eye to where it will happen, play the action fast, and let its meaning land slowly: hold the reaction. A sign or caption that repeats what the action already shows is the classic failure.
## Build
- A rig is a set of parameters over time: position, squash, arm angles, an eye state and a mouth state per moment. Write mood changes as keyed states (`[[t0,"normal"],[t1,"scared"]]`) the rig eases between.
- Hand-drawn life: redraw the outlines about 12 times a second with a jitter seeded by `Math.floor(t * 12)`, so the line boils while the character holds still, and every render matches.
- A long piece is one chapter per sub-composition, all drawing from the same rig and helpers.
@@ -0,0 +1,19 @@
# Numbers, charts and data stories
## One number at a time
- A number is earned by the scene before it, then made physical: a count that becomes a skyline, a share that becomes a crowd.
- The number is the biggest thing on screen. Tabular figures (`font-variant-numeric: tabular-nums`) so a count-up does not jitter; a count-up runs 1.2–1.6 s, then holds.
- Real data only, from the source, rounded the same way everywhere, with a small source line on screen. One data module drives every figure in the film.
## Charts as pictures
- Build a chart in reading order: the frame and axis, a reference line, then the data drawing in, then one annotation on the point that matters. Annotate on the mark; no legends.
- Highlight one series, grey the rest. A chart with one thing to say says it in its title.
- The same object carries across beats: a bar becomes the ground of the next scene, a line becomes the road the camera follows.
- The camera can dive into the one value that matters, then the rest falls away.
- Registry charts first (`npx hyperframes catalog --query chart --json`); drive any chart library from a GSAP tween's `onUpdate`, never its own transitions (`../runtimes.md`).
## Tells for this type
A grid of KPI cards; a dashboard; a pie chart; every number counting up at once; numbers with no source.
@@ -0,0 +1,34 @@
# Explainers, tutorials and "how it works"
One idea the viewer understands by the end (`../story.md` §1). The spine is one concrete example worked through, start to finish, not a tour of facts. Usually 30–90 s.
## Ground it first
Start where the viewer already is: a person, a place or a situation they know, and a question they would ask about it ("why does the astronaut come home younger than her twin?"). The abstract device (a light clock, a graph, a field) enters inside that situation, as the thing that answers the question, and the film returns to the person at the end with the answer. A film that opens on the device alone is a diagram, not an explainer.
## Devices that explain
- **The object is the explanation.** Build a thing whose working is the idea: a machine, a model, a map, a light clock, drawn so it reads as what it is (its material, its mount, where it sits in the scene). Every number on screen comes from it, and the camera tours through it: cutaways, an exploded view, a dive into the part that matters.
- **One diagram that grows.** Start from the simplest true picture and add one part per beat, each appearing when it is named; never a finished diagram dropped on screen at once.
- **Continuous transformation.** Each step morphs out of the last (a bar becomes an axis, a dot becomes a population), so the viewer never loses the thread to a cut.
- **The worked example.** One real input (a sentence, a number, a photo) travels through every step to the answer.
## Shapes of information
Most explainable lines are one of a few shapes, and each has its own picture: a number that lands (`data.md`); a myth turned over (the wrong idea struck through, the right one printed over it); before and after; many things converging into a few groups; a network that densifies; one chosen out of many; steps in order; a real screenshot with the detail circled; a quote set as a poster; a chapter card. Pick the shape first, then the picture, so similar lines get different pictures and every line gets the one that shows it.
## Words
- Narrated: the voice is the clock (`../sound.md` §2). Reveal each part when the voice names it, across the back half of the scene; keep a scene to about 19 spoken words (`../film.md` "Script and pacing").
- Not narrated: large lines carry it, up to about 10 words, each held 0.5 s + 0.3 s a word.
- Labels sit on the part they name, with a leader line when needed, and appear with it. No legends, no bullet slides.
## Getting it right
- Every claim, value and step traces to the source; a number the source did not give is left out or marked illustrative. One data module drives every figure so the numbers agree with each other.
- Check the science or the logic of each step before animating it; a beautiful wrong diagram is worse than none.
- End on the one idea in one line. No brand end card unless asked.
## Tells for this type
A slide of bullets; icons in circles; three cards in a row; the whole diagram at once; a narrator repeating the text on screen word for word.
@@ -0,0 +1,31 @@
# Launch, promo and feature films
The shape is `../story.md` §4 (hook → tension → reveal → proof → payoff → claim → end card), usually 20–45 s. What a viewer shares is real people and real output: in the official films, effects-heavy pieces drew about a tenth of the views of ones built on a real result.
## Real UI, never a mockup
Everything that claims to be the product is the product. One invented button loses everyone who uses it.
1. Where it comes from, best first: the person's recording or screenshots (ask, naming the screen per beat, when the proof needs one you cannot capture); Figma frames, rebuilt as states of one app so the change between two frames is a real click (`/figma`); walking the flow yourself when no sign-in is needed; `npx hyperframes capture` of the feature's own page; the product's code, PR or docs to confirm today's labels.
2. Missing a screen: a clearly labelled placeholder ("Screenshot: Settings → Motion"), said in the reply. A feature with nothing visible (speed, an API): the real UI that exposes it, a real analogue the viewer knows, real terminal output, or a checklist of real facts. Never a made-up dashboard.
3. Logo, icons and fonts are the brand's own files (`/media-use` for logos); never redrawn from memory, never a coloured square for an app icon.
## The product on screen
- Rebuild only the piece the beat uses (the panel, the prompt box, the row that changes) and show it at 1.6–2× its real size; text stays at least 26 px at 1080p after any push. Never a small window in the middle of an empty canvas, never product UI tilted in 3D.
- The camera cycles between the window on its ground, one pane, and a punch-in on one control. Moves take 0.4–0.6 s with motion blur, arrive at the control the cursor needs, then hold while the UI acts.
- Containers morph instead of cutting: the button becomes the menu, the message becomes the bubble.
- Pull back to reveal that the result was playing inside the product, or zoom through the player until the result fills the frame.
- The cursor is big (4–7% of the frame), enters from off-screen, and its click causes the next thing on the same frame.
## Worlds and transitions
Visit several worlds (`../story.md` §5) and enter each through an object on screen, with 3–5 kinds of transition in a film (`../motion.md` "Transitions with a cause").
## The end card
The mark lands in the last 5–10% of the film, centred, the lockup 15–30% of the frame width, performed rather than faded in: the cursor clicks the mark and a fill rises through it, or a character carries it in, then the wordmark wipes in beside it. Then the call to action (a real button, the command to type), held 1.5–3 s.
## Tells for this type
Typing into a prompt box as the opener; a feature list as three cards; a title over a dimmed screenshot; an invented dashboard; the logo docked in a corner.
@@ -0,0 +1,24 @@
# Music videos and films cut to a track
The song is the structure. Timing facts (hits, beat trust, loops) are in `../film.md` "On music"; this file is the shape.
## Song structure becomes edit structure
- Find the sections first: `npx hyperframes audio <track> --only sections,onsets,moments --json`. Each section is a chapter: intro, verse, chorus, bridge, outro.
- Cut density follows energy: slower shots in verses, one picture per strong beat in the chorus, a held image on a breakdown.
- The chorus returns to one home stage and escalates each time it comes back; the ending rhymes with the opening.
- Chapter breaks land on section boundaries, with a transition that belongs to the film's medium (a brush wipe, a paper tear, a match cut).
## Pictures for the words
- Each lyric line or phrase gets its own picture (shots of about 1.4–4 s): show what the line means, not the line itself. Lyrics on screen only when asked; then each line holds at least a beat and is set as type, not subtitles.
- Something happens in every shot, and every cut has a cause on screen.
- A song about something real gets real material (footage, photos, places); an abstract track gets one world with a strong medium.
## The look
A physical medium carries a music film further than effects: watercolour with ink outlines, cut paper, risograph print, film grain on footage. Characters, if any, act (`character.md`).
## Tells for this type
The same treatment for more than about 6 s; a spectrum bar or waveform as the main picture; every cut on every beat; words about the music rather than the song's own world.
@@ -0,0 +1,23 @@
# Vertical and social clips (9:16)
Made at 1080×1920 when the request or the platform says vertical; a landscape film is not squeezed into it but recomposed.
## The platform's own UI
The app draws over the frame. On 1080×1920: nothing in the top 168 px (status and search bar); every word and the product inside the band from 168 to 1330 px; below 1330 px (caption, handle, buttons) only texture or decoration; no text under the right-hand action rail. Ads and some apps reserve more at the bottom; check the platform the clip is for. Burned-in captions sit at the bottom of the band (`../captions.md`).
## The first second
- Frame 0 is the thumbnail and the hook: a face, the result, or one bold line already on screen. Never black, never a logo.
- The viewer decides in about a second whether to stay: the change or the question arrives in the first beat.
- Many viewers watch muted: speech is captioned, and the picture carries the hook without sound.
## Layout
- One subject, centred, stacked vertically; landscape layouts (side-by-side panels, wide charts) are rebuilt as a vertical sequence.
- Type is larger than in landscape (titles about ×1.35, `../overlays.md`); nothing meant to be read under 28 px, and a fixed word limit per line (about 8 Chinese characters or 5 English words for a claim): cut words, never shrink.
- Talking heads: punch-ins on emphasis, B-roll cut on the sentence, the face never under a card (`../overlays.md`).
## Pacing
Faster than landscape: shots of 1–2 s, a new beat every 2–4 s, the payoff before the swipe. A loop that cuts from the last frame back to the first is a choice, not a default.