docs: add Mintlify documentation site

Set up /docs directory with docs.json config, HeyGen branding (logo, favicon,
#7559FF purple), and 18 MDX pages covering:
- Getting started (introduction, quickstart)
- Concepts (compositions, data attributes, frame adapters, determinism)
- Guides (GSAP animation, templates, rendering, common mistakes, troubleshooting)
- Package docs (core, engine, producer, studio, CLI)
- Reference (HTML schema) and contributing guide

Content adapted from existing repo docs (core/docs/, cli/src/docs/, README).
Validated with `mint validate` and `mint broken-links`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
James
2026-03-23 22:39:08 +00:00
co-authored by Claude Opus 4.6
parent 7c48f2a98b
commit 00bd2e5ae2
23 changed files with 1546 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
---
title: Compositions
description: "The fundamental building block of a Hyperframes video."
---
A composition is an HTML document that defines a video timeline. Every clip — video, image, audio — must live inside a composition.
## Structure
Every composition needs a root element with `data-composition-id`:
```html
<div id="root" data-composition-id="root"
data-start="0" data-width="1920" data-height="1080">
<!-- Elements go here -->
</div>
```
The `index.html` file is the top-level composition. It can contain nested compositions within it. Any composition can be imported into another — there is no special "root" type.
## Clip Types
A clip is any discrete block on the timeline, represented as an HTML element with data attributes:
- `<video>` — Video clips, B-roll, A-roll
- `<img>` — Static images, overlays
- `<audio>` — Music, sound effects
- `<div data-composition-id="...">` — Nested compositions (animations, grouped sequences)
## Nested Compositions
Embed one composition inside another by loading it from an external HTML file:
```html
<div
id="el-5"
data-composition-id="intro-anim"
data-composition-src="compositions/intro-anim.html"
data-start="0"
data-track-index="3"
></div>
```
The framework automatically fetches the HTML file, extracts the `<template>` content, mounts it, executes scripts, and registers the timeline.
### Composition File Format
Each composition file wraps its content in a `<template>` tag:
```html
<!-- compositions/intro-anim.html -->
<template id="intro-anim-template">
<div data-composition-id="intro-anim" data-width="1920" data-height="1080">
<div class="title">Welcome!</div>
<style>
[data-composition-id="intro-anim"] .title {
font-size: 72px; color: white; text-align: center;
}
</style>
<script>
const tl = gsap.timeline({ paused: true });
tl.from(".title", { opacity: 0, y: -50, duration: 1 });
window.__timelines["intro-anim"] = tl;
</script>
</div>
</template>
```
### Project Structure
```
project/
├── index.html # Root composition
├── compositions/
│ ├── intro-anim.html # Intro animation
│ ├── caption-overlay.html # Captions
│ └── outro-title.html # Outro
```
## Two Layers: Primitives and Scripts
Every composition has two layers:
- **HTML** — primitive clips (`video`, `img`, `audio`, nested compositions). The declarative structure: what plays, when, and on which track.
- **Script** — effects, transitions, dynamic DOM, canvas, SVG — creative animation via GSAP. Scripts do **not** control media playback or clip visibility.
<Warning>
Never use scripts to play/pause/seek media elements or to show/hide clips based on timing. The framework handles this automatically from data attributes. Scripts that duplicate this behavior will conflict with the framework.
</Warning>
## Variables
Compositions can expose variables for dynamic content:
```html
<div data-composition-id="card" data-var-title="string" data-var-color="color">
```
## Listing Compositions
Use the CLI to see all compositions in a project:
```bash
npx hyperframes compositions
```
+79
View File
@@ -0,0 +1,79 @@
---
title: Data Attributes
description: "Core attributes for controlling element timing and behavior."
---
Hyperframes uses HTML data attributes to control timing, media playback, and composition structure. These are the declarative building blocks of every video.
## Timing Attributes
| Attribute | Example | Description |
|-----------|---------|-------------|
| `data-start` | `"0"` or `"intro"` | Start time in seconds, or a clip ID reference for [relative timing](#relative-timing) |
| `data-duration` | `"5"` | Duration in seconds. Required for images. Optional for video/audio (defaults to source duration). Not used on compositions. |
| `data-track-index` | `"0"` | Timeline track number. Controls z-ordering (higher = in front) and groups clips into rows. Clips on the same track cannot overlap. |
## Media Attributes
| Attribute | Example | Description |
|-----------|---------|-------------|
| `data-media-start` | `"2"` | Media playback offset / trim point in seconds. Default: `0` |
| `data-volume` | `"0.8"` | Audio/video volume, 0 to 1 |
| `data-has-audio` | `"true"` | Indicates video has an audio track |
## Composition Attributes
| Attribute | Example | Description |
|-----------|---------|-------------|
| `data-composition-id` | `"root"` | Unique ID for composition wrapper (required on every composition) |
| `data-width` | `"1920"` | Composition width in pixels |
| `data-height` | `"1080"` | Composition height in pixels |
| `data-composition-src` | `"./intro.html"` | Path to external composition HTML file |
## Element Visibility
Add `class="clip"` to all timed elements so the runtime can manage their visibility lifecycle:
```html
<h1 id="title" class="clip"
data-start="0" data-duration="5" data-track-index="0">
Hello World
</h1>
```
## Relative Timing
Instead of calculating absolute start times, a clip can reference another clip's `id` in its `data-start` attribute. This means "start when that clip ends":
```html
<video id="intro" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
<video id="main" data-start="intro" data-duration="20" data-track-index="0" src="..."></video>
<video id="outro" data-start="main" data-duration="5" data-track-index="0" src="..."></video>
```
`main` resolves to second 10, `outro` resolves to second 30. If `intro`'s duration changes, downstream clips shift automatically.
### Offsets (Gaps and Overlaps)
Add `+ N` or `- N` after the ID to offset from the end of the referenced clip:
```html
<!-- 2-second gap after intro -->
<video id="scene-a" data-start="intro + 2" data-duration="20"
data-track-index="0" src="..."></video>
<!-- 0.5-second overlap with intro (crossfade) -->
<video id="scene-b" data-start="intro - 0.5" data-duration="20"
data-track-index="1" src="..."></video>
```
<Note>
Overlapping clips must be on different tracks — clips on the same track cannot overlap in time.
</Note>
### Rules
- **Same composition only** — references resolve within the clip's parent composition
- **No circular references** — A cannot start after B if B starts after A
- **Referenced clip must have a known duration** — either explicit `data-duration` or inferred from source media
- **Parsing** — if the value is a valid number, it is absolute seconds; otherwise parsed as `<id>`, `<id> + <number>`, or `<id> - <number>`
+59
View File
@@ -0,0 +1,59 @@
---
title: Deterministic Rendering
description: "Same input, identical output. Every time."
---
Hyperframes is built around a core guarantee: **the same composition always produces the same video**. This is what makes automated pipelines, CI testing, and AI-driven workflows reliable.
## How It Works
The rendering pipeline is frame-by-frame and seek-driven:
1. **Frame clock**: `time = floor(frame) / fps` — no wall-clock dependency
2. **Seek contract**: `renderSeek(time)` pauses all animations and deterministically seeks to the exact frame
3. **Capture**: Chrome's `HeadlessExperimental.beginFrame` API captures the pixel buffer
4. **Encode**: FFmpeg encodes frames into the final video
No realtime playback is involved in rendering. Every frame is independently seeked and captured.
## What Makes It Deterministic
- **No wall-clock dependencies** — rendering doesn't use `Date.now()`, `requestAnimationFrame`, or system timers
- **No unseeded randomness** — `Math.random()` without a seed breaks determinism
- **No render-time network fetches** — all assets must be loaded before rendering starts
- **Fixed output parameters** — `fps`, `width`, and `height` are locked before the first frame
- **Finite duration** — every composition has a known, finite length
## Docker Mode
For maximum reproducibility, render in Docker:
```bash
npx hyperframes render --docker -o output.mp4
```
Docker mode uses an exact Chrome version and font set, ensuring:
- Same Chromium rendering engine across all platforms
- Same system fonts (no platform-specific font substitution)
- Same FFmpeg encoder version
## Preview vs. Render Parity
The browser preview and the rendered MP4 should match. Hyperframes achieves this through:
- **One runtime** — the same `hyperframe.runtime` drives both preview and render
- **Producer-canonical behavior** — the producer's seek semantics are the source of truth
- **Readiness gates** — `__playerReady` and `__renderReady` ensure the composition is fully loaded before any frame is captured
<Note>
Local rendering (without Docker) may show slight differences due to platform-specific font rendering and Chrome version. Use Docker mode when exact reproducibility matters.
</Note>
## For Adapter Authors
If you're building a [Frame Adapter](/concepts/frame-adapters), your adapter must follow the determinism contract:
- `seekFrame(frame)` must be idempotent — same frame, same result
- No side effects that depend on call order (must handle random access)
- No async operations that resolve after the frame is "committed"
- Clean lifecycle: `init` → `seekFrame` (N times) → `destroy`
+100
View File
@@ -0,0 +1,100 @@
---
title: Frame Adapters
description: "Bring your own animation runtime to Hyperframes."
---
The Frame Adapter pattern is how Hyperframes supports multiple animation runtimes. The core question every adapter answers:
> What should the screen look like at frame N?
If a runtime can answer that, it can plug into Hyperframes.
## Adapter API (v0)
```typescript
type FrameAdapterContext = {
compositionId: string;
fps: number;
width: number;
height: number;
rootElement?: HTMLElement;
};
type FrameAdapter = {
id: string;
init?: (ctx: FrameAdapterContext) => Promise<void> | void;
getDurationFrames: () => number;
seekFrame: (frame: number) => Promise<void> | void;
destroy?: () => Promise<void> | void;
};
```
## Required Semantics
- `getDurationFrames()` must return a finite integer >= 0
- `seekFrame(frame)` must support arbitrary seek order (forward, backward, random)
- `seekFrame(frame)` must be idempotent for the same input frame
- `seekFrame(frame)` must clamp internal time to the adapter's range
- Adapters should be paused/seek-driven, not clock-driven
## Host Orchestration
The host normalizes frames before calling the adapter:
```typescript
normalizedFrame = clamp(Math.floor(frame), 0, durationFrames);
```
A typical render loop:
```typescript
await adapter.init?.({ compositionId, fps, width, height, rootElement });
const durationFrames = adapter.getDurationFrames();
for (let frame = 0; frame <= durationFrames; frame += 1) {
await adapter.seekFrame(frame);
// capture pixel buffer for this frame
}
await adapter.destroy?.();
```
## Determinism Contract
These rules are non-negotiable for any adapter:
- Canonical clock: `t = frame / fps`
- No wall-clock dependencies (`Date.now`, drift-dependent logic)
- No unseeded randomness
- No render-time network fetches
- Fixed output params (`fps`, `width`, `height`)
- Finite duration only
- Deterministic frame quantization before seek
## Supported Runtimes
First-party adapters:
| Runtime | Seek Method | Status |
|---------|------------|--------|
| GSAP | `timeline.seek(frame / fps)` | Available |
| CSS/WAAPI | `animation.currentTime` | Planned |
| Lottie | Set animation frame/progress | Planned |
| Three.js/WebGL | Compute deterministic scene state | Planned |
| SVG/Anime | Implement seek + duration contract | Planned |
Community adapters are welcome — if it can seek by frame, it belongs in Hyperframes.
## Conformance Tests
Every adapter should pass these minimum tests:
1. **Repeatability** — seek same frame twice, get identical output
2. **Random seek** — seek order `[90, 10, 50, 10]` produces deterministic results
3. **Bounds** — negative and overflow frame values don't break
4. **Duration** — returned duration is a finite integer
5. **Cleanup** — no leaked timers/listeners after `destroy`
<Info>
The Adapter API is currently at **v0** (experimental). Breaking changes are possible until v1.
</Info>
+60
View File
@@ -0,0 +1,60 @@
---
title: Contributing
description: "How to contribute to Hyperframes."
---
Thanks for your interest in contributing to Hyperframes!
## Getting Started
1. Fork the repository
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/hyperframes.git`
3. Install dependencies: `pnpm install`
4. Create a branch: `git checkout -b my-feature`
## Development
```bash
pnpm install # Install all dependencies
pnpm dev # Run the studio (composition editor)
pnpm build # Build all packages
pnpm -r typecheck # Type-check all packages
```
### Running Tests
```bash
pnpm --filter @hyperframes/core test # Core unit tests
pnpm --filter @hyperframes/engine test # Engine unit tests
pnpm --filter @hyperframes/core test:hyperframe-runtime-ci # Runtime contract tests
```
## Packages
| Package | Description |
|---------|-------------|
| `@hyperframes/core` | Types, HTML generation, runtime, linter |
| `@hyperframes/engine` | Seekable page-to-video capture engine |
| `@hyperframes/producer` | Full rendering pipeline (capture + encode) |
| `@hyperframes/studio` | Composition editor UI |
| `hyperframes` | CLI for creating, previewing, and rendering |
## Pull Requests
- Use [conventional commit](https://www.conventionalcommits.org/) format for PR titles (e.g., `feat: add timeline export`, `fix: resolve seek overflow`)
- CI must pass before merge (build, typecheck, tests, semantic PR title)
- PRs require at least 1 approval
## Reporting Issues
- Use [GitHub Issues](https://github.com/heygen-com/hyperframes/issues) for bug reports and feature requests
- Search existing issues before creating a new one
- Include reproduction steps for bugs
## Code of Conduct
This project follows the [Contributor Covenant Code of Conduct](https://github.com/heygen-com/hyperframes/blob/main/CODE_OF_CONDUCT.md).
## License
By contributing, you agree that your contributions will be licensed under the [MIT License](https://github.com/heygen-com/hyperframes/blob/main/LICENSE).
+80
View File
@@ -0,0 +1,80 @@
{
"$schema": "https://mintlify.com/docs.json",
"name": "Hyperframes",
"theme": "mint",
"colors": {
"primary": "#7559FF",
"light": "#9F59FF",
"dark": "#735CE5"
},
"logo": {
"light": "/logo/light.svg",
"dark": "/logo/dark.svg"
},
"favicon": "/favicon.ico",
"navigation": {
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Getting Started",
"pages": ["introduction", "quickstart"]
},
{
"group": "Concepts",
"pages": [
"concepts/compositions",
"concepts/data-attributes",
"concepts/frame-adapters",
"concepts/determinism"
]
},
{
"group": "Guides",
"pages": [
"guides/gsap-animation",
"guides/templates",
"guides/rendering",
"guides/common-mistakes",
"guides/troubleshooting"
]
}
]
},
{
"tab": "Packages",
"groups": [
{
"group": "Packages",
"pages": [
"packages/core",
"packages/engine",
"packages/producer",
"packages/studio",
"packages/cli"
]
}
]
},
{
"tab": "Reference",
"groups": [
{
"group": "Reference",
"pages": ["reference/html-schema"]
},
{
"group": "Community",
"pages": ["contributing"]
}
]
}
]
},
"footer": {
"socials": {
"github": "https://github.com/heygen-com/hyperframes"
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+65
View File
@@ -0,0 +1,65 @@
---
title: Common Mistakes
description: "Pitfalls that break Hyperframes compositions."
---
These are mistakes that can't be caught by the linter. For automated checks, run `npx hyperframes lint`.
## Animating Video Element Dimensions
**Symptom**: Video frames stop updating, or browser performance drops.
**Cause**: GSAP animating `width`, `height`, `top`, `left` directly on a `<video>` element can cause the browser to stop rendering frames.
```javascript
// BROKEN — animating video element dimensions
tl.to("#el-video", { width: 500, height: 280, top: 700, left: 1400 }, 26);
// FIXED — animate a wrapper div, video fills it at 100%
tl.to("#pip-wrapper", { width: 500, height: 280, top: 700, left: 1400 }, 26);
```
Use a non-timed wrapper `<div>` for visual effects like picture-in-picture. Animate the wrapper; let the video fill it.
## Controlling Media Playback in Scripts
**Symptom**: Audio/video playback is out of sync, or plays when it shouldn't.
**Cause**: Calling `video.play()`, `video.pause()`, or setting `audio.currentTime` in your scripts. The framework owns all media playback.
```javascript
// BROKEN — conflicts with framework media sync
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;
// FIXED — don't do this. The framework handles it.
// Use GSAP for visual animations only:
tl.to("#el-video", { opacity: 1, duration: 0.5 }, 0);
```
## Composition Duration Shorter Than Video
**Symptom**: Video plays for a few seconds then stops. Timeline shows 8-10 seconds even though the video is minutes long.
**Cause**: The composition duration equals the GSAP timeline duration, not `data-duration` on the video. If your last GSAP animation ends at 8 seconds, the composition is 8 seconds long.
```javascript
// BROKEN — timeline is only 7.8s long, video cuts off
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
// FIXED — extend timeline to match video length
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
tl.set({}, {}, 283); // extends timeline to 283 seconds
```
`tl.set({}, {}, TIME)` adds a zero-duration tween at the specified time, extending the timeline without affecting any elements.
## Debugging Checklist
When something doesn't work, check in this order:
1. **Run the linter**: `npx hyperframes lint` — catches most structural issues
2. **Timeline registered?** Is `window.__timelines["<id>"]` set? Does the key match `data-composition-id`?
3. **GSAP-only animations?** Only animate visual properties (opacity, transform, color)
4. **Timeline long enough?** Add `tl.set({}, {}, DURATION)` at the end
5. **Console errors?** Open browser console — runtime errors show as `[Browser:ERROR]`
+69
View File
@@ -0,0 +1,69 @@
---
title: GSAP Animation
description: "Add animations to your Hyperframes compositions with GSAP."
---
Hyperframes uses [GSAP](https://gsap.com/) for animation. Timelines are paused and controlled by the runtime — you define the animations, the framework handles playback.
## Setup
Include GSAP and create a paused timeline:
```html
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script>
const tl = gsap.timeline({ paused: true });
tl.to("#title", { opacity: 1, duration: 0.5 }, 0);
window.__timelines = window.__timelines || {};
window.__timelines["root"] = tl;
</script>
```
## Key Rules
1. **Always create timelines with `{ paused: true }`** — the framework controls playback
2. **Register timelines on `window.__timelines`** with the `data-composition-id` as key
3. **Use the position parameter** (3rd argument) for absolute timing: `tl.to(el, vars, 1.5)`
4. **Only animate visual properties** — never control media playback in scripts
## Supported Methods
| Method | Description |
|--------|-------------|
| `tl.to(target, vars, position)` | Animate to values |
| `tl.from(target, vars, position)` | Animate from values |
| `tl.fromTo(target, fromVars, toVars, position)` | Animate from/to values |
| `tl.set(target, vars, position)` | Set values instantly |
## Supported Properties
`opacity`, `x`, `y`, `scale`, `scaleX`, `scaleY`, `rotation`, `width`, `height`, `visibility`, `color`, `backgroundColor`, and any CSS-animatable property.
## Timeline Duration
A composition's duration equals its GSAP timeline duration. If your last animation ends at 8 seconds, the composition is 8 seconds long.
To extend the timeline beyond the last animation (e.g., to match a video clip's length):
```javascript
// Extends timeline to 283 seconds without affecting any elements
tl.set({}, {}, 283);
```
## Sub-Composition Timelines
Each composition registers its own timeline. The framework automatically nests sub-composition timelines into the parent based on `data-start`:
```javascript
// In compositions/intro-anim.html
const tl = gsap.timeline({ paused: true });
tl.from(".title", { opacity: 0, y: -50, duration: 1 });
window.__timelines["intro-anim"] = tl;
// DO NOT manually add sub-timelines to the master:
// masterTL.add(window.__timelines["intro-anim"], 0); // UNNECESSARY
```
<Warning>
Don't animate `width`, `height`, `top`, or `left` directly on `<video>` elements — this can cause the browser to stop rendering frames. Wrap the video in a `<div>` and animate the wrapper instead.
</Warning>
+54
View File
@@ -0,0 +1,54 @@
---
title: Rendering
description: "Render compositions to MP4 locally or in Docker."
---
Render your Hyperframes compositions to MP4 with the CLI.
```bash
npx hyperframes render -o output.mp4
```
## Rendering Modes
### Local Mode (default)
Uses Puppeteer (bundled Chromium) + system FFmpeg. Fast for iteration.
**Requires**: FFmpeg installed on your system.
```bash
npx hyperframes render -o output.mp4
```
### Docker Mode
Deterministic output with an exact Chrome version and fonts. Use this for production renders and CI pipelines.
**Requires**: Docker installed and running.
```bash
npx hyperframes render --docker -o output.mp4
```
<Note>
Docker mode uses `chrome-headless-shell` with BeginFrame control for frame-perfect, deterministic capture. This is the same pipeline used in production.
</Note>
## Options
| Flag | Values | Default | Description |
|------|--------|---------|-------------|
| `-f, --fps` | 24, 30, 60 | 30 | Frames per second |
| `-q, --quality` | draft, standard, high | standard | Encoding quality preset |
| `-w, --workers` | 1-8 | auto | Parallel render workers |
| `--gpu` | — | off | GPU encoding (NVENC, VideoToolbox, VAAPI) |
| `-o, --output` | path | — | Output file path |
| `--docker` | — | off | Use Docker for deterministic rendering |
## Tips
- Use `draft` quality during development for fast previews
- Use `npx hyperframes benchmark` to find optimal settings for your system
- 4 workers is usually the sweet spot for most compositions
- Docker mode is slower but guarantees identical output across platforms
+47
View File
@@ -0,0 +1,47 @@
---
title: Templates
description: "Built-in templates for common video patterns."
---
Hyperframes includes starter templates to help you get going quickly.
## Using Templates
```bash
npx hyperframes init --template <name>
```
## Available Templates
### blank
Empty 1920x1080 composition with a GSAP timeline wired up. Start from scratch.
```bash
npx hyperframes init --template blank
```
### title-card
Animated title and subtitle with GSAP fade-in/out. Good for intro cards.
```bash
npx hyperframes init --template title-card
```
### video-edit
Video element with trimming, audio, and track controls. Starting point for video editing workflows.
```bash
npx hyperframes init --template video-edit
```
## Custom Templates
Any directory with an `index.html` can serve as a template. Copy it manually or build your own init workflow.
Your custom template just needs:
1. An `index.html` with a `data-composition-id` root element
2. A GSAP timeline registered in `window.__timelines`
3. Any assets in the same directory
+68
View File
@@ -0,0 +1,68 @@
---
title: Troubleshooting
description: "Solutions for common Hyperframes issues."
---
## "No composition found"
Your directory needs an `index.html` with a valid composition. Run `npx hyperframes init` to create one.
## "FFmpeg not found"
Local rendering requires FFmpeg. Install it for your platform:
<CodeGroup>
```bash macOS
brew install ffmpeg
```
```bash Ubuntu/Debian
sudo apt install ffmpeg
```
```bash Windows
# Download from https://ffmpeg.org/download.html
# Add to your PATH
```
</CodeGroup>
## Lint Errors
Run `npx hyperframes lint` to check for common issues:
- Missing `data-composition-id` on root element
- Missing `class="clip"` on timed elements
- Overlapping timelines or invalid data attributes
- Unmuted video elements
- Deprecated attribute names (`data-layer`, `data-end`)
## Preview Not Updating
Make sure you're editing the `index.html` in the project directory. The preview server watches for file changes and auto-reloads.
If changes still don't appear:
1. Check the terminal for errors
2. Try stopping and restarting `npx hyperframes dev`
3. Hard-refresh the browser (Ctrl+Shift+R / Cmd+Shift+R)
## Render Looks Different from Preview
Use `--docker` mode for deterministic output. Local renders may differ due to:
- Font availability (different fonts on different platforms)
- Chrome version (local Chromium vs. Docker's pinned version)
- System-specific rendering differences
```bash
npx hyperframes render --docker -o output.mp4
```
## System Diagnostics
Run `npx hyperframes doctor` to check your environment:
```bash
npx hyperframes doctor
```
This checks for Node.js version, FFmpeg availability, Docker status, and other requirements.
+58
View File
@@ -0,0 +1,58 @@
---
title: Introduction
description: "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.
## Why Hyperframes?
- **HTML-native** — AI agents already speak HTML. No React required.
- **Frame Adapter pattern** — bring your own animation runtime (GSAP, Lottie, CSS, Three.js).
- **Deterministic rendering** — same input = identical output. Built for automated pipelines.
- **AI-first design** — not a bolted-on afterthought.
## How It Works
Define your video as HTML with data attributes:
```html
<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" 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. Let AI agents compose videos using tools they already understand.
## Packages
| Package | npm | Description |
|---------|-----|-------------|
| [`@hyperframes/core`](/packages/core) | `@hyperframes/core` | Types, HTML generation, runtime, linter |
| [`@hyperframes/engine`](/packages/engine) | `@hyperframes/engine` | Seekable page-to-video capture engine |
| [`@hyperframes/producer`](/packages/producer) | `@hyperframes/producer` | Full rendering pipeline (capture + encode) |
| [`@hyperframes/studio`](/packages/studio) | `@hyperframes/studio` | Composition editor UI |
| [`hyperframes`](/packages/cli) | `hyperframes` | CLI for creating, previewing, and rendering |
## Next Steps
<CardGroup cols={2}>
<Card title="Quickstart" icon="rocket" href="/quickstart">
Create your first video in 60 seconds
</Card>
<Card title="Compositions" icon="layer-group" href="/concepts/compositions">
Learn the core data model
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Add animations to your videos
</Card>
<Card title="Rendering" icon="film" href="/guides/rendering">
Render to MP4 locally or in Docker
</Card>
</CardGroup>
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

+62
View File
@@ -0,0 +1,62 @@
<svg width="640" height="640" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="prefix__a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="37" y="12" width="588" height="556">
<path
d="M206.788 480.08c-5.026-2.455-7.729.722-7.729 8.439 0 7.717.294 35.453 6.306 47.25a57.88 57.88 0 0025.293 25.293c9.463 4.821 21.161 5.957 40.623 6.225 53.598-2.39 100.504-30.509 128.665-72.305 2.5-3.711-.25-8.557-4.725-8.557H267.276c-17.766 0-44.453 1.487-60.488-6.345zM184.75 241.45c0-7.896-10.44-10.725-14.387-3.886a781644.405 781644.405 0 01-49.776 86.209c-40.45 70.047-26.516 163.98 51.459 216.834 9.646 6.539 17.363-3.858 15.491-12.001-3.981-16.646-2.787-35.128-2.787-52.213V241.45zM495.215 314.856c.387 5.58 4.49 6.333 11.173 2.474 6.684-3.858 30.556-17.981 37.767-29.086a57.878 57.878 0 009.258-34.551c-.556-10.606-5.421-21.305-14.92-38.293-28.869-45.223-76.674-71.785-126.951-75.274-4.464-.31-7.286 4.494-5.048 8.369L470.467 259.3c8.882 15.385 23.514 37.753 24.748 55.556zM299.572 453.254c-6.838 3.948-4.068 14.403 3.828 14.403 20.774-.001 53.356-.002 99.547.002 80.888.007 155.269-59.027 162.055-152.982.839-11.623-12.023-13.108-18.139-7.415-12.425 11.77-29.028 19.978-43.824 28.52L299.572 453.254zM211.794 149.177c4.638-3.124 3.239-7.054-3.444-10.913-6.683-3.858-30.851-17.471-44.073-18.163a57.881 57.881 0 00-34.551 9.257c-8.907 5.785-15.74 15.348-25.703 32.068-24.73 47.613-23.83 102.294-1.714 147.58 1.964 4.021 7.535 4.062 9.773.187l63.972-110.804c8.883-15.386 20.939-39.241 35.74-49.212zM429.475 249.411c6.838 3.948 14.507-3.678 10.559-10.516-10.388-17.99-26.679-46.207-49.771-86.212C349.825 82.629 261.51 47.73 176.749 88.831c-10.486 5.084-5.34 16.966 2.648 19.416 16.406 4.876 31.815 15.15 46.611 23.693l203.467 117.471z"
fill="#D9D9D9" />
</mask>
<g mask="url(#prefix__a)">
<path fill="#28A7E5" d="M48.574 46.727h555.617v547.9H48.574z" />
<g filter="url(#prefix__filter0_f_477_98045)">
<circle cx="760.94" cy="400.003" r="525.682" fill="#9F59FF" />
</g>
<g filter="url(#prefix__filter1_f_477_98045)">
<circle cx="751.928" cy="179.218" r="230.8" fill="#FF2FC5" />
</g>
<g filter="url(#prefix__filter2_f_477_98045)">
<circle cx="-69.128" cy="621.8" r="289.384" fill="#42C7F1" />
</g>
</g>
<g style="mix-blend-mode:overlay" opacity=".6">
<path d="M184.769 452.351v-210.91c0-7.896-10.452-10.696-14.4-3.859l-53.111 91.991 67.511 122.778z"
fill="url(#prefix__paint0_linear_477_98045)" />
<path d="M482.477 347.684L299.823 453.14c-6.838 3.947-4.037 14.4 3.858 14.4h106.222l72.574-119.856z"
fill="url(#prefix__paint1_linear_477_98045)" />
<path d="M246.704 143.775L429.358 249.23c6.837 3.948 14.489-3.704 10.541-10.541l-53.111-91.991-140.084-2.923z"
fill="url(#prefix__paint2_linear_477_98045)" />
</g>
<defs>
<linearGradient id="prefix__paint0_linear_477_98045" x1="147.247" y1="212.641" x2="147.247" y2="451.866"
gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient id="prefix__paint1_linear_477_98045" x1="293.643" y1="500.035" x2="500.818" y2="380.422"
gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient id="prefix__paint2_linear_477_98045" x1="473.06" y1="231.135" x2="265.885" y2="111.522"
gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<filter id="prefix__filter0_f_477_98045" x="-227.756" y="-588.694" width="1977.39" height="1977.39"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="231.507" result="effect1_foregroundBlur_477_98045" />
</filter>
<filter id="prefix__filter1_f_477_98045" x="360.921" y="-211.79" width="782.014" height="782.016"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="80.104" result="effect1_foregroundBlur_477_98045" />
</filter>
<filter id="prefix__filter2_f_477_98045" x="-550.261" y="140.667" width="962.263" height="962.265"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="95.874" result="effect1_foregroundBlur_477_98045" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

+77
View File
@@ -0,0 +1,77 @@
<svg width="2122" height="695" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="prefix__a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="60" y="41" width="588" height="556">
<path
d="M229.788 509.08c-5.026-2.455-7.729.722-7.729 8.439 0 7.717.294 35.453 6.306 47.25a57.88 57.88 0 0025.293 25.293c9.463 4.821 21.161 5.957 40.623 6.225 53.598-2.39 100.504-30.509 128.665-72.305 2.5-3.711-.25-8.557-4.725-8.557H290.276c-17.766 0-44.453 1.487-60.488-6.345zM207.75 270.45c0-7.896-10.44-10.725-14.387-3.886a781644.405 781644.405 0 01-49.776 86.209c-40.45 70.047-26.516 163.98 51.459 216.834 9.646 6.539 17.363-3.858 15.491-12.001-3.981-16.646-2.787-35.128-2.787-52.213V270.45zM518.215 343.856c.387 5.58 4.49 6.333 11.173 2.474 6.684-3.858 30.556-17.981 37.767-29.086a57.878 57.878 0 009.258-34.551c-.556-10.606-5.421-21.305-14.92-38.293-28.869-45.223-76.674-71.785-126.951-75.274-4.464-.31-7.286 4.494-5.048 8.369L493.467 288.3c8.882 15.385 23.514 37.753 24.748 55.556zM322.572 482.254c-6.838 3.948-4.068 14.403 3.828 14.403 20.774-.001 53.356-.002 99.547.002 80.888.007 155.269-59.027 162.055-152.982.839-11.623-12.023-13.108-18.139-7.415-12.425 11.77-29.028 19.978-43.824 28.52L322.572 482.254zM234.794 178.177c4.638-3.124 3.239-7.054-3.444-10.913-6.683-3.858-30.851-17.471-44.073-18.163a57.881 57.881 0 00-34.551 9.257c-8.907 5.785-15.74 15.348-25.703 32.068-24.729 47.613-23.83 102.294-1.714 147.58 1.964 4.021 7.535 4.062 9.773.187l63.972-110.804c8.883-15.386 20.939-39.241 35.74-49.212zM452.475 278.411c6.838 3.948 14.507-3.678 10.559-10.516-10.388-17.99-26.679-46.207-49.771-86.212-40.438-70.054-128.753-104.953-213.514-63.852-10.486 5.084-5.34 16.966 2.648 19.416 16.406 4.876 31.815 15.15 46.611 23.693l203.467 117.471z"
fill="#D9D9D9" />
</mask>
<g mask="url(#prefix__a)">
<path fill="#28A7E5" d="M71.574 75.727h555.617v547.9H71.574z" />
<g filter="url(#prefix__filter0_f_473_94381)">
<circle cx="783.94" cy="429.003" r="525.682" fill="#9F59FF" />
</g>
<g filter="url(#prefix__filter1_f_473_94381)">
<circle cx="774.928" cy="208.218" r="230.8" fill="#FF2FC5" />
</g>
<g filter="url(#prefix__filter2_f_473_94381)">
<circle cx="-46.128" cy="650.8" r="289.384" fill="#42C7F1" />
</g>
</g>
<g style="mix-blend-mode:overlay" opacity=".6">
<path d="M207.769 481.351v-210.91c0-7.896-10.452-10.696-14.4-3.859l-53.111 91.991 67.511 122.778z"
fill="url(#prefix__paint0_linear_473_94381)" />
<path d="M505.477 376.684L322.823 482.14c-6.838 3.947-4.037 14.4 3.858 14.4h106.222l72.574-119.856z"
fill="url(#prefix__paint1_linear_473_94381)" />
<path d="M269.704 172.775L452.358 278.23c6.837 3.948 14.489-3.704 10.541-10.541l-53.111-91.991-140.084-2.923z"
fill="url(#prefix__paint2_linear_473_94381)" />
</g>
<rect x="750.836" y="233.035" width="50.876" height="232.957" rx="8.926" fill="#232833" />
<rect x="908.816" y="233.035" width="50.876" height="232.957" rx="8.926" fill="#232833" />
<rect x="948.09" y="325.861" width="42.843" height="191.899" rx="8.926" transform="rotate(90 948.09 325.861)"
fill="#232833" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M1180.16 300.625c0 .767.17 1.524.5 2.215l74.08 153.977a7.141 7.141 0 009.53 3.338l28.96-13.931c3.55-1.71 5.05-5.976 3.34-9.53l-65.98-137.134a7.142 7.142 0 00-6.44-4.044h-38.88a5.104 5.104 0 00-5.11 5.109z"
fill="#232833" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M1325.98 295.516a7.127 7.127 0 00-6.56 4.337l-69.52 162.868a51.763 51.763 0 01-36.07 30.144l-6.89 1.575a7.136 7.136 0 00-5.37 8.552l7.16 31.324a7.14 7.14 0 008.55 5.369l6.89-1.575a98.19 98.19 0 0068.42-57.17l74.9-175.481c2.01-4.71-1.45-9.943-6.57-9.943h-34.94zM1592.51 246.616c0-2.865-1.7-5.465-4.37-6.507-19.2-7.502-43.75-12.882-76.4-12.88-33.54.001-63.79 11.942-85.52 35.207-21.62 23.16-32.91 55.484-32.75 92.973.31 70.559 54.81 116.829 115.04 115.93 44.56-.665 66.84-9.611 79.35-16.372 10.15-5.483 15.81-15.96 15.81-26.624v-71.516c0-12.915-10.47-23.385-23.38-23.385h-64.98a7.136 7.136 0 00-7.14 7.141v32.132c0 3.943 3.19 7.14 7.14 7.14h34.81c3.94 0 7.14 3.197 7.14 7.14v25.902c0 3.017-1.89 5.731-4.79 6.569-9.12 2.636-23.18 5.145-44.65 5.466-35.45.529-67.75-26.021-67.93-69.722-.12-28.107 8.23-48.219 20.26-61.097 11.92-12.773 29.34-20.471 51.59-20.472 30.65-.001 50.65 5.61 64.46 11.767 1.75.779 3.39 1.554 4.93 2.326 5.06 2.531 11.38-.94 11.38-6.6v-34.518z"
fill="#232833" />
<rect x="1834.4" y="288.375" width="50.876" height="177.618" rx="8.926" fill="#232833" />
<rect x="1948.64" y="426.721" width="46.413" height="39.272" rx="8.926" fill="#232833" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M1862.75 351.384a5.36 5.36 0 01-1.58-3.798l3.57-14.047c0-1.714-13.41 11.546 2.22-4.313 15.64-15.859 33.93-34.157 61.83-34.603 35.28 0 66.27 20.083 66.27 58.81v97.319c0 .037-.03.068-.07.068h-46.28a.069.069 0 01-.07-.068V354.326c0-11.316-10.71-19.538-27.66-19.538-16.96 0-30.31 7.017-44.86 20.556-2.16 2.015-5.55 2.032-7.64-.054l-5.73-3.906zM1088.55 288.375c-26.99 0-50.02 9.123-66.2 26.31-16 16.987-23.385 39.795-23.385 63.792 0 22.378 4.885 46.256 20.575 64.782 16.26 19.193 40.95 28.982 72.64 28.982 26.85 0 51.31-7.601 70.74-16.448a3.534 3.534 0 002.06-3.222v-39.972c0-2.794-3.07-4.519-5.49-3.136-16.86 9.601-41.12 19.936-67.31 19.936-22.7 0-33.91-6.695-39.95-13.829-4.79-5.653-8.11-13.663-9.58-24.113h84.69l.12.002h35.71c1.97 0 3.57-1.597 3.54-3.568-.28-17.172-2.67-39.756-12.31-59.157-5.31-10.697-13.13-21.024-24.51-28.628-11.52-7.692-25.4-11.731-41.34-11.731zm32.59 74.52c-1.22-5.491-2.88-10.624-5.11-15.099-2.78-5.597-6.11-9.505-9.94-12.062-3.69-2.469-9.09-4.516-17.54-4.516-17.07 0-28.13 5.529-35.01 12.838-4.44 4.71-7.92 11.005-9.91 18.839h77.51zM1723.16 288.375c-27 0-50.02 9.123-66.21 26.31-15.99 16.987-23.38 39.795-23.38 63.792 0 22.378 4.88 46.256 20.58 64.782 16.25 19.193 40.94 28.982 72.64 28.982 26.85 0 51.31-7.601 70.74-16.448a3.534 3.534 0 002.06-3.222v-39.972c0-2.794-3.07-4.519-5.5-3.136-16.85 9.601-41.11 19.936-67.3 19.936-22.7 0-33.91-6.695-39.95-13.829-4.79-5.653-8.11-13.663-9.58-24.113h84.68l.13.002h35.7c1.97 0 3.58-1.598 3.53-3.57-.4-17.942-3.91-40.436-14.04-59.62-11.36-21.536-31.95-39.894-64.1-39.894zm31.77 74.52c-1.4-5.258-3.24-10.24-5.56-14.634-5.83-11.054-13.62-17.043-26.21-17.043-17.07 0-28.13 5.529-35.02 12.838-4.43 4.71-7.92 11.005-9.9 18.839h76.69z"
fill="#232833" />
<defs>
<linearGradient id="prefix__paint0_linear_473_94381" x1="170.247" y1="241.641" x2="170.247" y2="480.866"
gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient id="prefix__paint1_linear_473_94381" x1="316.643" y1="529.035" x2="523.818" y2="409.422"
gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient id="prefix__paint2_linear_473_94381" x1="496.06" y1="260.135" x2="288.885" y2="140.522"
gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<filter id="prefix__filter0_f_473_94381" x="-204.756" y="-559.694" width="1977.39" height="1977.39"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="231.507" result="effect1_foregroundBlur_473_94381" />
</filter>
<filter id="prefix__filter1_f_473_94381" x="383.921" y="-182.79" width="782.014" height="782.016"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="80.104" result="effect1_foregroundBlur_473_94381" />
</filter>
<filter id="prefix__filter2_f_473_94381" x="-527.261" y="169.667" width="962.263" height="962.265"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="95.874" result="effect1_foregroundBlur_473_94381" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

+97
View File
@@ -0,0 +1,97 @@
---
title: CLI
description: "Create, preview, and render HTML video compositions from the command line."
---
The `hyperframes` CLI is the primary way to work with Hyperframes. It handles project creation, live preview, rendering, linting, and diagnostics.
```bash
npm install -g hyperframes
# or use directly with npx
npx hyperframes <command>
```
## Commands
### `init`
Create a new composition project from a template:
```bash
npx hyperframes init --template title-card
```
See [Templates](/guides/templates) for available templates.
### `dev`
Start a live preview server with hot reload:
```bash
npx hyperframes dev
```
Opens your composition in the browser at `http://localhost:3000`. Edits to `index.html` are reflected instantly.
### `render`
Render a composition to MP4:
```bash
npx hyperframes render -o output.mp4
npx hyperframes render --docker -o output.mp4 # deterministic mode
```
See [Rendering](/guides/rendering) for all options.
### `lint`
Check a composition for common issues:
```bash
npx hyperframes lint
```
Detects missing attributes, deprecated names, structural problems, and more.
### `compositions`
List all compositions in the current project:
```bash
npx hyperframes compositions
```
### `benchmark`
Find optimal render settings for your system:
```bash
npx hyperframes benchmark
```
### `doctor`
Check your environment for required dependencies:
```bash
npx hyperframes doctor
```
Verifies Node.js version, FFmpeg, Docker, and other requirements.
### `info`
Display system and project information:
```bash
npx hyperframes info
```
### `upgrade`
Update Hyperframes to the latest version:
```bash
npx hyperframes upgrade
```
+58
View File
@@ -0,0 +1,58 @@
---
title: "@hyperframes/core"
description: "Types, HTML generation, runtime, and linter."
---
The core package provides the foundational types, HTML parsing/generation, runtime, and composition linter that all other packages depend on.
```bash
npm install @hyperframes/core
```
## What's Inside
| Module | Description |
|--------|-------------|
| `core.types` | TypeScript types for compositions, clips, timelines |
| `parsers/` | HTML-to-composition parsing |
| `generators/` | Composition-to-HTML generation |
| `runtime/` | The Hyperframes runtime (IIFE + ESM builds) |
| `lint/` | Composition linter rules |
| `adapters/` | Frame Adapter types and GSAP adapter |
| `templates/` | HTML composition templates |
## Runtime Builds
The runtime is built in two formats:
- **`hyperframe.runtime.iife.js`** — for browser iframe bootstrap (preview)
- **`hyperframe.runtime.mjs`** — for Node.js/tooling/tests
Build the runtime:
```bash
pnpm --filter @hyperframes/core build:hyperframes-runtime
```
## Linter
The composition linter checks for common structural issues:
```typescript
import { lintHyperframeHtml } from '@hyperframes/core';
const issues = lintHyperframeHtml(htmlString);
```
Detected issues include:
- Missing timeline registration
- Unmuted video elements
- Missing `class="clip"` on timed elements
- Deprecated attribute names
- Missing composition dimensions
## Types
```typescript
import type { Composition, Clip, RenderConfig } from '@hyperframes/core';
```
+44
View File
@@ -0,0 +1,44 @@
---
title: "@hyperframes/engine"
description: "Seekable page-to-video capture engine."
---
The engine package provides the low-level video capture pipeline: loading an HTML page in headless Chrome and capturing it frame-by-frame.
```bash
npm install @hyperframes/engine
```
## What It Does
The engine:
1. Launches headless Chrome (`chrome-headless-shell`)
2. Loads your HTML composition
3. Uses Chrome's `HeadlessExperimental.beginFrame` API for deterministic frame capture
4. Captures each frame as a pixel buffer
5. Hands frames to FFmpeg for encoding
## Key Features
- **BeginFrame rendering** — frame-perfect capture using Chrome DevTools Protocol
- **Deterministic seek** — every frame is independently seeked, not played in realtime
- **Configurable FPS** — 24, 30, or 60 frames per second
- **Quality presets** — draft, standard, high encoding settings
## Configuration
```typescript
import type { EngineConfig } from '@hyperframes/engine';
const config: EngineConfig = {
fps: 30,
width: 1920,
height: 1080,
quality: 'standard',
};
```
## When to Use
Most users should use the [`@hyperframes/producer`](/packages/producer) package or the [CLI](/packages/cli) instead of the engine directly. The engine is useful when you need low-level control over the capture pipeline.
+63
View File
@@ -0,0 +1,63 @@
---
title: "@hyperframes/producer"
description: "Full HTML-to-video rendering pipeline."
---
The producer package combines the engine's capture capabilities with FFmpeg encoding to deliver a complete rendering pipeline.
```bash
npm install @hyperframes/producer
```
## What It Does
The producer orchestrates the full render:
1. **Loads** the composition HTML
2. **Injects** the Hyperframes runtime
3. **Waits** for readiness gates (`__playerReady`, `__renderReady`)
4. **Captures** frames using the engine's BeginFrame pipeline
5. **Encodes** to MP4 via FFmpeg
6. **Mixes** audio tracks
## Features
- **Docker-based rendering** for deterministic output
- **Quality presets** — draft, standard, high
- **GPU encoding** support (NVENC, VideoToolbox, VAAPI)
- **Parallel workers** for faster rendering
- **Benchmark tooling** for finding optimal settings
- **Regression harness** for visual regression testing
## Programmatic Usage
```typescript
import { render } from '@hyperframes/producer';
await render({
input: './my-video/index.html',
output: './output.mp4',
fps: 30,
quality: 'standard',
});
```
## Docker Rendering
For deterministic output, the producer renders inside a Docker container with a pinned Chrome version and font set:
```bash
# Via the CLI
npx hyperframes render --docker -o output.mp4
```
## Regression Testing
The producer includes a regression harness for comparing render output against golden baselines:
```bash
cd packages/producer
pnpm docker:build:test # Build test Docker image
pnpm docker:test # Run regression tests
pnpm docker:test:update # Regenerate golden baselines
```
+40
View File
@@ -0,0 +1,40 @@
---
title: "@hyperframes/studio"
description: "Composition editor UI."
---
The studio package provides a visual editor for creating and previewing Hyperframes compositions in the browser.
```bash
npm install @hyperframes/studio
```
## What It Does
The studio is a React-based composition editor that provides:
- **Live preview** — see your composition rendered in real-time
- **Timeline view** — visual representation of clips and their timing
- **Player controls** — play, pause, seek through your composition
- **Hot reload** — edit HTML and see changes instantly
## Running the Studio
```bash
# From the monorepo root
pnpm dev
# Or directly
pnpm --filter @hyperframes/studio dev
```
The studio starts a development server with live preview.
## Architecture
The studio is a React application that:
1. Loads your composition HTML into an iframe
2. Injects the Hyperframes runtime for preview playback
3. Provides a player interface for seeking and playback control
4. Watches for file changes and hot-reloads the preview
+84
View File
@@ -0,0 +1,84 @@
---
title: Quickstart
description: "Create, preview, and render your first Hyperframes video."
---
## Create a Project
```bash
npx create-hyperframe my-video
cd my-video
```
This scaffolds a project with an `index.html` composition and assets directory.
## Preview in Browser
```bash
npx hyperframes dev
```
Opens a live preview at `http://localhost:3000`. Edit `index.html` and the preview updates automatically.
## Render to MP4
```bash
npx hyperframes render -o output.mp4
```
Renders your composition to an MP4 file using the local rendering pipeline (Puppeteer + FFmpeg).
<Note>
Local rendering requires FFmpeg. Install it with `brew install ffmpeg` (macOS), `sudo apt install ffmpeg` (Ubuntu), or download from [ffmpeg.org](https://ffmpeg.org/download.html).
</Note>
## Project Structure
After `create-hyperframe`, your project looks like this:
```
my-video/
├── index.html # Root composition
├── compositions/ # Sub-compositions (optional)
└── assets/ # Media files (video, audio, images)
```
## Your First Composition
Every Hyperframes video is an HTML file. Here's a minimal example:
```html
<div id="root" data-composition-id="my-video"
data-start="0" data-width="1920" data-height="1080">
<h1 id="title" class="clip"
data-start="0" data-duration="5" data-track-index="0"
style="font-size: 72px; color: white; text-align: center;">
Hello, Hyperframes!
</h1>
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script>
const tl = gsap.timeline({ paused: true });
tl.from("#title", { opacity: 0, y: -50, duration: 1 }, 0);
window.__timelines = window.__timelines || {};
window.__timelines["my-video"] = tl;
</script>
</div>
```
Key rules:
- **Root element** needs `data-composition-id`, `data-width`, and `data-height`
- **Timed elements** need `data-start`, `data-duration`, `data-track-index`, and `class="clip"`
- **GSAP timeline** must be paused and registered in `window.__timelines`
## Requirements
- **Node.js** 20+
- **pnpm** (recommended) or npm
- **FFmpeg** for local rendering
- **Docker** (optional) for deterministic rendering
<Card title="Templates" icon="grid-2" href="/guides/templates">
Browse built-in templates for common video patterns
</Card>
+174
View File
@@ -0,0 +1,174 @@
---
title: HTML Schema Reference
description: "Complete reference for authoring Hyperframes HTML compositions."
---
This is the full schema reference for Hyperframes compositions. For a gentler introduction, see [Compositions](/concepts/compositions) and [Data Attributes](/concepts/data-attributes).
## Overview
Hyperframes uses HTML as the source of truth for describing a video:
- **HTML clips** = video, image, audio, composition
- **Data attributes** = timing, metadata, styling
- **CSS** = positioning and appearance
- **GSAP timeline** = animations and playback sync
## Framework-Managed Behavior
The framework reads data attributes and automatically manages:
- **Primitive clip timeline entries** — reads `data-start`, `data-duration`, and `data-track-index` from clips and adds them to the GSAP timeline
- **Media playback** (play, pause, seek) for `<video>` and `<audio>`
- **Clip lifecycle** — clips are mounted/unmounted based on `data-start` and `data-duration`
- **Timeline synchronization** — keeps media in sync with the GSAP master timeline
- **Media loading** — waits for all media to load before resolving timing
Mounting/unmounting controls **presence**, not appearance. Transitions (fade in, slide in) are animated in scripts.
<Warning>
Do not manually call `video.play()`, `video.pause()`, set `audio.currentTime`, or mount/unmount clips in scripts. The framework owns media playback and clip lifecycle.
</Warning>
## Viewport
Every composition must include `data-width` and `data-height`:
```html
<div id="main" data-composition-id="my-video"
data-start="0" data-width="1920" data-height="1080">
<!-- clips -->
</div>
```
Common sizes:
- **Landscape**: `data-width="1920" data-height="1080"`
- **Portrait**: `data-width="1080" data-height="1920"`
## All Clip Attributes
| Attribute | Applies To | Required | Description |
|-----------|-----------|----------|-------------|
| `id` | All | Yes | Unique identifier (e.g., `"el-1"`) |
| `class="clip"` | Visible elements | Yes | Enables runtime visibility management |
| `data-start` | All | Yes | Start time in seconds, or clip ID reference |
| `data-duration` | video, img, audio | Img: yes, others: optional | Duration in seconds |
| `data-track-index` | All | Yes | Timeline track number (z-ordering) |
| `data-media-start` | video, audio | No | Playback offset in source file (seconds) |
| `data-volume` | audio, video | No | Volume 0-1 |
| `data-composition-id` | div | On compositions | Unique composition ID |
| `data-composition-src` | div | No | Path to external composition HTML file |
| `data-width` | div | On compositions | Composition width in pixels |
| `data-height` | div | On compositions | Composition height in pixels |
## Video Clips
```html
<video
id="el-1"
data-start="0"
data-duration="15"
data-track-index="0"
data-media-start="0"
src="./assets/video.mp4"
></video>
```
- `data-duration` is optional — defaults to remaining duration of source file from `data-media-start`
- If source media runs out before `data-duration`, the clip shows the last frame
## Image Clips
```html
<img
id="el-2"
class="clip"
data-start="5"
data-duration="4"
data-track-index="1"
src="./assets/overlay.png"
/>
```
- `data-duration` is **required** for images
## Audio Clips
```html
<audio
id="el-4"
data-start="0"
data-duration="30"
data-track-index="2"
src="./assets/music.mp3"
></audio>
```
- `data-duration` is optional — defaults to remaining duration of source file
- Audio clips are invisible
## Composition Clips
```html
<div
id="el-5"
data-composition-id="intro-anim"
data-composition-src="compositions/intro-anim.html"
data-start="0"
data-track-index="3"
></div>
```
- Compositions do **not** use `data-duration` — duration comes from the GSAP timeline
- External compositions are loaded from `data-composition-src` and wrapped in `<template>` tags
## Relative Timing
Reference another clip's ID in `data-start` to mean "start when that clip ends":
```html
<video id="intro" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
<video id="main" data-start="intro" data-duration="20" data-track-index="0" src="..."></video>
```
Offsets: `data-start="intro + 2"` (2s gap) or `data-start="intro - 0.5"` (0.5s overlap).
## Timeline Contract
The framework initializes `window.__timelines = {}` before any scripts run. Every composition must register a timeline:
```javascript
const tl = gsap.timeline({ paused: true });
// ... add tweens
window.__timelines["<data-composition-id>"] = tl;
```
### Rules
- Every composition needs a script to create and register its timeline
- All timelines start paused (`{ paused: true }`)
- The framework auto-nests sub-timelines — do not manually add them
- Duration comes from `tl.duration()` — no `data-duration` on compositions
- Timelines must be finite
## Caption Discoverability
For caption compositions, add these attributes to the root node:
```html
<div
data-composition-id="captions"
data-timeline-role="captions"
data-caption-root="true"
...
>
```
## Output Checklist
- [ ] Every composition has `data-width` and `data-height`
- [ ] Each reusable composition is in its own HTML file
- [ ] Compositions loaded via `data-composition-src`
- [ ] Each composition file uses `<template>` wrapper
- [ ] All timelines registered in `window.__timelines`
- [ ] Timed visible elements have `class="clip"`