Skip to content
All skills
OPERATOR content v1.0.0 · Apache-2.0

Hyperframes Video

Production pipeline for HTML-based video rendering using HeyGen's HyperFrames framework. HTML plus CSS plus GSAP rendered to deterministic MP4 via headless Chrome and FFmpeg. Agent-native with no React and no bundler. Use when generating marketing reels, educational carousels-as-video, animated explainers, or any composition where you would reach for Remotion. HyperFrames is Apache-2.0 licensed and frame-accurate for GSAP timelines.

Audited
Source
SHA-256
Last reviewed
How we audit →

Install in your agent

Tell your agent: "install the recipes skill, then add hyperframes-video"
Or via curl: curl -sL https://recipes.wisechef.ai/skill -o ~/.claude/skills/recipes/SKILL.md

Full skill source · SKILL.md

HyperFrames Video Pipeline

Official framework: github.com/heygen-com/hyperframes (Apache-2.0) Docs: hyperframes.heygen.com

Read The Installed Skills First (NON-NEGOTIABLE)

Before writing any composition HTML, load the hyperframes agent skill and its references — do NOT write from memory of what HyperFrames "probably" looks like. The framework has specific contracts (clip class, timeline registration, per-track overlap rules, transition discipline) that are not guessable. Every time an agent skips this step, the output is a render that is technically "successful" but visually broken.

Minimum pre-authoring read list:

  • hyperframes/SKILL.md — Data Attributes, Composition Structure, Timeline Contract, Rules (Non-Negotiable), Scene Transitions
  • hyperframes/references/transitions.md — mood-to-transition mapping and animation rules for multi-scene comps
  • hyperframes/patterns.md — canonical patterns (slideshow, PiP, title card)
  • hyperframes/references/typography.md — font size minimums for rendered video (60px+ headlines, 20px+ body)

If those files are not already on disk, install the bundle (see "Install HyperFrames Agent Skills" below) before writing HTML. Writing HTML without having read these is the single most common cause of broken renders in practice.

When to Use vs Alternatives

Task Use
HTML plus GSAP to MP4, agent-authored HyperFrames (this skill)
Existing React/JSX compositions, Lambda rendering Remotion (source-available)
Math/equation animations (3Blue1Brown style) manim-video skill
Video-to-ASCII, retro terminal look ascii-video skill
Simple image plus voiceover carousel (no motion) wisechef-content-engine

Prerequisites

Run hyperframes doctor to validate everything at once:

Need Verify command
Node.js 22+ node --version
FFmpeg plus ffprobe ffmpeg -version
Chromium headless-shell npx hyperframes browser ensure
Disk 2GB+ free for frame buffers
/dev/shm 1GB+ Chrome workers rely on shared memory

Pitfall: hyperframes doctor can pass the FFmpeg check while Chrome is still missing. Running render without Chrome produces a silent failure during frame capture. Always run npx hyperframes browser ensure after install.

Install

Install the CLI via npm (global or per-project, your preference):

npm i hyperframes              # local to a project
npm i -g hyperframes           # global (needs the privilege your env requires)
hyperframes --version          # expect 0.4.11+
npx hyperframes browser ensure # fetches Chromium headless-shell

Per-project scaffold:

hyperframes init my-video --no-install
cd my-video
npx hyperframes browser ensure

Install HyperFrames Agent Skills

HyperFrames ships a skill bundle that teaches agents the correct composition format. Clone the repo, copy the skills/ directory into your agent skill tree, restart the agent:

git clone --depth 1 https://github.com/heygen-com/hyperframes.git
cp -r hyperframes/skills/* <your-agent-skills-dir>/

Expected new skills: hyperframes, hyperframes-cli, gsap, hyperframes-registry, website-to-hyperframes. In Claude Code they register as /hyperframes, /hyperframes-cli, /gsap.

Project Layout (mandatory)

The CLI rejects bare HTML files. Every render happens inside a project scaffold:

my-video/
  hyperframes.json      schema + registry + paths config
  meta.json             project metadata
  index.html            the composition
  renders/              output MP4s (auto-created)
    <name>_<timestamp>.mp4

Running hyperframes render /path/to/bare.html returns "Not a directory". Always run hyperframes init first, then place index.html inside the project dir.

Composition Contract

Every composition HTML must satisfy three rules or the render silently produces wrong output.

Rule 1: Stage element with composition data attributes

<div id="stage"
     data-composition-id="my-video"
     data-start="0"
     data-duration="8"
     data-width="1920"
     data-height="1080"
     data-fps="30"
     style="width: 1920px; height: 1080px;">
  ...
</div>

Rule 2: Every timed child needs class="clip"

Without class="clip", timed elements stay visible for the entire composition duration regardless of data-start and data-duration. The linter warns timed_element_missing_clip_class — the render continues and output is broken.

The failure mode is subtle: a later slide with a solid background covers earlier slides for the full duration, so the video looks like "slide 2 is playing the whole time" rather than an obvious crash. If you see a composition where one scene appears to dominate the whole video, check for missing clip classes first.

<div id="slide-1" class="clip" data-start="0" data-duration="4" data-track-index="0">...</div>
<div id="slide-2" class="clip" data-start="4" data-duration="4" data-track-index="1">...</div>

Rule 2b: Same-track clips cannot overlap (hard error)

This is a hard lint error (overlapping_clips_same_track), not a warning. The engine will still render but the output is undefined. If you want two clips visible simultaneously during a transition (e.g. a 0.2s crossfade), put them on different data-track-index values and use CSS z-index to control layering.

# WRONG — both on track 0 with overlap
<div id="s1" class="clip" data-start="0"   data-duration="4.2" data-track-index="0">...</div>
<div id="s2" class="clip" data-start="4"   data-duration="4"   data-track-index="0">...</div>
# Lint: Track 0: clip ending at 4.2s overlaps with clip starting at 4s.

# RIGHT — separate tracks, z-index stacks
<div id="s1" class="clip" data-start="0" data-duration="4.2" data-track-index="0">...</div>
<div id="s2" class="clip" data-start="4" data-duration="4"   data-track-index="1" style="z-index:2;">...</div>

data-track-index is used by the scheduler for clip lifecycle; z-index is for visual layering. These are independent.

Rule 3: Register GSAP timeline on window.__timelines[id]

Without this registration, the engine falls back to wall-clock (not seekable, drifts between workers). Linter warns missing_timeline_registry.

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
window.addEventListener('load', () => {
  const tl = gsap.timeline({ paused: true });
  tl.from('#slide-1 h1', { y: 60, opacity: 0, duration: 0.6, ease: 'power2.out' }, 0.1);
  tl.from('#slide-2 h1', { scale: 0.9, opacity: 0, duration: 0.6, ease: 'back.out(1.5)' }, 4.1);
  window.__timelines['my-video'] = tl;   // id MUST match data-composition-id
});
</script>

Audio / Video / Image Tracks

<video id="bg"   class="clip" data-start="0" data-duration="5" data-track-index="0" src="intro.mp4" muted playsinline></video>
<img   id="logo" class="clip" data-start="2" data-duration="3" data-track-index="1" src="logo.png" />
<audio id="vo"                 data-start="0" data-duration="8" data-track-index="2" data-volume="1.0" src="voice.mp3"></audio>
<audio id="music"              data-start="0" data-duration="8" data-track-index="3" data-volume="0.3" src="music.mp3"></audio>

Audio elements do not need class="clip" — they follow data-start natively.

Scene Transition Discipline (Multi-Scene Compositions)

The upstream hyperframes skill flags this as non-negotiable. Any multi-scene composition that violates these rules is broken by definition — even if lint returns 0 errors.

  1. Always use a transition between scenes. No jump cuts.
  2. Every scene uses entrance animations. Every element animates in via gsap.from(...). No element may appear fully-formed. If a scene has 5 elements, it needs 5 entrance tweens.
  3. Exit animations are BANNED except on the final scene. Do NOT write gsap.to(el, { opacity: 0 }) or y: -100 to "fade out" a scene before its transition fires. The transition IS the exit. The outgoing scene's content must be fully visible at the moment the transition starts.
  4. Only the final scene may fade elements out.

Canonical pattern for a 2-scene composition with a 0.4s blur crossfade at t=4.0s:

# Slide 1: entrances at t=0.1 onward, NO exit tweens
tl.from('#s1 .chip',  { y: -30, opacity: 0, duration: 0.5, ease: 'back.out(1.6)' }, 0.15);
tl.from('#s1-title',  { y: 60,  opacity: 0, duration: 0.7, ease: 'power3.out' },    0.35);
tl.from('#s1-body',   { y: 40,  opacity: 0, duration: 0.6, ease: 'power2.out' },    0.75);

# Transition: slide 1 blur+fades out at 4.0; slide 2 (different track + z-index) fades in at 4.1
tl.to  ('#s1',        { opacity: 0, filter: 'blur(10px)', duration: 0.4, ease: 'sine.inOut' }, 4.0);
tl.from('#s2',        { opacity: 0, filter: 'blur(10px)', duration: 0.4, ease: 'sine.inOut' }, 4.1);

# Slide 2: entrances only
tl.from('#s2 .chip',  { scale: 0.6, opacity: 0, duration: 0.5, ease: 'back.out(1.8)' }, 4.45);
tl.from('#s2-title',  { y: 50, opacity: 0, duration: 0.7, ease: 'power3.out' },         4.65);

Animation guardrails (from hyperframes/SKILL.md § Animation Guardrails):

  • Offset the first tween in a scene by 0.1–0.3s, not t=0
  • Use at least 3 different eases per scene — repeating the same ease across all entrances looks mechanical
  • Do not animate the same property on the same element from two timelines simultaneously
  • Never repeat: -1 — the capture engine will hang. Use repeat: Math.ceil(duration / cycleDuration) - 1
  • Build timelines synchronously — no async, setTimeout, or Promise.then. The engine reads window.__timelines synchronously at load.
cd my-project
hyperframes lint              # catches missing clip class, missing timeline registry
hyperframes preview           # browser live-reload, use during authoring
hyperframes render            # writes renders/<name>_<timestamp>.mp4
hyperframes render --strict   # refuse to render if any lint warning exists

Measured timings (QEMU 4-core, 7.8GB RAM, 1024x1024 @ 30fps, 2026-04-21):

  • 8-second composition (240 frames): about 26s wall time
  • 3 workers auto-allocated (cores minus 1)
  • Output: H.264 MP4, approximately 10KB per second of content

Catalog Blocks

hyperframes add flash-through-white    # shader transitions
hyperframes add instagram-follow       # social overlays
hyperframes add data-chart             # animated charts
# Browse full catalog at hyperframes.heygen.com/catalog

Render Modes & Performance Flags

hyperframes render --mode fast         # lower quality, ~2x speed, for preview builds
hyperframes render --mode standard     # default
hyperframes render --mode high         # max quality, slower
hyperframes render --workers 6         # override auto-detect (default is cores - 1)
hyperframes render --output custom.mp4
hyperframes render --strict            # fail on any lint warning

Docker Deployment

Pass --shm-size=2g to docker run or Chrome will crash during frame capture on compositions longer than about 5 seconds. See upstream Docker docs for a working Dockerfile — the key points are: node:22 base, ffmpeg from apt, npx hyperframes browser ensure during image build, and --shm-size=2g at runtime.

Pitfalls (Ordered by Frequency Seen in Practice)

  1. Writing HTML without reading the installed hyperframes skill first. The single biggest failure mode. Always load hyperframes/SKILL.md + references/transitions.md + patterns.md before authoring. Hallucinating the composition format produces output that renders "successfully" but is visually wrong — the user will push back, not the CLI.
  2. Not a directory: index.html — CLI demands a project folder from hyperframes init. Bare HTML files fail.
  3. One scene appears to cover the whole video — missing class="clip" on timed elements. Lint warns but does not block. Verify visually, not just via exit code.
  4. overlapping_clips_same_track hard lint error — two clips on the same data-track-index with overlapping data-start/data-duration ranges. Move one to a different track and use CSS z-index for layering.
  5. Wall-clock drift in animations — missing window.__timelines[id] = timeline registration. Re-renders produce non-identical output. The id MUST match the data-composition-id.
  6. Scene change feels like a jump cut — missing transition tween between scenes. The hyperframes skill banning exit animations is often misread as "no motion between scenes at all"; the fix is a transition tween on the outgoing scene (blur/opacity/scale) paired with the incoming scene's entrance.
  7. "Flicker" right before a transition — an exit animation was written that empties the scene before the transition fires. Remove the exit; let the transition handle the handoff.
  8. Chrome missing after install — npm i hyperframes does not fetch Chrome. Always follow with npx hyperframes browser ensure.
  9. /dev/shm too small in Docker — pass --shm-size=2g or renders silently corrupt for compositions over 5s.
  10. Font loading flakes in headless Chrome — HyperFrames injects deterministic @font-face for requested families; stick to system-ui, -apple-system, 'Segoe UI' or declare an explicit @font-face with an accessible font file URL.
  11. Mixing hyperframes render <file> with in-dir render — only cd project-dir && hyperframes render is supported.
  12. Render succeeds but content wrong — lint warnings were ignored. Always run hyperframes lint pre-render, or wire render --strict for production pipelines.
  13. Installing the skill bundle but forgetting to restart the agent — new slash commands in the agent skills directory load at agent startup. Reload or restart to see /hyperframes.

Verification After Render (MANDATORY before declaring done)

Lint passing + exit 0 + MP4 exists ≠ correct output. The hyperframes-video pipeline has shipped renders where every slide rendered but a later-scene background covered earlier scenes for the full duration. Do at least these two checks before declaring a render "done":

  1. Sample frames from each scene's midpoint. ffmpeg -i render.mp4 -vf "select='eq(n,15)+eq(n,75)+eq(n,125)+eq(n,180)+eq(n,230)'" -vsync vfr frames/f_%02d.png
  2. Vision-check at least one frame per scene. Confirm the expected content (text, background color, brand mark, CTA) is actually visible in each scene's frame. If your agent has vision_analyze or equivalent, use it; otherwise open the frames yourself.

If a scene's sampled frame shows the wrong content, the clip class / track / z-index is wrong — not the GSAP timeline. Fix the structural rules first, then re-render.

Combining With Image Generators

For agent-generated slide backgrounds, generate a PNG via your preferred image-gen API (OpenAI Images gpt-image-1.5 at quality=high is what the wisechef-content-engine skill uses — see that skill for the cost/quality notes and the model-pinning rule). Reference the PNG inside the composition:

<img class="bg clip"
     src="slide_bg.png"
     data-start="0" data-duration="4" data-track-index="0"
     style="position:absolute; inset:0; object-fit:cover; opacity:0.35;" />

Benchmark for gpt-image-1.5 @ high @ 1024² (2026-04-21): about 41s and 0.19 USD per image. Full 5-slide carousel: about 0.95 USD and 3.5 minutes of image-gen wall time. Confirmed working end-to-end through the HyperFrames render path with a 76KB 8-second H.264 MP4 output.

Verified Install Log (2026-04-21, Ubuntu 22 VM, 4-core QEMU)

Check Result
Node 22.22.0 PASS
FFmpeg 6.1.1 PASS
Chrome headless-shell 131 PASS via browser ensure
HyperFrames 0.4.11 global PASS
5 skills copied to agent skills dir PASS
HTML+GSAP 2-slide composition to MP4 PASS, 26s render, 240 frames, 76KB H.264
2-slide with blur-crossfade + tracks 0+1 PASS after fixing overlap + clip class + timeline registration

Field Lessons (2026-04-21 InterPlus / MARCO smoke test)

Round 1 failed silently: composition rendered 8 seconds at the correct resolution, but slide 2's brown background covered slide 1 for the entire video. Root cause was a compound failure of three rules, all documented in the upstream skill:

  1. No class="clip" on timed slide divs → framework could not hide slide 2 during slide 1's time range
  2. No window.__timelines["marco-smoke-v2"] = tl registration → GSAP animations never ran
  3. Both slides on data-track-index="0" with overlapping time ranges → hard lint error

Round 2 fixed all three by reading the installed skill in full before rewriting the HTML. Lint went from 1 hard error + 2 warnings to 0 errors, 0 warnings. Both slides verified visible and readable via frame sampling + vision pass.

The takeaway this skill encodes: for HyperFrames, "read the installed skill before authoring" is not a style preference — it is a production-critical step. The framework's contracts are not guessable, the lint warnings are easy to ignore, and the user-visible failure mode (one scene covering another) does not look like a framework bug at first glance.

Remote Install on a Client VM

For multi-hop SSH installs on client VMs (e.g. through a jump host), this skill covers only the HyperFrames-specific mechanics above. For the SSH transport itself, secrets handling, and credential propagation, see:

  • hermes-remote-deploy — deploying agents on remote VPS via SSH
  • bitwarden-key-extraction — credential extraction patterns
  • wisechef-client-stack-deploy — the reference WiseChef multi-client deployment

Key lesson that crosses skill boundaries: do not use heredocs to ship Python or JS scripts through two nested SSH hops — escape rules compound and scripts die at parse time. Write the script locally, scp to each hop separately.

Related Skills

  • wisechef-content-engine — pain-first 6-slide carousel doctrine (parent marketing pattern)
  • manim-video — for math/technical animations instead of marketing motion
  • local-tts-kokoro — voiceover generation to feed into HyperFrames audio tracks
  • hermes-remote-deploy — the SSH/remote install context for client VMs

Known Limits

  • No Lambda or distributed rendering yet; single-machine only (unlike Remotion Lambda).
  • The timed_element_missing_clip_class lint is a warning, not an error. Agents will regularly ignore it unless --strict is wired in.
  • No official block-composition-from-prompt skill yet; may warrant a subagent skill later.