
Hyperframes
- 26 installs
- 226k repo stars
- Updated August 5, 2026
- nousresearch/hermes-agent
Helps with ai & agent building tasks.
About
hyperframes is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- hyperframes
- AI & Agent Building
- AI-coding skill
Hyperframes by the numbers
- 26 all-time installs (skills.sh)
- +4 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #9,667 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nousresearch/hermes-agent --skill hyperframesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 26 |
|---|---|
| repo stars | ★ 226k |
| Last updated | August 5, 2026 |
| Repository | nousresearch/hermes-agent ↗ |
What it does
Helps with ai & agent building tasks.
Files
HyperFrames
HTML is the source of truth for video. A composition is an HTML file with data-* attributes for timing, a GSAP timeline for animation, and CSS for appearance. The HyperFrames engine captures the page frame-by-frame and encodes to MP4/WebM with FFmpeg.
Complement to `manim-video`: Use manim-video for mathematical/geometric explainers (equations, 3B1B-style). Use hyperframes for motion-graphics, talking-head with captions, product tours, social overlays, shader transitions, and anything driven by real video/audio media.
When to Use
- User asks for a rendered video from text, a script, or a website
- Animated title cards, lower thirds, or typographic intros
- Captioned narration video (TTS + captions synced to waveform)
- Audio-reactive visuals (beat sync, spectrum bars, pulsing glow)
- Scene-to-scene transitions (crossfade, wipe, shader warp, flash-through-white)
- Social overlays (Instagram/TikTok/YouTube style)
- Website-to-video pipeline (capture a URL, produce a promo)
- Any HTML/CSS/JS animation that must render deterministically to a video file
Do not use this skill for:
- Pure math/equation animation (→
manim-video) - Image generation or memes (→
meme-generation, image models) - Live video conferencing or streaming
Quick Reference
npx hyperframes init my-video # scaffold a project
cd my-video
npx hyperframes lint # validate before preview/render
npx hyperframes preview # live-reload browser preview (port 3002)
npx hyperframes render --output final.mp4 # render to MP4
npx hyperframes doctor # diagnose environment issuesRender flags: --quality draft|standard|high · --fps 24|30|60 · --format mp4|webm · --docker (reproducible) · --strict.
Full CLI reference: references/cli.md.
Setup (one-time)
bash "$(dirname "$(find ~/.hermes/skills -path '*/hyperframes/SKILL.md' 2>/dev/null | head -1)")/scripts/setup.sh"The script: 1. Verifies Node.js >= 22 and FFmpeg are installed (prints fix instructions if not). 2. Installs the hyperframes CLI globally (npm install -g hyperframes@>=0.4.2). 3. Pre-caches chrome-headless-shell via Puppeteer — required for best-quality rendering via Chrome's HeadlessExperimental.beginFrame capture path. 4. Runs npx hyperframes doctor and reports the result.
See references/troubleshooting.md if setup fails.
Procedure
1. Plan before writing HTML
Before touching code, articulate at a high level:
- What — narrative arc, key moments, emotional beats
- Structure — compositions, tracks (video/audio/overlays), durations
- Visual identity — colors, fonts, motion character (explosive / cinematic / fluid / technical)
- Hero frame — for each scene, the moment when the most elements are simultaneously visible. This is the static layout you'll build first.
Visual Identity Gate (HARD-GATE). Before writing ANY composition HTML, a visual identity must be defined. Do NOT write compositions with default or generic colors (#333, #3b82f6, Roboto are tells that this step was skipped). Check in order:
1. `DESIGN.md` at project root? → Use its exact colors, fonts, motion rules, and "What NOT to Do" constraints. 2. User named a style (e.g. "Swiss Pulse", "dark and techy", "luxury brand")? → Generate a minimal DESIGN.md with ## Style Prompt, ## Colors (3-5 hex with roles), ## Typography (1-2 families), ## What NOT to Do (3-5 anti-patterns). 3. None of the above? → Ask 3 questions before writing any HTML:
- Mood? (explosive / cinematic / fluid / technical / chaotic / warm)
- Light or dark canvas?
- Any brand colors, fonts, or visual references?
Then generate a DESIGN.md from the answers. Every composition must trace its palette and typography back to DESIGN.md or explicit user direction.
2. Scaffold
npx hyperframes init my-video --non-interactiveTemplates: blank, warm-grain, play-mode, swiss-grid, vignelli, decision-tree, kinetic-type, product-promo, nyt-graph. Pass --example <name> to pick one, --video clip.mp4 or --audio track.mp3 to seed with media.
3. Layout before animation
Write the static HTML+CSS for the hero frame first — no GSAP yet. The .scene-content container must fill the scene (width:100%; height:100%; padding:Npx) with display:flex + gap. Use padding to push content inward — never position: absolute; top: Npx on a content container (content overflows when taller than the remaining space).
Only after the hero frame looks right, add gsap.from() entrances (animate to the CSS position) and gsap.to() exits (animate from it).
See references/composition.md for the full data-attribute schema and composition rules.
4. Animate with GSAP
Every composition must:
- Register its timeline:
window.__timelines["<composition-id>"] = tl - Start paused:
gsap.timeline({ paused: true })— the player controls playback - Use finite
repeatvalues (norepeat: -1— breaks the capture engine). Calculate:repeat: Math.ceil(duration / cycleDuration) - 1. - Be deterministic — no
Math.random(),Date.now(), or wall-clock logic. Use a seeded PRNG if you need pseudo-randomness. - Build synchronously — no
async/await,setTimeout, or Promises around timeline construction.
See references/gsap.md for the core GSAP API (tweens, eases, stagger, timelines).
5. Transitions between scenes
Multi-scene compositions require transitions. Rules: 1. Always use a transition between scenes — no jump cuts. 2. Always use entrance animations on every scene element (gsap.from(...)). 3. Never use exit animations except on the final scene — the transition IS the exit. 4. The final scene may fade out.
Use npx hyperframes add <transition-name> to install shader transitions (flash-through-white, liquid-wipe, etc.). Full list: npx hyperframes add --list.
6. Audio, captions, TTS, audio-reactive, highlighting
- Audio: always a separate
<audio>element (video ismuted playsinline). - TTS:
npx hyperframes tts "Script text" --voice af_nova --output narration.wav. List voices with--list. Voice ID first letter encodes language (a/b=English,e=Spanish,f=French,j=Japanese,z=Mandarin, etc.) — the CLI auto-infers the phonemizer locale; pass--langonly to override. Non-English phonemization requiresespeak-nginstalled system-wide. - Captions:
npx hyperframes transcribe narration.wav→ word-level transcript. Pick style from the transcript tone (hype / corporate / tutorial / storytelling / social — see the table inreferences/features.md). Language rule: never use.enwhisper models unless the audio is confirmed English —.entranslates non-English audio instead of transcribing it. Every caption group MUST have a hardtl.set(el, { opacity: 0, visibility: "hidden" }, group.end)kill after its exit tween — otherwise groups leak visible into later ones. - Audio-reactive visuals: pre-extract audio bands (bass / mid / treble) and sample per-frame inside the timeline with a
forloop oftl.call(draw, [], f / fps)— a single long tween does NOT react to audio. Map bass →scale(pulse), treble →textShadow/boxShadow(glow), overall amplitude →opacity/y/backgroundColor. Avoid equalizer-bar clichés — let content guide the visual, audio drive its behavior. - Marker-style highlighting: highlight, circle, burst, scribble, sketchout effects for text emphasis are deterministic CSS+GSAP — see
references/features.md#marker-highlighting. Fully seekable, no animated SVG filters. - Scene transitions: every multi-scene composition MUST use transitions (no jump cuts). Pick from CSS primitives (push slide, blur crossfade, zoom through, staggered blocks) or shader transitions (
flash-through-white,liquid-wipe,cross-warp-morph,chromatic-split, etc.) vianpx hyperframes add. Mood and energy tables live inreferences/features.md#transitions. Do not mix CSS and shader transitions in the same composition.
7. Lint, validate, inspect, preview, render
npx hyperframes lint # catches missing data-composition-id, overlapping tracks, unregistered timelines
npx hyperframes validate # WCAG contrast audit at 5 timestamps
npx hyperframes inspect # visual layout audit — overflow, off-frame elements, occluded text
npx hyperframes preview # live browser preview
npx hyperframes render --quality draft --output draft.mp4 # fast iteration
npx hyperframes render --quality high --output final.mp4 # final deliveryhyperframes validate samples background pixels behind every text element and warns on contrast ratios below 4.5:1 (or 3:1 for large text). hyperframes inspect is the layout-side companion — runs the page at multiple timestamps and flags issues that a static lint can't see (a caption that wraps past the safe area only at 4.5s, a card that overflows when its title is the longest variant, an element that ends up behind a transition shader). Run inspect especially on compositions with speech bubbles, cards, captions, or tight typography.
8. Website-to-video (if the user gives a URL)
Use the 7-step capture-to-video workflow in references/website-to-video.md: capture → DESIGN.md → SCRIPT.md → storyboard → composition → render → deliver.
Pitfalls
- `HeadlessExperimental.beginFrame' wasn't found` — Chromium 147+ removed this protocol. Ensure you're on
hyperframes@>=0.4.2(auto-detects and falls back to screenshot mode). Escape hatch:export PRODUCER_FORCE_SCREENSHOT=true. See hyperframes#294 and references/troubleshooting.md. - System Chrome (not `chrome-headless-shell`) — renders hang for 120s then timeout. Run
npx puppeteer browsers install chrome-headless-shell(setup.sh does this).hyperframes doctorreports which binary will be used. - `repeat: -1` anywhere — breaks the capture engine. Always compute a finite repeat count.
- `gsap.set()` on clip elements that enter later — the element doesn't exist at page load. Use
tl.set(selector, vars, timePosition)inside the timeline instead, at or after the clip'sdata-start. - `<br>` inside content text — forced breaks don't know the rendered font width, so natural wrap +
<br>double-breaks. Usemax-widthto let text wrap. Exception: short display titles where each word is deliberately on its own line. - Animating `visibility` or `display` — GSAP can't tween these. Use
autoAlpha(handles both visibility and opacity). - Calling `video.play()` or `audio.play()` — the framework owns playback. Never call these yourself.
- Building timelines async — the capture engine reads
window.__timelinessynchronously after page load. Never wrap timeline construction inasync,setTimeout, or a Promise. - Standalone `index.html` wrapped in `<template>` — hides all content from the browser. Only sub-compositions loaded via
data-composition-srcuse<template>. - Using video for audio — always muted
<video>+ separate<audio>.
Verification
Before and after rendering:
1. Lint + validate + inspect pass: npx hyperframes lint --strict && npx hyperframes validate && npx hyperframes inspect (lint catches structural issues, validate catches contrast, inspect catches visual layout / overflow issues — see troubleshooting.md if warnings appear). 2. Animation choreography — for new compositions or significant animation changes, run the animation map. npx hyperframes init copies the skill scripts into the project, so the path is project-local:
node skills/hyperframes/scripts/animation-map.mjs <composition-dir> \
--out <composition-dir>/.hyperframes/anim-mapOutputs a single animation-map.json with per-tween summaries, ASCII Gantt timeline, stagger detection, dead zones (>1s with no animation), element lifecycles, and flags (offscreen, collision, invisible, paced-fast <0.2s, paced-slow >2s). Scan summaries and flags — fix or justify each. Skip on small edits. 3. File exists + non-zero: ls -lh final.mp4. 4. Duration matches `data-duration`: ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 final.mp4. 5. Visual check: extract a mid-composition frame: ffmpeg -i final.mp4 -ss 00:00:05 -vframes 1 preview.png. 6. Audio present if expected: ffprobe -v error -show_streams -select_streams a -of default=nw=1:nk=1 final.mp4 | head -1.
If hyperframes render fails, run npx hyperframes doctor and attach its output when reporting.
References
- composition.md — data attributes, timeline contract, non-negotiable rules, typography/asset rules
- cli.md — every CLI command (init, capture, lint, validate, inspect, preview, render, transcribe, tts, doctor, browser, info, upgrade, benchmark)
- gsap.md — GSAP core API for HyperFrames (tweens, eases, stagger, timelines, matchMedia)
- features.md — captions, TTS, audio-reactive, marker highlighting, transitions (load on demand)
- website-to-video.md — 7-step capture-to-video workflow
- troubleshooting.md — OpenClaw fix, env vars, common render errors
HyperFrames CLI
Everything runs through npx hyperframes (or the globally-installed hyperframes after npm install -g hyperframes). Requires Node.js >= 22 and FFmpeg.
Workflow
1. Scaffold — npx hyperframes init my-video (or npx hyperframes capture <url> if starting from a website) 2. Write — author HTML composition (see composition.md) 3. Lint — npx hyperframes lint 4. Validate — npx hyperframes validate (WCAG contrast audit) 5. Inspect — npx hyperframes inspect (visual layout audit) 6. Preview — npx hyperframes preview 7. Render — npx hyperframes render
Always lint before preview/render — catches missing data-composition-id, overlapping tracks, and unregistered timelines.
init — Scaffold a Project
npx hyperframes init my-video # interactive wizard
npx hyperframes init my-video --example warm-grain # pick an example template
npx hyperframes init my-video --video clip.mp4 # seed with a video file
npx hyperframes init my-video --audio track.mp3 # seed with an audio file
npx hyperframes init my-video --non-interactive # skip prompts (CI / agent use)Templates: blank, warm-grain, play-mode, swiss-grid, vignelli, decision-tree, kinetic-type, product-promo, nyt-graph.
init creates the correct file structure, copies media, transcribes audio with Whisper, and installs authoring skills. Use it instead of creating files by hand.
capture — Website → Editable Components
npx hyperframes capture https://example.com # → captures/example.com/
npx hyperframes capture https://stripe.com -o stripe-video # custom output dir
npx hyperframes capture https://example.com --json # machine-readable output
npx hyperframes capture https://example.com --skip-assets # skip images/SVGsCaptures the site into captures/<hostname>/capture/ by default, producing capture/screenshots/, capture/assets/, capture/extracted/ (tokens.json, visible-text.txt, fonts.json), and a self-contained snapshot.
All downstream steps (DESIGN.md, SCRIPT.md, STORYBOARD, composition) read from the capture/ subfolder — see website-to-video.md.
lint
npx hyperframes lint # current directory
npx hyperframes lint ./my-project # specific project
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable outputLints index.html and all files in compositions/. Reports errors (must fix), warnings (should fix), and info (only with --verbose).
validate
npx hyperframes validate # WCAG contrast audit at 5 timestamps
npx hyperframes validate --no-contrast # skip while iteratingSeeks to 5 timestamps, screenshots the page, samples background pixels behind every text element, and warns on contrast ratios below 4.5:1 (normal text) or 3:1 (large text — 24px+, or 19px+ bold). Run before final render.
inspect
npx hyperframes inspect # visual layout audit at 5 timestamps
npx hyperframes inspect ./my-project # specific project
npx hyperframes inspect --json # agent-readable findings
npx hyperframes inspect --samples 15 # denser timeline sweep
npx hyperframes inspect --at 1.5,4,7.25 # explicit hero-frame timestampsUse this after lint and validate, especially for compositions with speech bubbles, cards, captions, or tight typography. Reports overflow, off-frame elements, occluded text, contrast warnings, and per-timestamp layout summaries — catches issues that pure timeline lint can't see (e.g., a caption that wraps past the safe area only at a specific timestamp).
npx hyperframes layout is a compatibility alias for the same visual inspection pass.
preview
npx hyperframes preview # serve current directory (port 3002)
npx hyperframes preview --port 4567 # custom portHot-reloads on file changes. Opens the Studio in your browser automatically.
render
npx hyperframes render # standard MP4
npx hyperframes render --output final.mp4 # named output
npx hyperframes render --quality draft # fast iteration
npx hyperframes render --fps 60 --quality high # final delivery
npx hyperframes render --format webm # transparent WebM
npx hyperframes render --docker # byte-identical reproducible render| Flag | Options | Default | Notes |
|---|---|---|---|
--output | path | renders/<name>_<timestamp>.mp4 | Output path |
--fps | 24, 30, 60 | 30 | 60fps doubles render time |
--quality | draft, standard, high | standard | draft for iterating |
--format | mp4, webm | mp4 | WebM supports transparency |
--workers | 1–8 or auto | auto | Each spawns Chrome |
--docker | flag | off | Reproducible output |
--gpu | flag | off | GPU-accelerated encoding |
--strict | flag | off | Fail on lint errors |
--strict-all | flag | off | Fail on errors AND warnings |
Quality guidance: draft while iterating, standard for review, high for final delivery.
transcribe
npx hyperframes transcribe audio.mp3
npx hyperframes transcribe video.mp4 --model medium.en --language en
npx hyperframes transcribe subtitles.srt # import existing
npx hyperframes transcribe subtitles.vtt
npx hyperframes transcribe openai-response.jsonProduces word-level timings suitable for caption components. First run downloads the Whisper model (cached after).
tts
npx hyperframes tts "Text here" --voice af_nova --output narration.wav
npx hyperframes tts script.txt --voice bf_emma
npx hyperframes tts "La reunión empieza a las nueve" --voice ef_dora --output es.wav
npx hyperframes tts "Hello there" --voice af_heart --lang fr-fr --output accented.wav
npx hyperframes tts --list # show all voicesUses Kokoro (local, no API key). Voice ID first letter encodes language: a American English, b British English, e Spanish, f French, h Hindi, i Italian, j Japanese, p Brazilian Portuguese, z Mandarin. The CLI auto-infers the phonemizer locale from that prefix — pass --lang only to override (e.g. stylized accents). Valid --lang codes: en-us, en-gb, es, fr-fr, hi, it, pt-br, ja, zh. Non-English phonemization requires espeak-ng installed system-wide (apt-get install espeak-ng / brew install espeak-ng).
doctor
npx hyperframes doctorVerifies environment:
- Node.js >= 22
- FFmpeg present on PATH
- Available RAM (renders are memory-hungry — 4 GB minimum)
- Chrome binary resolution (
chrome-headless-shellpreferred over system Chrome) - Current
hyperframesversion
Run this first when a render fails. See troubleshooting.md for interpreting the output.
browser
npx hyperframes browser --install # install the bundled chrome-headless-shell
npx hyperframes browser --path # print the resolved browser binary path
npx hyperframes browser --clean # clear the bundled browser cacheinfo
npx hyperframes infoPrints version, Node version, FFmpeg version, OS, and resolved browser path — useful in bug reports.
upgrade
npx hyperframes upgrade -yCheck for and install updates. Run this if you hit HeadlessExperimental.beginFrame errors — the auto-detect fix shipped in hyperframes@0.4.2 (commit 4c72ba4, March 2026).
Other
npx hyperframes compositions # list compositions in the project
npx hyperframes docs # open documentation in browser
npx hyperframes benchmark . # benchmark render performance
npx hyperframes add <block> # install a block/component from the catalog
npx hyperframes add --list # browse the catalogPopular catalog blocks: flash-through-white (shader transition), instagram-follow (social overlay), data-chart (animated chart), lower-third (talking-head overlay). See hyperframes.heygen.com/catalog.
Composition Authoring
HTML structure, data attributes, timeline contract, and non-negotiable rules.
Root Structure
Standalone index.html — the top-level composition. Does NOT use `<template>`. Put the data-composition-id div directly in <body>.
<!doctype html>
<html>
<body>
<div
id="stage"
data-composition-id="root"
data-start="0"
data-duration="10"
data-width="1920"
data-height="1080"
>
<!-- clips go here -->
<video id="clip-1" data-start="0" data-duration="5" data-track-index="0" src="intro.mp4" muted playsinline></video>
<img id="logo" data-start="2" data-duration="3" data-track-index="1" src="logo.png" />
<audio id="music" data-start="0" data-duration="10" data-track-index="2" data-volume="0.5" src="music.wav"></audio>
</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.from("#logo", { opacity: 0, y: 40, duration: 0.6 }, 2);
window.__timelines["root"] = tl;
</script>
</body>
</html>Sub-compositions loaded via data-composition-src DO use <template>:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- content + scoped <style> + <script> with window.__timelines["my-comp"] -->
</div>
</template>Load from the root: <div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="10" data-track-index="1"></div>
Data Attributes
All clips
| Attribute | Required | Values |
|---|---|---|
id | Yes | Unique identifier |
data-start | Yes | Seconds, or clip ID reference ("el-1", "intro + 2") |
data-duration | Required for img/div/compositions | Seconds. Video/audio defaults to media duration. |
data-track-index | Yes | Integer. Same-track clips cannot overlap. |
data-media-start | No | Trim offset into source (seconds) |
data-volume | No | 0–1 (default 1) |
data-track-index controls timeline layout only — not visual layering. Use CSS z-index for layering.
Composition clips
| Attribute | Required | Values |
|---|---|---|
data-composition-id | Yes | Unique composition ID |
data-start | Yes | Start time (root composition: "0") |
data-duration | Yes | Takes precedence over GSAP timeline duration |
data-width / data-height | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
data-composition-src | No | Path to external HTML file |
Timeline Contract
- Every timeline starts
{ paused: true }— the player controls playback. - Register every timeline:
window.__timelines["<composition-id>"] = tl. - Duration comes from
data-duration, not from the GSAP timeline length. - Framework auto-nests sub-timelines — do NOT manually add them.
- Never create empty tweens just to set duration.
Non-Negotiable Rules
1. Deterministic. No Math.random(), Date.now(), or time-based logic. Use a seeded PRNG (e.g. mulberry32) if you need pseudo-randomness. 2. GSAP only on visual properties. opacity, x, y, scale, rotation, color, backgroundColor, borderRadius, transforms. Never animate visibility, display, or call video.play()/audio.play(). 3. No property conflicts across timelines. Never animate the same property on the same element from multiple timelines simultaneously. 4. No `repeat: -1`. Infinite-repeat tweens break the capture engine. Compute repeat: Math.ceil(duration / cycleDuration) - 1. 5. Synchronous timeline construction. Never build timelines inside async/await, setTimeout, or Promises. The capture engine reads window.__timelines synchronously after page load. Fonts are embedded by the compiler — no need to wait for load. 6. Root composition has no `<template>` wrapper. Only sub-compositions use <template>. 7. Video is always `muted playsinline`. Audio is always a separate <audio> element — even if it's the same source file. 8. Content containers use padding, not absolute positioning. .scene-content { width: 100%; height: 100%; padding: Npx; display: flex; flex-direction: column; gap: Npx; box-sizing: border-box }. Absolute-positioned content containers overflow. Reserve position: absolute for decoratives only.
Scene Transitions
Multi-scene compositions MUST follow all of these:
1. Always use a transition between scenes. No jump cuts. 2. Always use entrance animations on every scene element. Every element animates IN via gsap.from(...). No element may appear fully-formed. 3. Never use exit animations (except on the final scene). This means NO gsap.to() that animates opacity to 0, y offscreen, etc. The transition IS the exit. Outgoing scene content must be fully visible at the moment the transition starts. 4. Final scene only: may fade elements out. This is the only scene where gsap.to(..., { opacity: 0 }) is allowed.
Typography and Assets
- Fonts: write the
font-familyyou want in CSS — the compiler embeds supported fonts automatically. Unsupported fonts produce a compiler warning. - Add
crossorigin="anonymous"to external media. - For dynamic text sizing, use
window.__hyperframes.fitTextFontSize(text, { maxWidth, fontFamily, fontWeight }). - All project files live at the project root alongside
index.html. Sub-compositions reference assets with../. - For rendered video: 60px+ headlines, 20px+ body, 16px+ data labels.
font-variant-numeric: tabular-numson number columns. Avoid full-screen linear gradients on dark backgrounds (H.264 banding — use radial or solid + localized glow).
Animation Guardrails
- Offset the first animation 0.1–0.3s (not
t=0). - Vary eases across entrance tweens — at least 3 different eases per scene.
- Don't repeat an entrance pattern within a scene.
Never Do
1. Forget window.__timelines registration. 2. Use video for audio — always muted video + separate <audio>. 3. Nest video inside a timed div — use a non-timed wrapper. 4. Use data-layer (use data-track-index) or data-end (use data-duration). 5. Animate video element dimensions — animate a wrapper div instead. 6. Call play/pause/seek on media — framework owns playback. 7. Create a top-level container without data-composition-id. 8. Use repeat: -1 on any timeline or tween. 9. Build timelines asynchronously. 10. Use gsap.set() on elements from later scenes — they don't exist in the DOM at page load. Use tl.set(selector, vars, timePosition) inside the timeline at or after the clip's data-start. 11. Use <br> in content text — causes unwanted extra breaks when the text wraps naturally. Use max-width instead. Exception: short display titles (e.g., "THE\nIMMORTAL\nGAME") where each word is deliberately on its own line.
HyperFrames Feature Reference
Load this file when a composition needs captions, TTS narration, audio-reactive visuals, marker-style text highlighting, or scene transitions. All patterns here are deterministic (no Math.random(), no Date.now(), no runtime audio analysis) and live on the same GSAP timeline as the rest of the composition.
Captions
Language Rule (Non-Negotiable)
Never use `.en` whisper models unless the audio is confirmed English. .en models TRANSLATE non-English audio into English instead of transcribing it.
- User says the language →
npx hyperframes transcribe audio.mp3 --model small --language <code>(no.en) - User confirms English →
--model small.en - Language unknown →
--model small(auto-detects)
Style Detection
If the user doesn't specify a caption style, detect it from the transcript tone:
| Tone | Font mood | Animation | Color | Size |
|---|---|---|---|---|
| Hype / launch | Heavy condensed, 800-900 | Scale-pop, back.out(1.7), 0.1-0.2s | Bright on dark | 72-96px |
| Corporate | Clean sans, 600-700 | Fade+slide, power3.out, 0.3s | White / neutral + muted accent | 56-72px |
| Tutorial | Mono / clean sans, 500-600 | Typewriter or fade, 0.4-0.5s | High contrast, minimal | 48-64px |
| Storytelling | Serif / elegant, 400-500 | Slow fade, power2.out, 0.5-0.6s | Warm muted tones | 44-56px |
| Social | Rounded sans, 700-800 | Bounce, elastic.out, word-by-word | Playful, colored pills | 56-80px |
Word Grouping
- High energy: 2-3 words, quick turnover.
- Conversational: 3-5 words, natural phrases.
- Measured / calm: 4-6 words.
Break on sentence boundaries, 150ms+ pauses, or a max word count.
Positioning
- Landscape (1920x1080): bottom 80-120px, centered.
- Portrait (1080x1920): ~600-700px from bottom, centered.
- Never cover the subject's face.
position: absolute(never relative). One caption group visible at a time.
Text Overflow Prevention
Use the runtime helper so captions never overflow:
const result = window.__hyperframes.fitTextFontSize(group.text.toUpperCase(), {
fontFamily: "Outfit",
fontWeight: 900,
maxWidth: 1600, // 1600 landscape, 900 portrait
});
el.style.fontSize = result.fontSize + "px";When per-word styling uses scale > 1.0, compute maxWidth = safeWidth / maxScale to leave headroom. Container needs overflow: visible (not hidden — hidden clips scaled emphasis words and glow).
Caption Exit Guarantee
Every group MUST have a hard kill after its exit tween — otherwise groups leak into later ones:
tl.to(groupEl, { opacity: 0, scale: 0.95, duration: 0.12, ease: "power2.in" }, group.end - 0.12);
tl.set(groupEl, { opacity: 0, visibility: "hidden" }, group.end); // deterministic killPer-Word Styling
Scan the transcript for words that deserve distinct treatment:
- Brand / product names — larger, unique color.
- ALL CAPS — scale boost, flash, accent color.
- Numbers / statistics — bold weight, accent color.
- Emotional keywords — exaggerated animation (overshoot, bounce).
- Call-to-action — highlight, underline, color pop.
TTS (Kokoro-82M)
Local, no API key. Runs on CPU. Model downloads on first use (~311 MB + ~27 MB voices, cached in ~/.cache/hyperframes/tts/).
Voice Selection
| Content type | Voice | Why |
|---|---|---|
| Product demo | af_heart / af_nova | Warm, professional |
| Tutorial | am_adam / bf_emma | Neutral, easy to follow |
| Marketing | af_sky / am_michael | Energetic or authoritative |
| Documentation | bf_emma / bm_george | Clear British English |
| Casual | af_heart / af_sky | Approachable, natural |
Run npx hyperframes tts --list for all 54 voices across 8 languages.
Multilingual Phonemization
Voice ID first letter encodes language: a=American English, b=British English, e=Spanish, f=French, h=Hindi, i=Italian, j=Japanese, p=Brazilian Portuguese, z=Mandarin. The CLI auto-infers the phonemizer locale from that prefix — you don't need --lang when voice and text match.
npx hyperframes tts "La reunión empieza a las nueve" --voice ef_dora --output es.wav
npx hyperframes tts "今日はいい天気ですね" --voice jf_alpha --output ja.wavPass --lang only to override auto-detection (e.g. stylized accents):
npx hyperframes tts "Hello there" --voice af_heart --lang fr-fr --output accented.wavValid --lang codes: en-us, en-gb, es, fr-fr, hi, it, pt-br, ja, zh. Non-English phonemization requires espeak-ng installed system-wide (apt-get install espeak-ng / brew install espeak-ng).
Speed
0.7-0.8— tutorial, complex content1.0— natural (default)1.1-1.2— intros, upbeat content1.5+— rarely appropriate
TTS + Captions Workflow
npx hyperframes tts script.txt --voice af_heart --output narration.wav
npx hyperframes transcribe narration.wav # → transcript.json (word-level)Audio-Reactive Visuals
Drive visuals from music, voice, or sound. Any GSAP-tweenable property can respond to pre-extracted audio data.
Data format
const AUDIO_DATA = {
fps: 30,
totalFrames: 900,
frames: [{ bands: [0.82, 0.45, 0.31, /* ... */] }, /* ... */],
};frames[i].bands[] are frequency band amplitudes, 0-1. Index 0 = bass, higher indices = treble. Each band is normalized independently across the full track.
Mapping audio to visuals
| Audio signal | Visual property | Effect |
|---|---|---|
Bass (bands[0]) | scale | Pulse on beat |
Treble (bands[12-14]) | textShadow, boxShadow | Glow intensity |
| Overall amplitude | opacity, y, backgroundColor | Breathe, lift, color shift |
Mid-range (bands[4-8]) | borderRadius, width | Shape morphing |
Any GSAP-tweenable property works — clipPath, filter, SVG attributes, CSS custom properties. Let content guide the visual and let audio drive its behavior. Never add equalizer bars, spectrum analyzers, waveform displays, rainbow cycling, or generic particle systems — they look cheap.
Sampling pattern (required)
Audio reactivity needs per-frame sampling via a for loop of tl.call(), NOT a single tween. A single long tween does NOT react to audio:
for (let f = 0; f < AUDIO_DATA.totalFrames; f++) {
tl.call(
((frame) => () => draw(frame))(AUDIO_DATA.frames[f]),
[],
f / AUDIO_DATA.fps,
);
}Gotchas
- textShadow on a container with semi-transparent children (e.g. inactive caption words at
rgba(255,255,255,0.3)) renders a visible glow rectangle behind every child. Apply the glow to active words individually, not to the container. - Subtlety for text — 3-6% scale variation, soft glow. Heavy pulsing makes text unreadable.
- Go bigger on non-text — backgrounds and shapes can handle 10-30% swings.
- Deterministic only — pre-extracted audio data, no Web Audio API, no runtime analysis.
Marker-Style Highlighting
Deterministic CSS + GSAP implementations of the classic "highlight / circle / burst / scribble / sketchout" drawing modes for emphasizing text. Fully seekable — no animated SVG filters, no JS timers.
Highlight (yellow marker sweep)
<span class="mh-highlight-wrap">
<span class="mh-highlight-bar" id="hl-1"></span>
<span class="mh-highlight-text">highlighted text</span>
</span>.mh-highlight-wrap { position: relative; display: inline; }
.mh-highlight-bar {
position: absolute; inset: 0 -6px;
background: #fdd835; opacity: 0.35;
transform: scaleX(0); transform-origin: left center;
border-radius: 3px; z-index: 0;
}
.mh-highlight-text { position: relative; z-index: 1; }tl.to("#hl-1", { scaleX: 1, duration: 0.5, ease: "power2.out" }, 0.6);Multi-line: apply to .mh-highlight-bar with stagger: 0.3.
Circle
Hand-drawn ellipse around a word. Use a positioned ::before with border-radius: 50%, slight rotation, and clip-path to avoid covering the letters. Animate clip-path or stroke-dashoffset on an inline SVG circle.
Burst
Short radiating lines around a word. Render 6-12 small <span> elements positioned in a radial pattern; animate scaleY from 0.
Scribble
A chaotic overlay created by animating stroke-dashoffset on an inline SVG <path> with a d attribute describing a zig-zag. Seed values, never Math.random().
Sketchout
A rough rectangle outline. Two <rect>s with slight transform offsets, animated via stroke-dashoffset.
All five modes tween CSS transforms or stroke-dashoffset only — both tween cleanly, are deterministic, and seek correctly.
Scene Transitions
Every multi-scene composition MUST use transitions. No jump cuts.
Energy → primary transition
| Energy | CSS primary | Shader primary | Accent | Duration | Easing |
|---|---|---|---|---|---|
| Calm (wellness, brand, luxury) | Blur crossfade, focus pull | Cross-warp morph, thermal distortion | Light leak, circle iris | 0.5-0.8s | sine.inOut, power1 |
| Medium (corporate, SaaS) | Push slide, staggered blocks | Whip pan, cinematic zoom | Squeeze, vertical push | 0.3-0.5s | power2, power3 |
| High (promos, sports, launch) | Zoom through, overexposure | Ridged burn, glitch, chromatic split | Staggered blocks, gravity drop | 0.15-0.3s | power4, expo |
Pick ONE primary (60-70% of scene changes) plus 1-2 accents. Never use a different transition for every scene.
Mood → transition type
| Mood | Transitions |
|---|---|
| Warm / inviting | Light leak, blur crossfade, focus pull, film burn · _Shader:_ thermal distortion, cross-warp morph |
| Cold / clinical | Squeeze, zoom out, blinds, shutter, grid dissolve · _Shader:_ gravitational lens |
| Editorial / magazine | Push slide, vertical push, diagonal split, shutter · _Shader:_ whip pan |
| Tech / futuristic | Grid dissolve, staggered blocks, blinds · _Shader:_ glitch, chromatic split |
| Tense / edgy | Glitch, VHS, chromatic aberration, ripple · _Shader:_ ridged burn, domain warp |
| Playful / fun | Elastic push, 3D flip, circle iris, morph circle · _Shader:_ swirl vortex, ripple waves |
| Dramatic / cinematic | Zoom through, gravity drop, overexposure · _Shader:_ cinematic zoom, gravitational lens |
| Premium / luxury | Focus pull, blur crossfade, color dip to black · _Shader:_ cross-warp morph |
| Retro / analog | Film burn, light leak, VHS, clock wipe · _Shader:_ light leak |
Presets
| Preset | Duration | Easing |
|---|---|---|
snappy | 0.2s | power4.inOut |
smooth | 0.4s | power2.inOut |
gentle | 0.6s | sine.inOut |
dramatic | 0.5s | power3.in → out |
instant | 0.15s | expo.inOut |
luxe | 0.7s | power1.inOut |
Install a shader transition
npx hyperframes add flash-through-white
npx hyperframes add --listCSS vs shader
- CSS transitions animate scene containers with opacity, transforms,
clip-path, and filters. Simpler to set up. - Shader transitions composite both scene textures per-pixel on a WebGL canvas — can warp, dissolve, and morph in ways CSS cannot. Import from
@hyperframes/shader-transitionsinstead of writing raw GLSL.
Don't mix CSS and shader transitions in the same composition — once a composition uses shader transitions, the WebGL canvas replaces DOM-based scene switching for every transition.
Shader-compatible CSS rules
Shader transitions capture DOM scenes to WebGL textures via html2canvas. The canvas 2D pipeline doesn't match CSS exactly:
1. No transparent keyword in gradients — use the target color at zero alpha: rgba(200,117,51,0) not transparent. (Canvas interpolates transparent as rgba(0,0,0,0) creating dark fringes.) 2. No gradient backgrounds on elements thinner than 4px. Use solid background-color on thin accent lines. 3. No CSS variables (var()) on elements visible during capture — html2canvas doesn't reliably resolve custom properties. Use literal color values. 4. Mark uncapturable decoratives with data-no-capture — they stay on the live DOM but are absent from the shader texture. 5. No gradient opacity below 0.15 — renders differently in canvas vs CSS. 6. Every .scene div must have explicit background-color, AND pass the same color as bgColor in the init() config. Without either, the texture renders as black.
These rules only apply to shader transition compositions. CSS-only compositions have no restrictions.
Don't
- Mix CSS and shader transitions in one composition.
- Use exit animations on any scene except the final scene — the transition IS the exit.
- Introduce a new transition type every scene — pick one primary + 1-2 accents.
- Use transitions that create visible geometric repetition (grids, hex cells, uniform dots) — they look artificial regardless of the math behind them. Prefer organic noise (FBM, domain warping).
GSAP for HyperFrames
GSAP is the animation engine for all HyperFrames compositions. Load from CDN inside the composition:
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>Core Tween Methods
- `gsap.to(targets, vars)` — animate from current state to
vars. Most common. - `gsap.from(targets, vars)` — animate from
varsto current state (entrances). - `gsap.fromTo(targets, fromVars, toVars)` — explicit start and end.
- `gsap.set(targets, vars)` — apply immediately (duration 0). Don't use on clip elements that enter later — use
tl.set(selector, vars, time)inside the timeline instead.
Always use camelCase property names (backgroundColor, rotationX, not background-color).
Common vars
- `duration` — seconds (default 0.5).
- `delay` — seconds before start.
- `ease` —
"power1.out"(default),"power3.inOut","back.out(1.7)","elastic.out(1, 0.3)","none","expo.out","circ.inOut". - `stagger` — number
0.1or object:{ amount: 0.3, from: "center" },{ each: 0.1, from: "random" }. - `overwrite` —
false(default),true, or"auto". - `repeat` — number (never
-1in HyperFrames). `yoyo` — alternates direction with repeat. - `onComplete`, `onStart`, `onUpdate` — callbacks.
- `immediateRender` — default
trueforfrom()/fromTo(). Setfalseon later tweens targeting the same property+element to avoid overwrite surprises.
Transforms
Prefer GSAP's transform aliases over raw CSS transform:
| GSAP property | Equivalent |
|---|---|
x, y, z | translateX/Y/Z (px) |
xPercent, yPercent | translateX/Y (%) |
scale, scaleX, scaleY | scale |
rotation | rotate (deg) |
rotationX, rotationY | 3D rotate |
skewX, skewY | skew |
transformOrigin | transform-origin |
- `autoAlpha` — prefer over
opacity. At 0, also setsvisibility: hidden. - CSS variables —
"--hue": 180. - Directional rotation —
"360_cw","-170_short","90_ccw". - `clearProps` —
"all"or comma-separated; removes inline styles on complete. - Relative values —
"+=20","-=10","*=2".
Function-based Values
gsap.to(".item", {
x: (i, target, targets) => i * 50,
stagger: 0.1,
});Easing
Built-in eases: power1 through power4, back, bounce, circ, elastic, expo, sine. Each has .in, .out, .inOut.
Rule of thumb:
- Entrances:
power3.out,expo.out,back.out(1.4) - Exits:
power2.in,expo.in - Scrubbed sections:
none(linear) - Vary eases across entrance tweens within a scene — at least 3 different eases.
Defaults
gsap.defaults({ duration: 0.6, ease: "power2.out" });Timelines (HyperFrames primary pattern)
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true, defaults: { duration: 0.6, ease: "power2.out" } });
tl.from(".title", { y: 50, opacity: 0 }, 0.3);
tl.from(".subtitle", { y: 30, opacity: 0 }, 0.5);
tl.from(".cta", { scale: 0.8, opacity: 0, ease: "back.out(1.7)" }, 0.8);
window.__timelines["root"] = tl;Position parameter
Third argument to .from() / .to() / .add():
- Absolute seconds:
0.5,2.1. - Relative to end:
">+0.2"(0.2s after previous),"<"(same time as previous),"<+0.3"(0.3s after previous's start). - Named labels:
tl.addLabel("act2", 5); tl.from(".x", { y: 30 }, "act2");
Nesting
HyperFrames auto-nests sub-composition timelines. Do not manually tl.add(subTl) — the framework wires sub-timelines into the parent at the sub-composition's data-start.
Playback
The player controls playback. Don't call tl.play(), tl.pause(), or tl.reverse() at construction time. { paused: true } is required.
Stagger
// even distribution
tl.from(".card", { opacity: 0, y: 40, stagger: 0.1 });
// control total amount
tl.from(".card", { opacity: 0, stagger: { amount: 0.6, from: "center" } });
// deterministic "random" stagger (HyperFrames compositions must be deterministic)
tl.from(".dot", { opacity: 0, stagger: { each: 0.05, from: "random" } });stagger.from: "start" | "end" | "center" | "edges" | "random" | index | [x, y] for grid.
Performance
- Animate transforms (
x,y,scale,rotation,opacity) — cheap, GPU-accelerated. - Avoid animating
width,height,top,left,margin— causes layout thrash. - Avoid box-shadow or filter animations on large elements — expensive.
will-changeis rarely needed; GSAP handles promotion.
gsap.matchMedia (rarely needed in HyperFrames)
Compositions have fixed dimensions (data-width/data-height), so responsive breakpoints don't apply. You may still use matchMedia for prefers-reduced-motion when authoring UI previews, but it's not used in rendered video output.
Don't Do
repeat: -1anywhere — breaks the capture engine.Math.random(),Date.now(), performance.now()` inside tween values — non-deterministic.async/setTimeout/Promisearound timeline construction — the capture engine readswindow.__timelinessynchronously.- Animate
visibilityordisplaydirectly — useautoAlpha. gsap.set()on clip elements that enter later in the timeline — they don't exist in the DOM at page-load. Usetl.set(sel, vars, time)inside the timeline.
Troubleshooting
HeadlessExperimental.beginFrame' wasn't found (first thing to check)
Symptom: npx hyperframes render fails with:
✗ Render failed
Protocol error (HeadlessExperimental.beginFrame):
'HeadlessExperimental.beginFrame' wasn't foundCause: Chromium 147+ removed the HeadlessExperimental.beginFrame CDP command. This affected sandbox environments (e.g., OpenClaw, some containerized agent hosts) that ship modern Chromium as the system browser. See hyperframes#294.
Fix (permanent — preferred): upgrade.
npx hyperframes upgrade -y
# or
npm install -g hyperframes@latesthyperframes >= 0.4.2 auto-detects whether the resolved browser supports beginFrame (checks for chrome-headless-shell in the binary path) and falls back to screenshot capture mode when it doesn't. Commit `4c72ba4` (March 2026) shipped this auto-detect.
Fix (escape hatch — if you can't upgrade):
export PRODUCER_FORCE_SCREENSHOT=true
npx hyperframes renderThis forces screenshot mode regardless of the binary. Screenshot mode is slightly slower but visually identical.
Fix (prevent — recommended): install chrome-headless-shell so the engine can use the fast BeginFrame path:
npx puppeteer browsers install chrome-headless-shell
# or let the CLI do it
npx hyperframes browser --installscripts/setup.sh runs this automatically.
npx hyperframes render hangs for 120s then times out
Cause: the resolved browser is system Chrome (e.g., /usr/bin/google-chrome) and doesn't support the BeginFrame path, but auto-detect also missed it (older hyperframes version).
Fix: 1. Check which binary is being used: npx hyperframes browser --path 2. If it's system Chrome, either:
- Install
chrome-headless-shell:npx hyperframes browser --install, OR - Set the escape hatch:
export PRODUCER_FORCE_SCREENSHOT=true, OR - Upgrade:
npx hyperframes upgrade -y
ffmpeg: command not found
Install FFmpeg via your system package manager:
| OS / distro | Command |
|---|---|
| Ubuntu / Debian | sudo apt-get install -y ffmpeg |
| Fedora / RHEL | sudo dnf install -y ffmpeg |
| Arch | sudo pacman -S ffmpeg |
| macOS | brew install ffmpeg |
| Windows | winget install Gyan.FFmpeg |
Verify: ffmpeg -version.
Node version X is not supported
HyperFrames requires Node.js >= 22. Check with node --version.
- nvm:
nvm install 22 && nvm use 22 - Homebrew (macOS):
brew install node@22 && brew link --overwrite node@22 - apt: follow nodesource for Node 22 LTS.
ENOSPC: no space left on device or OOM kills during render
Renders are memory- and disk-hungry. Minimums:
- RAM: 4 GB free (8 GB recommended for 60fps /
--quality high) - Disk: 2 GB free scratch space — frames are written to
/tmpduring capture
Mitigations:
- Lower quality:
--quality draft. - Lower fps:
--fps 24. - Lower worker count:
--workers 1. - Set
TMPDIRto a volume with more space:export TMPDIR=/mnt/scratch.
Lint passes but the render is blank / black frames
Check the browser console in preview — usually:
- A timeline was registered with the wrong key (
__timelines["typo"]instead of__timelines["root"]). - The root composition was wrapped in
<template>(only sub-compositions use<template>). - A script tag failed to load — check Network tab in preview.
Run npx hyperframes lint --verbose to see info-level findings.
Contrast warnings from hyperframes validate
⚠ WCAG AA contrast warnings (3):
· .subtitle "secondary text" — 2.67:1 (need 4.5:1, t=5.3s)- Dark backgrounds: brighten the failing color until it clears 4.5:1 (normal text) or 3:1 (large text — 24px+ or 19px+ bold).
- Light backgrounds: darken it.
- Stay within the palette family — don't invent a new color, adjust the existing one.
- Skip the check temporarily with
--no-contrastif iterating rapidly, but clear it before delivery.
Font family 'X' not supported by compiler
The compiler embeds a curated set of web-safe + open-source fonts. If a font isn't supported, either:
- Swap to a supported alternative from the warning.
- Register a custom font via
@font-facepointing to a.woff2in the project directory (the compiler embeds referenced@font-facefiles).
Video plays back muted or with no audio
Check:
- The
<video>element hasmuted playsinline(required — browser autoplay policy). - Audio is a separate
<audio>element, not the video element. - Audio
data-volumeis set (defaults to 1). - The audio file is at the expected path — compositions load relative to their own directory.
Docker render fails on Linux with rootless Docker
Add --privileged or pass --cap-add=SYS_ADMIN:
npx hyperframes render --docker --docker-args "--cap-add=SYS_ADMIN"The headless browser needs namespace permissions for sandboxing.
Bug reports
Include npx hyperframes info output + the full error log. File at github.com/heygen-com/hyperframes.
Website to Video
Capture a website, produce a professional video from it. Use when the user provides a URL and wants a video — social ad, product tour, 30-second promo, etc.
The workflow has 7 steps. Each produces an artifact that gates the next. Do not skip steps — each artifact prevents a downstream failure mode.
Step 1: Capture & Understand
npx hyperframes capture https://example.com -o example-videoProduces example-video/capture/ with:
capture/screenshots/— above-the-fold + section screenshots (up to--max-screenshots)capture/assets/— logos, hero images, background video (if any)capture/extracted/tokens.json— colors, fonts, and spacing tokenscapture/extracted/visible-text.txt— extracted headings, paragraphs, CTAscapture/extracted/fonts.json— font families and stacks detected in computed stylescapture/asset-descriptions.md— auto-generated asset catalog
All subsequent steps read from the capture/ subfolder — capture/extracted/tokens.json, capture/assets/hero.png, etc. Never strip the capture/ prefix when referencing these files.
Gate: Print a site summary — name, top 3 colors, primary + display fonts, hero asset path, one-sentence vibe. Keep it in your context — don't re-capture.
Step 2: Write DESIGN.md
Small brand reference at the project root. 6 sections, ~90 lines. This is the cheat sheet — not the creative plan.
# DESIGN
## Brand
- Name: Example Co.
- One-line mission: "…"
## Colors
- Background: #0B0F14
- Primary: #00E0A4 (accent, CTA)
- Secondary: #7A8B9B (body text)
- Text: #FFFFFF
## Typography
- Display: "Inter Tight", 700, tight letter-spacing
- Body: "Inter", 400
## Motion
- Mood: precise, technical, confident
- Eases: `power3.out` for entrances, `expo.in` for exits
## Assets
- Logo: `capture/assets/logo.svg`
- Hero image: `capture/assets/hero.png`
## What NOT to Do
- No purple, no pastels, no serif body
- No playful/bubbly eases (`elastic`, `bounce`)
- No drop shadows on textGate: DESIGN.md exists in the project directory.
Step 3: Write SCRIPT.md
Narration script. Story backbone. Scene durations come from the narration, not from guessing.
# SCRIPT
## Scene 1 — Hook (0:00–0:04)
"What if your dashboards wrote themselves?"
## Scene 2 — Problem (0:04–0:11)
"Teams spend hours stitching together queries, charts, and callouts — every Monday."
## Scene 3 — Solution (0:11–0:22)
"Example Co. watches your data streams and proposes the dashboard you'd have built — in seconds."
## Scene 4 — CTA (0:22–0:28)
"Try it free at example.com."Run npx hyperframes tts SCRIPT.md --voice af_nova --output narration.wav to generate TTS audio. Note the exact duration — that's the video's duration.
Gate: SCRIPT.md + narration.wav exist and durations match the plan (±0.3s).
Step 4: Storyboard
Text-only scene plan: for each scene, describe the hero frame — what's on screen at the scene's most-visible moment.
# STORYBOARD
## Scene 1 (0:00–0:04) — Hook
Hero frame: giant "WHAT IF YOUR DASHBOARDS WROTE THEMSELVES?" in display font, centered, on near-black. Logo top-left at 40% opacity.
Entrance: each word staggers in, 0.08s apart.
Transition out: flash-through-white into Scene 2.One paragraph per scene. Do NOT skip this step — it's where you catch narrative gaps before writing HTML.
Gate: STORYBOARD.md exists. Each scene has: hero frame, entrance, transition.
Step 5: Composition
Write index.html scene-by-scene:
- Each scene is a
<div class="scene scene-N">positioned absolutely, full-bleed. - Static HTML+CSS for the hero frame first (no GSAP).
- Layer the narration
<audio>atdata-start="0"on a high track index. - Add a transitions component (
flash-through-white,liquid-wipe, etc.) between each scene. - THEN add GSAP entrances (
gsap.from()), no exits — transitions own the exit. - Register
window.__timelines["root"] = tl.
Install transitions as needed:
npx hyperframes add flash-through-whiteStep 6: Render
npx hyperframes lint --strict # must pass
npx hyperframes validate # WCAG contrast audit
npx hyperframes render --quality draft --output draft.mp4Watch the draft. Note issues in a REVIEW.md bullet list (scene, timestamp, issue). Fix, re-render.
When happy:
npx hyperframes render --quality high --output final.mp4Step 7: Deliver
- Report file path + duration + file size to the user.
- If the user wants a vertical cut, re-render with a 9:16 composition (
data-width="1080" data-height="1920") — typically requires a separateindex-vertical.htmlwith tighter typography and re-stacked scene layout.
Common Failure Modes
- Skipped DESIGN.md → colors drift scene-to-scene; output feels like "AI slides."
- Skipped STORYBOARD.md → scenes overlap or hero frames collide with transitions.
- Exit animations before transitions → empty frames when the transition fires.
- Narration longer than `data-duration` → audio clips mid-sentence. Update the composition's
data-durationto match the TTS output length + 0.5s buffer.
#!/usr/bin/env bash
# HyperFrames setup for Hermes.
#
# Verifies Node >= 22 and FFmpeg, installs the `hyperframes` CLI globally,
# pre-caches `chrome-headless-shell`, and runs `hyperframes doctor`.
#
# Pins `hyperframes@>=0.4.2` so the OpenClaw/Chromium-147 fix from
# https://github.com/heygen-com/hyperframes/issues/294 (commit 4c72ba4)
# is always present — the engine auto-detects `HeadlessExperimental.beginFrame`
# support and falls back to screenshot capture otherwise.
#
# Idempotent: safe to re-run.
set -euo pipefail
MIN_NODE_MAJOR=22
MIN_HYPERFRAMES_VERSION="0.4.2"
red() { printf '\033[31m%s\033[0m\n' "$*"; }
green() { printf '\033[32m%s\033[0m\n' "$*"; }
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
bold "==> HyperFrames setup"
# --- 1. Node.js --------------------------------------------------------------
if ! command -v node >/dev/null 2>&1; then
red "✗ Node.js is not installed."
echo " Install Node.js >= ${MIN_NODE_MAJOR} (nvm, Homebrew, or your package manager) and re-run."
exit 1
fi
node_version="$(node --version | sed 's/^v//')"
node_major="$(echo "$node_version" | cut -d. -f1)"
if [ "$node_major" -lt "$MIN_NODE_MAJOR" ]; then
red "✗ Node.js ${node_version} is too old. HyperFrames requires Node.js >= ${MIN_NODE_MAJOR}."
echo " Upgrade with 'nvm install ${MIN_NODE_MAJOR} && nvm use ${MIN_NODE_MAJOR}' or your package manager."
exit 1
fi
green "✓ Node.js ${node_version}"
# --- 2. FFmpeg ---------------------------------------------------------------
if ! command -v ffmpeg >/dev/null 2>&1; then
red "✗ FFmpeg is not installed."
case "$(uname -s)" in
Linux*) echo " sudo apt-get install -y ffmpeg # Debian/Ubuntu"
echo " sudo dnf install -y ffmpeg # Fedora/RHEL";;
Darwin*) echo " brew install ffmpeg";;
MINGW*|MSYS*|CYGWIN*) echo " winget install Gyan.FFmpeg";;
*) echo " See https://ffmpeg.org/download.html";;
esac
exit 1
fi
green "✓ FFmpeg $(ffmpeg -version 2>&1 | head -1 | awk '{print $3}')"
# --- 3. npm ------------------------------------------------------------------
if ! command -v npm >/dev/null 2>&1; then
red "✗ npm is not installed (should ship with Node.js)."
exit 1
fi
# --- 4. Install / upgrade hyperframes CLI -----------------------------------
bold "==> Installing hyperframes CLI (>= ${MIN_HYPERFRAMES_VERSION})"
current_hyperframes=""
if command -v hyperframes >/dev/null 2>&1; then
current_hyperframes="$(hyperframes --version 2>/dev/null | tail -1 | sed 's/^v//')"
fi
if [ -n "$current_hyperframes" ]; then
yellow " Found hyperframes ${current_hyperframes}"
fi
# Always install/upgrade to >= MIN version.
# Using 'latest' so we pick up any newer auto-detect/capture fixes.
if ! npm install -g "hyperframes@latest" >/dev/null 2>&1; then
red "✗ npm install -g hyperframes@latest failed."
echo " Try: sudo npm install -g hyperframes@latest"
echo " Or use a user-scoped npm prefix: npm config set prefix ~/.npm-global && export PATH=\"\$HOME/.npm-global/bin:\$PATH\""
exit 1
fi
installed_version="$(hyperframes --version 2>/dev/null | tail -1 | sed 's/^v//')"
green "✓ hyperframes ${installed_version} installed globally"
# Sanity-check minimum version.
version_ge() {
# version_ge A B → true if A >= B
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -1)" = "$2" ]
}
if ! version_ge "$installed_version" "$MIN_HYPERFRAMES_VERSION"; then
red "✗ hyperframes ${installed_version} is below required minimum ${MIN_HYPERFRAMES_VERSION}."
echo " Try 'npm install -g hyperframes@latest' or 'sudo npm install -g hyperframes@latest'."
exit 1
fi
# --- 5. Pre-cache chrome-headless-shell --------------------------------------
#
# Chromium 147+ removed HeadlessExperimental.beginFrame. System Chrome (e.g.
# /usr/bin/google-chrome) can't render with the fast path, so the engine
# auto-detects and falls back to screenshot mode — but BeginFrame mode is
# faster and produces higher-quality output. Install chrome-headless-shell
# up front so the engine picks it over system Chrome.
bold "==> Pre-caching chrome-headless-shell (for best render quality)"
if ! npx --yes puppeteer browsers install chrome-headless-shell >/dev/null 2>&1; then
yellow "⚠ Could not pre-install chrome-headless-shell."
yellow " Rendering will still work via screenshot-mode fallback (slower)."
yellow " If you hit HeadlessExperimental.beginFrame errors:"
yellow " export PRODUCER_FORCE_SCREENSHOT=true"
yellow " See references/troubleshooting.md."
else
green "✓ chrome-headless-shell installed"
fi
# --- 6. Doctor ---------------------------------------------------------------
bold "==> Running hyperframes doctor"
if hyperframes doctor; then
green "✓ HyperFrames is ready"
echo
echo " Scaffold a project: npx hyperframes init my-video"
echo " Preview: npx hyperframes preview"
echo " Render: npx hyperframes render"
else
yellow "⚠ hyperframes doctor reported issues."
yellow " See references/troubleshooting.md or re-run 'hyperframes doctor'."
exit 1
fi