docs: improve quality based on Remotion/Stripe/Tailwind patterns

Major improvements across all 18 pages:

- Use Mintlify components: <Steps> for tutorials, <Tabs> for alternatives,
  <CodeGroup> for multi-platform commands, <Tree> for directory structures,
  <AccordionGroup> for FAQ/scannable content, <Mermaid> for diagrams
- Add filename annotations to all code blocks (e.g., ```html index.html)
- Add numbered comments inside multi-step code examples
- Show expected terminal output after CLI commands
- Add "When to use" / "When NOT to use" sections to all package pages
- Add "Next Steps" CardGroup to every page (no dead-end pages)
- Cross-link between pages at point of curiosity (not just "see also" dumps)
- Expand thin pages (engine, studio) with architecture details and examples
- Add decision guides (rendering modes, template selection)
- Use <Warning> and <Note> sparingly (max 2-3 per page)

Also adds DOCS_GUIDELINES.md at repo root with writing standards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
James
2026-03-23 23:57:01 +00:00
co-authored by Claude Opus 4.6
parent 00bd2e5ae2
commit 915fe2f47a
19 changed files with 2183 additions and 556 deletions
+148
View File
@@ -0,0 +1,148 @@
# Documentation Guidelines
Standards for writing and maintaining Hyperframes documentation. Based on patterns from Remotion, Stripe, Tailwind CSS, and Astro.
## Core Principles
1. **One-sentence intro rule** — Every page opens with a single sentence telling the reader what this page helps them do or understand. No preamble, no history.
2. **Outcome before implementation** — Show what the code produces (rendered result, terminal output, file structure) before showing the code itself.
3. **Show, don't tell** — Use concrete examples with realistic values. Never use `foo`/`bar`/`baz`. Prefer a working HTML snippet over a description of what to write.
4. **Two content modes** — Guides build narratives with progressive complexity. References enable scanning with standardized structure. Never mix them.
5. **No dead ends** — Every page links forward (next steps), backward (prerequisites), and sideways (related concepts). Readers should never reach a page with nowhere to go.
## Page Structure
### Guides (concepts/, guides/)
```
Title
├── One-sentence purpose statement
├── What this looks like (output, demo, or visual)
├── Minimal working example
├── Deeper explanation with progressive complexity
├── Common patterns / best practices
├── Warnings and pitfalls (sparingly)
└── Next steps (cards or links to related pages)
```
### Reference pages (reference/)
```
Title
├── One-sentence definition
├── Complete attribute/API table
├── Detailed section per item (type, default, description, example)
├── Rules and constraints
└── Related pages
```
### Package pages (packages/)
```
Title
├── One-line description + install command
├── When to use this package (and when NOT to)
├── Key features list
├── Minimal usage example with expected output
├── Configuration reference
└── Related packages
```
## Writing Style
- **Second person, active voice, imperative mood**: "Use X to do Y." Not "The developer should consider using X."
- **Present tense**: "The runtime manages media playback." Not "The runtime will manage..."
- **Be direct**: "This breaks rendering." Not "This may potentially cause issues with the rendering pipeline."
- **Prerequisites at point of need**: State requirements where they matter, not in a wall at the top.
- **Conversational but precise**: Friendly tone, exact technical details.
## Code Examples
### Always annotate code blocks
```mdx
```html index.html
<div data-composition-id="root" ...>
```
```
The filename after the language tag tells readers where the code goes.
### Use numbered comments for multi-step code
```javascript
// 1. Create a paused timeline
const tl = gsap.timeline({ paused: true });
// 2. Add animations
tl.from("#title", { opacity: 0, y: -50, duration: 1 }, 0);
// 3. Register the timeline
window.__timelines["my-video"] = tl;
```
### Show expected output
After CLI commands, show what the user should see:
```bash
npx hyperframes dev
# ✓ Server running at http://localhost:3000
# ✓ Watching for changes...
```
### Use CodeGroup for multi-platform commands
```mdx
<CodeGroup>
```bash macOS
brew install ffmpeg
```
```bash Ubuntu
sudo apt install ffmpeg
```
</CodeGroup>
```
## Mintlify Components — When to Use
| Component | Use When |
|-----------|----------|
| `<Steps>` | Sequential setup or tutorial instructions |
| `<CodeGroup>` | Same action across platforms/languages |
| `<Tabs>` | Alternative approaches with equal weight |
| `<Card>` / `<Columns>` | Navigation to related pages, next steps |
| `<Accordion>` | FAQ or optional detail that would bloat the page |
| `<Note>` | Non-obvious behavior the reader should know |
| `<Warning>` | Something that will break if ignored |
| `<Tip>` | Helpful shortcut or best practice |
| `<Info>` | Context that aids understanding |
| `<Tree>` | File/directory structure |
| `<Frame>` | Screenshots or diagrams with captions |
### Callout budget: max 2-3 per page
More than 3 callouts creates alert fatigue and readers skip them all. Reserve `<Warning>` for things that genuinely break. Use inline prose for tips.
## Cross-Linking
- **Link at the point of curiosity**: When you mention a concept that has its own page, link it immediately. Don't hoard links.
- **"See also" at page bottom**: Only for genuinely related content that doesn't fit inline.
- **Next steps cards**: End guide pages with `<Card>` links to logical next pages.
## File Conventions
- All doc pages are `.mdx` (not `.md`)
- Use kebab-case for filenames: `frame-adapters.mdx`, not `frameAdapters.mdx`
- Frontmatter requires `title` and `description`
- Description should be under 160 characters (used for SEO/social)
## Maintenance
- Docs live in the repo at `/docs` and deploy automatically on merge to `main`
- PRs that change user-facing behavior should update relevant doc pages
- Run `mint validate` and `mint broken-links` before pushing doc changes
+106 -52
View File
@@ -3,105 +3,159 @@ 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.
A composition is an HTML document that defines a video timeline. Every clip -- video, image, audio -- lives inside a composition.
## Structure
Every composition needs a root element with `data-composition-id`:
```html
```html index.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.
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:
A clip is any discrete block on the timeline, represented as an HTML element with [data attributes](/concepts/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)
- `<video>` -- Video clips, B-roll, A-roll
- `<img>` -- Static images, overlays
- `<audio>` -- Music, sound effects
- `<div data-composition-id="...">` -- Nested compositions (animations, grouped sequences)
See the [HTML Schema Reference](/reference/html-schema) for the full list of attributes on each clip type.
## Nested Compositions
Embed one composition inside another by loading it from an external HTML file:
You can embed one composition inside another in two ways: loading from an external file or defining it inline. External files are the recommended approach for reusable compositions.
```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>
```
<Tabs>
<Tab title="External file">
Reference another HTML file with `data-composition-src`. The framework automatically fetches the file, extracts the `<template>` content, mounts it, executes scripts, and registers the timeline.
The framework automatically fetches the HTML file, extracts the `<template>` content, mounts it, executes scripts, and registers the timeline.
```html index.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>
```
### Composition File Format
Each external composition file wraps its content in a `<template>` tag:
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>
```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>
<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>
```
</Tab>
<Tab title="Inline">
Define a nested composition directly inside the parent. This is simpler for one-off compositions that do not need to be reused.
<script>
const tl = gsap.timeline({ paused: true });
tl.from(".title", { opacity: 0, y: -50, duration: 1 });
window.__timelines["intro-anim"] = tl;
</script>
</div>
</template>
```
```html index.html
<div id="root" data-composition-id="root"
data-start="0" data-width="1920" data-height="1080">
<!-- Inline nested composition -->
<div id="el-5" data-composition-id="intro-anim"
data-start="0" data-track-index="3"
data-width="1920" data-height="1080">
<div class="title">Welcome!</div>
</div>
<script>
// Timeline for the inline composition
const introTl = gsap.timeline({ paused: true });
introTl.from(".title", { opacity: 0, y: -50, duration: 1 });
window.__timelines["intro-anim"] = introTl;
</script>
</div>
```
Inline compositions do not use `<template>` tags or `data-composition-src`.
</Tab>
</Tabs>
### Project Structure
```
project/
├── index.html # Root composition
├── compositions/
│ ├── intro-anim.html # Intro animation
├── caption-overlay.html # Captions
│ └── outro-title.html # Outro
```
<Tree>
<Tree.Folder name="project" defaultOpen>
<Tree.File name="index.html" />
<Tree.Folder name="compositions" defaultOpen>
<Tree.File name="intro-anim.html" />
<Tree.File name="caption-overlay.html" />
<Tree.File name="outro-title.html" />
</Tree.Folder>
<Tree.Folder name="assets">
<Tree.File name="video.mp4" />
<Tree.File name="music.mp3" />
<Tree.File name="logo.png" />
</Tree.Folder>
</Tree.Folder>
</Tree>
## 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.
- **HTML** -- primitive clips (`video`, `img`, `audio`, nested compositions). The declarative structure: what plays, when, and on which track. Controlled by [data attributes](/concepts/data-attributes).
- **Script** -- effects, transitions, dynamic DOM, canvas, SVG -- creative animation via [GSAP](/guides/gsap-animation). 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.
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. See [Common Mistakes](/guides/common-mistakes) for examples.
</Warning>
## Variables
Compositions can expose variables for dynamic content:
```html
```html compositions/card.html
<div data-composition-id="card" data-var-title="string" data-var-color="color">
```
Variables make compositions reusable as [templates](/guides/templates) -- the same composition can render different content by injecting variable values at render time.
## Listing Compositions
Use the CLI to see all compositions in a project:
Use the [CLI](/packages/cli) to see all compositions in a project:
```bash
npx hyperframes compositions
```
## Next Steps
<CardGroup cols={2}>
<Card title="Data Attributes" icon="code" href="/concepts/data-attributes">
Full reference for timing, media, and composition attributes
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Add animations to your compositions with GSAP timelines
</Card>
<Card title="Templates" icon="grid-2" href="/guides/templates">
Start from built-in templates for common video patterns
</Card>
<Card title="HTML Schema Reference" icon="book" href="/reference/html-schema">
Complete schema for authoring compositions
</Card>
</CardGroup>
+37 -12
View File
@@ -3,7 +3,7 @@ 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.
Hyperframes uses HTML data attributes to control timing, media playback, and [composition](/concepts/compositions) structure. These are the declarative building blocks of every video.
## Timing Attributes
@@ -25,16 +25,16 @@ Hyperframes uses HTML data attributes to control timing, media playback, and com
| Attribute | Example | Description |
|-----------|---------|-------------|
| `data-composition-id` | `"root"` | Unique ID for composition wrapper (required on every composition) |
| `data-composition-id` | `"root"` | Unique ID for [composition](/concepts/compositions) 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 |
| `data-composition-src` | `"./intro.html"` | Path to external [composition](/concepts/compositions) HTML file |
## Element Visibility
Add `class="clip"` to all timed elements so the runtime can manage their visibility lifecycle:
```html
```html index.html
<h1 id="title" class="clip"
data-start="0" data-duration="5" data-track-index="0">
Hello World
@@ -45,7 +45,7 @@ Add `class="clip"` to all timed elements so the runtime can manage their visibil
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
```html index.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>
@@ -57,7 +57,7 @@ Instead of calculating absolute start times, a clip can reference another clip's
Add `+ N` or `- N` after the ID to offset from the end of the referenced clip:
```html
```html index.html
<!-- 2-second gap after intro -->
<video id="scene-a" data-start="intro + 2" data-duration="20"
data-track-index="0" src="..."></video>
@@ -68,12 +68,37 @@ Add `+ N` or `- N` after the ID to offset from the end of the referenced clip:
```
<Note>
Overlapping clips must be on different tracks clips on the same track cannot overlap in time.
Overlapping clips must be on different tracks -- clips on the same track cannot overlap in time.
</Note>
### Rules
<Accordion title="Relative timing rules and constraints">
**Same composition only** -- references resolve within the clip's parent [composition](/concepts/compositions). You cannot reference a clip in a sibling or parent composition.
- **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>`
**No circular references** -- A cannot start after B if B starts after A. The resolver detects cycles and throws an error.
**Referenced clip must have a known duration** -- either an explicit `data-duration` or a duration inferred from source media. If the referenced clip has no known duration, the reference cannot resolve.
**Parsing rules** -- if the value is a valid number, it is treated as absolute seconds. Otherwise it is parsed as one of:
- `<id>` -- start when that clip ends
- `<id> + <number>` -- start N seconds after that clip ends
- `<id> - <number>` -- start N seconds before that clip ends
**Chain length** -- references can chain (`A` -> `B` -> `C`), but deeply nested chains make the timeline harder to reason about. Keep chains under 3-4 levels for readability.
</Accordion>
## Next Steps
<CardGroup cols={2}>
<Card title="Compositions" icon="layer-group" href="/concepts/compositions">
How compositions use data attributes to define video structure
</Card>
<Card title="HTML Schema Reference" icon="book" href="/reference/html-schema">
Complete attribute reference with per-element details
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Animate elements alongside data-attribute-driven timing
</Card>
<Card title="Common Mistakes" icon="triangle-exclamation" href="/guides/common-mistakes">
Pitfalls to avoid when setting up timing and attributes
</Card>
</CardGroup>
+60 -18
View File
@@ -3,26 +3,49 @@ 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.
Hyperframes is built around a core guarantee: **the same [composition](/concepts/compositions) 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:
The rendering pipeline is frame-by-frame and seek-driven. No realtime playback is involved -- every frame is independently seeked and captured.
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
<Steps>
<Step title="Frame clock">
The [engine](/packages/engine) computes the time for each frame using integer math: `time = floor(frame) / fps`. There is no wall-clock dependency -- rendering is entirely decoupled from real time.
</Step>
<Step title="Seek">
The [frame adapter](/concepts/frame-adapters) receives a `seekFrame(frame)` call and deterministically positions all animations, DOM state, and canvas content to the exact frame. The adapter's `renderSeek` pauses all [GSAP](/guides/gsap-animation) timelines and seeks them to the computed time.
</Step>
<Step title="Capture">
Chrome's `HeadlessExperimental.beginFrame` API captures the pixel buffer for the current frame. This is a single, atomic operation -- no partial paints or race conditions.
</Step>
<Step title="Encode">
FFmpeg encodes the captured frames into the final MP4 video. Audio tracks from `<audio>` and `<video>` elements are mixed in during this stage.
</Step>
</Steps>
No realtime playback is involved in rendering. Every frame is independently seeked and captured.
```mermaid
graph LR
A["Frame Clock<br/>t = frame / fps"] --> B["Seek<br/>adapter.seekFrame(frame)"]
B --> C["Capture<br/>beginFrame API"]
C --> D["Encode<br/>FFmpeg"]
D --> E["MP4"]
style A fill:#7559FF,color:#fff
style B fill:#7559FF,color:#fff
style C fill:#7559FF,color:#fff
style D fill:#7559FF,color:#fff
style E fill:#735CE5,color:#fff
```
## 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
- **No wall-clock dependencies** -- rendering does not 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](/concepts/compositions) has a known, finite length
These same rules apply to every [frame adapter](/concepts/frame-adapters). If you are building a custom adapter, you must follow the [determinism contract](/concepts/frame-adapters#determinism-contract).
## Docker Mode
@@ -37,13 +60,15 @@ Docker mode uses an exact Chrome version and font set, ensuring:
- Same system fonts (no platform-specific font substitution)
- Same FFmpeg encoder version
See the [Rendering guide](/guides/rendering) for all rendering options.
## 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
- **One runtime** -- the same `hyperframe.runtime` drives both preview and render
- **Producer-canonical behavior** -- the [producer's](/packages/producer) seek semantics are the source of truth
- **Readiness gates** -- `__playerReady` and `__renderReady` ensure the [composition](/concepts/compositions) 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.
@@ -51,9 +76,26 @@ The browser preview and the rendered MP4 should match. Hyperframes achieves this
## For Adapter Authors
If you're building a [Frame Adapter](/concepts/frame-adapters), your adapter must follow the determinism contract:
If you are building a [frame adapter](/concepts/frame-adapters), your adapter must follow the determinism contract:
- `seekFrame(frame)` must be idempotent same frame, same result
- `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`
- Clean lifecycle: `init` -> `seekFrame` (N times) -> `destroy`
## Next Steps
<CardGroup cols={2}>
<Card title="Frame Adapters" icon="plug" href="/concepts/frame-adapters">
Build adapters that uphold the determinism contract
</Card>
<Card title="Rendering" icon="film" href="/guides/rendering">
Render to MP4 locally or in Docker
</Card>
<Card title="@hyperframes/producer" icon="clapperboard" href="/packages/producer">
The full rendering pipeline that orchestrates deterministic output
</Card>
<Card title="Common Mistakes" icon="triangle-exclamation" href="/guides/common-mistakes">
Pitfalls that break determinism and how to avoid them
</Card>
</CardGroup>
+58 -14
View File
@@ -9,9 +9,40 @@ The Frame Adapter pattern is how Hyperframes supports multiple animation runtime
If a runtime can answer that, it can plug into Hyperframes.
<Info>
The Adapter API is currently at **v0** (experimental). Breaking changes are possible until v1. The core contract (seek-by-frame, deterministic output) is stable, but method signatures may evolve.
</Info>
## How It Works
The host application (the [engine](/packages/engine) or [producer](/packages/producer)) drives rendering by calling adapter methods in a strict sequence. The adapter never controls its own clock -- it only responds to seek commands.
```mermaid
sequenceDiagram
participant Host as Host (Engine)
participant Adapter as Frame Adapter
participant Chrome as Chrome / Browser
Host->>Adapter: init(context)
Adapter-->>Host: ready
Host->>Adapter: getDurationFrames()
Adapter-->>Host: 300 frames
loop For each frame 0..300
Host->>Host: normalize frame (clamp, floor)
Host->>Adapter: seekFrame(frame)
Adapter->>Chrome: Update DOM / canvas state
Adapter-->>Host: done
Host->>Chrome: Capture pixel buffer
end
Host->>Adapter: destroy()
Adapter-->>Host: cleaned up
```
## Adapter API (v0)
```typescript
```typescript adapters/types.ts
type FrameAdapterContext = {
compositionId: string;
fps: number;
@@ -41,13 +72,13 @@ type FrameAdapter = {
The host normalizes frames before calling the adapter:
```typescript
```typescript engine/render-loop.ts
normalizedFrame = clamp(Math.floor(frame), 0, durationFrames);
```
A typical render loop:
```typescript
```typescript engine/render-loop.ts
await adapter.init?.({ compositionId, fps, width, height, rootElement });
const durationFrames = adapter.getDurationFrames();
@@ -61,7 +92,7 @@ await adapter.destroy?.();
## Determinism Contract
These rules are non-negotiable for any adapter:
These rules are non-negotiable for any adapter. They are the foundation of Hyperframes' [deterministic rendering](/concepts/determinism) guarantee.
- Canonical clock: `t = frame / fps`
- No wall-clock dependencies (`Date.now`, drift-dependent logic)
@@ -77,24 +108,37 @@ First-party adapters:
| Runtime | Seek Method | Status |
|---------|------------|--------|
| GSAP | `timeline.seek(frame / fps)` | Available |
| [GSAP](/guides/gsap-animation) | `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.
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`
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 do not 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>
## Next Steps
<CardGroup cols={2}>
<Card title="Deterministic Rendering" icon="lock" href="/concepts/determinism">
Understand the determinism guarantees adapters must uphold
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
See the first-party GSAP adapter in action
</Card>
<Card title="@hyperframes/engine" icon="gear" href="/packages/engine">
The capture engine that drives adapters during rendering
</Card>
<Card title="Contributing" icon="code-branch" href="/contributing">
Build and contribute your own adapter
</Card>
</CardGroup>
+114 -22
View File
@@ -3,57 +3,149 @@ title: Contributing
description: "How to contribute to Hyperframes."
---
Thanks for your interest in contributing to Hyperframes!
Thanks for your interest in contributing to Hyperframes! This guide covers everything you need to get set up, run tests, and submit a pull request.
## 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`
<Steps>
<Step title="Fork and clone">
Fork the repository on GitHub, then clone your fork:
```bash
git clone https://github.com/YOUR_USERNAME/hyperframes.git
cd hyperframes
```
</Step>
<Step title="Install dependencies">
Hyperframes uses [pnpm](https://pnpm.io/) for package management:
```bash
pnpm install
```
</Step>
<Step title="Build all packages">
Build the monorepo to ensure everything compiles:
```bash
pnpm build
```
</Step>
<Step title="Run the studio">
Start the development server to verify your setup:
```bash
pnpm dev
```
If the studio opens at `http://localhost:3000` with a preview, your environment is ready.
</Step>
<Step title="Create a branch">
Create a feature branch for your work:
```bash
git checkout -b my-feature
```
</Step>
</Steps>
## Development
### Common Commands
```bash
pnpm install # Install all dependencies
pnpm dev # Run the studio (composition editor)
pnpm dev # Start the studio (composition editor + live preview)
pnpm build # Build all packages
pnpm -r typecheck # Type-check all packages
```
### Running Tests
<CodeGroup>
```bash Core
pnpm --filter @hyperframes/core test
```
```bash Engine
pnpm --filter @hyperframes/engine test
```
```bash Runtime Contract
pnpm --filter @hyperframes/core test:hyperframe-runtime-ci
```
```bash Producer (Docker)
cd packages/producer && pnpm docker:build:test && pnpm docker:test
```
</CodeGroup>
### Running All 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
pnpm -r test
```
## 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 |
| Package | Path | Description |
|---------|------|-------------|
| [`@hyperframes/core`](/packages/core) | `packages/core` | Types, HTML generation, runtime, linter |
| [`@hyperframes/engine`](/packages/engine) | `packages/engine` | Seekable page-to-video capture engine |
| [`@hyperframes/producer`](/packages/producer) | `packages/producer` | Full rendering pipeline (capture + encode) |
| [`@hyperframes/studio`](/packages/studio) | `packages/studio` | Composition editor UI |
| [`hyperframes`](/packages/cli) | `packages/cli` | CLI for creating, previewing, and rendering |
## What to Work On
Not sure where to start? Here are some ideas:
- **Good first issues** — look for issues labeled `good first issue` on GitHub
- **Documentation** — improve docs, add examples, fix typos
- **Linter rules** — add new rules to catch more composition mistakes
- **Templates** — create new starter templates
- **Bug fixes** — check the issue tracker for reported bugs
## 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
### Commit Format
Use [conventional commit](https://www.conventionalcommits.org/) format for all commits and PR titles:
```
feat: add timeline export
fix: resolve seek overflow at composition boundary
docs: add GSAP easing examples
refactor: extract frame buffer pool into shared module
test: add regression test for nested composition timing
```
### CI Requirements
All of the following must pass before your PR can be merged:
- **Build** — `pnpm build` succeeds
- **Type check** — `pnpm -r typecheck` reports no errors
- **Tests** — all test suites pass
- **Semantic PR title** — PR title follows conventional commit format
### Review Process
- PRs require at least 1 approval from a maintainer
- Keep PRs focused — one feature or fix per PR
- Include a clear description of what changed and why
- Add tests for new features and bug fixes
## 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
- For bug reports, include:
- Steps to reproduce
- Expected behavior vs. actual behavior
- Hyperframes version (`npx hyperframes info`)
- Operating system and Node.js version
## Code of Conduct
## Community
This project follows the [Contributor Covenant Code of Conduct](https://github.com/heygen-com/hyperframes/blob/main/CODE_OF_CONDUCT.md).
<CardGroup cols={2}>
<Card title="GitHub Issues" icon="github" href="https://github.com/heygen-com/hyperframes/issues">
Report bugs, request features, and discuss ideas.
</Card>
<Card title="Code of Conduct" icon="handshake" href="https://github.com/heygen-com/hyperframes/blob/main/CODE_OF_CONDUCT.md">
Our community standards and expectations.
</Card>
</CardGroup>
## License
+145 -38
View File
@@ -3,63 +3,170 @@ 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`.
These are mistakes that cannot be caught by the linter. For automated checks, run `npx hyperframes lint` (see [CLI](/packages/cli#lint)).
## Animating Video Element Dimensions
<Warning>
The first two mistakes — animating video element dimensions and controlling media playback in scripts — are the most common causes of broken compositions. If your video looks wrong, check these first.
</Warning>
**Symptom**: Video frames stop updating, or browser performance drops.
<AccordionGroup>
<Accordion title="Animating video element dimensions">
**Symptom:** Video frames stop updating, or browser performance drops severely.
**Cause**: GSAP animating `width`, `height`, `top`, `left` directly on a `<video>` element can cause the browser to stop rendering frames.
**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);
**Before (broken):**
// FIXED — animate a wrapper div, video fills it at 100%
tl.to("#pip-wrapper", { width: 500, height: 280, top: 700, left: 1400 }, 26);
```
```javascript index.html
// Animating the video element directly — causes frame rendering to stop
tl.to("#el-video", { 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.
**After (fixed):**
## Controlling Media Playback in Scripts
```html index.html
<!-- Wrap the video in a div and animate the wrapper -->
<div id="pip-wrapper" style="position: absolute; width: 1920px; height: 1080px;">
<video id="el-video" data-start="0" data-track-index="0"
src="./assets/video.mp4" style="width: 100%; height: 100%;"></video>
</div>
```
**Symptom**: Audio/video playback is out of sync, or plays when it shouldn't.
```javascript index.html
// Animate the wrapper — the video fills it at 100%
tl.to("#pip-wrapper", { width: 500, height: 280, top: 700, left: 1400 }, 26);
```
**Cause**: Calling `video.play()`, `video.pause()`, or setting `audio.currentTime` in your scripts. The framework owns all media playback.
Use a non-timed wrapper `<div>` for visual effects like picture-in-picture. Animate the wrapper; let the video fill it via CSS.
</Accordion>
```javascript
// BROKEN — conflicts with framework media sync
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;
<Accordion title="Controlling media playback in scripts">
**Symptom:** Audio/video playback is out of sync, or plays when it should not.
// 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);
```
**Cause:** Calling `video.play()`, `video.pause()`, or setting `audio.currentTime` in your scripts. The [framework owns all media playback](/reference/html-schema#framework-managed-behavior).
## Composition Duration Shorter Than Video
**Before (broken):**
**Symptom**: Video plays for a few seconds then stops. Timeline shows 8-10 seconds even though the video is minutes long.
```javascript index.html
// Conflicts with framework media sync
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;
```
**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.
**After (fixed):**
```javascript
// BROKEN — timeline is only 7.8s long, video cuts off
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
```javascript index.html
// Don't control media playback at all. The framework handles it.
// Use GSAP for visual animations only:
tl.to("#el-video", { opacity: 1, duration: 0.5 }, 0);
```
// 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
```
The framework reads [`data-start`](/concepts/data-attributes#timing-attributes), [`data-media-start`](/concepts/data-attributes#media-attributes), and [`data-volume`](/concepts/data-attributes#media-attributes) to control when and how media plays. See [Compositions: Two Layers](/concepts/compositions#two-layers-primitives-and-scripts) for the separation between HTML primitives and scripts.
</Accordion>
`tl.set({}, {}, TIME)` adds a zero-duration tween at the specified time, extending the timeline without affecting any elements.
<Accordion title="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](/guides/gsap-animation#timeline-duration-and-composition-duration), not `data-duration` on the video. If your last GSAP animation ends at 8 seconds, the composition is 8 seconds long — regardless of how long the video source is.
**Before (broken):**
```javascript index.html
// Timeline is only 7.8s long — video cuts off after 7.8 seconds
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
```
**After (fixed):**
```javascript index.html
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
// Extend the timeline to 283 seconds to match the video length
tl.set({}, {}, 283);
```
`tl.set({}, {}, TIME)` adds a zero-duration tween at the specified time, extending the timeline without affecting any elements.
<Tip>
A quick check: run `npx hyperframes compositions` to see the resolved duration of each composition. If it is shorter than expected, your timeline needs extending.
</Tip>
</Accordion>
<Accordion title="Missing class='clip' on timed elements">
**Symptom:** Elements are always visible, ignoring their `data-start` and `data-duration` timing.
**Cause:** The [`class="clip"`](/concepts/data-attributes#element-visibility) attribute tells the runtime to manage the element's visibility lifecycle. Without it, the element is always rendered.
**Before (broken):**
```html index.html
<!-- Missing class="clip" — this element is always visible -->
<h1 id="title" data-start="2" data-duration="5" data-track-index="0">
Hello World
</h1>
```
**After (fixed):**
```html index.html
<!-- With class="clip", the runtime shows this only from 2s to 7s -->
<h1 id="title" class="clip" data-start="2" data-duration="5" data-track-index="0">
Hello World
</h1>
```
<Note>
The linter catches this one: `npx hyperframes lint` will flag timed elements missing `class="clip"`.
</Note>
</Accordion>
<Accordion title="Timeline key doesn't match data-composition-id">
**Symptom:** Animations don't play. The composition appears static.
**Cause:** The key used in `window.__timelines` must exactly match the [`data-composition-id`](/concepts/data-attributes#composition-attributes) attribute on the composition root element.
**Before (broken):**
```javascript index.html
// Mismatch: HTML says "my-video", script registers "root"
// <div data-composition-id="my-video" ...>
window.__timelines["root"] = tl;
```
**After (fixed):**
```javascript index.html
// Key matches the data-composition-id attribute
// <div data-composition-id="my-video" ...>
window.__timelines["my-video"] = tl;
```
</Accordion>
</AccordionGroup>
## Debugging Checklist
When something doesn't work, check in this order:
When something does not 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
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`](/concepts/data-attributes#composition-attributes)?
3. **GSAP-only animations?** Only animate visual properties (opacity, transform, color) — see [GSAP Animation](/guides/gsap-animation#key-rules)
4. **Timeline long enough?** Add `tl.set({}, {}, DURATION)` at the end — see [Timeline Duration](/guides/gsap-animation#timeline-duration-and-composition-duration)
5. **Console errors?** Open browser console — runtime errors show as `[Browser:ERROR]`
6. **Still stuck?** See [Troubleshooting](/guides/troubleshooting) for environment and rendering issues
## Next Steps
<CardGroup cols={2}>
<Card title="Troubleshooting" icon="wrench" href="/guides/troubleshooting">
Fix environment and rendering issues
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Review animation rules and patterns
</Card>
<Card title="HTML Schema Reference" icon="code" href="/reference/html-schema">
Full attribute reference and checklist
</Card>
<Card title="Data Attributes" icon="database" href="/concepts/data-attributes">
Timing, media, and composition attributes
</Card>
</CardGroup>
+77 -12
View File
@@ -3,26 +3,37 @@ 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.
Hyperframes uses [GSAP](https://gsap.com/) for animation. Timelines are paused and controlled by the runtime — you define the animations, the framework handles playback. For background on how animation runtimes plug into Hyperframes, see [Frame Adapters](/concepts/frame-adapters).
## Setup
Include GSAP and create a paused timeline:
```html
```html index.html
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script>
// 1. Create a paused timeline — the framework controls playback
const tl = gsap.timeline({ paused: true });
// 2. Add animations using the position parameter (3rd arg) for absolute timing
tl.to("#title", { opacity: 1, duration: 0.5 }, 0);
// 3. Initialize the global timelines registry (if not already present)
window.__timelines = window.__timelines || {};
// 4. Register the timeline using the data-composition-id as the key
window.__timelines["root"] = tl;
</script>
```
<Note>
The key you use in `window.__timelines` must match the `data-composition-id` attribute on your composition's root element. See [Compositions](/concepts/compositions) for how the root element is structured.
</Note>
## 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
1. **Always create timelines with `{ paused: true }`** — the framework controls playback via [deterministic seeking](/concepts/determinism)
2. **Register timelines on `window.__timelines`** with the [`data-composition-id`](/concepts/data-attributes#composition-attributes) 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
@@ -39,22 +50,59 @@ Include GSAP and create a paused timeline:
`opacity`, `x`, `y`, `scale`, `scaleX`, `scaleY`, `rotation`, `width`, `height`, `visibility`, `color`, `backgroundColor`, and any CSS-animatable property.
## Timeline Duration
## Timeline Duration and Composition Duration
A composition's duration equals its GSAP timeline duration. If your last animation ends at 8 seconds, the composition is 8 seconds long.
A composition's duration equals its GSAP timeline duration. The two are directly linked:
To extend the timeline beyond the last animation (e.g., to match a video clip's length):
```javascript compositions/intro-anim.html
// Your last animation ends at 3 seconds...
tl.from("#title", { opacity: 0, y: -50, duration: 1 }, 0);
tl.to("#title", { opacity: 0, duration: 1 }, 2);
// ...so this composition is exactly 3 seconds long.
```
```javascript
// Extends timeline to 283 seconds without affecting any elements
If your composition contains a video clip that is 283 seconds long, but your last GSAP animation ends at 8 seconds, the composition will be only 8 seconds long and the video will be cut short. To extend the timeline to match the video:
```javascript index.html
// All your visual animations
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
// Extend the timeline to 283 seconds to match the video length.
// This adds a zero-duration tween at 283s without affecting any elements.
tl.set({}, {}, 283);
```
<Warning>
This is one of the most common mistakes in Hyperframes. If your video cuts off early, the timeline is too short. See [Common Mistakes: Composition Duration Shorter Than Video](/guides/common-mistakes) for more details.
</Warning>
## What NOT to Do
These patterns will break your composition or cause sync issues:
```javascript index.html
// WRONG: Playing media in scripts — the framework owns media playback
document.getElementById("el-video").play();
document.getElementById("el-audio").currentTime = 5;
// WRONG: Creating a non-paused timeline
const tl = gsap.timeline(); // missing { paused: true }!
// WRONG: Animating dimensions directly on a <video> element
tl.to("#el-video", { width: 500, height: 280 }, 5);
// WRONG: Manually nesting sub-timelines
const masterTL = window.__timelines["root"];
masterTL.add(window.__timelines["intro-anim"], 0);
```
The framework automatically manages [media playback](/reference/html-schema#framework-managed-behavior), [clip lifecycle](/concepts/compositions#two-layers-primitives-and-scripts), and [sub-composition nesting](#sub-composition-timelines). Scripts that duplicate this behavior will conflict.
## Sub-Composition Timelines
Each composition registers its own timeline. The framework automatically nests sub-composition timelines into the parent based on `data-start`:
Each [nested composition](/concepts/compositions#nested-compositions) registers its own timeline. The framework automatically nests sub-composition timelines into the parent based on [`data-start`](/concepts/data-attributes#timing-attributes):
```javascript
```javascript compositions/intro-anim.html
// In compositions/intro-anim.html
const tl = gsap.timeline({ paused: true });
tl.from(".title", { opacity: 0, y: -50, duration: 1 });
@@ -65,5 +113,22 @@ window.__timelines["intro-anim"] = tl;
```
<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.
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. See [Common Mistakes](/guides/common-mistakes) for a detailed explanation.
</Warning>
## Next Steps
<CardGroup cols={2}>
<Card title="Compositions" icon="layer-group" href="/concepts/compositions">
Understand the building blocks that timelines animate
</Card>
<Card title="Frame Adapters" icon="plug" href="/concepts/frame-adapters">
Learn how GSAP plugs into the render pipeline
</Card>
<Card title="Common Mistakes" icon="triangle-exclamation" href="/guides/common-mistakes">
Avoid pitfalls that break animations
</Card>
<Card title="HTML Schema Reference" icon="code" href="/reference/html-schema">
Full reference for composition attributes
</Card>
</CardGroup>
+116 -22
View File
@@ -3,37 +3,110 @@ title: Rendering
description: "Render compositions to MP4 locally or in Docker."
---
Render your Hyperframes compositions to MP4 with the CLI.
Render your Hyperframes [compositions](/concepts/compositions) to MP4 with the [CLI](/packages/cli). The rendering pipeline is frame-by-frame and seek-driven — see [Deterministic Rendering](/concepts/determinism) for how this works under the hood.
```bash
npx hyperframes render -o output.mp4
```
## Getting Started
<Steps>
<Step title="Verify your environment">
Run the diagnostics command to check for required dependencies:
```bash Terminal
npx hyperframes doctor
```
Expected output:
```
✓ Node.js 20.x
✓ FFmpeg found (7.x)
✓ Docker available
✓ Disk space OK
```
</Step>
<Step title="Preview your composition">
Before rendering, preview your composition in the browser to verify it looks correct:
```bash Terminal
npx hyperframes dev
```
</Step>
<Step title="Render to MP4">
Run the render command from your project directory:
```bash Terminal
npx hyperframes render -o output.mp4
```
Expected output:
```
⠋ Rendering composition "root" (30fps, standard quality)
✓ Captured 240 frames in 8.2s
✓ Encoded to output.mp4 (8.0s, 1920x1080, 4.2MB)
```
</Step>
</Steps>
## Rendering Modes
### Local Mode (default)
<Tabs>
<Tab title="Local Mode">
### Local Mode (default)
Uses Puppeteer (bundled Chromium) + system FFmpeg. Fast for iteration.
Uses Puppeteer (bundled Chromium) and your system's FFmpeg. Fast for iteration during development.
**Requires**: FFmpeg installed on your system.
**Requires:** FFmpeg installed on your system. See [Troubleshooting](/guides/troubleshooting) if FFmpeg is not found.
```bash
npx hyperframes render -o output.mp4
```
```bash Terminal
npx hyperframes render -o output.mp4
```
### Docker Mode
**Pros:**
- Fast startup, no container overhead
- Uses your system GPU for hardware-accelerated encoding (with `--gpu`)
- Best for iterative development
Deterministic output with an exact Chrome version and fonts. Use this for production renders and CI pipelines.
**Cons:**
- Output may vary across platforms due to font and Chrome version differences
- Not suitable for CI/CD pipelines that require reproducibility
</Tab>
<Tab title="Docker Mode">
### Docker Mode
**Requires**: Docker installed and running.
[Deterministic](/concepts/determinism) output with an exact Chrome version and font set. Use this for production renders and CI pipelines.
```bash
npx hyperframes render --docker -o output.mp4
```
**Requires:** Docker installed and running.
<Note>
Docker mode uses `chrome-headless-shell` with BeginFrame control for frame-perfect, deterministic capture. This is the same pipeline used in production.
</Note>
```bash Terminal
npx hyperframes render --docker -o output.mp4
```
**Pros:**
- Identical output on every platform — same Chrome, same fonts, same FFmpeg
- The same pipeline used in production
- Ideal for CI/CD and automated workflows
**Cons:**
- Slower startup due to container initialization
- No GPU acceleration inside the container
<Note>
Docker mode uses `chrome-headless-shell` with [BeginFrame](/concepts/determinism#how-it-works) control for frame-perfect, deterministic capture.
</Note>
</Tab>
</Tabs>
## When to Use Each Mode
| Scenario | Recommended Mode |
|----------|-----------------|
| Local development and iteration | Local |
| CI/CD pipeline | Docker |
| Sharing renders with a team | Docker |
| Quick preview export | Local |
| AI agent-driven rendering | Docker |
| Benchmarking performance | Local |
## Options
@@ -44,11 +117,32 @@ npx hyperframes render --docker -o output.mp4
| `-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 |
| `--docker` | — | off | Use Docker for [deterministic rendering](/concepts/determinism) |
## Tips
- Use `draft` quality during development for fast previews
<Tip>
Use `draft` quality during development for fast previews. Switch to `standard` or `high` for final output.
</Tip>
- 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
- Docker mode is slower but guarantees [identical output](/concepts/determinism) across platforms
- For compositions with many frames, `--gpu` can significantly speed up local encoding
## Next Steps
<CardGroup cols={2}>
<Card title="Deterministic Rendering" icon="lock" href="/concepts/determinism">
Understand the determinism guarantees
</Card>
<Card title="CLI Reference" icon="terminal" href="/packages/cli">
Full list of CLI commands and flags
</Card>
<Card title="Troubleshooting" icon="wrench" href="/guides/troubleshooting">
Fix common rendering issues
</Card>
<Card title="Common Mistakes" icon="triangle-exclamation" href="/guides/common-mistakes">
Avoid pitfalls that affect render output
</Card>
</CardGroup>
+115 -22
View File
@@ -3,45 +3,138 @@ title: Templates
description: "Built-in templates for common video patterns."
---
Hyperframes includes starter templates to help you get going quickly.
Hyperframes includes starter templates to help you scaffold compositions quickly. Each template gives you a working project with the correct [composition structure](/concepts/compositions), [data attributes](/concepts/data-attributes), and a [GSAP timeline](/guides/gsap-animation) already wired up.
## Using Templates
```bash
```bash Terminal
npx hyperframes init --template <name>
```
This creates a new project directory with an `index.html` composition and any required assets.
## Available Templates
### blank
<Tabs>
<Tab title="blank">
### blank
Empty 1920x1080 composition with a GSAP timeline wired up. Start from scratch.
An empty 1920x1080 composition with a GSAP timeline wired up and nothing else. Start from scratch.
```bash
npx hyperframes init --template blank
```
**What it produces:** A black (empty) canvas at 1920x1080 resolution. No visible elements, no animations. The timeline is registered and ready for you to add tweens.
### title-card
**When to use it:** You have a specific design in mind and want full control. Good for AI agent workflows that will generate the entire composition programmatically.
Animated title and subtitle with GSAP fade-in/out. Good for intro cards.
```bash Terminal
npx hyperframes init --template blank
```
```bash
npx hyperframes init --template title-card
```
**What you get:**
```
my-video/
├── index.html # Empty root composition with GSAP setup
└── assets/ # Empty directory for your media files
```
</Tab>
<Tab title="title-card">
### title-card
### video-edit
Animated title and subtitle with GSAP fade-in/out transitions.
Video element with trimming, audio, and track controls. Starting point for video editing workflows.
**What it produces:** A centered title and subtitle that fade in from the top, hold for a few seconds, then fade out. Clean, minimal typography on a solid background. Good for intro cards, chapter markers, or end screens.
```bash
npx hyperframes init --template video-edit
```
**When to use it:** You need a simple text-based segment — an intro, outro, or interstitial card between video clips.
```bash Terminal
npx hyperframes init --template title-card
```
**What you get:**
```
my-video/
├── index.html # Title + subtitle with fade animations
└── assets/ # Empty directory for your media files
```
</Tab>
<Tab title="video-edit">
### video-edit
A video element with trimming, audio, and track controls.
**What it produces:** A full-screen video clip with [`data-media-start`](/concepts/data-attributes#media-attributes) for trimming, a background audio track on a separate [timeline track](/concepts/data-attributes#timing-attributes), and a lower-third text overlay animated with GSAP. Demonstrates how multiple clip types work together.
**When to use it:** You are building a video editing workflow — cutting clips, adding overlays, mixing audio. This template shows the patterns for media-heavy compositions.
```bash Terminal
npx hyperframes init --template video-edit
```
**What you get:**
```
my-video/
├── index.html # Video + audio + overlay composition
└── assets/ # Place your video and audio files here
```
</Tab>
</Tabs>
## Choosing a Template
| Template | Best for | Complexity |
|----------|----------|------------|
| `blank` | Full control, agent-generated compositions | Minimal |
| `title-card` | Text intros, outros, chapter markers | Simple |
| `video-edit` | Video cutting, overlays, multi-track editing | Moderate |
<Tip>
If you are new to Hyperframes, start with `title-card` to see a working animation, then move to `blank` when you are comfortable with the [composition model](/concepts/compositions) and [GSAP animation](/guides/gsap-animation).
</Tip>
## Custom Templates
Any directory with an `index.html` can serve as a template. Copy it manually or build your own init workflow.
Any directory with an `index.html` can serve as a template. You can copy a directory 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
Your custom template needs:
1. An `index.html` with a [`data-composition-id`](/concepts/data-attributes#composition-attributes) root element
2. A [GSAP timeline](/guides/gsap-animation) registered in `window.__timelines`
3. Any assets in the same directory or a subdirectory
```html index.html
<div id="root" data-composition-id="my-template"
data-start="0" data-width="1920" data-height="1080">
<!-- Your elements here -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script>
const tl = gsap.timeline({ paused: true });
// Add your animations...
window.__timelines = window.__timelines || {};
window.__timelines["my-template"] = tl;
</script>
</div>
```
After creating a custom template, validate it with the [linter](/packages/cli#lint):
```bash Terminal
npx hyperframes lint
```
## Next Steps
<CardGroup cols={2}>
<Card title="Quickstart" icon="rocket" href="/quickstart">
Create, preview, and render your first video
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Add animations to your template
</Card>
<Card title="Compositions" icon="layer-group" href="/concepts/compositions">
Understand the composition data model
</Card>
<Card title="Rendering" icon="film" href="/guides/rendering">
Render your composition to MP4
</Card>
</CardGroup>
+118 -39
View File
@@ -3,66 +3,145 @@ title: Troubleshooting
description: "Solutions for common Hyperframes issues."
---
## "No composition found"
If your issue is about a specific coding mistake (animations not working, video cutting off early), see [Common Mistakes](/guides/common-mistakes) first. This page covers environment, tooling, and rendering issues.
Your directory needs an `index.html` with a valid composition. Run `npx hyperframes init` to create one.
<AccordionGroup>
<Accordion title='"No composition found"'>
Your directory needs an `index.html` with a valid [composition](/concepts/compositions). The root element must have a [`data-composition-id`](/concepts/data-attributes#composition-attributes) attribute.
## "FFmpeg not found"
**Fix:** Run `npx hyperframes init` to create a composition from a [template](/guides/templates), or verify your `index.html` has the correct structure:
Local rendering requires FFmpeg. Install it for your platform:
```html index.html
<div id="root" data-composition-id="my-video"
data-start="0" data-width="1920" data-height="1080">
<!-- elements here -->
</div>
```
</Accordion>
<CodeGroup>
```bash macOS
brew install ffmpeg
```
<Accordion title='"FFmpeg not found"'>
Local [rendering](/guides/rendering) requires FFmpeg installed on your system. Install it for your platform:
```bash Ubuntu/Debian
sudo apt install ffmpeg
```
<CodeGroup>
```bash macOS
brew install ffmpeg
```
```bash Windows
# Download from https://ffmpeg.org/download.html
# Add to your PATH
```
</CodeGroup>
```bash Ubuntu/Debian
sudo apt install ffmpeg
```
## Lint Errors
```bash Windows
# Download from https://ffmpeg.org/download.html
# Add the bin directory to your PATH
```
Run `npx hyperframes lint` to check for common issues:
```bash Verify installation
ffmpeg -version
```
</CodeGroup>
- 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`)
After installing, run `npx hyperframes doctor` to verify the CLI can find it.
## Preview Not Updating
<Tip>
If you cannot install FFmpeg, use [Docker mode](/guides/rendering) instead — it bundles FFmpeg inside the container: `npx hyperframes render --docker -o output.mp4`
</Tip>
</Accordion>
Make sure you're editing the `index.html` in the project directory. The preview server watches for file changes and auto-reloads.
<Accordion title="Lint errors">
Run `npx hyperframes lint` to check for common structural issues (see [CLI: lint](/packages/cli#lint)):
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)
| Error | Meaning |
|-------|---------|
| Missing `data-composition-id` | Root element needs this attribute. See [Compositions](/concepts/compositions). |
| Missing `class="clip"` | Timed visible elements need this class. See [Data Attributes](/concepts/data-attributes#element-visibility). |
| Overlapping timelines | Clips on the same [`data-track-index`](/concepts/data-attributes#timing-attributes) cannot overlap in time. |
| Unmuted video elements | Video elements should be `muted` unless `data-has-audio="true"` is set. |
| Deprecated attribute names | `data-layer` and `data-end` have been replaced. Check the [HTML Schema Reference](/reference/html-schema). |
</Accordion>
## Render Looks Different from Preview
<Accordion title="Preview not updating">
Make sure you are editing the `index.html` in the project directory. The [preview server](/packages/cli#dev) watches for file changes and auto-reloads.
Use `--docker` mode for deterministic output. Local renders may differ due to:
If changes still do not appear:
- Font availability (different fonts on different platforms)
- Chrome version (local Chromium vs. Docker's pinned version)
- System-specific rendering differences
1. Check the terminal for errors from the dev server
2. Stop and restart `npx hyperframes dev`
3. Hard-refresh the browser: **Ctrl+Shift+R** (Windows/Linux) or **Cmd+Shift+R** (macOS)
4. Clear the browser cache if CSS changes are not reflected
</Accordion>
```bash
npx hyperframes render --docker -o output.mp4
```
<Accordion title="Render looks different from preview">
Use `--docker` mode for [deterministic output](/concepts/determinism). Local renders may differ due to:
- **Font availability** — different fonts on different platforms cause text reflow
- **Chrome version** — local Chromium vs. Docker's pinned version can render slightly differently
- **System-specific rendering** — GPU compositing, subpixel antialiasing, etc.
```bash Terminal
npx hyperframes render --docker -o output.mp4
```
See [Rendering: When to Use Each Mode](/guides/rendering#when-to-use-each-mode) for guidance on choosing between local and Docker rendering.
</Accordion>
<Accordion title="Docker mode fails to start">
Verify Docker is installed and the daemon is running:
```bash Terminal
docker info
```
Common issues:
- **Docker not running:** Start Docker Desktop or the Docker daemon
- **Permission denied:** Add your user to the `docker` group (`sudo usermod -aG docker $USER`) and restart your shell
- **Image pull fails:** Check your internet connection; the first render downloads the Hyperframes Docker image
</Accordion>
<Accordion title="Render is slow">
Try these optimizations:
1. Use `--quality draft` during development for faster encoding
2. Run `npx hyperframes benchmark` to find the optimal worker count for your system
3. Use `--gpu` for hardware-accelerated encoding (local mode only)
4. Reduce `--fps` to 24 if 30fps is not needed
5. Check that your composition does not have unnecessary elements or overly complex animations
See [Rendering: Options](/guides/rendering#options) for all available flags.
</Accordion>
</AccordionGroup>
## System Diagnostics
Run `npx hyperframes doctor` to check your environment:
```bash
```bash Terminal
npx hyperframes doctor
```
This checks for Node.js version, FFmpeg availability, Docker status, and other requirements.
This checks for Node.js version, FFmpeg availability, Docker status, and other requirements. If `doctor` reports issues, address them before rendering.
## Still Stuck?
If none of the above resolves your issue:
1. Run `npx hyperframes info` to gather system and project details
2. Check [GitHub Issues](https://github.com/heygen-com/hyperframes/issues) for similar reports
3. Open a new issue with the output of `npx hyperframes info` and steps to reproduce
## Next Steps
<CardGroup cols={2}>
<Card title="Common Mistakes" icon="triangle-exclamation" href="/guides/common-mistakes">
Coding pitfalls that break compositions
</Card>
<Card title="Rendering" icon="film" href="/guides/rendering">
Rendering modes, options, and tips
</Card>
<Card title="CLI Reference" icon="terminal" href="/packages/cli">
Full list of CLI commands
</Card>
<Card title="Contributing" icon="code-branch" href="/contributing">
Report bugs and contribute fixes
</Card>
</CardGroup>
+67 -26
View File
@@ -3,56 +3,97 @@ 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.
Hyperframes is an open-source framework that turns HTML into deterministic, frame-by-frame rendered video — so you can define a video the same way you build a web page.
## Why Hyperframes?
## See It in Action
- **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:
Here is a video defined entirely as HTML:
```html
<div id="stage" data-composition-id="my-video"
<div id="root" data-composition-id="demo"
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"
<h1 id="title" class="clip"
data-start="1" data-duration="4" data-track-index="1"
style="font-size: 72px; color: white;">
Welcome to Hyperframes
</h1>
<audio id="bg-music" data-start="0" data-duration="5"
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.
Run `npx hyperframes render -o demo.mp4` and this produces an MP4 with deterministic, frame-by-frame capture. Same input, identical output, every time. No timeline editor. No proprietary format. Just HTML.
## Why Hyperframes?
<Tabs>
<Tab title="For developers">
**You already know the stack.** Compositions are HTML files with data attributes. Animations use GSAP, Lottie, CSS, or any runtime that can seek to a given frame. There is no custom DSL, no proprietary component system, and no React requirement. If you can build a web page, you can build a video.
</Tab>
<Tab title="For AI agents">
**Agents already speak HTML.** Most video tools require complex APIs or drag-and-drop interfaces that agents cannot operate. Hyperframes compositions are plain HTML documents — the format LLMs are best at generating. An agent can compose, modify, and render videos using tools it already understands.
</Tab>
<Tab title="For automated pipelines">
**Determinism by design.** The rendering pipeline is seek-driven with no wall-clock dependencies. `frame = floor(time * fps)` — every frame is independently captured via Chrome's `beginFrame` API and encoded with FFmpeg. Same input always produces identical output, making CI testing and batch rendering reliable.
</Tab>
</Tabs>
<Tip>
Hyperframes was designed from the ground up for AI agent integration. Because compositions are plain HTML, any LLM can generate, edit, and iterate on video content without specialized tooling. Pair it with function-calling agents to build fully automated video pipelines.
</Tip>
## How It Works
<Steps>
<Step title="Write HTML">
Define your video as an HTML document. Each element gets data attributes for timing (`data-start`, `data-duration`) and layout (`data-track-index`). Add animations with GSAP, Lottie, CSS transitions, or any seekable runtime via the Frame Adapter pattern.
</Step>
<Step title="Preview in the browser">
Run `npx hyperframes dev` to open a live preview at `localhost:3000`. Edit your HTML and see changes instantly — no build step, no compilation.
</Step>
<Step title="Render to MP4">
Run `npx hyperframes render -o output.mp4` to produce a final video. The engine seeks each frame in headless Chrome, captures it with `beginFrame`, and pipes the result through FFmpeg. Run locally or in Docker for fully reproducible output.
</Step>
</Steps>
## 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 |
<CardGroup cols={2}>
<Card title="@hyperframes/core" icon="cube" href="/packages/core">
Types, HTML parsing, runtime, and composition linter — the foundation everything else builds on.
</Card>
<Card title="@hyperframes/engine" icon="gear" href="/packages/engine">
Seekable page-to-video capture engine. Loads HTML in headless Chrome and captures frame-by-frame.
</Card>
<Card title="@hyperframes/producer" icon="video" href="/packages/producer">
Full rendering pipeline combining capture and FFmpeg encoding into a single API call.
</Card>
<Card title="@hyperframes/studio" icon="palette" href="/packages/studio">
Visual composition editor UI for building and previewing timelines interactively.
</Card>
<Card title="hyperframes (CLI)" icon="terminal" href="/packages/cli">
Command-line tool for creating, previewing, and rendering compositions.
</Card>
</CardGroup>
## Next Steps
<CardGroup cols={2}>
<Card title="Quickstart" icon="rocket" href="/quickstart">
Create your first video in 60 seconds
Build and render your first video in 60 seconds
</Card>
<Card title="Compositions" icon="layer-group" href="/concepts/compositions">
Learn the core data model
Understand the HTML-based data model behind every video
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Add animations to your videos
Add timeline-driven animations with GSAP
</Card>
<Card title="Rendering" icon="film" href="/guides/rendering">
Render to MP4 locally or in Docker
Render locally, in Docker, or in a CI pipeline
</Card>
</CardGroup>
+221 -52
View File
@@ -3,7 +3,7 @@ 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.
The `hyperframes` CLI is the primary way to work with Hyperframes. It handles project creation, live preview, rendering, linting, and diagnostics — all from your terminal.
```bash
npm install -g hyperframes
@@ -11,87 +11,256 @@ npm install -g hyperframes
npx hyperframes <command>
```
## When to Use
**Use the CLI when you want to:**
- Create a new composition project from a template
- Preview compositions with live hot reload during development
- Render compositions to MP4 (locally or in Docker)
- Lint compositions for structural issues
- Check your environment for missing dependencies
**Use a different package if you want to:**
- Render programmatically from Node.js code — use the [producer](/packages/producer)
- Build a custom frame capture pipeline — use the [engine](/packages/engine)
- Embed a composition editor in your own web app — use the [studio](/packages/studio)
- Parse or generate composition HTML in code — use [core](/packages/core)
<Tip>
The CLI is the recommended starting point for all Hyperframes users. It wraps the producer, engine, and studio packages so you do not need to install them separately.
</Tip>
## Getting Started
<Steps>
<Step title="Create a project">
Scaffold a new composition from a template:
```bash
npx hyperframes init --template title-card
```
```
Creating composition in ./title-card...
index.html
assets/
package.json
Done! Run `cd title-card && npx hyperframes dev` to preview.
```
See [Templates](/guides/templates) for all available templates.
</Step>
<Step title="Preview in browser">
Start the development server with live hot reload:
```bash
cd title-card
npx hyperframes dev
```
```
Hyperframes Studio v0.1.0
Local: http://localhost:3000
Watching for changes...
```
Edit `index.html` and the preview updates instantly.
</Step>
<Step title="Lint your composition">
Check for structural issues before rendering:
```bash
npx hyperframes lint
```
```
Linting index.html...
No issues found.
```
</Step>
<Step title="Render to MP4">
Produce the final video:
```bash
npx hyperframes render -o output.mp4
```
```
Rendering index.html...
[========================================] 100% (900/900 frames)
Output: output.mp4 (30s, 1920x1080, 30fps)
```
For deterministic output, add `--docker`:
```bash
npx hyperframes render --docker -o output.mp4
```
</Step>
</Steps>
## Commands
### `init`
<Tabs>
<Tab title="Create">
### `init`
Create a new composition project from a template:
Create a new composition project from a template:
```bash
npx hyperframes init --template title-card
```
```bash
npx hyperframes init --template <name>
```
See [Templates](/guides/templates) for available templates.
| Template | Description |
|----------|-------------|
| `blank` | Empty 1920x1080 composition with a GSAP timeline wired up |
| `title-card` | Animated title and subtitle with GSAP fade-in/out |
| `slideshow` | Image slideshow with crossfade transitions |
| `lower-third` | Broadcast-style lower-third overlay |
### `dev`
See [Templates](/guides/templates) for full details and previews.
Start a live preview server with hot reload:
### `compositions`
```bash
npx hyperframes dev
```
List all compositions in the current project:
Opens your composition in the browser at `http://localhost:3000`. Edits to `index.html` are reflected instantly.
```bash
npx hyperframes compositions
```
```
Compositions in ./my-video:
root index.html (30s, 1920x1080)
intro-anim compositions/intro.html (5s, 1920x1080)
```
</Tab>
<Tab title="Develop">
### `dev`
### `render`
Start a live preview server with hot reload:
Render a composition to MP4:
```bash
npx hyperframes dev
```
```
Hyperframes Studio v0.1.0
Local: http://localhost:3000
Watching for changes...
```
```bash
npx hyperframes render -o output.mp4
npx hyperframes render --docker -o output.mp4 # deterministic mode
```
Opens your composition in the browser. Edits to `index.html` and any referenced sub-compositions are reflected instantly. The preview uses the same Hyperframes runtime as production rendering, so what you see is what you get.
See [Rendering](/guides/rendering) for all options.
### `lint`
### `lint`
Check a composition for common issues:
Check a composition for common issues:
```bash
npx hyperframes lint
```
```
Linting index.html...
```bash
npx hyperframes lint
```
WARNING unmuted-video
Video element 'clip-1' should have the 'muted' attribute for reliable autoplay.
at index.html:5
Detects missing attributes, deprecated names, structural problems, and more.
1 issue found (0 errors, 1 warning)
```
### `compositions`
The linter detects missing attributes, deprecated names, structural problems, and more. See [Common Mistakes](/guides/common-mistakes) for details on each rule.
</Tab>
<Tab title="Build">
### `render`
List all compositions in the current project:
Render a composition to MP4:
```bash
npx hyperframes compositions
```
```bash
# Local mode (fast iteration)
npx hyperframes render -o output.mp4
### `benchmark`
# Docker mode (deterministic output)
npx hyperframes render --docker -o output.mp4
Find optimal render settings for your system:
# With options
npx hyperframes render -o output.mp4 --fps 60 --quality high
```
```
Rendering index.html...
[========================================] 100% (900/900 frames)
Output: output.mp4 (30s, 1920x1080, 30fps)
```
```bash
npx hyperframes benchmark
```
See [Rendering](/guides/rendering) for all options and modes.
### `doctor`
### `benchmark`
Check your environment for required dependencies:
Find optimal render settings for your system:
```bash
npx hyperframes doctor
```
```bash
npx hyperframes benchmark
```
```
Running benchmark suite...
Verifies Node.js version, FFmpeg, Docker, and other requirements.
Quality: draft FPS: 30 Time: 4.2s Speed: 7.1x realtime
Quality: standard FPS: 30 Time: 8.7s Speed: 3.4x realtime
Quality: high FPS: 30 Time: 15.1s Speed: 2.0x realtime
Quality: standard FPS: 60 Time: 16.3s Speed: 1.8x realtime
### `info`
Recommended: quality=standard fps=30 (best speed/quality balance)
```
</Tab>
<Tab title="Utilities">
### `doctor`
Display system and project information:
Check your environment for required dependencies:
```bash
npx hyperframes info
```
```bash
npx hyperframes doctor
```
```
Checking environment...
Node.js v20.11.0 OK
FFmpeg 6.1.1 OK
Docker 24.0.7 OK
Chrome 120.0.6099 OK (bundled)
### `upgrade`
All checks passed.
```
Update Hyperframes to the latest version:
Verifies Node.js version, FFmpeg, Docker, Chrome, and other requirements.
```bash
npx hyperframes upgrade
```
### `info`
Display system and project information:
```bash
npx hyperframes info
```
```
Hyperframes v0.1.0
Node.js v20.11.0
Platform linux x64
FFmpeg 6.1.1
Project ./my-video (2 compositions)
```
### `upgrade`
Update Hyperframes to the latest version:
```bash
npx hyperframes upgrade
```
```
Current: 0.1.0
Latest: 0.2.0
Upgrading...
Done! Run `npx hyperframes doctor` to verify.
```
</Tab>
</Tabs>
## Related Packages
<CardGroup cols={2}>
<Card title="Producer" icon="film" href="/packages/producer">
The rendering pipeline the CLI calls under the hood. Use directly for programmatic rendering.
</Card>
<Card title="Studio" icon="palette" href="/packages/studio">
The editor UI that powers `hyperframes dev`. Use directly to embed in your own app.
</Card>
<Card title="Core" icon="cube" href="/packages/core">
Types, linter, and runtime. Use directly for custom tooling and integrations.
</Card>
<Card title="Engine" icon="gear" href="/packages/engine">
The capture engine. Use directly for custom frame capture pipelines.
</Card>
</CardGroup>
+132 -28
View File
@@ -1,58 +1,162 @@
---
title: "@hyperframes/core"
description: "Types, HTML generation, runtime, and linter."
description: "Types, HTML generation, runtime, and linter — the foundation every other package depends on."
---
The core package provides the foundational types, HTML parsing/generation, runtime, and composition linter that all other packages depend on.
The core package provides the foundational types, HTML parsing/generation, runtime, and composition linter that all other Hyperframes packages build on. If you are building tooling, writing a custom integration, or extending Hyperframes itself, this is the package you need.
```bash
npm install @hyperframes/core
```
## When to Use
<Tip>
**Most users do not need to install `@hyperframes/core` directly.** The [CLI](/packages/cli), [producer](/packages/producer), and [studio](/packages/studio) packages all depend on core internally. You only need it if you are doing one of the things listed below.
</Tip>
**Use `@hyperframes/core` when you need to:**
- Lint compositions programmatically (CI pipelines, editor plugins)
- Parse HTML compositions into structured TypeScript objects
- Generate composition HTML from data (e.g., from an API or AI agent)
- Access the Hyperframes type system for your own tooling
- Embed the Hyperframes runtime in a custom player
**Use a different package if you want to:**
- Preview compositions in the browser — use the [CLI](/packages/cli) (`npx hyperframes dev`) or [studio](/packages/studio)
- Render compositions to MP4 — use the [CLI](/packages/cli) (`npx hyperframes render`) or [producer](/packages/producer)
- Capture frames from a headless browser — use the [engine](/packages/engine)
## 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 |
| `core.types` | TypeScript types for compositions, clips, timelines, and render config |
| `parsers/` | HTML-to-composition parsing — turns an HTML string into a typed `Composition` object |
| `generators/` | Composition-to-HTML generation — turns a `Composition` object back into HTML |
| `runtime/` | The Hyperframes runtime that manages playback, seeking, and clip lifecycle |
| `lint/` | Composition linter with rules for structural correctness |
| `adapters/` | Frame Adapter types and the built-in GSAP adapter |
| `templates/` | HTML composition templates used by `hyperframes init` |
## Linter
The composition linter checks for structural issues that would cause rendering failures or unexpected behavior. You can run it from the CLI with `npx hyperframes lint`, or call it programmatically:
```typescript
import { lintHyperframeHtml } from '@hyperframes/core';
const html = `
<div id="root" data-composition-id="root"
data-start="0" data-width="1920" data-height="1080">
<video id="clip-1" data-start="0" data-track-index="0"
src="intro.mp4"></video>
</div>
`;
const issues = lintHyperframeHtml(html);
// => [{ rule: "unmuted-video", message: "Video element 'clip-1' should have the 'muted' attribute ...", severity: "warning" }]
```
Detected issues include:
- Missing timeline registration (`window.__timelines`)
- Unmuted video elements (causes autoplay failures)
- Missing `class="clip"` on timed visible elements
- Deprecated attribute names
- Missing composition dimensions (`data-width`, `data-height`)
- Invalid `data-start` references to nonexistent clip IDs
<Info>
For a full list of what the linter catches and how to fix each issue, see [Common Mistakes](/guides/common-mistakes) and [Troubleshooting](/guides/troubleshooting).
</Info>
## Types
Import the core types for use in your own tooling or integrations:
```typescript
import type {
Composition,
Clip,
RenderConfig,
FrameAdapterContext,
} from '@hyperframes/core';
// Example: define a render configuration
const config: RenderConfig = {
fps: 30,
width: 1920,
height: 1080,
quality: 'standard',
};
// Example: work with a parsed composition
function getClipCount(composition: Composition): number {
return composition.clips.length;
}
```
## Parsing and Generating HTML
Round-trip between HTML and structured data:
```typescript
import { parseHyperframeHtml, generateHyperframeHtml } from '@hyperframes/core';
// Parse HTML into a Composition object
const composition = parseHyperframeHtml(htmlString);
console.log(composition.id); // "root"
console.log(composition.width); // 1920
console.log(composition.clips); // [{ id: "clip-1", start: 0, ... }, ...]
// Generate HTML from a Composition object
const html = generateHyperframeHtml(composition);
```
This is especially useful for AI agents that generate video programmatically — they can construct a `Composition` object in code and then serialize it to HTML for rendering.
## Runtime Builds
The runtime is built in two formats:
The runtime is the JavaScript that runs inside the browser (or headless Chrome) to manage clip lifecycle, media playback, and timeline synchronization. It is built in two formats:
- **`hyperframe.runtime.iife.js`** — for browser iframe bootstrap (preview)
- **`hyperframe.runtime.mjs`** — for Node.js/tooling/tests
- **`hyperframe.runtime.iife.js`** — injected into browser iframes for preview playback
- **`hyperframe.runtime.mjs`** — for Node.js tooling and tests
Build the runtime:
Build the runtime from source:
```bash
pnpm --filter @hyperframes/core build:hyperframes-runtime
```
## Linter
<Warning>
You should not need to build the runtime yourself unless you are developing the Hyperframes framework itself. The CLI and producer packages bundle the runtime automatically.
</Warning>
The composition linter checks for common structural issues:
## Frame Adapters
The core package defines the [Frame Adapter](/concepts/frame-adapters) interface — the abstraction that lets Hyperframes work with any animation runtime. The built-in GSAP adapter lives here:
```typescript
import { lintHyperframeHtml } from '@hyperframes/core';
import type { FrameAdapterContext } from '@hyperframes/core';
const issues = lintHyperframeHtml(htmlString);
// Every adapter must answer: "what should the screen look like at this time?"
// See the Frame Adapters concept page for the full API.
```
Detected issues include:
- Missing timeline registration
- Unmuted video elements
- Missing `class="clip"` on timed elements
- Deprecated attribute names
- Missing composition dimensions
## Related Packages
## Types
```typescript
import type { Composition, Clip, RenderConfig } from '@hyperframes/core';
```
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/packages/cli">
The easiest way to create, preview, lint, and render compositions.
</Card>
<Card title="Engine" icon="gear" href="/packages/engine">
Low-level frame capture pipeline that uses core types and runtime.
</Card>
<Card title="Producer" icon="film" href="/packages/producer">
Full rendering pipeline built on top of core and engine.
</Card>
<Card title="Studio" icon="palette" href="/packages/studio">
Visual composition editor that embeds the core runtime for preview.
</Card>
</CardGroup>
+126 -20
View File
@@ -1,30 +1,54 @@
---
title: "@hyperframes/engine"
description: "Seekable page-to-video capture engine."
description: "Seekable page-to-video capture engine using Chrome's BeginFrame API."
---
The engine package provides the low-level video capture pipeline: loading an HTML page in headless Chrome and capturing it frame-by-frame.
The engine package provides the low-level video capture pipeline: it loads an HTML page in headless Chrome, seeks to each frame independently, and captures pixel buffers using Chrome's `HeadlessExperimental.beginFrame` API. This is the layer that makes Hyperframes rendering deterministic.
```bash
npm install @hyperframes/engine
```
## What It Does
## When to Use
The engine:
<Warning>
**Most users should NOT use the engine directly.** Use the [CLI](/packages/cli) (`npx hyperframes render`) or the [producer](/packages/producer) package instead — they handle runtime injection, audio mixing, and encoding for you.
</Warning>
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
**Use `@hyperframes/engine` when you need to:**
- Build a custom rendering pipeline with full control over frame capture
- Integrate Hyperframes capture into an existing video processing system
- Capture individual frames (e.g., for thumbnails or sprite sheets) without encoding to video
- Implement a custom encoding backend (not FFmpeg)
## Key Features
**Use a different package if you want to:**
- Render an HTML composition to a finished MP4 — use the [producer](/packages/producer) or [CLI](/packages/cli)
- Preview compositions in the browser — use the [CLI](/packages/cli) or [studio](/packages/studio)
- Lint or parse composition HTML — use [core](/packages/core)
- **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
## How It Works
The engine implements a **seek-and-capture** loop that is fundamentally different from screen recording:
<Steps>
<Step title="Launch headless Chrome">
The engine starts `chrome-headless-shell`, a minimal headless Chrome binary optimized for programmatic control via the Chrome DevTools Protocol (CDP).
</Step>
<Step title="Load the composition">
Your HTML composition is loaded into a browser page. The Hyperframes runtime is injected to manage timeline seeking.
</Step>
<Step title="Seek to each frame">
For every frame in the video (e.g., 900 frames for a 30-second video at 30fps), the engine calls `renderSeek(time)` to advance the composition to the exact timestamp. No wall clock is involved — each frame is independently positioned.
</Step>
<Step title="Capture via BeginFrame">
Chrome's `HeadlessExperimental.beginFrame` API captures the compositor output as a pixel buffer. This produces pixel-perfect frames without any screen recording artifacts.
</Step>
<Step title="Hand off frames">
Captured frame buffers are passed to a consumer — typically FFmpeg (via the producer) for encoding into MP4, but you can provide your own consumer.
</Step>
</Steps>
This approach guarantees [deterministic rendering](/concepts/determinism): the same HTML always produces the identical video, regardless of system load or timing.
## Configuration
@@ -32,13 +56,95 @@ The engine:
import type { EngineConfig } from '@hyperframes/engine';
const config: EngineConfig = {
fps: 30,
width: 1920,
height: 1080,
quality: 'standard',
fps: 30, // Frames per second: 24, 30, or 60
width: 1920, // Output width in pixels
height: 1080, // Output height in pixels
quality: 'standard', // Encoding preset: 'draft', 'standard', or 'high'
};
```
## When to Use
### Quality Presets
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.
| Preset | Use Case | Speed |
|--------|----------|-------|
| `draft` | Fast iteration during development | Fastest |
| `standard` | Production renders with good quality/speed balance | Moderate |
| `high` | Final delivery, maximum quality | Slowest |
### FPS Options
| FPS | Use Case |
|-----|----------|
| `24` | Cinematic look, smaller file size |
| `30` | Standard web video, good balance |
| `60` | Smooth motion, UI animations, screen recordings |
## Programmatic Usage
```typescript
import { createEngine } from '@hyperframes/engine';
const engine = createEngine({
fps: 30,
width: 1920,
height: 1080,
});
// Capture all frames from a composition
const frames = await engine.capture('./my-video/index.html');
// Each frame is a pixel buffer (PNG/raw)
for (const frame of frames) {
// Process frames however you need:
// - pipe to FFmpeg
// - save as individual PNGs
// - generate a thumbnail
// - feed into a custom encoder
}
await engine.close();
```
## Key Concepts
### BeginFrame Rendering
Traditional screen capture records at wall-clock speed — if your system is under load, frames get dropped. The engine uses Chrome's `HeadlessExperimental.beginFrame` to explicitly advance the compositor, producing each frame on demand. This means:
- **No dropped frames** — every frame is captured
- **No timing dependency** — a 60-second video does not take 60 seconds to capture
- **Pixel-perfect output** — the compositor produces the exact pixels it would display
For more on how this enables deterministic output, see [Deterministic Rendering](/concepts/determinism).
### Seek Contract
The engine relies on the Hyperframes runtime's `renderSeek(time)` function. When called, `renderSeek`:
1. Pauses all GSAP timelines
2. Seeks every timeline to the exact timestamp
3. Updates all media elements (video, audio) to match
4. Mounts/unmounts clips based on their `data-start` and `data-duration`
This contract is what makes frame-by-frame capture possible — each frame is a complete, independent snapshot of the composition at that point in time.
### Chrome Requirements
The engine requires `chrome-headless-shell`, which is included when you install the package. It uses a pinned Chrome version to ensure consistent rendering across environments. For fully deterministic output (including fonts), use Docker mode via the [producer](/packages/producer).
## Related Packages
<CardGroup cols={2}>
<Card title="Producer" icon="film" href="/packages/producer">
Wraps the engine with runtime injection, FFmpeg encoding, and audio mixing for complete MP4 output.
</Card>
<Card title="Core" icon="cube" href="/packages/core">
Provides the types, runtime, and linter that the engine depends on.
</Card>
<Card title="CLI" icon="terminal" href="/packages/cli">
The easiest way to render — calls the producer (and engine) under the hood.
</Card>
<Card title="Studio" icon="palette" href="/packages/studio">
Visual editor for building compositions before rendering them with the engine.
</Card>
</CardGroup>
+135 -25
View File
@@ -1,63 +1,173 @@
---
title: "@hyperframes/producer"
description: "Full HTML-to-video rendering pipeline."
description: "Full HTML-to-video rendering pipeline with encoding, audio mixing, and Docker support."
---
The producer package combines the engine's capture capabilities with FFmpeg encoding to deliver a complete rendering pipeline.
The producer package combines the [engine's](/packages/engine) frame capture with FFmpeg encoding to deliver a complete HTML-to-MP4 rendering pipeline. It handles runtime injection, readiness gates, audio mixing, and optional Docker-based deterministic rendering.
```bash
npm install @hyperframes/producer
```
## When to Use
**Use `@hyperframes/producer` when you need to:**
- Render compositions to MP4 programmatically from Node.js (e.g., in a backend service or CI pipeline)
- Build a custom rendering service with fine-grained control over the pipeline
- Run visual regression tests against golden baselines
- Benchmark render performance across different configurations
**Use a different package if you want to:**
- Render from the command line without writing code — use the [CLI](/packages/cli) (`npx hyperframes render`)
- Preview compositions in the browser — use the [CLI](/packages/cli) or [studio](/packages/studio)
- Capture frames without encoding — use the [engine](/packages/engine)
- Lint or parse composition HTML — use [core](/packages/core)
<Tip>
If you are building a web application or script that just needs to render a video, the [CLI](/packages/cli) is the fastest path. The producer package is for when you need programmatic control inside Node.js.
</Tip>
## What It Does
The producer orchestrates the full render:
The producer orchestrates the full render pipeline:
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
<Steps>
<Step title="Load the composition HTML">
Reads your `index.html` and any referenced sub-compositions.
</Step>
<Step title="Inject the Hyperframes runtime">
Adds the runtime script that manages timeline seeking, clip lifecycle, and media playback.
</Step>
<Step title="Wait for readiness gates">
Polls for `window.__playerReady` and `window.__renderReady` to ensure all assets (fonts, images, video) are loaded before capture begins.
</Step>
<Step title="Capture frames via the engine">
Uses the [engine's](/packages/engine) BeginFrame pipeline to capture each frame as a pixel buffer.
</Step>
<Step title="Encode to MP4 via FFmpeg">
Pipes frame buffers into FFmpeg with the selected quality preset and encoding settings.
</Step>
<Step title="Mix audio tracks">
Extracts audio from video clips and audio elements, applies `data-volume` and `data-media-start` offsets, and mixes them into the final MP4.
</Step>
</Steps>
## Programmatic Usage
```typescript
import { render } from '@hyperframes/producer';
await render({
const result = await render({
input: './my-video/index.html',
output: './output.mp4',
fps: 30,
quality: 'standard',
});
console.log(result.duration); // Total render time in ms
console.log(result.frameCount); // Number of frames captured
console.log(result.outputPath); // Absolute path to the output file
```
### With All Options
```typescript
await render({
input: './my-video/index.html',
output: './output.mp4',
fps: 30,
width: 1920,
height: 1080,
quality: 'high',
docker: true, // Use Docker for deterministic rendering
});
```
## Docker Rendering
For deterministic output, the producer renders inside a Docker container with a pinned Chrome version and font set:
For deterministic output, the producer can render inside a Docker container with a pinned Chrome version and font set. This guarantees identical output across machines — critical for CI pipelines and production services.
```bash
# Via the CLI
# Via the CLI (recommended)
npx hyperframes render --docker -o output.mp4
# Via the producer API
await render({ input: './index.html', output: './out.mp4', docker: true });
```
<Info>
Docker mode requires Docker to be installed and running. Run `npx hyperframes doctor` to verify your environment. See [Deterministic Rendering](/concepts/determinism) for details on what makes Docker mode deterministic.
</Info>
## Quality Presets
| Preset | Resolution | Encoding | Use Case |
|--------|-----------|----------|----------|
| `draft` | Original | Fast CRF | Quick iteration, previewing edits |
| `standard` | Original | Balanced CRF | Production renders, sharing |
| `high` | Original | High-quality CRF | Final delivery, archival |
## GPU Encoding
The producer supports hardware-accelerated encoding for faster renders:
| Platform | Encoder | Flag |
|----------|---------|------|
| NVIDIA | NVENC | Auto-detected |
| macOS | VideoToolbox | Auto-detected |
| Linux | VAAPI | Auto-detected |
GPU encoding is automatically used when available. To check your system's capabilities:
```bash
npx hyperframes doctor
```
## Regression Testing
The producer includes a regression harness for comparing render output against golden baselines:
The producer includes a regression harness for comparing render output against golden baselines. This is useful for catching visual regressions when changing the runtime, engine, or rendering pipeline.
```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
# Build the test Docker image
pnpm docker:build:test
# Run regression tests (compares output against golden baselines)
pnpm docker:test
# Regenerate golden baselines after intentional changes
pnpm docker:test:update
```
## Benchmarking
Find optimal render settings for your hardware:
```bash
# Via the CLI
npx hyperframes benchmark
# Directly from the producer package
cd packages/producer
pnpm benchmark
```
The benchmark runs several compositions with different quality and FPS settings and reports timing for each combination.
## Related Packages
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/packages/cli">
Command-line interface that wraps the producer for rendering, previewing, and more.
</Card>
<Card title="Engine" icon="gear" href="/packages/engine">
The low-level capture pipeline that the producer uses to grab frames.
</Card>
<Card title="Core" icon="cube" href="/packages/core">
Types, runtime, and linter that the producer depends on.
</Card>
<Card title="Studio" icon="palette" href="/packages/studio">
Visual editor for building compositions before rendering with the producer.
</Card>
</CardGroup>
+108 -16
View File
@@ -1,40 +1,132 @@
---
title: "@hyperframes/studio"
description: "Composition editor UI."
description: "Visual composition editor with live preview, timeline view, and hot reload."
---
The studio package provides a visual editor for creating and previewing Hyperframes compositions in the browser.
The studio package provides a browser-based visual editor for creating and previewing Hyperframes compositions. It gives you a real-time preview of your video, a visual timeline of all clips, and player controls for seeking and playback — all updating live as you edit your HTML.
```bash
npm install @hyperframes/studio
```
## What It Does
## When to Use
The studio is a React-based composition editor that provides:
**Use `@hyperframes/studio` when you need to:**
- Build a custom composition editor UI (e.g., embedded in your own web application)
- Integrate the Hyperframes preview player into a larger product
- Extend the editor with custom panels, toolbars, or integrations
- **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
**Use a different package if you want to:**
- Preview compositions during development — use the [CLI](/packages/cli) (`npx hyperframes dev`), which launches the studio for you
- Render compositions to MP4 — use the [CLI](/packages/cli) or [producer](/packages/producer)
- Capture frames programmatically — use the [engine](/packages/engine)
<Tip>
**For most development workflows, you do not need to install the studio directly.** Running `npx hyperframes dev` starts the studio automatically with hot reload. Install `@hyperframes/studio` only if you are embedding the editor into your own application.
</Tip>
## Running the Studio
### Via the CLI (recommended)
```bash
# From the monorepo root
npx hyperframes dev
```
This starts the studio development server, opens your composition in the browser, and watches for file changes. This is the easiest way to get a live preview.
### From the monorepo
```bash
# From the root
pnpm dev
# Or directly
# Or target the studio package directly
pnpm --filter @hyperframes/studio dev
```
The studio starts a development server with live preview.
The studio starts at `http://localhost:3000` by default.
## Features
### Live Preview
The studio renders your composition in an iframe using the Hyperframes runtime. What you see in the preview is exactly what will be captured during rendering — the same runtime code, the same seek logic, the same clip lifecycle.
Changes to your HTML are picked up automatically through hot reload, so you can edit `index.html` in your editor and see the result in the browser within milliseconds.
### Timeline View
The timeline panel provides a visual representation of your composition's structure:
- Each clip appears as a colored bar on its track
- Bar position and width reflect `data-start` and `data-duration`
- Tracks are stacked by `data-track-index` (higher tracks render in front)
- Relative timing references (e.g., `data-start="intro"`) are resolved and displayed as absolute positions
This makes it easy to understand the temporal structure of complex compositions with many overlapping clips.
### Player Controls
The studio includes a full set of playback controls:
- **Play / Pause** — start and stop playback
- **Seek** — click anywhere on the timeline to jump to that point
- **Scrub** — drag the playhead to scrub through the composition frame by frame
- **Frame step** — advance or rewind one frame at a time for precise positioning
### Hot Reload
File changes are detected and applied without restarting the server. The preview maintains its current playback position when possible, so you can tweak an animation at the 5-second mark without having to seek back to it after every save.
## Architecture
The studio is a React application that:
The studio is a React application with the following structure:
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
1. **Iframe preview** — your composition HTML is loaded in an isolated iframe with the Hyperframes runtime injected. This ensures the preview uses the same rendering path as production.
2. **Runtime bridge** — the studio communicates with the iframe via `postMessage` to control playback (play, pause, seek) and receive state updates (current time, duration, readiness).
3. **Timeline component** — parses the composition using `@hyperframes/core` to extract clip timing data and renders the visual timeline panel.
4. **File watcher** — a development server (Vite-based) watches your project files and triggers hot module replacement when changes are detected.
## Embedding in Your Own Application
If you are building a product that includes a composition editor, you can use the studio's components directly:
```typescript
import { Player, Timeline } from '@hyperframes/studio';
// Embed the preview player
<Player
src="./my-composition/index.html"
width={1920}
height={1080}
autoPlay={false}
/>
// Embed the timeline view
<Timeline compositionHtml={htmlString} />
```
<Info>
The studio depends on `@hyperframes/core` for parsing and runtime injection. You do not need to install core separately — it is included as a dependency.
</Info>
## Related Packages
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/packages/cli">
Launches the studio via `npx hyperframes dev` — the easiest way to preview compositions.
</Card>
<Card title="Core" icon="cube" href="/packages/core">
Types, parsing, and runtime that the studio uses for preview and timeline rendering.
</Card>
<Card title="Producer" icon="film" href="/packages/producer">
Renders the compositions you build in the studio to finished MP4 files.
</Card>
<Card title="Engine" icon="gear" href="/packages/engine">
The capture engine that powers production rendering of your compositions.
</Card>
</CardGroup>
+160 -59
View File
@@ -1,84 +1,185 @@
---
title: Quickstart
description: "Create, preview, and render your first Hyperframes video."
description: "Create, preview, and render your first Hyperframes video in under two minutes."
---
## Create a Project
Go from zero to a rendered MP4 in four steps: scaffold a project, preview it live, customize the composition, and render.
```bash
npx create-hyperframe my-video
cd my-video
```
## What you'll build
This scaffolds a project with an `index.html` composition and assets directory.
A 1920x1080 video with an animated title that fades in from above — rendered to MP4 on your local machine. The entire composition is a single HTML file.
## Preview in Browser
## Prerequisites
```bash
npx hyperframes dev
```
<Steps>
<Step title="Install Node.js 20+">
Hyperframes requires Node.js 20 or later. Check your version:
Opens a live preview at `http://localhost:3000`. Edit `index.html` and the preview updates automatically.
```bash
node --version
```
## Render to MP4
```bash Expected output
v20.11.0 # or any version >= 20
```
</Step>
```bash
npx hyperframes render -o output.mp4
```
<Step title="Install FFmpeg">
FFmpeg is required for local video rendering (encoding captured frames into MP4).
Renders your composition to an MP4 file using the local rendering pipeline (Puppeteer + FFmpeg).
<CodeGroup>
```bash macOS
brew install ffmpeg
```
```bash Ubuntu / Debian
sudo apt install ffmpeg
```
```bash Windows
# Download from https://ffmpeg.org/download.html
# or install via winget:
winget install ffmpeg
```
</CodeGroup>
<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>
Verify the installation:
## Project Structure
```bash
ffmpeg -version
```
After `create-hyperframe`, your project looks like this:
```bash Expected output
ffmpeg version 7.x ...
```
</Step>
</Steps>
```
my-video/
├── index.html # Root composition
├── compositions/ # Sub-compositions (optional)
└── assets/ # Media files (video, audio, images)
```
## Create your first video
## Your First Composition
<Steps>
<Step title="Scaffold the project">
```bash
npx create-hyperframe my-video
cd my-video
```
Every Hyperframes video is an HTML file. Here's a minimal example:
```bash Expected output
✔ Created my-video/
✔ index.html
✔ assets/
Done. Run `npx hyperframes dev` to preview.
```
```html
<div id="root" data-composition-id="my-video"
data-start="0" data-width="1920" data-height="1080">
This generates the following project structure:
<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>
<Tree>
<Tree.Folder name="my-video" defaultOpen>
<Tree.File name="index.html" />
<Tree.Folder name="compositions" defaultOpen>
<Tree.File name=".gitkeep" />
</Tree.Folder>
<Tree.Folder name="assets" defaultOpen>
<Tree.File name=".gitkeep" />
</Tree.Folder>
</Tree.Folder>
</Tree>
<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>
```
| Path | Purpose |
|------|---------|
| `index.html` | Root composition — your video's entry point |
| `compositions/` | Sub-compositions loaded via `data-composition-src` |
| `assets/` | Media files (video, audio, images) |
</Step>
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`
<Step title="Preview in the browser">
```bash
npx hyperframes dev
```
## Requirements
```bash Expected output
✔ Hyperframes dev server running
→ http://localhost:3000
```
- **Node.js** 20+
- **pnpm** (recommended) or npm
- **FFmpeg** for local rendering
- **Docker** (optional) for deterministic rendering
Open [http://localhost:3000](http://localhost:3000) to see the live preview. Edits to `index.html` reload automatically.
<Card title="Templates" icon="grid-2" href="/guides/templates">
Browse built-in templates for common video patterns
</Card>
<Tip>
The dev server supports hot reload — save your HTML file and the preview updates instantly, no manual refresh needed.
</Tip>
</Step>
<Step title="Edit the composition">
Open `index.html` and replace it with this composition:
```html index.html
<div id="root" data-composition-id="my-video"
data-start="0" data-width="1920" data-height="1080">
<!-- 1. Define a timed text clip on track 0 -->
<h1 id="title" class="clip"
data-start="0" data-duration="5" data-track-index="0"
style="font-size: 72px; color: white; text-align: center;
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);">
Hello, Hyperframes!
</h1>
<!-- 2. Load GSAP for animation -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<!-- 3. Create a paused timeline and register it -->
<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>
```
Three rules to remember:
- **Root element** must have `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 created with `{ paused: true }` and registered on `window.__timelines`
</Step>
<Step title="Render to MP4">
```bash
npx hyperframes render -o output.mp4
```
```bash Expected output
✔ Capturing frames... 150/150
✔ Encoding MP4...
✔ output.mp4 (1920x1080, 5.0s, 30fps)
```
Your video is now at `output.mp4`. Open it with any media player.
</Step>
</Steps>
## Requirements summary
| Dependency | Required | Notes |
|-----------|----------|-------|
| **Node.js** 20+ | Yes | Runtime for CLI and dev server |
| **pnpm** or npm | Yes | Package manager (pnpm recommended) |
| **FFmpeg** | Yes | Video encoding for local renders |
| **Docker** | No | Optional — for deterministic, reproducible renders |
## Next steps
<CardGroup cols={2}>
<Card title="Compositions" icon="layer-group" href="/concepts/compositions">
Learn how compositions, clips, and nested timelines work together
</Card>
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
Add fade, slide, scale, and custom animations to your videos
</Card>
<Card title="Templates" icon="grid-2" href="/guides/templates">
Start from built-in templates like title-card and video-edit
</Card>
<Card title="Rendering" icon="film" href="/guides/rendering">
Explore render options: quality presets, Docker mode, and GPU encoding
</Card>
</CardGroup>
+140 -79
View File
@@ -10,9 +10,9 @@ This is the full schema reference for Hyperframes compositions. For a gentler in
Hyperframes uses HTML as the source of truth for describing a video:
- **HTML clips** = video, image, audio, composition
- **Data attributes** = timing, metadata, styling
- **[Data attributes](/concepts/data-attributes)** = timing, metadata, styling
- **CSS** = positioning and appearance
- **GSAP timeline** = animations and playback sync
- **GSAP timeline** = animations and playback sync (see [GSAP Animation](/guides/gsap-animation))
## Framework-Managed Behavior
@@ -27,12 +27,12 @@ The framework reads data attributes and automatically manages:
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.
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. See [Common Mistakes](/guides/common-mistakes) for more details.
</Warning>
## Viewport
Every composition must include `data-width` and `data-height`:
Every composition must include `data-width` and `data-height` on the root element:
```html
<div id="main" data-composition-id="my-video"
@@ -49,78 +49,112 @@ Common sizes:
| 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 |
| `id` | All | Yes | Unique identifier (e.g., `"el-1"`). Used for relative timing references and CSS targeting. |
| `class="clip"` | Visible elements | Yes | Enables runtime visibility management. Omit for audio-only clips. |
| `data-start` | All | Yes | Start time in seconds (e.g., `"0"`, `"5.5"`), or a clip ID reference for [relative timing](#relative-timing) (e.g., `"intro"`). |
| `data-duration` | video, img, audio | See below | Duration in seconds. **Required** for images. Optional for video/audio (defaults to source duration). Not used on compositions. |
| `data-track-index` | All | Yes | Timeline track number. Controls z-ordering (higher = in front). Clips on the same track cannot overlap. |
| `data-media-start` | video, audio | No | Playback offset / trim point in source file (seconds). Default: `0`. See [Data Attributes](/concepts/data-attributes). |
| `data-volume` | audio, video | No | Volume level from `0` to `1`. Default: `1`. |
| `data-composition-id` | div | On compositions | Unique composition ID. Must match the key used in `window.__timelines`. |
| `data-composition-src` | div | No | Path to external composition HTML file (for [nested compositions](#composition-clips)). |
| `data-width` | div | On compositions | Composition width in pixels. |
| `data-height` | div | On compositions | Composition height in pixels. |
## Video Clips
## Clip Types
```html
<video
id="el-1"
data-start="0"
data-duration="15"
data-track-index="0"
data-media-start="0"
src="./assets/video.mp4"
></video>
```
<AccordionGroup>
<Accordion title="Video Clips">
Video clips embed `<video>` elements with timing and playback attributes.
- `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
```html
<video
id="el-1"
data-start="0"
data-duration="15"
data-track-index="0"
data-media-start="0"
src="./assets/video.mp4"
></video>
```
## Image Clips
**Key behavior:**
- `data-duration` is **optional** — defaults to the remaining duration of the source file from `data-media-start`
- If source media runs out before `data-duration`, the clip shows the last frame (freeze frame)
- `data-media-start` trims the beginning of the source video — `data-media-start="5"` starts playback 5 seconds into the source file
- `data-volume` controls the audio volume of the video — set to `"0"` for silent video
- Do **not** add `class="clip"` to video elements — the framework manages their visibility directly
```html
<img
id="el-2"
class="clip"
data-start="5"
data-duration="4"
data-track-index="1"
src="./assets/overlay.png"
/>
```
<Warning>
Do not animate `width`, `height`, `top`, or `left` directly on `<video>` elements with GSAP. This can cause Chrome to stop rendering video frames. Wrap the video in a `<div>` and animate the wrapper instead. See [Common Mistakes](/guides/common-mistakes).
</Warning>
</Accordion>
- `data-duration` is **required** for images
<Accordion title="Image Clips">
Image clips display static images with controlled timing.
## Audio Clips
```html
<img
id="el-2"
class="clip"
data-start="5"
data-duration="4"
data-track-index="1"
src="./assets/overlay.png"
/>
```
```html
<audio
id="el-4"
data-start="0"
data-duration="30"
data-track-index="2"
src="./assets/music.mp3"
></audio>
```
**Key behavior:**
- `data-duration` is **required** for images (unlike video/audio, there is no source duration to default to)
- `class="clip"` is **required** — this enables the runtime to show/hide the image based on timing
- Supported formats: PNG, JPG, WebP, SVG, GIF (first frame only)
- Position and size with CSS — the image renders at its natural size unless styled otherwise
</Accordion>
- `data-duration` is optional — defaults to remaining duration of source file
- Audio clips are invisible
<Accordion title="Audio Clips">
Audio clips add sound to the composition without any visual element.
## Composition Clips
```html
<audio
id="el-4"
data-start="0"
data-duration="30"
data-track-index="2"
src="./assets/music.mp3"
></audio>
```
```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>
```
**Key behavior:**
- `data-duration` is **optional** — defaults to the remaining duration of the source file from `data-media-start`
- Audio clips are invisible — do not add `class="clip"` (there is nothing to show/hide)
- `data-volume` controls volume — use `"0.5"` for background music at 50% volume
- `data-media-start` trims the beginning of the audio source, just like video
- Multiple audio clips can overlap on different tracks for layered sound design
</Accordion>
- 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
<Accordion title="Composition Clips (Nested)">
Composition clips embed one composition inside another, enabling modular, reusable video building blocks.
```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>
```
**Key behavior:**
- Compositions do **not** use `data-duration` — duration is determined by the composition's GSAP timeline (`tl.duration()`)
- External compositions are loaded from `data-composition-src` and wrapped in `<template>` tags
- Each nested composition has its own `window.__timelines` entry, registered by its own `<script>` block
- The framework automatically nests sub-timelines — do not manually add them to the parent timeline
- Any composition can be nested inside any other — there is no special "root" type
For more on how compositions work, see [Compositions](/concepts/compositions).
</Accordion>
</AccordionGroup>
## Relative Timing
@@ -131,29 +165,49 @@ Reference another clip's ID in `data-start` to mean "start when that clip ends":
<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).
`main` starts at second 10 (when `intro` ends).
**Offsets** let you add gaps or overlaps:
```html
<!-- 2-second gap after intro -->
<video id="main" data-start="intro + 2" data-duration="20" data-track-index="0" src="..."></video>
<!-- 0.5-second overlap with intro -->
<video id="main" data-start="intro - 0.5" data-duration="20" data-track-index="0" src="..."></video>
```
For a deeper explanation, see the [relative timing section](/concepts/data-attributes#relative-timing) in the Data Attributes concept page.
## Timeline Contract
The framework initializes `window.__timelines = {}` before any scripts run. Every composition must register a timeline:
The framework initializes `window.__timelines = {}` before any scripts run. Every composition must register a GSAP timeline at the key matching its `data-composition-id`:
```javascript
const tl = gsap.timeline({ paused: true });
// ... add tweens
// Add animations
tl.to("#title", { opacity: 1, duration: 0.5 }, 0);
tl.to("#title", { opacity: 0, duration: 0.5 }, 4.5);
// Register the timeline
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
- Every composition needs a `<script>` block that creates and registers its timeline
- All timelines must start paused (`{ paused: true }`)
- The framework auto-nests sub-timelines into the parent — do **not** manually add them
- Duration comes from `tl.duration()` — do **not** add `data-duration` on composition elements
- Timelines must be finite (no infinite loops or repeats)
- The timeline ID must exactly match the `data-composition-id` attribute on the root element
For a complete guide to working with GSAP timelines, see [GSAP Animation](/guides/gsap-animation).
## Caption Discoverability
For caption compositions, add these attributes to the root node:
For caption compositions, add these attributes to the root node so the framework can identify and special-case caption rendering:
```html
<div
@@ -166,9 +220,16 @@ For caption compositions, add these attributes to the root node:
## 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"`
<Check>
Before rendering, verify your composition meets these requirements:
- Every composition has `data-width` and `data-height` on the root element
- Each reusable composition is in its own HTML file
- External compositions are loaded via `data-composition-src`
- Each external composition file uses a `<template>` wrapper
- All GSAP timelines are registered in `window.__timelines` with the correct ID
- Timed visible elements (images, divs) have `class="clip"`
- Video elements do **not** have `class="clip"` (framework manages them directly)
- All `data-start` references point to existing clip IDs
- Run `npx hyperframes lint` to catch structural issues automatically
</Check>