+```
+
+## Listing Compositions
+
+Use the CLI to see all compositions in a project:
+
+```bash
+npx hyperframes compositions
+```
diff --git a/docs/concepts/data-attributes.mdx b/docs/concepts/data-attributes.mdx
new file mode 100644
index 000000000..0a3d04c78
--- /dev/null
+++ b/docs/concepts/data-attributes.mdx
@@ -0,0 +1,79 @@
+---
+title: Data Attributes
+description: "Core attributes for controlling element timing and behavior."
+---
+
+Hyperframes uses HTML data attributes to control timing, media playback, and composition structure. These are the declarative building blocks of every video.
+
+## Timing Attributes
+
+| Attribute | Example | Description |
+|-----------|---------|-------------|
+| `data-start` | `"0"` or `"intro"` | Start time in seconds, or a clip ID reference for [relative timing](#relative-timing) |
+| `data-duration` | `"5"` | Duration in seconds. Required for images. Optional for video/audio (defaults to source duration). Not used on compositions. |
+| `data-track-index` | `"0"` | Timeline track number. Controls z-ordering (higher = in front) and groups clips into rows. Clips on the same track cannot overlap. |
+
+## Media Attributes
+
+| Attribute | Example | Description |
+|-----------|---------|-------------|
+| `data-media-start` | `"2"` | Media playback offset / trim point in seconds. Default: `0` |
+| `data-volume` | `"0.8"` | Audio/video volume, 0 to 1 |
+| `data-has-audio` | `"true"` | Indicates video has an audio track |
+
+## Composition Attributes
+
+| Attribute | Example | Description |
+|-----------|---------|-------------|
+| `data-composition-id` | `"root"` | Unique ID for composition wrapper (required on every composition) |
+| `data-width` | `"1920"` | Composition width in pixels |
+| `data-height` | `"1080"` | Composition height in pixels |
+| `data-composition-src` | `"./intro.html"` | Path to external composition HTML file |
+
+## Element Visibility
+
+Add `class="clip"` to all timed elements so the runtime can manage their visibility lifecycle:
+
+```html
+
+ 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
+
+
+
+```
+
+`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
+
+
+
+
+
+```
+
+
+ Overlapping clips must be on different tracks — clips on the same track cannot overlap in time.
+
+
+### Rules
+
+- **Same composition only** — references resolve within the clip's parent composition
+- **No circular references** — A cannot start after B if B starts after A
+- **Referenced clip must have a known duration** — either explicit `data-duration` or inferred from source media
+- **Parsing** — if the value is a valid number, it is absolute seconds; otherwise parsed as `
`, ` + `, or ` - `
diff --git a/docs/concepts/determinism.mdx b/docs/concepts/determinism.mdx
new file mode 100644
index 000000000..4a4080e36
--- /dev/null
+++ b/docs/concepts/determinism.mdx
@@ -0,0 +1,59 @@
+---
+title: Deterministic Rendering
+description: "Same input, identical output. Every time."
+---
+
+Hyperframes is built around a core guarantee: **the same composition always produces the same video**. This is what makes automated pipelines, CI testing, and AI-driven workflows reliable.
+
+## How It Works
+
+The rendering pipeline is frame-by-frame and seek-driven:
+
+1. **Frame clock**: `time = floor(frame) / fps` — no wall-clock dependency
+2. **Seek contract**: `renderSeek(time)` pauses all animations and deterministically seeks to the exact frame
+3. **Capture**: Chrome's `HeadlessExperimental.beginFrame` API captures the pixel buffer
+4. **Encode**: FFmpeg encodes frames into the final video
+
+No realtime playback is involved in rendering. Every frame is independently seeked and captured.
+
+## What Makes It Deterministic
+
+- **No wall-clock dependencies** — rendering doesn't use `Date.now()`, `requestAnimationFrame`, or system timers
+- **No unseeded randomness** — `Math.random()` without a seed breaks determinism
+- **No render-time network fetches** — all assets must be loaded before rendering starts
+- **Fixed output parameters** — `fps`, `width`, and `height` are locked before the first frame
+- **Finite duration** — every composition has a known, finite length
+
+## Docker Mode
+
+For maximum reproducibility, render in Docker:
+
+```bash
+npx hyperframes render --docker -o output.mp4
+```
+
+Docker mode uses an exact Chrome version and font set, ensuring:
+- Same Chromium rendering engine across all platforms
+- Same system fonts (no platform-specific font substitution)
+- Same FFmpeg encoder version
+
+## Preview vs. Render Parity
+
+The browser preview and the rendered MP4 should match. Hyperframes achieves this through:
+
+- **One runtime** — the same `hyperframe.runtime` drives both preview and render
+- **Producer-canonical behavior** — the producer's seek semantics are the source of truth
+- **Readiness gates** — `__playerReady` and `__renderReady` ensure the composition is fully loaded before any frame is captured
+
+
+ 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're building a [Frame Adapter](/concepts/frame-adapters), your adapter must follow the determinism contract:
+
+- `seekFrame(frame)` must be idempotent — same frame, same result
+- No side effects that depend on call order (must handle random access)
+- No async operations that resolve after the frame is "committed"
+- Clean lifecycle: `init` → `seekFrame` (N times) → `destroy`
diff --git a/docs/concepts/frame-adapters.mdx b/docs/concepts/frame-adapters.mdx
new file mode 100644
index 000000000..c56004fb2
--- /dev/null
+++ b/docs/concepts/frame-adapters.mdx
@@ -0,0 +1,100 @@
+---
+title: Frame Adapters
+description: "Bring your own animation runtime to Hyperframes."
+---
+
+The Frame Adapter pattern is how Hyperframes supports multiple animation runtimes. The core question every adapter answers:
+
+> What should the screen look like at frame N?
+
+If a runtime can answer that, it can plug into Hyperframes.
+
+## Adapter API (v0)
+
+```typescript
+type FrameAdapterContext = {
+ compositionId: string;
+ fps: number;
+ width: number;
+ height: number;
+ rootElement?: HTMLElement;
+};
+
+type FrameAdapter = {
+ id: string;
+ init?: (ctx: FrameAdapterContext) => Promise | void;
+ 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
+normalizedFrame = clamp(Math.floor(frame), 0, durationFrames);
+```
+
+A typical render loop:
+
+```typescript
+await adapter.init?.({ compositionId, fps, width, height, rootElement });
+const durationFrames = adapter.getDurationFrames();
+
+for (let frame = 0; frame <= durationFrames; frame += 1) {
+ await adapter.seekFrame(frame);
+ // capture pixel buffer for this frame
+}
+
+await adapter.destroy?.();
+```
+
+## Determinism Contract
+
+These rules are non-negotiable for any adapter:
+
+- Canonical clock: `t = frame / fps`
+- No wall-clock dependencies (`Date.now`, drift-dependent logic)
+- No unseeded randomness
+- No render-time network fetches
+- Fixed output params (`fps`, `width`, `height`)
+- Finite duration only
+- Deterministic frame quantization before seek
+
+## Supported Runtimes
+
+First-party adapters:
+
+| Runtime | Seek Method | Status |
+|---------|------------|--------|
+| GSAP | `timeline.seek(frame / fps)` | Available |
+| CSS/WAAPI | `animation.currentTime` | Planned |
+| Lottie | Set animation frame/progress | Planned |
+| Three.js/WebGL | Compute deterministic scene state | Planned |
+| SVG/Anime | Implement seek + duration contract | Planned |
+
+Community adapters are welcome — if it can seek by frame, it belongs in Hyperframes.
+
+## Conformance Tests
+
+Every adapter should pass these minimum tests:
+
+1. **Repeatability** — seek same frame twice, get identical output
+2. **Random seek** — seek order `[90, 10, 50, 10]` produces deterministic results
+3. **Bounds** — negative and overflow frame values don't break
+4. **Duration** — returned duration is a finite integer
+5. **Cleanup** — no leaked timers/listeners after `destroy`
+
+
+ The Adapter API is currently at **v0** (experimental). Breaking changes are possible until v1.
+
diff --git a/docs/contributing.mdx b/docs/contributing.mdx
new file mode 100644
index 000000000..61e0aeace
--- /dev/null
+++ b/docs/contributing.mdx
@@ -0,0 +1,60 @@
+---
+title: Contributing
+description: "How to contribute to Hyperframes."
+---
+
+Thanks for your interest in contributing to Hyperframes!
+
+## Getting Started
+
+1. Fork the repository
+2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/hyperframes.git`
+3. Install dependencies: `pnpm install`
+4. Create a branch: `git checkout -b my-feature`
+
+## Development
+
+```bash
+pnpm install # Install all dependencies
+pnpm dev # Run the studio (composition editor)
+pnpm build # Build all packages
+pnpm -r typecheck # Type-check all packages
+```
+
+### Running Tests
+
+```bash
+pnpm --filter @hyperframes/core test # Core unit tests
+pnpm --filter @hyperframes/engine test # Engine unit tests
+pnpm --filter @hyperframes/core test:hyperframe-runtime-ci # Runtime contract tests
+```
+
+## Packages
+
+| Package | Description |
+|---------|-------------|
+| `@hyperframes/core` | Types, HTML generation, runtime, linter |
+| `@hyperframes/engine` | Seekable page-to-video capture engine |
+| `@hyperframes/producer` | Full rendering pipeline (capture + encode) |
+| `@hyperframes/studio` | Composition editor UI |
+| `hyperframes` | CLI for creating, previewing, and rendering |
+
+## Pull Requests
+
+- Use [conventional commit](https://www.conventionalcommits.org/) format for PR titles (e.g., `feat: add timeline export`, `fix: resolve seek overflow`)
+- CI must pass before merge (build, typecheck, tests, semantic PR title)
+- PRs require at least 1 approval
+
+## Reporting Issues
+
+- Use [GitHub Issues](https://github.com/heygen-com/hyperframes/issues) for bug reports and feature requests
+- Search existing issues before creating a new one
+- Include reproduction steps for bugs
+
+## Code of Conduct
+
+This project follows the [Contributor Covenant Code of Conduct](https://github.com/heygen-com/hyperframes/blob/main/CODE_OF_CONDUCT.md).
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the [MIT License](https://github.com/heygen-com/hyperframes/blob/main/LICENSE).
diff --git a/docs/docs.json b/docs/docs.json
new file mode 100644
index 000000000..e887ad96c
--- /dev/null
+++ b/docs/docs.json
@@ -0,0 +1,80 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "name": "Hyperframes",
+ "theme": "mint",
+ "colors": {
+ "primary": "#7559FF",
+ "light": "#9F59FF",
+ "dark": "#735CE5"
+ },
+ "logo": {
+ "light": "/logo/light.svg",
+ "dark": "/logo/dark.svg"
+ },
+ "favicon": "/favicon.ico",
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Documentation",
+ "groups": [
+ {
+ "group": "Getting Started",
+ "pages": ["introduction", "quickstart"]
+ },
+ {
+ "group": "Concepts",
+ "pages": [
+ "concepts/compositions",
+ "concepts/data-attributes",
+ "concepts/frame-adapters",
+ "concepts/determinism"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "guides/gsap-animation",
+ "guides/templates",
+ "guides/rendering",
+ "guides/common-mistakes",
+ "guides/troubleshooting"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Packages",
+ "groups": [
+ {
+ "group": "Packages",
+ "pages": [
+ "packages/core",
+ "packages/engine",
+ "packages/producer",
+ "packages/studio",
+ "packages/cli"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Reference",
+ "groups": [
+ {
+ "group": "Reference",
+ "pages": ["reference/html-schema"]
+ },
+ {
+ "group": "Community",
+ "pages": ["contributing"]
+ }
+ ]
+ }
+ ]
+ },
+ "footer": {
+ "socials": {
+ "github": "https://github.com/heygen-com/hyperframes"
+ }
+ }
+}
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 000000000..f6b3b86a3
Binary files /dev/null and b/docs/favicon.ico differ
diff --git a/docs/guides/common-mistakes.mdx b/docs/guides/common-mistakes.mdx
new file mode 100644
index 000000000..778beaaa2
--- /dev/null
+++ b/docs/guides/common-mistakes.mdx
@@ -0,0 +1,65 @@
+---
+title: Common Mistakes
+description: "Pitfalls that break Hyperframes compositions."
+---
+
+These are mistakes that can't be caught by the linter. For automated checks, run `npx hyperframes lint`.
+
+## Animating Video Element Dimensions
+
+**Symptom**: Video frames stop updating, or browser performance drops.
+
+**Cause**: GSAP animating `width`, `height`, `top`, `left` directly on a `` element can cause the browser to stop rendering frames.
+
+```javascript
+// BROKEN — animating video element dimensions
+tl.to("#el-video", { width: 500, height: 280, top: 700, left: 1400 }, 26);
+
+// FIXED — animate a wrapper div, video fills it at 100%
+tl.to("#pip-wrapper", { width: 500, height: 280, top: 700, left: 1400 }, 26);
+```
+
+Use a non-timed wrapper `` for visual effects like picture-in-picture. Animate the wrapper; let the video fill it.
+
+## Controlling Media Playback in Scripts
+
+**Symptom**: Audio/video playback is out of sync, or plays when it shouldn't.
+
+**Cause**: Calling `video.play()`, `video.pause()`, or setting `audio.currentTime` in your scripts. The framework owns all media playback.
+
+```javascript
+// BROKEN — conflicts with framework media sync
+document.getElementById("el-video").play();
+document.getElementById("el-audio").currentTime = 5;
+
+// FIXED — don't do this. The framework handles it.
+// Use GSAP for visual animations only:
+tl.to("#el-video", { opacity: 1, duration: 0.5 }, 0);
+```
+
+## Composition Duration Shorter Than Video
+
+**Symptom**: Video plays for a few seconds then stops. Timeline shows 8-10 seconds even though the video is minutes long.
+
+**Cause**: The composition duration equals the GSAP timeline duration, not `data-duration` on the video. If your last GSAP animation ends at 8 seconds, the composition is 8 seconds long.
+
+```javascript
+// BROKEN — timeline is only 7.8s long, video cuts off
+tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
+
+// FIXED — extend timeline to match video length
+tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
+tl.set({}, {}, 283); // extends timeline to 283 seconds
+```
+
+`tl.set({}, {}, TIME)` adds a zero-duration tween at the specified time, extending the timeline without affecting any elements.
+
+## Debugging Checklist
+
+When something doesn't work, check in this order:
+
+1. **Run the linter**: `npx hyperframes lint` — catches most structural issues
+2. **Timeline registered?** Is `window.__timelines["
"]` set? Does the key match `data-composition-id`?
+3. **GSAP-only animations?** Only animate visual properties (opacity, transform, color)
+4. **Timeline long enough?** Add `tl.set({}, {}, DURATION)` at the end
+5. **Console errors?** Open browser console — runtime errors show as `[Browser:ERROR]`
diff --git a/docs/guides/gsap-animation.mdx b/docs/guides/gsap-animation.mdx
new file mode 100644
index 000000000..7c24782af
--- /dev/null
+++ b/docs/guides/gsap-animation.mdx
@@ -0,0 +1,69 @@
+---
+title: GSAP Animation
+description: "Add animations to your Hyperframes compositions with GSAP."
+---
+
+Hyperframes uses [GSAP](https://gsap.com/) for animation. Timelines are paused and controlled by the runtime — you define the animations, the framework handles playback.
+
+## Setup
+
+Include GSAP and create a paused timeline:
+
+```html
+
+
+```
+
+## Key Rules
+
+1. **Always create timelines with `{ paused: true }`** — the framework controls playback
+2. **Register timelines on `window.__timelines`** with the `data-composition-id` as key
+3. **Use the position parameter** (3rd argument) for absolute timing: `tl.to(el, vars, 1.5)`
+4. **Only animate visual properties** — never control media playback in scripts
+
+## Supported Methods
+
+| Method | Description |
+|--------|-------------|
+| `tl.to(target, vars, position)` | Animate to values |
+| `tl.from(target, vars, position)` | Animate from values |
+| `tl.fromTo(target, fromVars, toVars, position)` | Animate from/to values |
+| `tl.set(target, vars, position)` | Set values instantly |
+
+## Supported Properties
+
+`opacity`, `x`, `y`, `scale`, `scaleX`, `scaleY`, `rotation`, `width`, `height`, `visibility`, `color`, `backgroundColor`, and any CSS-animatable property.
+
+## Timeline Duration
+
+A composition's duration equals its GSAP timeline duration. If your last animation ends at 8 seconds, the composition is 8 seconds long.
+
+To extend the timeline beyond the last animation (e.g., to match a video clip's length):
+
+```javascript
+// Extends timeline to 283 seconds without affecting any elements
+tl.set({}, {}, 283);
+```
+
+## Sub-Composition Timelines
+
+Each composition registers its own timeline. The framework automatically nests sub-composition timelines into the parent based on `data-start`:
+
+```javascript
+// In compositions/intro-anim.html
+const tl = gsap.timeline({ paused: true });
+tl.from(".title", { opacity: 0, y: -50, duration: 1 });
+window.__timelines["intro-anim"] = tl;
+
+// DO NOT manually add sub-timelines to the master:
+// masterTL.add(window.__timelines["intro-anim"], 0); // UNNECESSARY
+```
+
+
+ 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.
+
diff --git a/docs/guides/rendering.mdx b/docs/guides/rendering.mdx
new file mode 100644
index 000000000..c7e4a72f8
--- /dev/null
+++ b/docs/guides/rendering.mdx
@@ -0,0 +1,54 @@
+---
+title: Rendering
+description: "Render compositions to MP4 locally or in Docker."
+---
+
+Render your Hyperframes compositions to MP4 with the CLI.
+
+```bash
+npx hyperframes render -o output.mp4
+```
+
+## Rendering Modes
+
+### Local Mode (default)
+
+Uses Puppeteer (bundled Chromium) + system FFmpeg. Fast for iteration.
+
+**Requires**: FFmpeg installed on your system.
+
+```bash
+npx hyperframes render -o output.mp4
+```
+
+### Docker Mode
+
+Deterministic output with an exact Chrome version and fonts. Use this for production renders and CI pipelines.
+
+**Requires**: Docker installed and running.
+
+```bash
+npx hyperframes render --docker -o output.mp4
+```
+
+
+ Docker mode uses `chrome-headless-shell` with BeginFrame control for frame-perfect, deterministic capture. This is the same pipeline used in production.
+
+
+## Options
+
+| Flag | Values | Default | Description |
+|------|--------|---------|-------------|
+| `-f, --fps` | 24, 30, 60 | 30 | Frames per second |
+| `-q, --quality` | draft, standard, high | standard | Encoding quality preset |
+| `-w, --workers` | 1-8 | auto | Parallel render workers |
+| `--gpu` | — | off | GPU encoding (NVENC, VideoToolbox, VAAPI) |
+| `-o, --output` | path | — | Output file path |
+| `--docker` | — | off | Use Docker for deterministic rendering |
+
+## Tips
+
+- Use `draft` quality during development for fast previews
+- Use `npx hyperframes benchmark` to find optimal settings for your system
+- 4 workers is usually the sweet spot for most compositions
+- Docker mode is slower but guarantees identical output across platforms
diff --git a/docs/guides/templates.mdx b/docs/guides/templates.mdx
new file mode 100644
index 000000000..9fc8dd049
--- /dev/null
+++ b/docs/guides/templates.mdx
@@ -0,0 +1,47 @@
+---
+title: Templates
+description: "Built-in templates for common video patterns."
+---
+
+Hyperframes includes starter templates to help you get going quickly.
+
+## Using Templates
+
+```bash
+npx hyperframes init --template
+```
+
+## Available Templates
+
+### blank
+
+Empty 1920x1080 composition with a GSAP timeline wired up. Start from scratch.
+
+```bash
+npx hyperframes init --template blank
+```
+
+### title-card
+
+Animated title and subtitle with GSAP fade-in/out. Good for intro cards.
+
+```bash
+npx hyperframes init --template title-card
+```
+
+### video-edit
+
+Video element with trimming, audio, and track controls. Starting point for video editing workflows.
+
+```bash
+npx hyperframes init --template video-edit
+```
+
+## Custom Templates
+
+Any directory with an `index.html` can serve as a template. Copy it manually or build your own init workflow.
+
+Your custom template just needs:
+1. An `index.html` with a `data-composition-id` root element
+2. A GSAP timeline registered in `window.__timelines`
+3. Any assets in the same directory
diff --git a/docs/guides/troubleshooting.mdx b/docs/guides/troubleshooting.mdx
new file mode 100644
index 000000000..b9a1c6fdd
--- /dev/null
+++ b/docs/guides/troubleshooting.mdx
@@ -0,0 +1,68 @@
+---
+title: Troubleshooting
+description: "Solutions for common Hyperframes issues."
+---
+
+## "No composition found"
+
+Your directory needs an `index.html` with a valid composition. Run `npx hyperframes init` to create one.
+
+## "FFmpeg not found"
+
+Local rendering requires FFmpeg. Install it for your platform:
+
+
+```bash macOS
+brew install ffmpeg
+```
+
+```bash Ubuntu/Debian
+sudo apt install ffmpeg
+```
+
+```bash Windows
+# Download from https://ffmpeg.org/download.html
+# Add to your PATH
+```
+
+
+## Lint Errors
+
+Run `npx hyperframes lint` to check for common issues:
+
+- Missing `data-composition-id` on root element
+- Missing `class="clip"` on timed elements
+- Overlapping timelines or invalid data attributes
+- Unmuted video elements
+- Deprecated attribute names (`data-layer`, `data-end`)
+
+## Preview Not Updating
+
+Make sure you're editing the `index.html` in the project directory. The preview server watches for file changes and auto-reloads.
+
+If changes still don't appear:
+1. Check the terminal for errors
+2. Try stopping and restarting `npx hyperframes dev`
+3. Hard-refresh the browser (Ctrl+Shift+R / Cmd+Shift+R)
+
+## Render Looks Different from Preview
+
+Use `--docker` mode for deterministic output. Local renders may differ due to:
+
+- Font availability (different fonts on different platforms)
+- Chrome version (local Chromium vs. Docker's pinned version)
+- System-specific rendering differences
+
+```bash
+npx hyperframes render --docker -o output.mp4
+```
+
+## System Diagnostics
+
+Run `npx hyperframes doctor` to check your environment:
+
+```bash
+npx hyperframes doctor
+```
+
+This checks for Node.js version, FFmpeg availability, Docker status, and other requirements.
diff --git a/docs/introduction.mdx b/docs/introduction.mdx
new file mode 100644
index 000000000..d7467d7aa
--- /dev/null
+++ b/docs/introduction.mdx
@@ -0,0 +1,58 @@
+---
+title: Introduction
+description: "Write HTML. Render video. Built for agents."
+---
+
+Hyperframes is an open-source video rendering framework that lets you create, preview, and render HTML-based video compositions — with first-class support for AI agents.
+
+## Why Hyperframes?
+
+- **HTML-native** — AI agents already speak HTML. No React required.
+- **Frame Adapter pattern** — bring your own animation runtime (GSAP, Lottie, CSS, Three.js).
+- **Deterministic rendering** — same input = identical output. Built for automated pipelines.
+- **AI-first design** — not a bolted-on afterthought.
+
+## How It Works
+
+Define your video as HTML with data attributes:
+
+```html
+
+
+
+
+
+```
+
+Preview instantly in the browser. Render to MP4 locally. Let AI agents compose videos using tools they already understand.
+
+## Packages
+
+| Package | npm | Description |
+|---------|-----|-------------|
+| [`@hyperframes/core`](/packages/core) | `@hyperframes/core` | Types, HTML generation, runtime, linter |
+| [`@hyperframes/engine`](/packages/engine) | `@hyperframes/engine` | Seekable page-to-video capture engine |
+| [`@hyperframes/producer`](/packages/producer) | `@hyperframes/producer` | Full rendering pipeline (capture + encode) |
+| [`@hyperframes/studio`](/packages/studio) | `@hyperframes/studio` | Composition editor UI |
+| [`hyperframes`](/packages/cli) | `hyperframes` | CLI for creating, previewing, and rendering |
+
+## Next Steps
+
+
+
+ Create your first video in 60 seconds
+
+
+ Learn the core data model
+
+
+ Add animations to your videos
+
+
+ Render to MP4 locally or in Docker
+
+
diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg
new file mode 100644
index 000000000..a1e976ba9
--- /dev/null
+++ b/docs/logo/dark.svg
@@ -0,0 +1 @@
+
diff --git a/docs/logo/icon.svg b/docs/logo/icon.svg
new file mode 100644
index 000000000..ab6f785c3
--- /dev/null
+++ b/docs/logo/icon.svg
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/logo/light.svg b/docs/logo/light.svg
new file mode 100644
index 000000000..9883738e8
--- /dev/null
+++ b/docs/logo/light.svg
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/packages/cli.mdx b/docs/packages/cli.mdx
new file mode 100644
index 000000000..3d4393fd2
--- /dev/null
+++ b/docs/packages/cli.mdx
@@ -0,0 +1,97 @@
+---
+title: CLI
+description: "Create, preview, and render HTML video compositions from the command line."
+---
+
+The `hyperframes` CLI is the primary way to work with Hyperframes. It handles project creation, live preview, rendering, linting, and diagnostics.
+
+```bash
+npm install -g hyperframes
+# or use directly with npx
+npx hyperframes
+```
+
+## Commands
+
+### `init`
+
+Create a new composition project from a template:
+
+```bash
+npx hyperframes init --template title-card
+```
+
+See [Templates](/guides/templates) for available templates.
+
+### `dev`
+
+Start a live preview server with hot reload:
+
+```bash
+npx hyperframes dev
+```
+
+Opens your composition in the browser at `http://localhost:3000`. Edits to `index.html` are reflected instantly.
+
+### `render`
+
+Render a composition to MP4:
+
+```bash
+npx hyperframes render -o output.mp4
+npx hyperframes render --docker -o output.mp4 # deterministic mode
+```
+
+See [Rendering](/guides/rendering) for all options.
+
+### `lint`
+
+Check a composition for common issues:
+
+```bash
+npx hyperframes lint
+```
+
+Detects missing attributes, deprecated names, structural problems, and more.
+
+### `compositions`
+
+List all compositions in the current project:
+
+```bash
+npx hyperframes compositions
+```
+
+### `benchmark`
+
+Find optimal render settings for your system:
+
+```bash
+npx hyperframes benchmark
+```
+
+### `doctor`
+
+Check your environment for required dependencies:
+
+```bash
+npx hyperframes doctor
+```
+
+Verifies Node.js version, FFmpeg, Docker, and other requirements.
+
+### `info`
+
+Display system and project information:
+
+```bash
+npx hyperframes info
+```
+
+### `upgrade`
+
+Update Hyperframes to the latest version:
+
+```bash
+npx hyperframes upgrade
+```
diff --git a/docs/packages/core.mdx b/docs/packages/core.mdx
new file mode 100644
index 000000000..101fa12f7
--- /dev/null
+++ b/docs/packages/core.mdx
@@ -0,0 +1,58 @@
+---
+title: "@hyperframes/core"
+description: "Types, HTML generation, runtime, and linter."
+---
+
+The core package provides the foundational types, HTML parsing/generation, runtime, and composition linter that all other packages depend on.
+
+```bash
+npm install @hyperframes/core
+```
+
+## What's Inside
+
+| Module | Description |
+|--------|-------------|
+| `core.types` | TypeScript types for compositions, clips, timelines |
+| `parsers/` | HTML-to-composition parsing |
+| `generators/` | Composition-to-HTML generation |
+| `runtime/` | The Hyperframes runtime (IIFE + ESM builds) |
+| `lint/` | Composition linter rules |
+| `adapters/` | Frame Adapter types and GSAP adapter |
+| `templates/` | HTML composition templates |
+
+## Runtime Builds
+
+The runtime is built in two formats:
+
+- **`hyperframe.runtime.iife.js`** — for browser iframe bootstrap (preview)
+- **`hyperframe.runtime.mjs`** — for Node.js/tooling/tests
+
+Build the runtime:
+
+```bash
+pnpm --filter @hyperframes/core build:hyperframes-runtime
+```
+
+## Linter
+
+The composition linter checks for common structural issues:
+
+```typescript
+import { lintHyperframeHtml } from '@hyperframes/core';
+
+const issues = lintHyperframeHtml(htmlString);
+```
+
+Detected issues include:
+- Missing timeline registration
+- Unmuted video elements
+- Missing `class="clip"` on timed elements
+- Deprecated attribute names
+- Missing composition dimensions
+
+## Types
+
+```typescript
+import type { Composition, Clip, RenderConfig } from '@hyperframes/core';
+```
diff --git a/docs/packages/engine.mdx b/docs/packages/engine.mdx
new file mode 100644
index 000000000..975e2ab45
--- /dev/null
+++ b/docs/packages/engine.mdx
@@ -0,0 +1,44 @@
+---
+title: "@hyperframes/engine"
+description: "Seekable page-to-video capture engine."
+---
+
+The engine package provides the low-level video capture pipeline: loading an HTML page in headless Chrome and capturing it frame-by-frame.
+
+```bash
+npm install @hyperframes/engine
+```
+
+## What It Does
+
+The engine:
+
+1. Launches headless Chrome (`chrome-headless-shell`)
+2. Loads your HTML composition
+3. Uses Chrome's `HeadlessExperimental.beginFrame` API for deterministic frame capture
+4. Captures each frame as a pixel buffer
+5. Hands frames to FFmpeg for encoding
+
+## Key Features
+
+- **BeginFrame rendering** — frame-perfect capture using Chrome DevTools Protocol
+- **Deterministic seek** — every frame is independently seeked, not played in realtime
+- **Configurable FPS** — 24, 30, or 60 frames per second
+- **Quality presets** — draft, standard, high encoding settings
+
+## Configuration
+
+```typescript
+import type { EngineConfig } from '@hyperframes/engine';
+
+const config: EngineConfig = {
+ fps: 30,
+ width: 1920,
+ height: 1080,
+ quality: 'standard',
+};
+```
+
+## When to Use
+
+Most users should use the [`@hyperframes/producer`](/packages/producer) package or the [CLI](/packages/cli) instead of the engine directly. The engine is useful when you need low-level control over the capture pipeline.
diff --git a/docs/packages/producer.mdx b/docs/packages/producer.mdx
new file mode 100644
index 000000000..0764c10e8
--- /dev/null
+++ b/docs/packages/producer.mdx
@@ -0,0 +1,63 @@
+---
+title: "@hyperframes/producer"
+description: "Full HTML-to-video rendering pipeline."
+---
+
+The producer package combines the engine's capture capabilities with FFmpeg encoding to deliver a complete rendering pipeline.
+
+```bash
+npm install @hyperframes/producer
+```
+
+## What It Does
+
+The producer orchestrates the full render:
+
+1. **Loads** the composition HTML
+2. **Injects** the Hyperframes runtime
+3. **Waits** for readiness gates (`__playerReady`, `__renderReady`)
+4. **Captures** frames using the engine's BeginFrame pipeline
+5. **Encodes** to MP4 via FFmpeg
+6. **Mixes** audio tracks
+
+## Features
+
+- **Docker-based rendering** for deterministic output
+- **Quality presets** — draft, standard, high
+- **GPU encoding** support (NVENC, VideoToolbox, VAAPI)
+- **Parallel workers** for faster rendering
+- **Benchmark tooling** for finding optimal settings
+- **Regression harness** for visual regression testing
+
+## Programmatic Usage
+
+```typescript
+import { render } from '@hyperframes/producer';
+
+await render({
+ input: './my-video/index.html',
+ output: './output.mp4',
+ fps: 30,
+ quality: 'standard',
+});
+```
+
+## Docker Rendering
+
+For deterministic output, the producer renders inside a Docker container with a pinned Chrome version and font set:
+
+```bash
+# Via the CLI
+npx hyperframes render --docker -o output.mp4
+```
+
+## Regression Testing
+
+The producer includes a regression harness for comparing render output against golden baselines:
+
+```bash
+cd packages/producer
+pnpm docker:build:test # Build test Docker image
+pnpm docker:test # Run regression tests
+pnpm docker:test:update # Regenerate golden baselines
+```
diff --git a/docs/packages/studio.mdx b/docs/packages/studio.mdx
new file mode 100644
index 000000000..8a54b286a
--- /dev/null
+++ b/docs/packages/studio.mdx
@@ -0,0 +1,40 @@
+---
+title: "@hyperframes/studio"
+description: "Composition editor UI."
+---
+
+The studio package provides a visual editor for creating and previewing Hyperframes compositions in the browser.
+
+```bash
+npm install @hyperframes/studio
+```
+
+## What It Does
+
+The studio is a React-based composition editor that provides:
+
+- **Live preview** — see your composition rendered in real-time
+- **Timeline view** — visual representation of clips and their timing
+- **Player controls** — play, pause, seek through your composition
+- **Hot reload** — edit HTML and see changes instantly
+
+## Running the Studio
+
+```bash
+# From the monorepo root
+pnpm dev
+
+# Or directly
+pnpm --filter @hyperframes/studio dev
+```
+
+The studio starts a development server with live preview.
+
+## Architecture
+
+The studio is a React application that:
+
+1. Loads your composition HTML into an iframe
+2. Injects the Hyperframes runtime for preview playback
+3. Provides a player interface for seeking and playback control
+4. Watches for file changes and hot-reloads the preview
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
new file mode 100644
index 000000000..a72a6a71e
--- /dev/null
+++ b/docs/quickstart.mdx
@@ -0,0 +1,84 @@
+---
+title: Quickstart
+description: "Create, preview, and render your first Hyperframes video."
+---
+
+## Create a Project
+
+```bash
+npx create-hyperframe my-video
+cd my-video
+```
+
+This scaffolds a project with an `index.html` composition and assets directory.
+
+## Preview in Browser
+
+```bash
+npx hyperframes dev
+```
+
+Opens a live preview at `http://localhost:3000`. Edit `index.html` and the preview updates automatically.
+
+## Render to MP4
+
+```bash
+npx hyperframes render -o output.mp4
+```
+
+Renders your composition to an MP4 file using the local rendering pipeline (Puppeteer + FFmpeg).
+
+
+ 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).
+
+
+## Project Structure
+
+After `create-hyperframe`, your project looks like this:
+
+```
+my-video/
+├── index.html # Root composition
+├── compositions/ # Sub-compositions (optional)
+└── assets/ # Media files (video, audio, images)
+```
+
+## Your First Composition
+
+Every Hyperframes video is an HTML file. Here's a minimal example:
+
+```html
+
+
+
+ Hello, Hyperframes!
+
+
+
+
+
+```
+
+Key rules:
+- **Root element** needs `data-composition-id`, `data-width`, and `data-height`
+- **Timed elements** need `data-start`, `data-duration`, `data-track-index`, and `class="clip"`
+- **GSAP timeline** must be paused and registered in `window.__timelines`
+
+## Requirements
+
+- **Node.js** 20+
+- **pnpm** (recommended) or npm
+- **FFmpeg** for local rendering
+- **Docker** (optional) for deterministic rendering
+
+
+ Browse built-in templates for common video patterns
+
diff --git a/docs/reference/html-schema.mdx b/docs/reference/html-schema.mdx
new file mode 100644
index 000000000..0c7cc4c22
--- /dev/null
+++ b/docs/reference/html-schema.mdx
@@ -0,0 +1,174 @@
+---
+title: HTML Schema Reference
+description: "Complete reference for authoring Hyperframes HTML compositions."
+---
+
+This is the full schema reference for Hyperframes compositions. For a gentler introduction, see [Compositions](/concepts/compositions) and [Data Attributes](/concepts/data-attributes).
+
+## Overview
+
+Hyperframes uses HTML as the source of truth for describing a video:
+
+- **HTML clips** = video, image, audio, composition
+- **Data attributes** = timing, metadata, styling
+- **CSS** = positioning and appearance
+- **GSAP timeline** = animations and playback sync
+
+## Framework-Managed Behavior
+
+The framework reads data attributes and automatically manages:
+
+- **Primitive clip timeline entries** — reads `data-start`, `data-duration`, and `data-track-index` from clips and adds them to the GSAP timeline
+- **Media playback** (play, pause, seek) for `` 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.
+
+
+## Viewport
+
+Every composition must include `data-width` and `data-height`:
+
+```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"`) |
+| `class="clip"` | Visible elements | Yes | Enables runtime visibility management |
+| `data-start` | All | Yes | Start time in seconds, or clip ID reference |
+| `data-duration` | video, img, audio | Img: yes, others: optional | Duration in seconds |
+| `data-track-index` | All | Yes | Timeline track number (z-ordering) |
+| `data-media-start` | video, audio | No | Playback offset in source file (seconds) |
+| `data-volume` | audio, video | No | Volume 0-1 |
+| `data-composition-id` | div | On compositions | Unique composition ID |
+| `data-composition-src` | div | No | Path to external composition HTML file |
+| `data-width` | div | On compositions | Composition width in pixels |
+| `data-height` | div | On compositions | Composition height in pixels |
+
+## Video Clips
+
+```html
+
+```
+
+- `data-duration` is optional — defaults to remaining duration of source file from `data-media-start`
+- If source media runs out before `data-duration`, the clip shows the last frame
+
+## Image Clips
+
+```html
+
+```
+
+- `data-duration` is **required** for images
+
+## Audio Clips
+
+```html
+
+```
+
+- `data-duration` is optional — defaults to remaining duration of source file
+- Audio clips are invisible
+
+## Composition Clips
+
+```html
+
+```
+
+- Compositions do **not** use `data-duration` — duration comes from the GSAP timeline
+- External compositions are loaded from `data-composition-src` and wrapped in `` tags
+
+## Relative Timing
+
+Reference another clip's ID in `data-start` to mean "start when that clip ends":
+
+```html
+
+
+```
+
+Offsets: `data-start="intro + 2"` (2s gap) or `data-start="intro - 0.5"` (0.5s overlap).
+
+## Timeline Contract
+
+The framework initializes `window.__timelines = {}` before any scripts run. Every composition must register a timeline:
+
+```javascript
+const tl = gsap.timeline({ paused: true });
+// ... add tweens
+window.__timelines[""] = tl;
+```
+
+### Rules
+
+- Every composition needs a script to create and register its timeline
+- All timelines start paused (`{ paused: true }`)
+- The framework auto-nests sub-timelines — do not manually add them
+- Duration comes from `tl.duration()` — no `data-duration` on compositions
+- Timelines must be finite
+
+## Caption Discoverability
+
+For caption compositions, add these attributes to the root node:
+
+```html
+
+```
+
+## 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 `` wrapper
+- [ ] All timelines registered in `window.__timelines`
+- [ ] Timed visible elements have `class="clip"`