mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-08-31 02:41:44 +00:00
docs: add Mintlify documentation site
Set up /docs directory with docs.json config, HeyGen branding (logo, favicon, #7559FF purple), and 18 MDX pages covering: - Getting started (introduction, quickstart) - Concepts (compositions, data attributes, frame adapters, determinism) - Guides (GSAP animation, templates, rendering, common mistakes, troubleshooting) - Package docs (core, engine, producer, studio, CLI) - Reference (HTML schema) and contributing guide Content adapted from existing repo docs (core/docs/, cli/src/docs/, README). Validated with `mint validate` and `mint broken-links`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: Compositions
|
||||
description: "The fundamental building block of a Hyperframes video."
|
||||
---
|
||||
|
||||
A composition is an HTML document that defines a video timeline. Every clip — video, image, audio — must live inside a composition.
|
||||
|
||||
## Structure
|
||||
|
||||
Every composition needs a root element with `data-composition-id`:
|
||||
|
||||
```html
|
||||
<div id="root" data-composition-id="root"
|
||||
data-start="0" data-width="1920" data-height="1080">
|
||||
<!-- Elements go here -->
|
||||
</div>
|
||||
```
|
||||
|
||||
The `index.html` file is the top-level composition. It can contain nested compositions within it. Any composition can be imported into another — there is no special "root" type.
|
||||
|
||||
## Clip Types
|
||||
|
||||
A clip is any discrete block on the timeline, represented as an HTML element with data attributes:
|
||||
|
||||
- `<video>` — Video clips, B-roll, A-roll
|
||||
- `<img>` — Static images, overlays
|
||||
- `<audio>` — Music, sound effects
|
||||
- `<div data-composition-id="...">` — Nested compositions (animations, grouped sequences)
|
||||
|
||||
## Nested Compositions
|
||||
|
||||
Embed one composition inside another by loading it from an external HTML file:
|
||||
|
||||
```html
|
||||
<div
|
||||
id="el-5"
|
||||
data-composition-id="intro-anim"
|
||||
data-composition-src="compositions/intro-anim.html"
|
||||
data-start="0"
|
||||
data-track-index="3"
|
||||
></div>
|
||||
```
|
||||
|
||||
The framework automatically fetches the HTML file, extracts the `<template>` content, mounts it, executes scripts, and registers the timeline.
|
||||
|
||||
### Composition File Format
|
||||
|
||||
Each composition file wraps its content in a `<template>` tag:
|
||||
|
||||
```html
|
||||
<!-- compositions/intro-anim.html -->
|
||||
<template id="intro-anim-template">
|
||||
<div data-composition-id="intro-anim" data-width="1920" data-height="1080">
|
||||
<div class="title">Welcome!</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="intro-anim"] .title {
|
||||
font-size: 72px; color: white; text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
tl.from(".title", { opacity: 0, y: -50, duration: 1 });
|
||||
window.__timelines["intro-anim"] = tl;
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── index.html # Root composition
|
||||
├── compositions/
|
||||
│ ├── intro-anim.html # Intro animation
|
||||
│ ├── caption-overlay.html # Captions
|
||||
│ └── outro-title.html # Outro
|
||||
```
|
||||
|
||||
## Two Layers: Primitives and Scripts
|
||||
|
||||
Every composition has two layers:
|
||||
|
||||
- **HTML** — primitive clips (`video`, `img`, `audio`, nested compositions). The declarative structure: what plays, when, and on which track.
|
||||
- **Script** — effects, transitions, dynamic DOM, canvas, SVG — creative animation via GSAP. Scripts do **not** control media playback or clip visibility.
|
||||
|
||||
<Warning>
|
||||
Never use scripts to play/pause/seek media elements or to show/hide clips based on timing. The framework handles this automatically from data attributes. Scripts that duplicate this behavior will conflict with the framework.
|
||||
</Warning>
|
||||
|
||||
## Variables
|
||||
|
||||
Compositions can expose variables for dynamic content:
|
||||
|
||||
```html
|
||||
<div data-composition-id="card" data-var-title="string" data-var-color="color">
|
||||
```
|
||||
|
||||
## Listing Compositions
|
||||
|
||||
Use the CLI to see all compositions in a project:
|
||||
|
||||
```bash
|
||||
npx hyperframes compositions
|
||||
```
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Data Attributes
|
||||
description: "Core attributes for controlling element timing and behavior."
|
||||
---
|
||||
|
||||
Hyperframes uses HTML data attributes to control timing, media playback, and composition structure. These are the declarative building blocks of every video.
|
||||
|
||||
## Timing Attributes
|
||||
|
||||
| Attribute | Example | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `data-start` | `"0"` or `"intro"` | Start time in seconds, or a clip ID reference for [relative timing](#relative-timing) |
|
||||
| `data-duration` | `"5"` | Duration in seconds. Required for images. Optional for video/audio (defaults to source duration). Not used on compositions. |
|
||||
| `data-track-index` | `"0"` | Timeline track number. Controls z-ordering (higher = in front) and groups clips into rows. Clips on the same track cannot overlap. |
|
||||
|
||||
## Media Attributes
|
||||
|
||||
| Attribute | Example | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `data-media-start` | `"2"` | Media playback offset / trim point in seconds. Default: `0` |
|
||||
| `data-volume` | `"0.8"` | Audio/video volume, 0 to 1 |
|
||||
| `data-has-audio` | `"true"` | Indicates video has an audio track |
|
||||
|
||||
## Composition Attributes
|
||||
|
||||
| Attribute | Example | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `data-composition-id` | `"root"` | Unique ID for composition wrapper (required on every composition) |
|
||||
| `data-width` | `"1920"` | Composition width in pixels |
|
||||
| `data-height` | `"1080"` | Composition height in pixels |
|
||||
| `data-composition-src` | `"./intro.html"` | Path to external composition HTML file |
|
||||
|
||||
## Element Visibility
|
||||
|
||||
Add `class="clip"` to all timed elements so the runtime can manage their visibility lifecycle:
|
||||
|
||||
```html
|
||||
<h1 id="title" class="clip"
|
||||
data-start="0" data-duration="5" data-track-index="0">
|
||||
Hello World
|
||||
</h1>
|
||||
```
|
||||
|
||||
## Relative Timing
|
||||
|
||||
Instead of calculating absolute start times, a clip can reference another clip's `id` in its `data-start` attribute. This means "start when that clip ends":
|
||||
|
||||
```html
|
||||
<video id="intro" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
|
||||
<video id="main" data-start="intro" data-duration="20" data-track-index="0" src="..."></video>
|
||||
<video id="outro" data-start="main" data-duration="5" data-track-index="0" src="..."></video>
|
||||
```
|
||||
|
||||
`main` resolves to second 10, `outro` resolves to second 30. If `intro`'s duration changes, downstream clips shift automatically.
|
||||
|
||||
### Offsets (Gaps and Overlaps)
|
||||
|
||||
Add `+ N` or `- N` after the ID to offset from the end of the referenced clip:
|
||||
|
||||
```html
|
||||
<!-- 2-second gap after intro -->
|
||||
<video id="scene-a" data-start="intro + 2" data-duration="20"
|
||||
data-track-index="0" src="..."></video>
|
||||
|
||||
<!-- 0.5-second overlap with intro (crossfade) -->
|
||||
<video id="scene-b" data-start="intro - 0.5" data-duration="20"
|
||||
data-track-index="1" src="..."></video>
|
||||
```
|
||||
|
||||
<Note>
|
||||
Overlapping clips must be on different tracks — clips on the same track cannot overlap in time.
|
||||
</Note>
|
||||
|
||||
### Rules
|
||||
|
||||
- **Same composition only** — references resolve within the clip's parent composition
|
||||
- **No circular references** — A cannot start after B if B starts after A
|
||||
- **Referenced clip must have a known duration** — either explicit `data-duration` or inferred from source media
|
||||
- **Parsing** — if the value is a valid number, it is absolute seconds; otherwise parsed as `<id>`, `<id> + <number>`, or `<id> - <number>`
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Deterministic Rendering
|
||||
description: "Same input, identical output. Every time."
|
||||
---
|
||||
|
||||
Hyperframes is built around a core guarantee: **the same composition always produces the same video**. This is what makes automated pipelines, CI testing, and AI-driven workflows reliable.
|
||||
|
||||
## How It Works
|
||||
|
||||
The rendering pipeline is frame-by-frame and seek-driven:
|
||||
|
||||
1. **Frame clock**: `time = floor(frame) / fps` — no wall-clock dependency
|
||||
2. **Seek contract**: `renderSeek(time)` pauses all animations and deterministically seeks to the exact frame
|
||||
3. **Capture**: Chrome's `HeadlessExperimental.beginFrame` API captures the pixel buffer
|
||||
4. **Encode**: FFmpeg encodes frames into the final video
|
||||
|
||||
No realtime playback is involved in rendering. Every frame is independently seeked and captured.
|
||||
|
||||
## What Makes It Deterministic
|
||||
|
||||
- **No wall-clock dependencies** — rendering doesn't use `Date.now()`, `requestAnimationFrame`, or system timers
|
||||
- **No unseeded randomness** — `Math.random()` without a seed breaks determinism
|
||||
- **No render-time network fetches** — all assets must be loaded before rendering starts
|
||||
- **Fixed output parameters** — `fps`, `width`, and `height` are locked before the first frame
|
||||
- **Finite duration** — every composition has a known, finite length
|
||||
|
||||
## Docker Mode
|
||||
|
||||
For maximum reproducibility, render in Docker:
|
||||
|
||||
```bash
|
||||
npx hyperframes render --docker -o output.mp4
|
||||
```
|
||||
|
||||
Docker mode uses an exact Chrome version and font set, ensuring:
|
||||
- Same Chromium rendering engine across all platforms
|
||||
- Same system fonts (no platform-specific font substitution)
|
||||
- Same FFmpeg encoder version
|
||||
|
||||
## Preview vs. Render Parity
|
||||
|
||||
The browser preview and the rendered MP4 should match. Hyperframes achieves this through:
|
||||
|
||||
- **One runtime** — the same `hyperframe.runtime` drives both preview and render
|
||||
- **Producer-canonical behavior** — the producer's seek semantics are the source of truth
|
||||
- **Readiness gates** — `__playerReady` and `__renderReady` ensure the composition is fully loaded before any frame is captured
|
||||
|
||||
<Note>
|
||||
Local rendering (without Docker) may show slight differences due to platform-specific font rendering and Chrome version. Use Docker mode when exact reproducibility matters.
|
||||
</Note>
|
||||
|
||||
## For Adapter Authors
|
||||
|
||||
If you're building a [Frame Adapter](/concepts/frame-adapters), your adapter must follow the determinism contract:
|
||||
|
||||
- `seekFrame(frame)` must be idempotent — same frame, same result
|
||||
- No side effects that depend on call order (must handle random access)
|
||||
- No async operations that resolve after the frame is "committed"
|
||||
- Clean lifecycle: `init` → `seekFrame` (N times) → `destroy`
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Frame Adapters
|
||||
description: "Bring your own animation runtime to Hyperframes."
|
||||
---
|
||||
|
||||
The Frame Adapter pattern is how Hyperframes supports multiple animation runtimes. The core question every adapter answers:
|
||||
|
||||
> What should the screen look like at frame N?
|
||||
|
||||
If a runtime can answer that, it can plug into Hyperframes.
|
||||
|
||||
## Adapter API (v0)
|
||||
|
||||
```typescript
|
||||
type FrameAdapterContext = {
|
||||
compositionId: string;
|
||||
fps: number;
|
||||
width: number;
|
||||
height: number;
|
||||
rootElement?: HTMLElement;
|
||||
};
|
||||
|
||||
type FrameAdapter = {
|
||||
id: string;
|
||||
init?: (ctx: FrameAdapterContext) => Promise<void> | void;
|
||||
getDurationFrames: () => number;
|
||||
seekFrame: (frame: number) => Promise<void> | void;
|
||||
destroy?: () => Promise<void> | void;
|
||||
};
|
||||
```
|
||||
|
||||
## Required Semantics
|
||||
|
||||
- `getDurationFrames()` must return a finite integer >= 0
|
||||
- `seekFrame(frame)` must support arbitrary seek order (forward, backward, random)
|
||||
- `seekFrame(frame)` must be idempotent for the same input frame
|
||||
- `seekFrame(frame)` must clamp internal time to the adapter's range
|
||||
- Adapters should be paused/seek-driven, not clock-driven
|
||||
|
||||
## Host Orchestration
|
||||
|
||||
The host normalizes frames before calling the adapter:
|
||||
|
||||
```typescript
|
||||
normalizedFrame = clamp(Math.floor(frame), 0, durationFrames);
|
||||
```
|
||||
|
||||
A typical render loop:
|
||||
|
||||
```typescript
|
||||
await adapter.init?.({ compositionId, fps, width, height, rootElement });
|
||||
const durationFrames = adapter.getDurationFrames();
|
||||
|
||||
for (let frame = 0; frame <= durationFrames; frame += 1) {
|
||||
await adapter.seekFrame(frame);
|
||||
// capture pixel buffer for this frame
|
||||
}
|
||||
|
||||
await adapter.destroy?.();
|
||||
```
|
||||
|
||||
## Determinism Contract
|
||||
|
||||
These rules are non-negotiable for any adapter:
|
||||
|
||||
- Canonical clock: `t = frame / fps`
|
||||
- No wall-clock dependencies (`Date.now`, drift-dependent logic)
|
||||
- No unseeded randomness
|
||||
- No render-time network fetches
|
||||
- Fixed output params (`fps`, `width`, `height`)
|
||||
- Finite duration only
|
||||
- Deterministic frame quantization before seek
|
||||
|
||||
## Supported Runtimes
|
||||
|
||||
First-party adapters:
|
||||
|
||||
| Runtime | Seek Method | Status |
|
||||
|---------|------------|--------|
|
||||
| GSAP | `timeline.seek(frame / fps)` | Available |
|
||||
| CSS/WAAPI | `animation.currentTime` | Planned |
|
||||
| Lottie | Set animation frame/progress | Planned |
|
||||
| Three.js/WebGL | Compute deterministic scene state | Planned |
|
||||
| SVG/Anime | Implement seek + duration contract | Planned |
|
||||
|
||||
Community adapters are welcome — if it can seek by frame, it belongs in Hyperframes.
|
||||
|
||||
## Conformance Tests
|
||||
|
||||
Every adapter should pass these minimum tests:
|
||||
|
||||
1. **Repeatability** — seek same frame twice, get identical output
|
||||
2. **Random seek** — seek order `[90, 10, 50, 10]` produces deterministic results
|
||||
3. **Bounds** — negative and overflow frame values don't break
|
||||
4. **Duration** — returned duration is a finite integer
|
||||
5. **Cleanup** — no leaked timers/listeners after `destroy`
|
||||
|
||||
<Info>
|
||||
The Adapter API is currently at **v0** (experimental). Breaking changes are possible until v1.
|
||||
</Info>
|
||||
@@ -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).
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Common Mistakes
|
||||
description: "Pitfalls that break Hyperframes compositions."
|
||||
---
|
||||
|
||||
These are mistakes that can't be caught by the linter. For automated checks, run `npx hyperframes lint`.
|
||||
|
||||
## Animating Video Element Dimensions
|
||||
|
||||
**Symptom**: Video frames stop updating, or browser performance drops.
|
||||
|
||||
**Cause**: GSAP animating `width`, `height`, `top`, `left` directly on a `<video>` element can cause the browser to stop rendering frames.
|
||||
|
||||
```javascript
|
||||
// BROKEN — animating video element dimensions
|
||||
tl.to("#el-video", { width: 500, height: 280, top: 700, left: 1400 }, 26);
|
||||
|
||||
// FIXED — animate a wrapper div, video fills it at 100%
|
||||
tl.to("#pip-wrapper", { width: 500, height: 280, top: 700, left: 1400 }, 26);
|
||||
```
|
||||
|
||||
Use a non-timed wrapper `<div>` for visual effects like picture-in-picture. Animate the wrapper; let the video fill it.
|
||||
|
||||
## Controlling Media Playback in Scripts
|
||||
|
||||
**Symptom**: Audio/video playback is out of sync, or plays when it shouldn't.
|
||||
|
||||
**Cause**: Calling `video.play()`, `video.pause()`, or setting `audio.currentTime` in your scripts. The framework owns all media playback.
|
||||
|
||||
```javascript
|
||||
// BROKEN — conflicts with framework media sync
|
||||
document.getElementById("el-video").play();
|
||||
document.getElementById("el-audio").currentTime = 5;
|
||||
|
||||
// FIXED — don't do this. The framework handles it.
|
||||
// Use GSAP for visual animations only:
|
||||
tl.to("#el-video", { opacity: 1, duration: 0.5 }, 0);
|
||||
```
|
||||
|
||||
## Composition Duration Shorter Than Video
|
||||
|
||||
**Symptom**: Video plays for a few seconds then stops. Timeline shows 8-10 seconds even though the video is minutes long.
|
||||
|
||||
**Cause**: The composition duration equals the GSAP timeline duration, not `data-duration` on the video. If your last GSAP animation ends at 8 seconds, the composition is 8 seconds long.
|
||||
|
||||
```javascript
|
||||
// BROKEN — timeline is only 7.8s long, video cuts off
|
||||
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
|
||||
|
||||
// FIXED — extend timeline to match video length
|
||||
tl.to("#lower-third", { left: -640, duration: 0.6 }, 7.2);
|
||||
tl.set({}, {}, 283); // extends timeline to 283 seconds
|
||||
```
|
||||
|
||||
`tl.set({}, {}, TIME)` adds a zero-duration tween at the specified time, extending the timeline without affecting any elements.
|
||||
|
||||
## Debugging Checklist
|
||||
|
||||
When something doesn't work, check in this order:
|
||||
|
||||
1. **Run the linter**: `npx hyperframes lint` — catches most structural issues
|
||||
2. **Timeline registered?** Is `window.__timelines["<id>"]` set? Does the key match `data-composition-id`?
|
||||
3. **GSAP-only animations?** Only animate visual properties (opacity, transform, color)
|
||||
4. **Timeline long enough?** Add `tl.set({}, {}, DURATION)` at the end
|
||||
5. **Console errors?** Open browser console — runtime errors show as `[Browser:ERROR]`
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: GSAP Animation
|
||||
description: "Add animations to your Hyperframes compositions with GSAP."
|
||||
---
|
||||
|
||||
Hyperframes uses [GSAP](https://gsap.com/) for animation. Timelines are paused and controlled by the runtime — you define the animations, the framework handles playback.
|
||||
|
||||
## Setup
|
||||
|
||||
Include GSAP and create a paused timeline:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
tl.to("#title", { opacity: 1, duration: 0.5 }, 0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["root"] = tl;
|
||||
</script>
|
||||
```
|
||||
|
||||
## Key Rules
|
||||
|
||||
1. **Always create timelines with `{ paused: true }`** — the framework controls playback
|
||||
2. **Register timelines on `window.__timelines`** with the `data-composition-id` as key
|
||||
3. **Use the position parameter** (3rd argument) for absolute timing: `tl.to(el, vars, 1.5)`
|
||||
4. **Only animate visual properties** — never control media playback in scripts
|
||||
|
||||
## Supported Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `tl.to(target, vars, position)` | Animate to values |
|
||||
| `tl.from(target, vars, position)` | Animate from values |
|
||||
| `tl.fromTo(target, fromVars, toVars, position)` | Animate from/to values |
|
||||
| `tl.set(target, vars, position)` | Set values instantly |
|
||||
|
||||
## Supported Properties
|
||||
|
||||
`opacity`, `x`, `y`, `scale`, `scaleX`, `scaleY`, `rotation`, `width`, `height`, `visibility`, `color`, `backgroundColor`, and any CSS-animatable property.
|
||||
|
||||
## Timeline Duration
|
||||
|
||||
A composition's duration equals its GSAP timeline duration. If your last animation ends at 8 seconds, the composition is 8 seconds long.
|
||||
|
||||
To extend the timeline beyond the last animation (e.g., to match a video clip's length):
|
||||
|
||||
```javascript
|
||||
// Extends timeline to 283 seconds without affecting any elements
|
||||
tl.set({}, {}, 283);
|
||||
```
|
||||
|
||||
## Sub-Composition Timelines
|
||||
|
||||
Each composition registers its own timeline. The framework automatically nests sub-composition timelines into the parent based on `data-start`:
|
||||
|
||||
```javascript
|
||||
// In compositions/intro-anim.html
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
tl.from(".title", { opacity: 0, y: -50, duration: 1 });
|
||||
window.__timelines["intro-anim"] = tl;
|
||||
|
||||
// DO NOT manually add sub-timelines to the master:
|
||||
// masterTL.add(window.__timelines["intro-anim"], 0); // UNNECESSARY
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Don't animate `width`, `height`, `top`, or `left` directly on `<video>` elements — this can cause the browser to stop rendering frames. Wrap the video in a `<div>` and animate the wrapper instead.
|
||||
</Warning>
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Rendering
|
||||
description: "Render compositions to MP4 locally or in Docker."
|
||||
---
|
||||
|
||||
Render your Hyperframes compositions to MP4 with the CLI.
|
||||
|
||||
```bash
|
||||
npx hyperframes render -o output.mp4
|
||||
```
|
||||
|
||||
## Rendering Modes
|
||||
|
||||
### Local Mode (default)
|
||||
|
||||
Uses Puppeteer (bundled Chromium) + system FFmpeg. Fast for iteration.
|
||||
|
||||
**Requires**: FFmpeg installed on your system.
|
||||
|
||||
```bash
|
||||
npx hyperframes render -o output.mp4
|
||||
```
|
||||
|
||||
### Docker Mode
|
||||
|
||||
Deterministic output with an exact Chrome version and fonts. Use this for production renders and CI pipelines.
|
||||
|
||||
**Requires**: Docker installed and running.
|
||||
|
||||
```bash
|
||||
npx hyperframes render --docker -o output.mp4
|
||||
```
|
||||
|
||||
<Note>
|
||||
Docker mode uses `chrome-headless-shell` with BeginFrame control for frame-perfect, deterministic capture. This is the same pipeline used in production.
|
||||
</Note>
|
||||
|
||||
## Options
|
||||
|
||||
| Flag | Values | Default | Description |
|
||||
|------|--------|---------|-------------|
|
||||
| `-f, --fps` | 24, 30, 60 | 30 | Frames per second |
|
||||
| `-q, --quality` | draft, standard, high | standard | Encoding quality preset |
|
||||
| `-w, --workers` | 1-8 | auto | Parallel render workers |
|
||||
| `--gpu` | — | off | GPU encoding (NVENC, VideoToolbox, VAAPI) |
|
||||
| `-o, --output` | path | — | Output file path |
|
||||
| `--docker` | — | off | Use Docker for deterministic rendering |
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `draft` quality during development for fast previews
|
||||
- Use `npx hyperframes benchmark` to find optimal settings for your system
|
||||
- 4 workers is usually the sweet spot for most compositions
|
||||
- Docker mode is slower but guarantees identical output across platforms
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Templates
|
||||
description: "Built-in templates for common video patterns."
|
||||
---
|
||||
|
||||
Hyperframes includes starter templates to help you get going quickly.
|
||||
|
||||
## Using Templates
|
||||
|
||||
```bash
|
||||
npx hyperframes init --template <name>
|
||||
```
|
||||
|
||||
## Available Templates
|
||||
|
||||
### blank
|
||||
|
||||
Empty 1920x1080 composition with a GSAP timeline wired up. Start from scratch.
|
||||
|
||||
```bash
|
||||
npx hyperframes init --template blank
|
||||
```
|
||||
|
||||
### title-card
|
||||
|
||||
Animated title and subtitle with GSAP fade-in/out. Good for intro cards.
|
||||
|
||||
```bash
|
||||
npx hyperframes init --template title-card
|
||||
```
|
||||
|
||||
### video-edit
|
||||
|
||||
Video element with trimming, audio, and track controls. Starting point for video editing workflows.
|
||||
|
||||
```bash
|
||||
npx hyperframes init --template video-edit
|
||||
```
|
||||
|
||||
## Custom Templates
|
||||
|
||||
Any directory with an `index.html` can serve as a template. Copy it manually or build your own init workflow.
|
||||
|
||||
Your custom template just needs:
|
||||
1. An `index.html` with a `data-composition-id` root element
|
||||
2. A GSAP timeline registered in `window.__timelines`
|
||||
3. Any assets in the same directory
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
description: "Solutions for common Hyperframes issues."
|
||||
---
|
||||
|
||||
## "No composition found"
|
||||
|
||||
Your directory needs an `index.html` with a valid composition. Run `npx hyperframes init` to create one.
|
||||
|
||||
## "FFmpeg not found"
|
||||
|
||||
Local rendering requires FFmpeg. Install it for your platform:
|
||||
|
||||
<CodeGroup>
|
||||
```bash macOS
|
||||
brew install ffmpeg
|
||||
```
|
||||
|
||||
```bash Ubuntu/Debian
|
||||
sudo apt install ffmpeg
|
||||
```
|
||||
|
||||
```bash Windows
|
||||
# Download from https://ffmpeg.org/download.html
|
||||
# Add to your PATH
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Lint Errors
|
||||
|
||||
Run `npx hyperframes lint` to check for common issues:
|
||||
|
||||
- Missing `data-composition-id` on root element
|
||||
- Missing `class="clip"` on timed elements
|
||||
- Overlapping timelines or invalid data attributes
|
||||
- Unmuted video elements
|
||||
- Deprecated attribute names (`data-layer`, `data-end`)
|
||||
|
||||
## Preview Not Updating
|
||||
|
||||
Make sure you're editing the `index.html` in the project directory. The preview server watches for file changes and auto-reloads.
|
||||
|
||||
If changes still don't appear:
|
||||
1. Check the terminal for errors
|
||||
2. Try stopping and restarting `npx hyperframes dev`
|
||||
3. Hard-refresh the browser (Ctrl+Shift+R / Cmd+Shift+R)
|
||||
|
||||
## Render Looks Different from Preview
|
||||
|
||||
Use `--docker` mode for deterministic output. Local renders may differ due to:
|
||||
|
||||
- Font availability (different fonts on different platforms)
|
||||
- Chrome version (local Chromium vs. Docker's pinned version)
|
||||
- System-specific rendering differences
|
||||
|
||||
```bash
|
||||
npx hyperframes render --docker -o output.mp4
|
||||
```
|
||||
|
||||
## System Diagnostics
|
||||
|
||||
Run `npx hyperframes doctor` to check your environment:
|
||||
|
||||
```bash
|
||||
npx hyperframes doctor
|
||||
```
|
||||
|
||||
This checks for Node.js version, FFmpeg availability, Docker status, and other requirements.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: "Write HTML. Render video. Built for agents."
|
||||
---
|
||||
|
||||
Hyperframes is an open-source video rendering framework that lets you create, preview, and render HTML-based video compositions — with first-class support for AI agents.
|
||||
|
||||
## Why Hyperframes?
|
||||
|
||||
- **HTML-native** — AI agents already speak HTML. No React required.
|
||||
- **Frame Adapter pattern** — bring your own animation runtime (GSAP, Lottie, CSS, Three.js).
|
||||
- **Deterministic rendering** — same input = identical output. Built for automated pipelines.
|
||||
- **AI-first design** — not a bolted-on afterthought.
|
||||
|
||||
## How It Works
|
||||
|
||||
Define your video as HTML with data attributes:
|
||||
|
||||
```html
|
||||
<div id="stage" data-composition-id="my-video"
|
||||
data-start="0" data-width="1920" data-height="1080">
|
||||
<video id="clip-1" data-start="0" data-duration="5"
|
||||
data-track-index="0" src="intro.mp4" muted playsinline></video>
|
||||
<img id="overlay" data-start="2" data-duration="3"
|
||||
data-track-index="1" src="logo.png" />
|
||||
<audio id="bg-music" data-start="0" data-duration="9"
|
||||
data-track-index="2" data-volume="0.5" src="music.wav"></audio>
|
||||
</div>
|
||||
```
|
||||
|
||||
Preview instantly in the browser. Render to MP4 locally. Let AI agents compose videos using tools they already understand.
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | npm | Description |
|
||||
|---------|-----|-------------|
|
||||
| [`@hyperframes/core`](/packages/core) | `@hyperframes/core` | Types, HTML generation, runtime, linter |
|
||||
| [`@hyperframes/engine`](/packages/engine) | `@hyperframes/engine` | Seekable page-to-video capture engine |
|
||||
| [`@hyperframes/producer`](/packages/producer) | `@hyperframes/producer` | Full rendering pipeline (capture + encode) |
|
||||
| [`@hyperframes/studio`](/packages/studio) | `@hyperframes/studio` | Composition editor UI |
|
||||
| [`hyperframes`](/packages/cli) | `hyperframes` | CLI for creating, previewing, and rendering |
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Quickstart" icon="rocket" href="/quickstart">
|
||||
Create your first video in 60 seconds
|
||||
</Card>
|
||||
<Card title="Compositions" icon="layer-group" href="/concepts/compositions">
|
||||
Learn the core data model
|
||||
</Card>
|
||||
<Card title="GSAP Animation" icon="wand-magic-sparkles" href="/guides/gsap-animation">
|
||||
Add animations to your videos
|
||||
</Card>
|
||||
<Card title="Rendering" icon="film" href="/guides/rendering">
|
||||
Render to MP4 locally or in Docker
|
||||
</Card>
|
||||
</CardGroup>
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.9 KiB |
@@ -0,0 +1,62 @@
|
||||
<svg width="640" height="640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="prefix__a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="37" y="12" width="588" height="556">
|
||||
<path
|
||||
d="M206.788 480.08c-5.026-2.455-7.729.722-7.729 8.439 0 7.717.294 35.453 6.306 47.25a57.88 57.88 0 0025.293 25.293c9.463 4.821 21.161 5.957 40.623 6.225 53.598-2.39 100.504-30.509 128.665-72.305 2.5-3.711-.25-8.557-4.725-8.557H267.276c-17.766 0-44.453 1.487-60.488-6.345zM184.75 241.45c0-7.896-10.44-10.725-14.387-3.886a781644.405 781644.405 0 01-49.776 86.209c-40.45 70.047-26.516 163.98 51.459 216.834 9.646 6.539 17.363-3.858 15.491-12.001-3.981-16.646-2.787-35.128-2.787-52.213V241.45zM495.215 314.856c.387 5.58 4.49 6.333 11.173 2.474 6.684-3.858 30.556-17.981 37.767-29.086a57.878 57.878 0 009.258-34.551c-.556-10.606-5.421-21.305-14.92-38.293-28.869-45.223-76.674-71.785-126.951-75.274-4.464-.31-7.286 4.494-5.048 8.369L470.467 259.3c8.882 15.385 23.514 37.753 24.748 55.556zM299.572 453.254c-6.838 3.948-4.068 14.403 3.828 14.403 20.774-.001 53.356-.002 99.547.002 80.888.007 155.269-59.027 162.055-152.982.839-11.623-12.023-13.108-18.139-7.415-12.425 11.77-29.028 19.978-43.824 28.52L299.572 453.254zM211.794 149.177c4.638-3.124 3.239-7.054-3.444-10.913-6.683-3.858-30.851-17.471-44.073-18.163a57.881 57.881 0 00-34.551 9.257c-8.907 5.785-15.74 15.348-25.703 32.068-24.73 47.613-23.83 102.294-1.714 147.58 1.964 4.021 7.535 4.062 9.773.187l63.972-110.804c8.883-15.386 20.939-39.241 35.74-49.212zM429.475 249.411c6.838 3.948 14.507-3.678 10.559-10.516-10.388-17.99-26.679-46.207-49.771-86.212C349.825 82.629 261.51 47.73 176.749 88.831c-10.486 5.084-5.34 16.966 2.648 19.416 16.406 4.876 31.815 15.15 46.611 23.693l203.467 117.471z"
|
||||
fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#prefix__a)">
|
||||
<path fill="#28A7E5" d="M48.574 46.727h555.617v547.9H48.574z" />
|
||||
<g filter="url(#prefix__filter0_f_477_98045)">
|
||||
<circle cx="760.94" cy="400.003" r="525.682" fill="#9F59FF" />
|
||||
</g>
|
||||
<g filter="url(#prefix__filter1_f_477_98045)">
|
||||
<circle cx="751.928" cy="179.218" r="230.8" fill="#FF2FC5" />
|
||||
</g>
|
||||
<g filter="url(#prefix__filter2_f_477_98045)">
|
||||
<circle cx="-69.128" cy="621.8" r="289.384" fill="#42C7F1" />
|
||||
</g>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity=".6">
|
||||
<path d="M184.769 452.351v-210.91c0-7.896-10.452-10.696-14.4-3.859l-53.111 91.991 67.511 122.778z"
|
||||
fill="url(#prefix__paint0_linear_477_98045)" />
|
||||
<path d="M482.477 347.684L299.823 453.14c-6.838 3.947-4.037 14.4 3.858 14.4h106.222l72.574-119.856z"
|
||||
fill="url(#prefix__paint1_linear_477_98045)" />
|
||||
<path d="M246.704 143.775L429.358 249.23c6.837 3.948 14.489-3.704 10.541-10.541l-53.111-91.991-140.084-2.923z"
|
||||
fill="url(#prefix__paint2_linear_477_98045)" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="prefix__paint0_linear_477_98045" x1="147.247" y1="212.641" x2="147.247" y2="451.866"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="prefix__paint1_linear_477_98045" x1="293.643" y1="500.035" x2="500.818" y2="380.422"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="prefix__paint2_linear_477_98045" x1="473.06" y1="231.135" x2="265.885" y2="111.522"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<filter id="prefix__filter0_f_477_98045" x="-227.756" y="-588.694" width="1977.39" height="1977.39"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="231.507" result="effect1_foregroundBlur_477_98045" />
|
||||
</filter>
|
||||
<filter id="prefix__filter1_f_477_98045" x="360.921" y="-211.79" width="782.014" height="782.016"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="80.104" result="effect1_foregroundBlur_477_98045" />
|
||||
</filter>
|
||||
<filter id="prefix__filter2_f_477_98045" x="-550.261" y="140.667" width="962.263" height="962.265"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="95.874" result="effect1_foregroundBlur_477_98045" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,77 @@
|
||||
<svg width="2122" height="695" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="prefix__a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="60" y="41" width="588" height="556">
|
||||
<path
|
||||
d="M229.788 509.08c-5.026-2.455-7.729.722-7.729 8.439 0 7.717.294 35.453 6.306 47.25a57.88 57.88 0 0025.293 25.293c9.463 4.821 21.161 5.957 40.623 6.225 53.598-2.39 100.504-30.509 128.665-72.305 2.5-3.711-.25-8.557-4.725-8.557H290.276c-17.766 0-44.453 1.487-60.488-6.345zM207.75 270.45c0-7.896-10.44-10.725-14.387-3.886a781644.405 781644.405 0 01-49.776 86.209c-40.45 70.047-26.516 163.98 51.459 216.834 9.646 6.539 17.363-3.858 15.491-12.001-3.981-16.646-2.787-35.128-2.787-52.213V270.45zM518.215 343.856c.387 5.58 4.49 6.333 11.173 2.474 6.684-3.858 30.556-17.981 37.767-29.086a57.878 57.878 0 009.258-34.551c-.556-10.606-5.421-21.305-14.92-38.293-28.869-45.223-76.674-71.785-126.951-75.274-4.464-.31-7.286 4.494-5.048 8.369L493.467 288.3c8.882 15.385 23.514 37.753 24.748 55.556zM322.572 482.254c-6.838 3.948-4.068 14.403 3.828 14.403 20.774-.001 53.356-.002 99.547.002 80.888.007 155.269-59.027 162.055-152.982.839-11.623-12.023-13.108-18.139-7.415-12.425 11.77-29.028 19.978-43.824 28.52L322.572 482.254zM234.794 178.177c4.638-3.124 3.239-7.054-3.444-10.913-6.683-3.858-30.851-17.471-44.073-18.163a57.881 57.881 0 00-34.551 9.257c-8.907 5.785-15.74 15.348-25.703 32.068-24.729 47.613-23.83 102.294-1.714 147.58 1.964 4.021 7.535 4.062 9.773.187l63.972-110.804c8.883-15.386 20.939-39.241 35.74-49.212zM452.475 278.411c6.838 3.948 14.507-3.678 10.559-10.516-10.388-17.99-26.679-46.207-49.771-86.212-40.438-70.054-128.753-104.953-213.514-63.852-10.486 5.084-5.34 16.966 2.648 19.416 16.406 4.876 31.815 15.15 46.611 23.693l203.467 117.471z"
|
||||
fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#prefix__a)">
|
||||
<path fill="#28A7E5" d="M71.574 75.727h555.617v547.9H71.574z" />
|
||||
<g filter="url(#prefix__filter0_f_473_94381)">
|
||||
<circle cx="783.94" cy="429.003" r="525.682" fill="#9F59FF" />
|
||||
</g>
|
||||
<g filter="url(#prefix__filter1_f_473_94381)">
|
||||
<circle cx="774.928" cy="208.218" r="230.8" fill="#FF2FC5" />
|
||||
</g>
|
||||
<g filter="url(#prefix__filter2_f_473_94381)">
|
||||
<circle cx="-46.128" cy="650.8" r="289.384" fill="#42C7F1" />
|
||||
</g>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity=".6">
|
||||
<path d="M207.769 481.351v-210.91c0-7.896-10.452-10.696-14.4-3.859l-53.111 91.991 67.511 122.778z"
|
||||
fill="url(#prefix__paint0_linear_473_94381)" />
|
||||
<path d="M505.477 376.684L322.823 482.14c-6.838 3.947-4.037 14.4 3.858 14.4h106.222l72.574-119.856z"
|
||||
fill="url(#prefix__paint1_linear_473_94381)" />
|
||||
<path d="M269.704 172.775L452.358 278.23c6.837 3.948 14.489-3.704 10.541-10.541l-53.111-91.991-140.084-2.923z"
|
||||
fill="url(#prefix__paint2_linear_473_94381)" />
|
||||
</g>
|
||||
<rect x="750.836" y="233.035" width="50.876" height="232.957" rx="8.926" fill="#232833" />
|
||||
<rect x="908.816" y="233.035" width="50.876" height="232.957" rx="8.926" fill="#232833" />
|
||||
<rect x="948.09" y="325.861" width="42.843" height="191.899" rx="8.926" transform="rotate(90 948.09 325.861)"
|
||||
fill="#232833" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M1180.16 300.625c0 .767.17 1.524.5 2.215l74.08 153.977a7.141 7.141 0 009.53 3.338l28.96-13.931c3.55-1.71 5.05-5.976 3.34-9.53l-65.98-137.134a7.142 7.142 0 00-6.44-4.044h-38.88a5.104 5.104 0 00-5.11 5.109z"
|
||||
fill="#232833" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M1325.98 295.516a7.127 7.127 0 00-6.56 4.337l-69.52 162.868a51.763 51.763 0 01-36.07 30.144l-6.89 1.575a7.136 7.136 0 00-5.37 8.552l7.16 31.324a7.14 7.14 0 008.55 5.369l6.89-1.575a98.19 98.19 0 0068.42-57.17l74.9-175.481c2.01-4.71-1.45-9.943-6.57-9.943h-34.94zM1592.51 246.616c0-2.865-1.7-5.465-4.37-6.507-19.2-7.502-43.75-12.882-76.4-12.88-33.54.001-63.79 11.942-85.52 35.207-21.62 23.16-32.91 55.484-32.75 92.973.31 70.559 54.81 116.829 115.04 115.93 44.56-.665 66.84-9.611 79.35-16.372 10.15-5.483 15.81-15.96 15.81-26.624v-71.516c0-12.915-10.47-23.385-23.38-23.385h-64.98a7.136 7.136 0 00-7.14 7.141v32.132c0 3.943 3.19 7.14 7.14 7.14h34.81c3.94 0 7.14 3.197 7.14 7.14v25.902c0 3.017-1.89 5.731-4.79 6.569-9.12 2.636-23.18 5.145-44.65 5.466-35.45.529-67.75-26.021-67.93-69.722-.12-28.107 8.23-48.219 20.26-61.097 11.92-12.773 29.34-20.471 51.59-20.472 30.65-.001 50.65 5.61 64.46 11.767 1.75.779 3.39 1.554 4.93 2.326 5.06 2.531 11.38-.94 11.38-6.6v-34.518z"
|
||||
fill="#232833" />
|
||||
<rect x="1834.4" y="288.375" width="50.876" height="177.618" rx="8.926" fill="#232833" />
|
||||
<rect x="1948.64" y="426.721" width="46.413" height="39.272" rx="8.926" fill="#232833" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M1862.75 351.384a5.36 5.36 0 01-1.58-3.798l3.57-14.047c0-1.714-13.41 11.546 2.22-4.313 15.64-15.859 33.93-34.157 61.83-34.603 35.28 0 66.27 20.083 66.27 58.81v97.319c0 .037-.03.068-.07.068h-46.28a.069.069 0 01-.07-.068V354.326c0-11.316-10.71-19.538-27.66-19.538-16.96 0-30.31 7.017-44.86 20.556-2.16 2.015-5.55 2.032-7.64-.054l-5.73-3.906zM1088.55 288.375c-26.99 0-50.02 9.123-66.2 26.31-16 16.987-23.385 39.795-23.385 63.792 0 22.378 4.885 46.256 20.575 64.782 16.26 19.193 40.95 28.982 72.64 28.982 26.85 0 51.31-7.601 70.74-16.448a3.534 3.534 0 002.06-3.222v-39.972c0-2.794-3.07-4.519-5.49-3.136-16.86 9.601-41.12 19.936-67.31 19.936-22.7 0-33.91-6.695-39.95-13.829-4.79-5.653-8.11-13.663-9.58-24.113h84.69l.12.002h35.71c1.97 0 3.57-1.597 3.54-3.568-.28-17.172-2.67-39.756-12.31-59.157-5.31-10.697-13.13-21.024-24.51-28.628-11.52-7.692-25.4-11.731-41.34-11.731zm32.59 74.52c-1.22-5.491-2.88-10.624-5.11-15.099-2.78-5.597-6.11-9.505-9.94-12.062-3.69-2.469-9.09-4.516-17.54-4.516-17.07 0-28.13 5.529-35.01 12.838-4.44 4.71-7.92 11.005-9.91 18.839h77.51zM1723.16 288.375c-27 0-50.02 9.123-66.21 26.31-15.99 16.987-23.38 39.795-23.38 63.792 0 22.378 4.88 46.256 20.58 64.782 16.25 19.193 40.94 28.982 72.64 28.982 26.85 0 51.31-7.601 70.74-16.448a3.534 3.534 0 002.06-3.222v-39.972c0-2.794-3.07-4.519-5.5-3.136-16.85 9.601-41.11 19.936-67.3 19.936-22.7 0-33.91-6.695-39.95-13.829-4.79-5.653-8.11-13.663-9.58-24.113h84.68l.13.002h35.7c1.97 0 3.58-1.598 3.53-3.57-.4-17.942-3.91-40.436-14.04-59.62-11.36-21.536-31.95-39.894-64.1-39.894zm31.77 74.52c-1.4-5.258-3.24-10.24-5.56-14.634-5.83-11.054-13.62-17.043-26.21-17.043-17.07 0-28.13 5.529-35.02 12.838-4.43 4.71-7.92 11.005-9.9 18.839h76.69z"
|
||||
fill="#232833" />
|
||||
<defs>
|
||||
<linearGradient id="prefix__paint0_linear_473_94381" x1="170.247" y1="241.641" x2="170.247" y2="480.866"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="prefix__paint1_linear_473_94381" x1="316.643" y1="529.035" x2="523.818" y2="409.422"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="prefix__paint2_linear_473_94381" x1="496.06" y1="260.135" x2="288.885" y2="140.522"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<filter id="prefix__filter0_f_473_94381" x="-204.756" y="-559.694" width="1977.39" height="1977.39"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="231.507" result="effect1_foregroundBlur_473_94381" />
|
||||
</filter>
|
||||
<filter id="prefix__filter1_f_473_94381" x="383.921" y="-182.79" width="782.014" height="782.016"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="80.104" result="effect1_foregroundBlur_473_94381" />
|
||||
</filter>
|
||||
<filter id="prefix__filter2_f_473_94381" x="-527.261" y="169.667" width="962.263" height="962.265"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="95.874" result="effect1_foregroundBlur_473_94381" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: CLI
|
||||
description: "Create, preview, and render HTML video compositions from the command line."
|
||||
---
|
||||
|
||||
The `hyperframes` CLI is the primary way to work with Hyperframes. It handles project creation, live preview, rendering, linting, and diagnostics.
|
||||
|
||||
```bash
|
||||
npm install -g hyperframes
|
||||
# or use directly with npx
|
||||
npx hyperframes <command>
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### `init`
|
||||
|
||||
Create a new composition project from a template:
|
||||
|
||||
```bash
|
||||
npx hyperframes init --template title-card
|
||||
```
|
||||
|
||||
See [Templates](/guides/templates) for available templates.
|
||||
|
||||
### `dev`
|
||||
|
||||
Start a live preview server with hot reload:
|
||||
|
||||
```bash
|
||||
npx hyperframes dev
|
||||
```
|
||||
|
||||
Opens your composition in the browser at `http://localhost:3000`. Edits to `index.html` are reflected instantly.
|
||||
|
||||
### `render`
|
||||
|
||||
Render a composition to MP4:
|
||||
|
||||
```bash
|
||||
npx hyperframes render -o output.mp4
|
||||
npx hyperframes render --docker -o output.mp4 # deterministic mode
|
||||
```
|
||||
|
||||
See [Rendering](/guides/rendering) for all options.
|
||||
|
||||
### `lint`
|
||||
|
||||
Check a composition for common issues:
|
||||
|
||||
```bash
|
||||
npx hyperframes lint
|
||||
```
|
||||
|
||||
Detects missing attributes, deprecated names, structural problems, and more.
|
||||
|
||||
### `compositions`
|
||||
|
||||
List all compositions in the current project:
|
||||
|
||||
```bash
|
||||
npx hyperframes compositions
|
||||
```
|
||||
|
||||
### `benchmark`
|
||||
|
||||
Find optimal render settings for your system:
|
||||
|
||||
```bash
|
||||
npx hyperframes benchmark
|
||||
```
|
||||
|
||||
### `doctor`
|
||||
|
||||
Check your environment for required dependencies:
|
||||
|
||||
```bash
|
||||
npx hyperframes doctor
|
||||
```
|
||||
|
||||
Verifies Node.js version, FFmpeg, Docker, and other requirements.
|
||||
|
||||
### `info`
|
||||
|
||||
Display system and project information:
|
||||
|
||||
```bash
|
||||
npx hyperframes info
|
||||
```
|
||||
|
||||
### `upgrade`
|
||||
|
||||
Update Hyperframes to the latest version:
|
||||
|
||||
```bash
|
||||
npx hyperframes upgrade
|
||||
```
|
||||
@@ -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';
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Quickstart
|
||||
description: "Create, preview, and render your first Hyperframes video."
|
||||
---
|
||||
|
||||
## Create a Project
|
||||
|
||||
```bash
|
||||
npx create-hyperframe my-video
|
||||
cd my-video
|
||||
```
|
||||
|
||||
This scaffolds a project with an `index.html` composition and assets directory.
|
||||
|
||||
## Preview in Browser
|
||||
|
||||
```bash
|
||||
npx hyperframes dev
|
||||
```
|
||||
|
||||
Opens a live preview at `http://localhost:3000`. Edit `index.html` and the preview updates automatically.
|
||||
|
||||
## Render to MP4
|
||||
|
||||
```bash
|
||||
npx hyperframes render -o output.mp4
|
||||
```
|
||||
|
||||
Renders your composition to an MP4 file using the local rendering pipeline (Puppeteer + FFmpeg).
|
||||
|
||||
<Note>
|
||||
Local rendering requires FFmpeg. Install it with `brew install ffmpeg` (macOS), `sudo apt install ffmpeg` (Ubuntu), or download from [ffmpeg.org](https://ffmpeg.org/download.html).
|
||||
</Note>
|
||||
|
||||
## Project Structure
|
||||
|
||||
After `create-hyperframe`, your project looks like this:
|
||||
|
||||
```
|
||||
my-video/
|
||||
├── index.html # Root composition
|
||||
├── compositions/ # Sub-compositions (optional)
|
||||
└── assets/ # Media files (video, audio, images)
|
||||
```
|
||||
|
||||
## Your First Composition
|
||||
|
||||
Every Hyperframes video is an HTML file. Here's a minimal example:
|
||||
|
||||
```html
|
||||
<div id="root" data-composition-id="my-video"
|
||||
data-start="0" data-width="1920" data-height="1080">
|
||||
|
||||
<h1 id="title" class="clip"
|
||||
data-start="0" data-duration="5" data-track-index="0"
|
||||
style="font-size: 72px; color: white; text-align: center;">
|
||||
Hello, Hyperframes!
|
||||
</h1>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
tl.from("#title", { opacity: 0, y: -50, duration: 1 }, 0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["my-video"] = tl;
|
||||
</script>
|
||||
</div>
|
||||
```
|
||||
|
||||
Key rules:
|
||||
- **Root element** needs `data-composition-id`, `data-width`, and `data-height`
|
||||
- **Timed elements** need `data-start`, `data-duration`, `data-track-index`, and `class="clip"`
|
||||
- **GSAP timeline** must be paused and registered in `window.__timelines`
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Node.js** 20+
|
||||
- **pnpm** (recommended) or npm
|
||||
- **FFmpeg** for local rendering
|
||||
- **Docker** (optional) for deterministic rendering
|
||||
|
||||
<Card title="Templates" icon="grid-2" href="/guides/templates">
|
||||
Browse built-in templates for common video patterns
|
||||
</Card>
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: HTML Schema Reference
|
||||
description: "Complete reference for authoring Hyperframes HTML compositions."
|
||||
---
|
||||
|
||||
This is the full schema reference for Hyperframes compositions. For a gentler introduction, see [Compositions](/concepts/compositions) and [Data Attributes](/concepts/data-attributes).
|
||||
|
||||
## Overview
|
||||
|
||||
Hyperframes uses HTML as the source of truth for describing a video:
|
||||
|
||||
- **HTML clips** = video, image, audio, composition
|
||||
- **Data attributes** = timing, metadata, styling
|
||||
- **CSS** = positioning and appearance
|
||||
- **GSAP timeline** = animations and playback sync
|
||||
|
||||
## Framework-Managed Behavior
|
||||
|
||||
The framework reads data attributes and automatically manages:
|
||||
|
||||
- **Primitive clip timeline entries** — reads `data-start`, `data-duration`, and `data-track-index` from clips and adds them to the GSAP timeline
|
||||
- **Media playback** (play, pause, seek) for `<video>` and `<audio>`
|
||||
- **Clip lifecycle** — clips are mounted/unmounted based on `data-start` and `data-duration`
|
||||
- **Timeline synchronization** — keeps media in sync with the GSAP master timeline
|
||||
- **Media loading** — waits for all media to load before resolving timing
|
||||
|
||||
Mounting/unmounting controls **presence**, not appearance. Transitions (fade in, slide in) are animated in scripts.
|
||||
|
||||
<Warning>
|
||||
Do not manually call `video.play()`, `video.pause()`, set `audio.currentTime`, or mount/unmount clips in scripts. The framework owns media playback and clip lifecycle.
|
||||
</Warning>
|
||||
|
||||
## Viewport
|
||||
|
||||
Every composition must include `data-width` and `data-height`:
|
||||
|
||||
```html
|
||||
<div id="main" data-composition-id="my-video"
|
||||
data-start="0" data-width="1920" data-height="1080">
|
||||
<!-- clips -->
|
||||
</div>
|
||||
```
|
||||
|
||||
Common sizes:
|
||||
- **Landscape**: `data-width="1920" data-height="1080"`
|
||||
- **Portrait**: `data-width="1080" data-height="1920"`
|
||||
|
||||
## All Clip Attributes
|
||||
|
||||
| Attribute | Applies To | Required | Description |
|
||||
|-----------|-----------|----------|-------------|
|
||||
| `id` | All | Yes | Unique identifier (e.g., `"el-1"`) |
|
||||
| `class="clip"` | Visible elements | Yes | Enables runtime visibility management |
|
||||
| `data-start` | All | Yes | Start time in seconds, or clip ID reference |
|
||||
| `data-duration` | video, img, audio | Img: yes, others: optional | Duration in seconds |
|
||||
| `data-track-index` | All | Yes | Timeline track number (z-ordering) |
|
||||
| `data-media-start` | video, audio | No | Playback offset in source file (seconds) |
|
||||
| `data-volume` | audio, video | No | Volume 0-1 |
|
||||
| `data-composition-id` | div | On compositions | Unique composition ID |
|
||||
| `data-composition-src` | div | No | Path to external composition HTML file |
|
||||
| `data-width` | div | On compositions | Composition width in pixels |
|
||||
| `data-height` | div | On compositions | Composition height in pixels |
|
||||
|
||||
## Video Clips
|
||||
|
||||
```html
|
||||
<video
|
||||
id="el-1"
|
||||
data-start="0"
|
||||
data-duration="15"
|
||||
data-track-index="0"
|
||||
data-media-start="0"
|
||||
src="./assets/video.mp4"
|
||||
></video>
|
||||
```
|
||||
|
||||
- `data-duration` is optional — defaults to remaining duration of source file from `data-media-start`
|
||||
- If source media runs out before `data-duration`, the clip shows the last frame
|
||||
|
||||
## Image Clips
|
||||
|
||||
```html
|
||||
<img
|
||||
id="el-2"
|
||||
class="clip"
|
||||
data-start="5"
|
||||
data-duration="4"
|
||||
data-track-index="1"
|
||||
src="./assets/overlay.png"
|
||||
/>
|
||||
```
|
||||
|
||||
- `data-duration` is **required** for images
|
||||
|
||||
## Audio Clips
|
||||
|
||||
```html
|
||||
<audio
|
||||
id="el-4"
|
||||
data-start="0"
|
||||
data-duration="30"
|
||||
data-track-index="2"
|
||||
src="./assets/music.mp3"
|
||||
></audio>
|
||||
```
|
||||
|
||||
- `data-duration` is optional — defaults to remaining duration of source file
|
||||
- Audio clips are invisible
|
||||
|
||||
## Composition Clips
|
||||
|
||||
```html
|
||||
<div
|
||||
id="el-5"
|
||||
data-composition-id="intro-anim"
|
||||
data-composition-src="compositions/intro-anim.html"
|
||||
data-start="0"
|
||||
data-track-index="3"
|
||||
></div>
|
||||
```
|
||||
|
||||
- Compositions do **not** use `data-duration` — duration comes from the GSAP timeline
|
||||
- External compositions are loaded from `data-composition-src` and wrapped in `<template>` tags
|
||||
|
||||
## Relative Timing
|
||||
|
||||
Reference another clip's ID in `data-start` to mean "start when that clip ends":
|
||||
|
||||
```html
|
||||
<video id="intro" data-start="0" data-duration="10" data-track-index="0" src="..."></video>
|
||||
<video id="main" data-start="intro" data-duration="20" data-track-index="0" src="..."></video>
|
||||
```
|
||||
|
||||
Offsets: `data-start="intro + 2"` (2s gap) or `data-start="intro - 0.5"` (0.5s overlap).
|
||||
|
||||
## Timeline Contract
|
||||
|
||||
The framework initializes `window.__timelines = {}` before any scripts run. Every composition must register a timeline:
|
||||
|
||||
```javascript
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
// ... add tweens
|
||||
window.__timelines["<data-composition-id>"] = tl;
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Every composition needs a script to create and register its timeline
|
||||
- All timelines start paused (`{ paused: true }`)
|
||||
- The framework auto-nests sub-timelines — do not manually add them
|
||||
- Duration comes from `tl.duration()` — no `data-duration` on compositions
|
||||
- Timelines must be finite
|
||||
|
||||
## Caption Discoverability
|
||||
|
||||
For caption compositions, add these attributes to the root node:
|
||||
|
||||
```html
|
||||
<div
|
||||
data-composition-id="captions"
|
||||
data-timeline-role="captions"
|
||||
data-caption-root="true"
|
||||
...
|
||||
>
|
||||
```
|
||||
|
||||
## Output Checklist
|
||||
|
||||
- [ ] Every composition has `data-width` and `data-height`
|
||||
- [ ] Each reusable composition is in its own HTML file
|
||||
- [ ] Compositions loaded via `data-composition-src`
|
||||
- [ ] Each composition file uses `<template>` wrapper
|
||||
- [ ] All timelines registered in `window.__timelines`
|
||||
- [ ] Timed visible elements have `class="clip"`
|
||||
Reference in New Issue
Block a user