cosmicstack-labs/hyperframes
Create, compose, animate, and render HTML-based video compositions using HyperFrames — an open-source video rendering framework built for AI agents. Covers composition authoring with data attributes, GSAP timelines, caption/subtitle generation, text-to-speech narration, audio-reactive animations, scene transitions, variable-driven parametrized renders, and the full video production workflow.
npx skills add https://github.com/cosmicstack-labs/mercury-agent-skills --skill hyperframes
Write HTML. Render video. Built for agents.
HyperFrames is an open-source video rendering framework where HTML is the source of truth. A composition is an HTML file with data-* attributes for timing, a GSAP timeline for animation, and CSS for appearance. The framework handles clip visibility, media playback, and timeline sync.
Requirements: Node.js >= 22, FFmpeg
Install: \Created my-video/
AGENTS.md
CLAUDE.md
hyperframes.json
index.html
meta.json
package.json
Get started:
npx skills add heygen-com/hyperframes
cd my-video then start Claude Code, Cursor, or your preferred agent
"Using /hyperframes, create a 15-second intro about [your topic]"
More patterns: hyperframes.heygen.com/guides/prompting
cd my-video && npm run dev
cd my-video && npm run check
cd my-video && npm run render
Full docs: hyperframes.heygen.com
License: Apache 2.0 (fully open source)
Every video is a composition — an HTML document with a root element identified by . Inside it, clips (video, audio, img, div elements) are placed on tracks using . Each clip has a and in seconds. The composition has explicit and in pixels.
| Attribute | Required | Values |
|-----------|----------|--------|
| uid=501(salmanqureshi) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae),701(com.apple.sharepoint.group.1) | Yes | Unique identifier |
| | Yes | Seconds or clip ID reference (, ) |
| | Required for img/div/compositions | Seconds. Video/audio defaults to media duration. |
| | Yes | Integer. Same-track clips cannot overlap. |
| | No | Trim offset into source (seconds) |
| | No | 0-1 (default 1) |
does not affect visual layering — use CSS .
| Attribute | Required | Values |
|-----------|----------|--------|
| | Yes | Unique composition ID |
| / | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
| | No | Path to external HTML file for sub-compositions |
| | No | JSON object of per-instance variable overrides |
| Attribute | Values |
|-----------|--------|
| | JSON array of declared variables for parametrized renders |
For open-ended requests ("make a product launch video") where no direction is given, understand intent first:
For specific requests ("add a title card", "fix timing on scene 3"), skip discovery.
If design.md or DESIGN.md exists in the project, read it first. It defines brand colors, fonts, and constraints. Use exact values — don't invent colors or substitute fonts.
If fonts are named but no fonts/ directory with .woff2 files exists, warn the user.
If no design.md exists, ask:
Build the end-state first. Position every element where it should be at its most visible moment. Write as static HTML+CSS. No GSAP yet.
The process:
.scene-content container MUST fill the full scene: width:100%; height:100%; padding:Npx; display:flex; flex-direction:column; gap:Npx; box-sizing:border-box. Use padding to push content inward — NEVER position:absolute; top:Npx on a content container.gsap.from() — animate FROM offscreen/invisible TO the CSS position.gsap.to() — animate TO offscreen/invisible FROM the CSS position..scene-content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
padding: 120px 160px;
gap: 24px;
box-sizing: border-box;
}
.title { font-size: 120px; }
.subtitle { font-size: 42px; }
const tl = gsap.timeline({ paused: true });
// Entrances
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
// Exits
tl.to(".title", { y: -40, opacity: 0, duration: 0.4, ease: "power2.in" }, 3);
tl.to(".subtitle", { y: -30, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.1);
Standalone compositions (main index.html): Put the data-composition-id div directly in <body>. No <template> wrapper.
Sub-compositions loaded via data-composition-src use a <template> wrapper:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<style>
[data-composition-id="my-comp"] { /* scoped styles */ }
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
window.__timelines["my-comp"] = tl;
</script>
</div>
</template>
Load in root: <div data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="10" data-track-index="1"></div>
Render the same composition with different content without editing source HTML.
Three-step pattern:
<html> with data-composition-variables:<html data-composition-variables='[
{"id":"title","type":"string","label":"Title","default":"Hello"},
{"id":"theme","type":"enum","label":"Theme","default":"light","options":[
{"value":"light","label":"Light"},
{"value":"dark","label":"Dark"}
]}
]'>
const { title, theme } = window.__hyperframes.getVariables();
document.getElementById("hero").textContent = title;
document.body.dataset.theme = theme;
npx hyperframes render --variables '{"title":"Q4 Report","theme":"dark"}' --output q4.mp4
Sub-composition per-instance values:
<div data-composition-id="card-pro" data-composition-src="compositions/card.html"
data-variable-values='{"title":"Pro","price":"$29"}'></div>
Types supported: string, number, color, boolean, enum.
Transitions between scenes use the shader-transitions package or CSS approaches:
| Transition | Method |
|------------|--------|
| Crossfade | GSAP opacity cross between scenes |
| Wipe | CSS clip-path animation |
| Slide reveal | GSAP x or y transforms |
| Flash through white | Shader transition via catalog |
| Shader dissolve | WebGL via @hyperframes/shader-transitions |
Install transitions: npx hyperframes add flash-through-white
Generate word-level captions synced to audio:
npx hyperframes tts "Your script" --voice af_heart --output narration.wavnpx hyperframes transcribe narration.wav (produces transcript.json)Two key rules:
<div> and animate the wrapper's opacity, not the video element.data-start="0" and data-media-start="0" for frame-accurate sync.<video src="presenter.mp4" id="bg" data-start="0" data-duration="6" data-track-index="0" muted playsinline></video>
<h1 id="headline" style="z-index:2;">MAKE IT IN HYPERFRAMES</h1>
<div class="cutout-wrap" style="position:absolute;inset:0;z-index:3;opacity:0">
<video src="presenter.webm" data-start="0" data-duration="6" data-track-index="1" muted playsinline></video>
</div>
Register each animation runtime on window.__hf* for deterministic seeking:
| Adapter | Registration | Notes |
|---------|-------------|-------|
| GSAP | window.__timelines["comp-id"] = tl | Paused timeline, paused:true |
| Anime.js | window.__hfAnime | Registered animations on global |
| CSS Animations | Automatic discovery | Browser pauses/resumes keyframes |
| Lottie | window.__hfLottie | lottie-web or dotLottie players |
| Three.js | window.__hfThreeTime | Uses seek events, not wall-clock |
| WAAPI | document.getAnimations() | Standard Web Animations API |
When no design.md exists, offer mood-based presets:
| Mood | Theme | Palette |
|------|-------|---------|
| Professional | Light, clean | Slate blues, whites, accent teal |
| Energetic | Dark, vibrant | Deep navy, bright cyan, amber accents |
| Warm | Cream/beige | Warm grays, terracotta, gold |
| Tech-forward | Dark mode | Near-black, electric blue, neon green |
| Minimal | Light, ample space | White, light gray, single accent |
| Luxury | Dark, rich | Charcoal, gold, deep burgundy |
| Playful | Bright, colorful | Pastels, saturated primaries |
| Cinematic | Dark, high contrast | Black, white, single saturated color |
<div data-composition-id="product-intro" data-width="1920" data-height="1080">
<div class="scene-content">
<h1 class="title">Product Name</h1>
<p class="subtitle">The next generation of productivity</p>
</div>
<style>
.scene-content { display:flex; flex-direction:column; justify-content:center; align-items:center; width:100%; height:100%; padding:120px; gap:24px; box-sizing:border-box; background:linear-gradient(135deg,#0f0c29,#302b63,#24243e); color:white; font-family:system-ui,sans-serif; }
.title { font-size:120px; font-weight:800; margin:0; }
.subtitle { font-size:42px; opacity:0.8; margin:0; }
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({paused:true});
tl.from(".title", {y:80,opacity:0,duration:0.8,ease:"power3.out"}, 0);
tl.from(".subtitle", {y:40,opacity:0,duration:0.6,ease:"power3.out"}, 0.3);
tl.to(".title", {y:-40,opacity:0,duration:0.5,ease:"power2.in"}, 8);
tl.to(".subtitle", {y:-30,opacity:0,duration:0.4,ease:"power2.in"}, 8.2);
window.__timelines["product-intro"] = tl;
</script>
</div>
Use HTML tables or SVG bars with GSAP stagger animations. Each bar's height animates from 0 to its data value.
Use data-width="1080" data-height="1920" for vertical format. Bouncy captions synced to TTS, fast cuts, bold text.
npx hyperframes lint catches missing IDs, overlapping tracks, unregistered timelines.npx hyperframes inspect seeks through the timeline and reports text spilling off-canvas.--quality draft for fast iterations, standard for review, high for final delivery.npx hyperframes doctor if rendering fails — checks for Chrome, FFmpeg, Node version, and memory.data-composition-src mount at data-start; ensure data-media-start="0" for frame-accurate sync.--docker flag ensures byte-identical output across machines.Cold start (describe what you want):
> Using the hyperframes skill, create a 10-second product intro with a fade-in title, a background video, and background music.
Warm start (turn context into video):
> Take this GitHub repo and explain its uses and architecture to me using a hyperframes video.
> Summarize this PDF into a 45-second pitch video.
> Turn this CSV into an animated bar chart race.
Format-specific:
> Make a 9:16 TikTok-style hook video about AI agents, with bouncy captions synced to TTS narration.
Iteration (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.
| Issue | Solution |
|-------|----------|
| npx hyperframes not found | Node.js >= 22 required |
| FFmpeg not found | Install via brew install ffmpeg or apt install ffmpeg |
| Chrome issues | Run npx hyperframes browser to manage bundled Chrome |
| Render fails | Run npx hyperframes doctor for diagnostics |
| Lint errors | Fix missing data-composition-id, overlapping tracks, unregistered timelines |
| Inspect warnings | Check text overflow, clipping containers, off-canvas elements |
| Version check | npx hyperframes info or npx hyperframes upgrade |
| Skill | Purpose |
|-------|---------|
| hyperframes-cli | CLI dev loop — init, lint, inspect, preview, render, doctor |
| hyperframes-media | Asset preprocessing — TTS, transcribe, background removal |
Take cosmicstack-labs/hyperframes from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx, brew.
Without those the skill loads but fails at the first command.