` -- 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
+
+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.
+
+
+
+## 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. 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.
+
+
+
+## Variables
+
+Compositions can expose variables for dynamic content:
+
+```html compositions/card.html
+
+```
+
+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](/packages/cli) to see all compositions in a project:
+
+```bash
+npx hyperframes compositions
+```
+
+## Next Steps
+
+
+
+ Full reference for timing, media, and composition attributes
+
+
+ Add animations to your compositions with GSAP timelines
+
+
+ Start from built-in templates for common video patterns
+
+
+ Complete schema for authoring compositions
+
+
diff --git a/docs/concepts/data-attributes.mdx b/docs/concepts/data-attributes.mdx
new file mode 100644
index 000000000..0b7b66e3c
--- /dev/null
+++ b/docs/concepts/data-attributes.mdx
@@ -0,0 +1,104 @@
+---
+title: Data Attributes
+description: "Core attributes for controlling element timing and behavior."
+---
+
+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
+
+| Attribute | Example | Description |
+|-----------|---------|-------------|
+| `data-start` | `"0"` or `"intro"` | Start time in seconds, or a clip ID reference for [relative timing](#relative-timing) |
+| `data-duration` | `"5"` | Duration in seconds. Required for images. Optional for video/audio (defaults to source duration). Not used on compositions. |
+| `data-track-index` | `"0"` | Timeline track number. Controls z-ordering (higher = in front) and groups clips into rows. Clips on the same track cannot overlap. |
+
+## Media Attributes
+
+| Attribute | Example | Description |
+|-----------|---------|-------------|
+| `data-media-start` | `"2"` | Media playback offset / trim point in seconds. Default: `0` |
+| `data-volume` | `"0.8"` | Audio/video volume, 0 to 1 |
+| `data-has-audio` | `"true"` | Indicates video has an audio track |
+
+## Composition Attributes
+
+| Attribute | Example | Description |
+|-----------|---------|-------------|
+| `data-composition-id` | `"root"` | Unique ID for [composition](/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](/concepts/compositions) HTML file |
+
+## Element Visibility
+
+Add `class="clip"` to all timed elements so the runtime can manage their visibility lifecycle:
+
+```html index.html
+
+ Hello World
+
+```
+
+## Relative Timing
+
+Instead of calculating absolute start times, a clip can reference another clip's `id` in its `data-start` attribute. This means "start when that clip ends":
+
+```html index.html
+
+
+
+```
+
+`main` resolves to second 10, `outro` resolves to second 30. If `intro`'s duration changes, downstream clips shift automatically.
+
+### Offsets (Gaps and Overlaps)
+
+Add `+ N` or `- N` after the ID to offset from the end of the referenced clip:
+
+```html index.html
+
+
+
+
+
+```
+
+
+ Overlapping clips must be on different tracks -- clips on the same track cannot overlap in time.
+
+
+
+ **Same composition only** -- references resolve within the clip's parent [composition](/concepts/compositions). You cannot reference a clip in a sibling or parent composition.
+
+ **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:
+ - `` -- start when that clip ends
+ - ` + ` -- start N seconds after that clip ends
+ - ` - ` -- 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.
+
+
+## Next Steps
+
+
+
+ How compositions use data attributes to define video structure
+
+
+ Complete attribute reference with per-element details
+
+
+ Animate elements alongside data-attribute-driven timing
+
+
+ Pitfalls to avoid when setting up timing and attributes
+
+
diff --git a/docs/concepts/determinism.mdx b/docs/concepts/determinism.mdx
new file mode 100644
index 000000000..11788c6a2
--- /dev/null
+++ b/docs/concepts/determinism.mdx
@@ -0,0 +1,101 @@
+---
+title: Deterministic Rendering
+description: "Same input, identical output. Every time."
+---
+
+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. No realtime playback is involved -- every frame is independently seeked and captured.
+
+
+
+ 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.
+
+
+ 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.
+
+
+ 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.
+
+
+ FFmpeg encodes the captured frames into the final MP4 video. Audio tracks from `` and `` elements are mixed in during this stage.
+
+
+
+```mermaid
+graph LR
+ A["Frame Clock
t = frame / fps"] --> B["Seek
adapter.seekFrame(frame)"]
+ B --> C["Capture
beginFrame API"]
+ C --> D["Encode
FFmpeg"]
+ D --> E["MP4"]
+ style A fill:#00C4FF,color:#fff
+ style B fill:#00C4FF,color:#fff
+ style C fill:#00C4FF,color:#fff
+ style D fill:#00C4FF,color:#fff
+ style E fill:#00A8E1,color:#fff
+```
+
+## What Makes It Deterministic
+
+- **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
+
+For maximum reproducibility, render in Docker:
+
+```bash
+npx hyperframes render --docker -o output.mp4
+```
+
+Docker mode uses an exact Chrome version and font set, ensuring:
+- Same Chromium rendering engine across all platforms
+- Same system fonts (no platform-specific font substitution)
+- Same FFmpeg encoder version
+
+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](/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
+
+
+ Local rendering (without Docker) may show slight differences due to platform-specific font rendering and Chrome version. Use Docker mode when exact reproducibility matters.
+
+
+## For Adapter Authors
+
+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
+- 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`
+
+## Next Steps
+
+
+
+ Build adapters that uphold the determinism contract
+
+
+ Render to MP4 locally or in Docker
+
+
+ The full rendering pipeline that orchestrates deterministic output
+
+
+ Pitfalls that break determinism and how to avoid them
+
+
diff --git a/docs/concepts/frame-adapters.mdx b/docs/concepts/frame-adapters.mdx
new file mode 100644
index 000000000..3a1d9c3c1
--- /dev/null
+++ b/docs/concepts/frame-adapters.mdx
@@ -0,0 +1,144 @@
+---
+title: Frame Adapters
+description: "Bring your own animation runtime to Hyperframes."
+---
+
+The Frame Adapter pattern is how Hyperframes supports multiple animation runtimes. The core question every adapter answers:
+
+> What should the screen look like at frame N?
+
+If a runtime can answer that, it can plug into Hyperframes.
+
+
+ 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.
+
+
+## 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 adapters/types.ts
+type FrameAdapterContext = {
+ compositionId: string;
+ fps: number;
+ width: number;
+ height: number;
+ rootElement?: HTMLElement;
+};
+
+type FrameAdapter = {
+ id: string;
+ init?: (ctx: FrameAdapterContext) => Promise
| void;
+ getDurationFrames: () => number;
+ seekFrame: (frame: number) => Promise | void;
+ destroy?: () => Promise | void;
+};
+```
+
+## Required Semantics
+
+- `getDurationFrames()` must return a finite integer >= 0
+- `seekFrame(frame)` must support arbitrary seek order (forward, backward, random)
+- `seekFrame(frame)` must be idempotent for the same input frame
+- `seekFrame(frame)` must clamp internal time to the adapter's range
+- Adapters should be paused/seek-driven, not clock-driven
+
+## Host Orchestration
+
+The host normalizes frames before calling the adapter:
+
+```typescript engine/render-loop.ts
+normalizedFrame = clamp(Math.floor(frame), 0, durationFrames);
+```
+
+A typical render loop:
+
+```typescript engine/render-loop.ts
+await adapter.init?.({ compositionId, fps, width, height, rootElement });
+const durationFrames = adapter.getDurationFrames();
+
+for (let frame = 0; frame <= durationFrames; frame += 1) {
+ await adapter.seekFrame(frame);
+ // capture pixel buffer for this frame
+}
+
+await adapter.destroy?.();
+```
+
+## Determinism Contract
+
+These rules are non-negotiable for any adapter. 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)
+- No unseeded randomness
+- No render-time network fetches
+- Fixed output params (`fps`, `width`, `height`)
+- Finite duration only
+- Deterministic frame quantization before seek
+
+## Supported Runtimes
+
+First-party adapters:
+
+| Runtime | Seek Method | Status |
+|---------|------------|--------|
+| [GSAP](/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.
+
+## 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 do not break
+4. **Duration** -- returned duration is a finite integer
+5. **Cleanup** -- no leaked timers/listeners after `destroy`
+
+## Next Steps
+
+
+
+ Understand the determinism guarantees adapters must uphold
+
+
+ See the first-party GSAP adapter in action
+
+
+ The capture engine that drives adapters during rendering
+
+
+ Build and contribute your own adapter
+
+
diff --git a/docs/contributing.mdx b/docs/contributing.mdx
new file mode 100644
index 000000000..fa8879ce1
--- /dev/null
+++ b/docs/contributing.mdx
@@ -0,0 +1,152 @@
+---
+title: Contributing
+description: "How to contribute 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
+
+
+
+ Fork the repository on GitHub, then clone your fork:
+ ```bash
+ git clone https://github.com/YOUR_USERNAME/hyperframes.git
+ cd hyperframes
+ ```
+
+
+ Hyperframes uses [pnpm](https://pnpm.io/) for package management:
+ ```bash
+ pnpm install
+ ```
+
+
+ Build the monorepo to ensure everything compiles:
+ ```bash
+ pnpm build
+ ```
+
+
+ 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.
+
+
+ Create a feature branch for your work:
+ ```bash
+ git checkout -b my-feature
+ ```
+
+
+
+## Development
+
+### Common Commands
+
+```bash
+pnpm install # Install all dependencies
+pnpm dev # Start the studio (composition editor + live preview)
+pnpm build # Build all packages
+pnpm -r typecheck # Type-check all packages
+```
+
+### Running Tests
+
+
+```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
+```
+
+
+### Running All Tests
+
+```bash
+pnpm -r test
+```
+
+## Packages
+
+| 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
+
+### 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
+- For bug reports, include:
+ - Steps to reproduce
+ - Expected behavior vs. actual behavior
+ - Hyperframes version (`npx hyperframes info`)
+ - Operating system and Node.js version
+
+## Community
+
+
+
+ Report bugs, request features, and discuss ideas.
+
+
+ Our community standards and expectations.
+
+
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the [MIT License](https://github.com/heygen-com/hyperframes/blob/main/LICENSE).
diff --git a/docs/docs.json b/docs/docs.json
new file mode 100644
index 000000000..b55aff270
--- /dev/null
+++ b/docs/docs.json
@@ -0,0 +1,80 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "name": "Hyperframes",
+ "theme": "mint",
+ "colors": {
+ "primary": "#00C4FF",
+ "light": "#00C4FF",
+ "dark": "#00A8E1"
+ },
+ "logo": {
+ "light": "/logo/light.svg",
+ "dark": "/logo/dark.svg"
+ },
+ "favicon": "/favicon.svg",
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Documentation",
+ "groups": [
+ {
+ "group": "Getting Started",
+ "pages": ["introduction", "quickstart"]
+ },
+ {
+ "group": "Concepts",
+ "pages": [
+ "concepts/compositions",
+ "concepts/data-attributes",
+ "concepts/frame-adapters",
+ "concepts/determinism"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "guides/gsap-animation",
+ "guides/templates",
+ "guides/rendering",
+ "guides/common-mistakes",
+ "guides/troubleshooting"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Packages",
+ "groups": [
+ {
+ "group": "Packages",
+ "pages": [
+ "packages/core",
+ "packages/engine",
+ "packages/producer",
+ "packages/studio",
+ "packages/cli"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Reference",
+ "groups": [
+ {
+ "group": "Reference",
+ "pages": ["reference/html-schema"]
+ },
+ {
+ "group": "Community",
+ "pages": ["contributing"]
+ }
+ ]
+ }
+ ]
+ },
+ "footer": {
+ "socials": {
+ "github": "https://github.com/heygen-com/hyperframes"
+ }
+ }
+}
diff --git a/docs/favicon.svg b/docs/favicon.svg
new file mode 100644
index 000000000..dd3a15678
--- /dev/null
+++ b/docs/favicon.svg
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/guides/common-mistakes.mdx b/docs/guides/common-mistakes.mdx
new file mode 100644
index 000000000..c1d8ae0cd
--- /dev/null
+++ b/docs/guides/common-mistakes.mdx
@@ -0,0 +1,172 @@
+---
+title: Common Mistakes
+description: "Pitfalls that break Hyperframes compositions."
+---
+
+These are mistakes that cannot be caught by the linter. For automated checks, run `npx hyperframes lint` (see [CLI](/packages/cli#lint)).
+
+
+ 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.
+
+
+
+
+ **Symptom:** Video frames stop updating, or browser performance drops severely.
+
+ **Cause:** GSAP animating `width`, `height`, `top`, `left` directly on a `` element can cause the browser to stop rendering frames.
+
+ **Before (broken):**
+
+ ```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);
+ ```
+
+ **After (fixed):**
+
+ ```html index.html
+
+
+
+
+ ```
+
+ ```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);
+ ```
+
+ Use a non-timed wrapper `` for visual effects like picture-in-picture. Animate the wrapper; let the video fill it via CSS.
+
+
+
+ **Symptom:** Audio/video playback is out of sync, or plays when it should not.
+
+ **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).
+
+ **Before (broken):**
+
+ ```javascript index.html
+ // Conflicts with framework media sync
+ document.getElementById("el-video").play();
+ document.getElementById("el-audio").currentTime = 5;
+ ```
+
+ **After (fixed):**
+
+ ```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);
+ ```
+
+ 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.
+
+
+
+ **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.
+
+
+ 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.
+
+
+
+
+ **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
+
+
+ Hello World
+
+ ```
+
+ **After (fixed):**
+
+ ```html index.html
+
+
+ Hello World
+
+ ```
+
+
+ The linter catches this one: `npx hyperframes lint` will flag timed elements missing `class="clip"`.
+
+
+
+
+ **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"
+ //
+ window.__timelines["root"] = tl;
+ ```
+
+ **After (fixed):**
+
+ ```javascript index.html
+ // Key matches the data-composition-id attribute
+ //
+ window.__timelines["my-video"] = tl;
+ ```
+
+
+
+## Debugging Checklist
+
+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["
"]` 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
+
+
+
+ Fix environment and rendering issues
+
+
+ Review animation rules and patterns
+
+
+ Full attribute reference and checklist
+
+
+ Timing, media, and composition attributes
+
+
diff --git a/docs/guides/gsap-animation.mdx b/docs/guides/gsap-animation.mdx
new file mode 100644
index 000000000..26d78c6a5
--- /dev/null
+++ b/docs/guides/gsap-animation.mdx
@@ -0,0 +1,134 @@
+---
+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. For background on how animation runtimes plug into Hyperframes, see [Frame Adapters](/concepts/frame-adapters).
+
+## Setup
+
+Include GSAP and create a paused timeline:
+
+```html index.html
+
+
+```
+
+
+ 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.
+
+
+## Key Rules
+
+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
+
+## Supported Methods
+
+| Method | Description |
+|--------|-------------|
+| `tl.to(target, vars, position)` | Animate to values |
+| `tl.from(target, vars, position)` | Animate from values |
+| `tl.fromTo(target, fromVars, toVars, position)` | Animate from/to values |
+| `tl.set(target, vars, position)` | Set values instantly |
+
+## Supported Properties
+
+`opacity`, `x`, `y`, `scale`, `scaleX`, `scaleY`, `rotation`, `width`, `height`, `visibility`, `color`, `backgroundColor`, and any CSS-animatable property.
+
+## Timeline Duration and Composition Duration
+
+A composition's duration equals its GSAP timeline duration. The two are directly linked:
+
+```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.
+```
+
+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);
+```
+
+
+ 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.
+
+
+## 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 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 [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 compositions/intro-anim.html
+// In compositions/intro-anim.html
+const tl = gsap.timeline({ paused: true });
+tl.from(".title", { opacity: 0, y: -50, duration: 1 });
+window.__timelines["intro-anim"] = tl;
+
+// DO NOT manually add sub-timelines to the master:
+// masterTL.add(window.__timelines["intro-anim"], 0); // UNNECESSARY
+```
+
+
+ Don't animate `width`, `height`, `top`, or `left` directly on `` elements — this can cause the browser to stop rendering frames. Wrap the video in a `` and animate the wrapper instead. See [Common Mistakes](/guides/common-mistakes) for a detailed explanation.
+
+
+## Next Steps
+
+
+
+ Understand the building blocks that timelines animate
+
+
+ Learn how GSAP plugs into the render pipeline
+
+
+ Avoid pitfalls that break animations
+
+
+ Full reference for composition attributes
+
+
diff --git a/docs/guides/rendering.mdx b/docs/guides/rendering.mdx
new file mode 100644
index 000000000..18a3ce22c
--- /dev/null
+++ b/docs/guides/rendering.mdx
@@ -0,0 +1,148 @@
+---
+title: Rendering
+description: "Render compositions to MP4 locally or in Docker."
+---
+
+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.
+
+## Getting Started
+
+
+
+ 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
+ ```
+
+
+ Before rendering, preview your composition in the browser to verify it looks correct:
+
+ ```bash Terminal
+ npx hyperframes dev
+ ```
+
+
+ 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)
+ ```
+
+
+
+## Rendering Modes
+
+
+
+ ### Local Mode (default)
+
+ Uses Puppeteer (bundled Chromium) and your system's FFmpeg. Fast for iteration during development.
+
+ **Requires:** FFmpeg installed on your system. See [Troubleshooting](/guides/troubleshooting) if FFmpeg is not found.
+
+ ```bash Terminal
+ npx hyperframes render -o output.mp4
+ ```
+
+ **Pros:**
+ - Fast startup, no container overhead
+ - Uses your system GPU for hardware-accelerated encoding (with `--gpu`)
+ - Best for iterative development
+
+ **Cons:**
+ - Output may vary across platforms due to font and Chrome version differences
+ - Not suitable for CI/CD pipelines that require reproducibility
+
+
+ ### Docker Mode
+
+ [Deterministic](/concepts/determinism) output with an exact Chrome version and font set. Use this for production renders and CI pipelines.
+
+ **Requires:** Docker installed and running.
+
+ ```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
+
+
+ Docker mode uses `chrome-headless-shell` with [BeginFrame](/concepts/determinism#how-it-works) control for frame-perfect, deterministic capture.
+
+
+
+
+## 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
+
+| Flag | Values | Default | Description |
+|------|--------|---------|-------------|
+| `-f, --fps` | 24, 30, 60 | 30 | Frames per second |
+| `-q, --quality` | draft, standard, high | standard | Encoding quality preset |
+| `-w, --workers` | 1-8 | auto | Parallel render workers |
+| `--gpu` | — | off | GPU encoding (NVENC, VideoToolbox, VAAPI) |
+| `-o, --output` | path | — | Output file path |
+| `--docker` | — | off | Use Docker for [deterministic rendering](/concepts/determinism) |
+
+## Tips
+
+
+ Use `draft` quality during development for fast previews. Switch to `standard` or `high` for final output.
+
+
+- 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](/concepts/determinism) across platforms
+- For compositions with many frames, `--gpu` can significantly speed up local encoding
+
+## Next Steps
+
+
+
+ Understand the determinism guarantees
+
+
+ Full list of CLI commands and flags
+
+
+ Fix common rendering issues
+
+
+ Avoid pitfalls that affect render output
+
+
diff --git a/docs/guides/templates.mdx b/docs/guides/templates.mdx
new file mode 100644
index 000000000..7f24e5a38
--- /dev/null
+++ b/docs/guides/templates.mdx
@@ -0,0 +1,140 @@
+---
+title: Templates
+description: "Built-in templates for common video patterns."
+---
+
+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 Terminal
+npx hyperframes init --template
+```
+
+This creates a new project directory with an `index.html` composition and any required assets.
+
+## Available Templates
+
+
+
+ ### blank
+
+ An empty 1920x1080 composition with a GSAP timeline wired up and nothing else. Start from scratch.
+
+ **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.
+
+ **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.
+
+ ```bash Terminal
+ npx hyperframes init --template blank
+ ```
+
+ **What you get:**
+ ```
+ my-video/
+ ├── index.html # Empty root composition with GSAP setup
+ └── assets/ # Empty directory for your media files
+ ```
+
+
+ ### title-card
+
+ Animated title and subtitle with GSAP fade-in/out transitions.
+
+ **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.
+
+ **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
+ ```
+
+
+ ### 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
+ ```
+
+
+
+## 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 |
+
+
+ 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).
+
+
+## Custom Templates
+
+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 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
+
+
+
+
+
+
+
+```
+
+After creating a custom template, validate it with the [linter](/packages/cli#lint):
+
+```bash Terminal
+npx hyperframes lint
+```
+
+## Next Steps
+
+
+
+ Create, preview, and render your first video
+
+
+ Add animations to your template
+
+
+ Understand the composition data model
+
+
+ Render your composition to MP4
+
+
diff --git a/docs/guides/troubleshooting.mdx b/docs/guides/troubleshooting.mdx
new file mode 100644
index 000000000..5458cef33
--- /dev/null
+++ b/docs/guides/troubleshooting.mdx
@@ -0,0 +1,147 @@
+---
+title: Troubleshooting
+description: "Solutions for common Hyperframes issues."
+---
+
+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](/concepts/compositions). The root element must have a [`data-composition-id`](/concepts/data-attributes#composition-attributes) attribute.
+
+ **Fix:** Run `npx hyperframes init` to create a composition from a [template](/guides/templates), or verify your `index.html` has the correct structure:
+
+ ```html index.html
+
+
+
+ ```
+
+
+
+ Local [rendering](/guides/rendering) requires FFmpeg installed on your system. Install it for your platform:
+
+
+ ```bash macOS
+ brew install ffmpeg
+ ```
+
+ ```bash Ubuntu/Debian
+ sudo apt install ffmpeg
+ ```
+
+ ```bash Windows
+ # Download from https://ffmpeg.org/download.html
+ # Add the bin directory to your PATH
+ ```
+
+ ```bash Verify installation
+ ffmpeg -version
+ ```
+
+
+ After installing, run `npx hyperframes doctor` to verify the CLI can find it.
+
+
+ If you cannot install FFmpeg, use [Docker mode](/guides/rendering) instead — it bundles FFmpeg inside the container: `npx hyperframes render --docker -o output.mp4`
+
+
+
+
+ Run `npx hyperframes lint` to check for common structural issues (see [CLI: lint](/packages/cli#lint)):
+
+ | 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). |
+
+
+
+ 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.
+
+ If changes still do not appear:
+
+ 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
+
+
+
+ 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.
+
+
+
+ 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
+
+
+
+ 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.
+
+
+
+## System Diagnostics
+
+Run `npx hyperframes doctor` to check your environment:
+
+```bash Terminal
+npx hyperframes doctor
+```
+
+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
+
+
+
+ Coding pitfalls that break compositions
+
+
+ Rendering modes, options, and tips
+
+
+ Full list of CLI commands
+
+
+ Report bugs and contribute fixes
+
+
diff --git a/docs/introduction.mdx b/docs/introduction.mdx
new file mode 100644
index 000000000..aac295f03
--- /dev/null
+++ b/docs/introduction.mdx
@@ -0,0 +1,99 @@
+---
+title: Introduction
+description: "Write HTML. Render video. Built for 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.
+
+## See It in Action
+
+Here is a video defined entirely as HTML:
+
+```html
+
+
+
+
+
+ Welcome to Hyperframes
+
+
+
+
+```
+
+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?
+
+
+
+ **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.
+
+
+ **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.
+
+
+ **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.
+
+
+
+
+ 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.
+
+
+## How It Works
+
+
+
+ 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.
+
+
+ Run `npx hyperframes dev` to open a live preview at `localhost:3000`. Edit your HTML and see changes instantly — no build step, no compilation.
+
+
+ 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.
+
+
+
+## Packages
+
+
+
+ Types, HTML parsing, runtime, and composition linter — the foundation everything else builds on.
+
+
+ Seekable page-to-video capture engine. Loads HTML in headless Chrome and captures frame-by-frame.
+
+
+ Full rendering pipeline combining capture and FFmpeg encoding into a single API call.
+
+
+ Visual composition editor UI for building and previewing timelines interactively.
+
+
+ Command-line tool for creating, previewing, and rendering compositions.
+
+
+
+## Next Steps
+
+
+
+ Build and render your first video in 60 seconds
+
+
+ Understand the HTML-based data model behind every video
+
+
+ Add timeline-driven animations with GSAP
+
+
+ Render locally, in Docker, or in a CI pipeline
+
+
diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg
new file mode 100644
index 000000000..5d9cf5446
--- /dev/null
+++ b/docs/logo/dark.svg
@@ -0,0 +1,311 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/logo/light.svg b/docs/logo/light.svg
new file mode 100644
index 000000000..dc7fa020d
--- /dev/null
+++ b/docs/logo/light.svg
@@ -0,0 +1,307 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/packages/cli.mdx b/docs/packages/cli.mdx
new file mode 100644
index 000000000..4a2a9c605
--- /dev/null
+++ b/docs/packages/cli.mdx
@@ -0,0 +1,266 @@
+---
+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 — all from your terminal.
+
+```bash
+npm install -g hyperframes
+# or use directly with npx
+npx hyperframes
+```
+
+## 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)
+
+
+ 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.
+
+
+## Getting Started
+
+
+
+ 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.
+
+
+ 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.
+
+
+ Check for structural issues before rendering:
+ ```bash
+ npx hyperframes lint
+ ```
+ ```
+ Linting index.html...
+ No issues found.
+ ```
+
+
+ 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
+ ```
+
+
+
+## Commands
+
+
+
+ ### `init`
+
+ Create a new composition project from a template:
+
+ ```bash
+ npx hyperframes init --template
+ ```
+
+ | 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 |
+
+ See [Templates](/guides/templates) for full details and previews.
+
+ ### `compositions`
+
+ List all compositions in the current project:
+
+ ```bash
+ npx hyperframes compositions
+ ```
+ ```
+ Compositions in ./my-video:
+ root index.html (30s, 1920x1080)
+ intro-anim compositions/intro.html (5s, 1920x1080)
+ ```
+
+
+ ### `dev`
+
+ Start a live preview server with hot reload:
+
+ ```bash
+ npx hyperframes dev
+ ```
+ ```
+ Hyperframes Studio v0.1.0
+ Local: http://localhost:3000
+ Watching for changes...
+ ```
+
+ 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.
+
+ ### `lint`
+
+ Check a composition for common issues:
+
+ ```bash
+ npx hyperframes lint
+ ```
+ ```
+ Linting index.html...
+
+ WARNING unmuted-video
+ Video element 'clip-1' should have the 'muted' attribute for reliable autoplay.
+ at index.html:5
+
+ 1 issue found (0 errors, 1 warning)
+ ```
+
+ The linter detects missing attributes, deprecated names, structural problems, and more. See [Common Mistakes](/guides/common-mistakes) for details on each rule.
+
+
+ ### `render`
+
+ Render a composition to MP4:
+
+ ```bash
+ # Local mode (fast iteration)
+ npx hyperframes render -o output.mp4
+
+ # Docker mode (deterministic output)
+ npx hyperframes render --docker -o output.mp4
+
+ # 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)
+ ```
+
+ See [Rendering](/guides/rendering) for all options and modes.
+
+ ### `benchmark`
+
+ Find optimal render settings for your system:
+
+ ```bash
+ npx hyperframes benchmark
+ ```
+ ```
+ Running benchmark suite...
+
+ 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
+
+ Recommended: quality=standard fps=30 (best speed/quality balance)
+ ```
+
+
+ ### `doctor`
+
+ Check your environment for required dependencies:
+
+ ```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)
+
+ All checks passed.
+ ```
+
+ Verifies Node.js version, FFmpeg, Docker, Chrome, and other requirements.
+
+ ### `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.
+ ```
+
+
+
+## Related Packages
+
+
+
+ The rendering pipeline the CLI calls under the hood. Use directly for programmatic rendering.
+
+
+ The editor UI that powers `hyperframes dev`. Use directly to embed in your own app.
+
+
+ Types, linter, and runtime. Use directly for custom tooling and integrations.
+
+
+ The capture engine. Use directly for custom frame capture pipelines.
+
+
diff --git a/docs/packages/core.mdx b/docs/packages/core.mdx
new file mode 100644
index 000000000..f9c4b4e33
--- /dev/null
+++ b/docs/packages/core.mdx
@@ -0,0 +1,162 @@
+---
+title: "@hyperframes/core"
+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 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
+
+
+ **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.
+
+
+**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, 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 = `
+
+
+
+`;
+
+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
+
+
+ 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).
+
+
+## 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 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`** — injected into browser iframes for preview playback
+- **`hyperframe.runtime.mjs`** — for Node.js tooling and tests
+
+Build the runtime from source:
+
+```bash
+pnpm --filter @hyperframes/core build:hyperframes-runtime
+```
+
+
+ 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.
+
+
+## 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 type { FrameAdapterContext } from '@hyperframes/core';
+
+// Every adapter must answer: "what should the screen look like at this time?"
+// See the Frame Adapters concept page for the full API.
+```
+
+## Related Packages
+
+
+
+ The easiest way to create, preview, lint, and render compositions.
+
+
+ Low-level frame capture pipeline that uses core types and runtime.
+
+
+ Full rendering pipeline built on top of core and engine.
+
+
+ Visual composition editor that embeds the core runtime for preview.
+
+
diff --git a/docs/packages/engine.mdx b/docs/packages/engine.mdx
new file mode 100644
index 000000000..a57d4b592
--- /dev/null
+++ b/docs/packages/engine.mdx
@@ -0,0 +1,150 @@
+---
+title: "@hyperframes/engine"
+description: "Seekable page-to-video capture engine using Chrome's BeginFrame API."
+---
+
+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
+```
+
+## When to Use
+
+
+ **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.
+
+
+**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)
+
+**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)
+
+## How It Works
+
+The engine implements a **seek-and-capture** loop that is fundamentally different from screen recording:
+
+
+
+ The engine starts `chrome-headless-shell`, a minimal headless Chrome binary optimized for programmatic control via the Chrome DevTools Protocol (CDP).
+
+
+ Your HTML composition is loaded into a browser page. The Hyperframes runtime is injected to manage timeline seeking.
+
+
+ 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.
+
+
+ Chrome's `HeadlessExperimental.beginFrame` API captures the compositor output as a pixel buffer. This produces pixel-perfect frames without any screen recording artifacts.
+
+
+ Captured frame buffers are passed to a consumer — typically FFmpeg (via the producer) for encoding into MP4, but you can provide your own consumer.
+
+
+
+This approach guarantees [deterministic rendering](/concepts/determinism): the same HTML always produces the identical video, regardless of system load or timing.
+
+## Configuration
+
+```typescript
+import type { EngineConfig } from '@hyperframes/engine';
+
+const config: EngineConfig = {
+ 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'
+};
+```
+
+### Quality Presets
+
+| 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
+
+
+
+ Wraps the engine with runtime injection, FFmpeg encoding, and audio mixing for complete MP4 output.
+
+
+ Provides the types, runtime, and linter that the engine depends on.
+
+
+ The easiest way to render — calls the producer (and engine) under the hood.
+
+
+ Visual editor for building compositions before rendering them with the engine.
+
+
diff --git a/docs/packages/producer.mdx b/docs/packages/producer.mdx
new file mode 100644
index 000000000..689aa2e98
--- /dev/null
+++ b/docs/packages/producer.mdx
@@ -0,0 +1,173 @@
+---
+title: "@hyperframes/producer"
+description: "Full HTML-to-video rendering pipeline with encoding, audio mixing, and Docker support."
+---
+
+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)
+
+
+ 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.
+
+
+## What It Does
+
+The producer orchestrates the full render pipeline:
+
+
+
+ Reads your `index.html` and any referenced sub-compositions.
+
+
+ Adds the runtime script that manages timeline seeking, clip lifecycle, and media playback.
+
+
+ Polls for `window.__playerReady` and `window.__renderReady` to ensure all assets (fonts, images, video) are loaded before capture begins.
+
+
+ Uses the [engine's](/packages/engine) BeginFrame pipeline to capture each frame as a pixel buffer.
+
+
+ Pipes frame buffers into FFmpeg with the selected quality preset and encoding settings.
+
+
+ Extracts audio from video clips and audio elements, applies `data-volume` and `data-media-start` offsets, and mixes them into the final MP4.
+
+
+
+## Programmatic Usage
+
+```typescript
+import { render } from '@hyperframes/producer';
+
+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 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 (recommended)
+npx hyperframes render --docker -o output.mp4
+
+# Via the producer API
+await render({ input: './index.html', output: './out.mp4', docker: true });
+```
+
+
+ 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.
+
+
+## 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. This is useful for catching visual regressions when changing the runtime, engine, or rendering pipeline.
+
+```bash
+cd packages/producer
+
+# 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
+
+
+
+ Command-line interface that wraps the producer for rendering, previewing, and more.
+
+
+ The low-level capture pipeline that the producer uses to grab frames.
+
+
+ Types, runtime, and linter that the producer depends on.
+
+
+ Visual editor for building compositions before rendering with the producer.
+
+
diff --git a/docs/packages/studio.mdx b/docs/packages/studio.mdx
new file mode 100644
index 000000000..4f953026c
--- /dev/null
+++ b/docs/packages/studio.mdx
@@ -0,0 +1,132 @@
+---
+title: "@hyperframes/studio"
+description: "Visual composition editor with live preview, timeline view, and hot reload."
+---
+
+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
+```
+
+## When to Use
+
+**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
+
+**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)
+
+
+ **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.
+
+
+## Running the Studio
+
+### Via the CLI (recommended)
+
+```bash
+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 target the studio package directly
+pnpm --filter @hyperframes/studio dev
+```
+
+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 with the following structure:
+
+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
+
+
+// Embed the timeline view
+
+```
+
+
+ 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.
+
+
+## Related Packages
+
+
+
+ Launches the studio via `npx hyperframes dev` — the easiest way to preview compositions.
+
+
+ Types, parsing, and runtime that the studio uses for preview and timeline rendering.
+
+
+ Renders the compositions you build in the studio to finished MP4 files.
+
+
+ The capture engine that powers production rendering of your compositions.
+
+
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
new file mode 100644
index 000000000..1feba800a
--- /dev/null
+++ b/docs/quickstart.mdx
@@ -0,0 +1,185 @@
+---
+title: Quickstart
+description: "Create, preview, and render your first Hyperframes video in under two minutes."
+---
+
+Go from zero to a rendered MP4 in four steps: scaffold a project, preview it live, customize the composition, and render.
+
+## What you'll build
+
+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.
+
+## Prerequisites
+
+
+
+ Hyperframes requires Node.js 20 or later. Check your version:
+
+ ```bash
+ node --version
+ ```
+
+ ```bash Expected output
+ v20.11.0 # or any version >= 20
+ ```
+
+
+
+ FFmpeg is required for local video rendering (encoding captured frames into MP4).
+
+
+ ```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
+ ```
+
+
+ Verify the installation:
+
+ ```bash
+ ffmpeg -version
+ ```
+
+ ```bash Expected output
+ ffmpeg version 7.x ...
+ ```
+
+
+
+## Create your first video
+
+
+
+ ```bash
+ npx create-hyperframe my-video
+ cd my-video
+ ```
+
+ ```bash Expected output
+ ✔ Created my-video/
+ ✔ index.html
+ ✔ assets/
+ Done. Run `npx hyperframes dev` to preview.
+ ```
+
+ This generates the following project structure:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 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) |
+
+
+
+ ```bash
+ npx hyperframes dev
+ ```
+
+ ```bash Expected output
+ ✔ Hyperframes dev server running
+ → http://localhost:3000
+ ```
+
+ Open [http://localhost:3000](http://localhost:3000) to see the live preview. Edits to `index.html` reload automatically.
+
+
+ The dev server supports hot reload — save your HTML file and the preview updates instantly, no manual refresh needed.
+
+
+
+
+ Open `index.html` and replace it with this composition:
+
+ ```html index.html
+
+
+
+
+ Hello, Hyperframes!
+
+
+
+
+
+
+
+
+ ```
+
+ 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`
+
+
+
+ ```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.
+
+
+
+## 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
+
+
+
+ Learn how compositions, clips, and nested timelines work together
+
+
+ Add fade, slide, scale, and custom animations to your videos
+
+
+ Start from built-in templates like title-card and video-edit
+
+
+ Explore render options: quality presets, Docker mode, and GPU encoding
+
+
diff --git a/docs/reference/html-schema.mdx b/docs/reference/html-schema.mdx
new file mode 100644
index 000000000..dd8877867
--- /dev/null
+++ b/docs/reference/html-schema.mdx
@@ -0,0 +1,235 @@
+---
+title: HTML Schema Reference
+description: "Complete reference for authoring Hyperframes HTML compositions."
+---
+
+This is the full schema reference for Hyperframes compositions. For a gentler introduction, see [Compositions](/concepts/compositions) and [Data Attributes](/concepts/data-attributes).
+
+## Overview
+
+Hyperframes uses HTML as the source of truth for describing a video:
+
+- **HTML clips** = video, image, audio, composition
+- **[Data attributes](/concepts/data-attributes)** = timing, metadata, styling
+- **CSS** = positioning and appearance
+- **GSAP timeline** = animations and playback sync (see [GSAP Animation](/guides/gsap-animation))
+
+## Framework-Managed Behavior
+
+The framework reads data attributes and automatically manages:
+
+- **Primitive clip timeline entries** — reads `data-start`, `data-duration`, and `data-track-index` from clips and adds them to the GSAP timeline
+- **Media playback** (play, pause, seek) for `` and ``
+- **Clip lifecycle** — clips are mounted/unmounted based on `data-start` and `data-duration`
+- **Timeline synchronization** — keeps media in sync with the GSAP master timeline
+- **Media loading** — waits for all media to load before resolving timing
+
+Mounting/unmounting controls **presence**, not appearance. Transitions (fade in, slide in) are animated in scripts.
+
+
+ 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.
+
+
+## Viewport
+
+Every composition must include `data-width` and `data-height` on the root element:
+
+```html
+
+
+
+```
+
+Common sizes:
+- **Landscape**: `data-width="1920" data-height="1080"`
+- **Portrait**: `data-width="1080" data-height="1920"`
+
+## All Clip Attributes
+
+| Attribute | Applies To | Required | Description |
+|-----------|-----------|----------|-------------|
+| `id` | All | Yes | Unique identifier (e.g., `"el-1"`). 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. |
+
+## Clip Types
+
+
+
+ Video clips embed `` elements with timing and playback attributes.
+
+ ```html
+
+ ```
+
+ **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
+
+
+ Do not animate `width`, `height`, `top`, or `left` directly on `` elements with GSAP. This can cause Chrome to stop rendering video frames. Wrap the video in a `` and animate the wrapper instead. See [Common Mistakes](/guides/common-mistakes).
+
+
+
+
+ Image clips display static images with controlled timing.
+
+ ```html
+
+ ```
+
+ **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
+
+
+
+ Audio clips add sound to the composition without any visual element.
+
+ ```html
+
+ ```
+
+ **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
+
+
+
+ Composition clips embed one composition inside another, enabling modular, reusable video building blocks.
+
+ ```html
+
+ ```
+
+ **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 `` tags
+ - Each nested composition has its own `window.__timelines` entry, registered by its own `