* feat(studio): add pasteboard background to preview viewport Adds bg-neutral-800 to the preview viewport so the area outside the canvas is visually distinct from the composition content — consistent with professional video editors (Premiere, DaVinci, Figma). * feat(studio): pasteboard background and canvas outline around preview - NLEPreview: viewport gets bg-neutral-700 (#404040) as the pasteboard color surrounding the canvas — distinct from the app chrome (#0a0a0a) - Player wrapper: drop bg-black so the pasteboard shows around the canvas (loading overlays still cover the area with bg-black during load) - Player: set host background to transparent via inline style (overrides :host { background: #000 } in shadow DOM), and inject a style rule into the open shadow root so .hfp-container has overflow:visible and the canvas iframe gets a thin white ring + soft drop-shadow — making the canvas boundary legible against the pasteboard * feat(studio): disable manual positioning JSON by default, add toggle Manual edits were always stored in `.hyperframes/studio-manual-edits.json`, making it hard to share source without the sidecar file and easy to accidentally reposition elements via drag. Changes: - `enabled` field added to `StudioManualEditManifest` (defaults to `false` when absent — existing projects are unaffected until they opt in) - Drag handles, resize, and rotation handles are hidden when disabled - Layout X/Y/W/H/R fields in the Design panel are read-only when disabled - "Manual positioning" toggle added at the bottom of the Design panel, visible whether or not an element is selected - Toggle state is persisted to `.hyperframes/studio-manual-edits.json` so each project can opt in independently - `STUDIO_PREVIEW_MANUAL_EDITING_ENABLED` env flag still acts as a hard cap (env off → feature off regardless of project setting) * feat(studio): enable manual positioning by default (opt-out) * feat(studio): allow absolute elements to drag without toggle; gate JSON-backed drag behind toggle * feat(studio): persist positions directly to HTML; remove JSON sidecar and manual positioning toggle Replace the `.hyperframes/studio-manual-edits.json` sidecar with inline-style persistence baked directly into the HTML source. Drag/resize/rotation values are written as CSS custom properties (`--hf-studio-offset-x/y`, `--hf-studio-width/height`, `--hf-studio-rotation`) plus `translate`/`width`/`height`/`rotate` inline styles via `persistDomEditOperations` — no re-apply step needed on load. Key changes: - `sourcePatcher`: add `value: string | null` to `PatchOperation` — null removes the property/attribute from the HTML tag instead of setting it - `manualEditsDom`: add `build*Patches` / `buildClear*Patches` helpers that capture live element state into `PatchOperation[]` for HTML source writes; add `reapplyPositionEditsAfterSeek` (DOM-query-based seek hook, queries data-attribute markers) - `manualEdits.ts`: remove `applyStudioManualEditManifest` and all manifest target resolution; export `reapplyPositionEditsAfterSeek`; keep seek/play wrap infrastructure - `useManifestPersistence`: remove all JSON I/O — no disk read on load, no manifest state, no toggle state; `applyCurrentStudioManualEditsToPreview` now only installs seek hooks via `reapplyPositionEditsAfterSeek` - `useDomEditCommits`: replace `commitStudioManualEditManifestOptimistically` calls with direct DOM apply + `commitPositionPatchToHtml` (queued HTML patch write, skipRefresh) - `DomEditOverlay`: remove `manualEditsEnabled` prop; revert all `canMove || manualEditsEnabled` gates to just `canApplyManualOffset` — every draggable element is always draggable - `PropertyPanel`: remove `ManualPositioningToggle` component and all toggle props - `manualEditsParsing/manualEditsTypes`: remove manifest types, upsert functions, and `STUDIO_MANUAL_EDITS_PATH`; keep `finiteNumber`, `readStudioFileChangePath`, `roundRotationAngle`, and snapshot/CSS-property types * fix(studio): sync keyboard shortcut handler with main; fix keepPlaying seek assertions in test * fix(studio): strip GSAP-cached translate from transform on path offset apply * fix(studio): remove Reset edits button from design panel * feat(studio): wire reloadPreview into manifest persistence; drop stale group-selection refresh - Pass `reloadPreview` into `useManifestPersistence` so undo/redo reloads via the refresh-key path instead of directly touching the iframe. - Remove `refreshDomEditGroupSelectionsFromPreview` from commit handlers; HTML is now the source of truth so no stale-ref refresh is needed. - Add `manualEditsRenderScript` helper; export via studio-api and apply it in `htmlCompiler` during HTML compilation. * fix(studio): prevent root composition from being selected; correct overlay drift on resize - Guard `getDomLayerPatchTarget` against elements with `data-composition-id` so the root composition div is never returned as a visual selection target. - Apply the same guard to the raw `elementFromPoint` fallback in `getPreviewTargetFromPointer`, which was the actual escape path. - Thread `iframeRef` into gesture handler opts; after applying draft dimensions during resize, re-read the element BCR via `toOverlayRect` and update the overlay box position to compensate for visual drift on elements with centered transform-origin (e.g. GSAP scale tweens). * fix(studio): correct resize overlay for scaled elements; block invisible element selection - Resize: use BCR from `toOverlayRect` for both position and size after applying draft dimensions — GSAP scale makes visual size diverge from raw CSS size, BCR is the only accurate source during a gesture. - Click selection: add `isElementComputedVisible` guard to the `elementFromPoint` fallback so opacity-0 / autoAlpha-hidden elements cannot be selected even though the browser hit-test returns them. * fix(studio): reload preview on external file changes via SSE/HMR Share the app-level domEditSaveTimestampRef with useManifestPersistence so the SSE/HMR handler can suppress echoes from all studio saves (code tab, timeline, DOM edits), then call reloadPreview() for non-motion external changes that aren't echoes of our own saves. * fix(studio): suppress post-resize click to keep selection on resized element * fix(studio): serve registry blocks without index.html in preview Blocks ship as {id}.html + assets/ with no index.html. The preview route hard-coded index.html so these projects returned 404 and their assets (e.g. korea-map.png, map-nyc-paris.png) were never served. Add resolveProjectMainHtml() that falls back to {id}.html, thread the resolved compositionPath through transformPreviewHtml and injectStudioPreviewAugmentations, and update listProjects() in the vite adapter to surface block directories in the project list. * fix(render): preserve studio drag/resize/rotation offsets in rendered video Three issues caused studio-edited positions to be lost during rendering: 1. The seek-reapply script used setInterval to wrap window.__hf.seek, but Puppeteer's page.evaluate() calls don't yield the event loop for macrotasks — the interval never fired, so reapplyAll() never ran after GSAP seeks. Fix: use Object.defineProperty to trap writes to the seek property, wrapping it synchronously the instant the bridge assigns it. 2. MEDIA_VISUAL_STYLE_PROPERTIES (copied from <video> to proxy <img> during render) included "transform" but not "translate", "rotate", or "scale" — the CSS Transforms Level 2 individual properties used by studio drag/resize/rotation. The proxy was positioned at offsetLeft/ offsetTop without the translate offset. 3. getViewportMatrix (HDR compositor) only read cs.transform, missing individual transform properties entirely. Added composeIndividualTransforms to build the translate × rotate × scale matrix and compose it before the legacy transform matrix. * fix(studio): select elements with pointer-events: none in preview Compositions often set pointer-events: none on scenes, avatar wrappers, and decorative layers. elementsFromPoint() skips these elements entirely, making them unselectable in the Studio. Fix: temporarily inject a * { pointer-events: auto !important } stylesheet during hit-testing, then remove it immediately after. Also adds a pointer_events_none lint rule (info severity, visible with --verbose) so authors know which selectors may affect Studio selection.
Write HTML. Render video. Built for agents.
Hyperframes is an open-source video rendering framework that lets you create, preview, and render HTML-based video compositions — with first-class support for AI agents.
Quick Start
Option 1: With an AI coding agent (recommended)
Install the HyperFrames skills, then describe the video you want:
npx skills add heygen-com/hyperframes
This teaches your agent (Claude Code, Cursor, Gemini CLI, Codex) how to write correct compositions, GSAP timelines, Tailwind v4 browser-runtime styles, and first-party adapter animations. In Claude Code, the skills register as slash commands — invoke /hyperframes to author compositions, /hyperframes-cli for the dev-loop commands (init, lint, preview, render), /hyperframes-media for asset preprocessing (TTS, transcription, background removal), /tailwind for init --tailwind projects, /gsap for timeline animation help, or the adapter skills (/animejs, /css-animations, /lottie, /three, /waapi) when a composition uses those runtimes.
For Claude Design, open docs/guides/claude-design-hyperframes.md on GitHub and click the download button (↓) to save it, then attach the file to your Claude Design chat. It produces a valid first draft; refine in any AI coding agent. See the Claude Design guide.
For Codex specifically, the same skills are also exposed as an OpenAI Codex plugin — sparse-install just the plugin surface:
codex plugin marketplace add heygen-com/hyperframes --sparse .codex-plugin --sparse skills --sparse assets
For Claude Code, the repo also ships a Claude Code plugin manifest: test it locally with claude --plugin-dir .. The manifest intentionally omits skills because Claude Code auto-discovers the root skills/ directory by convention, and for marketplace submission use the title HyperFrames by HeyGen plus the black/white icon assets at assets/claude-code-icon-dark.svg and assets/claude-code-icon-light.svg for the two theme slots.
For Cursor, the same skills are packaged as a Cursor plugin — install from the Cursor Marketplace, or sideload by cloning this repo and pointing Settings → Plugins → Load unpacked at the repo root.
Try it: example prompts
Copy any of these into your agent to get started. The /hyperframes prefix loads the skill context explicitly so you get correct output the first time.
Cold start — describe what you want:
Using
/hyperframes, create a 10-second product intro with a fade-in title, a background video, and background music.
Warm start — turn existing context into a video:
Take a look at this GitHub repo https://github.com/heygen-com/hyperframes and explain its uses and architecture to me using
/hyperframes.
Summarize the attached PDF into a 45-second pitch video using
/hyperframes.
Turn this CSV into an animated bar chart race using
/hyperframes.
Format-specific:
Make a 9:16 TikTok-style hook video about [topic] using
/hyperframes, with bouncy captions synced to a TTS narration.
Iterate — talk to the agent like a video editor:
Make the title 2x bigger, swap to dark mode, and add a fade-out at the end.
Add a lower third at 0:03 with my name and title.
The agent handles scaffolding, animation, and rendering. See the prompting guide for more patterns.
Option 2: Start a project manually
npx hyperframes init my-video
cd my-video
npx hyperframes preview # preview in browser (live reload)
npx hyperframes render # render to MP4
hyperframes init installs skills automatically, so you can hand off to your AI agent at any point.
Requirements: Node.js >= 22, FFmpeg
Why Hyperframes?
- HTML-native — compositions are HTML files with data attributes. No React, no proprietary DSL.
- AI-first — agents already speak HTML. The CLI is non-interactive by default, designed for agent-driven workflows.
- Deterministic rendering — same input = identical output. Built for automated pipelines.
- Frame Adapter pattern — bring your own animation runtime (GSAP, Lottie, CSS, Three.js).
Hyperframes vs Remotion
Hyperframes is inspired by Remotion — we used Remotion at HeyGen in production, learned a ton from it, and kept attribution comments in the source for the patterns it pioneered (Chrome launch flags, image2pipe → FFmpeg streaming, frame buffering). Both tools drive headless Chrome and both are deterministic. They differ on one decision: what the primary author writes. Remotion's bet is React components; Hyperframes' bet is HTML.
| Hyperframes | Remotion | |
|---|---|---|
| Authoring | HTML + CSS + GSAP | React components (TSX) |
| Build step | None; index.html plays as-is |
Required (bundler) |
| Library-clock animations (GSAP, Anime.js, Motion One) | Seekable, frame-accurate | Plays at wall-clock during render |
| Arbitrary HTML / CSS passthrough | Paste and animate | Rewrite as JSX |
| Distributed rendering | Single-machine today | Lambda, production-ready |
Licensing: fully open source vs source-available
Hyperframes is completely open source under Apache 2.0 — an OSI-approved license. Use it commercially at any scale, with no per-render fees, no seat caps, no company-size thresholds.
Remotion is source-available, not open source. The code is on GitHub under a custom Remotion License that requires a paid company license above small-team thresholds. It's a great product with a real team behind it — but if open-source licensing matters to you (OSI compliance, redistribution rights, no per-use fees), that's a first-order decision point.
Full write-up with benchmarks, an honest list of where each tool wins, and a GSAP side-by-side: Hyperframes vs Remotion guide.
How It Works
Define your video as HTML with data attributes:
<div id="stage" data-composition-id="my-video" data-start="0" data-width="1920" data-height="1080">
<video
id="clip-1"
data-start="0"
data-duration="5"
data-track-index="0"
src="intro.mp4"
muted
playsinline
></video>
<img
id="overlay"
class="clip"
data-start="2"
data-duration="3"
data-track-index="1"
src="logo.png"
/>
<audio
id="bg-music"
data-start="0"
data-duration="9"
data-track-index="2"
data-volume="0.5"
src="music.wav"
></audio>
</div>
Preview instantly in the browser. Render to MP4 locally or in Docker.
Catalog
50+ ready-to-use blocks and components — social overlays, shader transitions, data visualizations, and cinematic effects:
npx hyperframes add flash-through-white # shader transition
npx hyperframes add instagram-follow # social overlay
npx hyperframes add data-chart # animated chart
Browse the full catalog at hyperframes.heygen.com/catalog.
Documentation
Full documentation at hyperframes.heygen.com/introduction — Quickstart | Guides | API Reference | Catalog
Packages
| Package | Description |
|---|---|
hyperframes |
CLI — create, preview, lint, and render compositions |
@hyperframes/core |
Types, parsers, generators, linter, runtime, frame adapters |
@hyperframes/engine |
Seekable page-to-video capture engine (Puppeteer + FFmpeg) |
@hyperframes/producer |
Full rendering pipeline (capture + encode + audio mix) |
@hyperframes/studio |
Browser-based composition editor UI |
@hyperframes/player |
Embeddable <hyperframes-player> web component |
@hyperframes/shader-transitions |
WebGL shader transitions for compositions |
Skills
HyperFrames ships skills that teach AI agents framework-specific patterns that generic docs don't cover.
npx skills add heygen-com/hyperframes
| Skill | What it teaches |
|---|---|
hyperframes |
HTML composition authoring, captions, TTS, audio-reactive animation, transitions |
hyperframes-cli |
Dev-loop CLI: init, lint, inspect, preview, render, doctor |
hyperframes-media |
Asset preprocessing: tts (Kokoro), transcribe (Whisper), remove-background (u2net) — voice/model/codec selection |
hyperframes-registry |
Block and component installation via hyperframes add |
website-to-hyperframes |
Capture a URL and turn it into a video — full website-to-video pipeline |
remotion-to-hyperframes |
Translate a Remotion (React) composition into a HyperFrames HTML composition |
gsap |
GSAP timelines for HyperFrames: paused registration, deterministic seeking, easing, sequencing, performance |
animejs |
Anime.js animations and timelines registered on window.__hfAnime for deterministic HyperFrames seeking |
css-animations |
CSS keyframe animation patterns that HyperFrames can discover, pause, and seek |
lottie |
lottie-web and dotLottie players registered on window.__hfLottie with local assets and paused playback |
three |
Three.js scenes that render from HyperFrames hf-seek events and window.__hfThreeTime instead of wall-clock time |
waapi |
Web Animations API element.animate() patterns seeked through document.getAnimations() |
Contributing
See CONTRIBUTING.md for guidelines.
Cloning the repo
The repo uses Git LFS for golden regression-test baselines under packages/producer/tests/**/output.mp4 (~240 MB of .mp4 files). If you're cloning the full repo for development, install Git LFS first:
# macOS
brew install git-lfs
# Ubuntu/Debian
sudo apt install git-lfs
# Windows
winget install GitHub.GitLFS
# (or install Git for Windows, which bundles Git LFS as an optional component)
# Then (once, per machine)
git lfs install
If you hit git-lfs filter-process: command not found during git clone or npx skills add heygen-com/hyperframes, install Git LFS and retry. You can also skip LFS content if you only need the source files:
GIT_LFS_SKIP_SMUDGE=1 git clone https://github.com/heygen-com/hyperframes.git
