Files
hyperframes/docs/reference/html-schema.mdx
T
Miguel Ángel 0c158be165 feat(studio): edit a clip's speed ramp with a rate lane and presets (#4153)
* feat(studio): edit a clip's speed ramp with a rate lane and presets

* fix(studio): keep the live preview attribute in sync with what gets persisted

* fix(cli): snapshot picks the source frame through a rate lane and up to 10x

hyperframes snapshot clamped the constant playback rate at 5 and ignored a
rate lane in data-automation, so a sped-up or ramped clip showed the wrong
frame. It now reads the rate through the runtime's own reader and maps time
with the shared source-time function.

* docs(skills): teach speed ramps as a rate lane in one reference page

Adds the speed-ramps reference, corrects the 0.1 to 5 rate range to 0.1 to 10
and the 'speed ramps are not supported' lines in the skills, and regenerates
the skills manifest.

* docs(skills): say what fixes a ramped clip's length and how the render approximates pitch

* fix(studio): hide the speed preset until the clip has a known duration

A preset on a clip with no data-duration was stretched over a made-up one
second. The panel now offers presets only when the duration is known.

* docs(skills): fix the last stale ramp line in the editing recipes and the slice-cap wording
2026-09-20 12:21:47 -04:00

310 lines
10 KiB
Plaintext

---
title: "HTML schema reference"
description: "The current contract for a HyperFrames composition."
---
HyperFrames uses normal HTML and CSS for appearance. A small set of attributes
declares the frame size, duration, clips, media, and nested compositions.
For a gentler explanation, start with [Compositions](/concepts/compositions) and
[Data attributes](/concepts/data-attributes).
## Minimal composition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
body { margin: 0; }
#root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
}
.clip {
position: absolute;
inset: 0;
display: grid;
place-items: center;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="5"
data-width="1920"
data-height="1080"
>
<section
id="title-card"
class="clip"
data-start="0"
data-duration="5"
data-track-index="1"
>
<h1 id="title">Hello HyperFrames</h1>
</section>
</div>
<script>
window.__timelines = window.__timelines || {};
const timeline = gsap.timeline({ paused: true });
timeline.fromTo(
"#title",
{ y: 48, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.6, ease: "power3.out" },
0.2,
);
window.__timelines.main = timeline;
</script>
</body>
</html>
```
The root is a real, explicitly sized box. Its `data-composition-id` matches the
timeline registry key.
## Composition root
| Attribute | Required | Meaning |
| --- | --- | --- |
| `data-composition-id` | Yes | Unique composition ID |
| `data-start="0"` | Yes on the top-level root | Start of the composition |
| `data-width` and `data-height` | Yes | Authored frame dimensions in pixels |
| `data-duration` | Usually | Total render duration in seconds |
| `data-no-timeline` | Only for a timeline-free composition | Tells the runtime not to wait for a timeline |
An explicit root `data-duration` is the render length. The compiler reads it
before composition scripts run, so a script or variable override cannot change
that value for the same render.
The root may omit `data-duration` only when HyperFrames can infer a finite
duration from the registered animation runtime or timed media. Three.js,
unbounded animation, and timeline-free compositions need an explicit duration.
## Timed clips
| Attribute | Required | Meaning |
| --- | --- | --- |
| `id` | Yes | Stable identifier for timing, editing, and animation |
| `data-start` | Yes | Start in seconds or a relative timing expression |
| `data-duration` | Yes for DOM and nested-composition clips; optional for image (3 s default), video and audio (source length) | Visible slot length in seconds |
| `data-track-index` | No | Studio timeline lane, display only. The render never reads it and it does not prevent overlap |
| `class="clip"` | Recommended for authored timed DOM and image elements | Layout and tooling convention. Visibility is keyed off `data-start`, but the shared `.clip` rule supplies the full-frame box |
`data-track-index` does not control paint order. Use CSS `z-index` for
front-to-back layering. Two clips on the same track may overlap in time; the
lane is a Studio display row, not a scheduling constraint.
Video visibility is managed as media and does not require `class="clip"`.
Audio has no visual lifecycle.
## Media
```html
<video
id="demo"
src="./assets/demo.mp4"
data-start="0"
data-duration="8"
data-track-index="0"
muted
playsinline
></video>
```
| Attribute | Applies to | Meaning |
| --- | --- | --- |
| `data-media-start` / `data-playback-start` | Video, audio, nested composition | Offset into the source file, used by trim and split. Two groups of readers disagree, so the right name depends on the element. **Read only `data-media-start`:** the timing compiler, the HTML parser, `hyperframes validate` (which only inspects `<audio>`), and the engine's audio mixer (which feeds ffmpeg `-ss`). **Read `data-playback-start` first, falling back to `data-media-start`:** the runtime player, Studio (which also writes it), and `hyperframes snapshot`. Because the audio mixer reads only `data-media-start`, a `<video>` authored with just `data-playback-start` renders a trimmed picture over untrimmed audio. Set the name by kind: **`<video>` / `<audio>` → `data-media-start`**; **nested composition → `data-playback-start`** — composition hosts are inspected only by the playback-start-first readers (the media-start-only ones are all `<video>`/`<audio>`-scoped). `data-media-start` still works there as a fallback, but `data-playback-start` is the canonical name Studio writes and normalises to for new composition hosts, so the other name works until an edit rewrites it (it is the [child-timeline offset](/concepts/compositions)). |
| `data-playback-rate` | Video, audio, nested composition | Playback multiplier from `0.1` to `10`. A `rate` lane in `data-automation` overrides it; see [Speed ramps](/reference/speed-ramps) |
| `data-volume` | Video and audio | Static gain. `1` is 0 dB, `0` is silence, and values above `1` boost up to `3.98` (+12 dB) |
| `data-has-audio="true"` | Video | Declares that the video contributes audio |
Video and audio may omit `data-duration` when their intrinsic duration is known
and the whole remaining source should play.
Do not imperatively call `play()`, `pause()`, or set `currentTime`.
HyperFrames owns media playback and seeking. A video should be `muted` unless it
is intentionally audible and declares `data-has-audio="true"`.
Media can live inside nested composition markup. Keep element IDs unique across
the assembled project because render-time frame injection targets those IDs.
## Color grading and media effects
Studio and the CLI store color correction, grading, finishing controls, LUTs,
and media effects on an image or video with `data-color-grading`:
```html
<video
id="demo"
src="./assets/demo.mp4"
data-color-grading='{
"preset": "clean-studio",
"intensity": 0.8,
"adjust": { "highlights": -0.08, "shadows": 0.06 },
"details": { "grain": 0.12, "vignette": 0.08 },
"effects": { "bloom": 0.12 },
"colorSpace": "rec709"
}'
></video>
```
The current effect families include essentials, retro and glitch, print, and
art treatments. Run
`npx hyperframes media-treatment --capabilities --json` for the current
machine-readable surface instead of hard-coding an old effect list.
The grading pipeline applies to real `<img>` and `<video>` elements. It does
not grade a complete HTML scene. Use Studio or
[`media-treatment`](/packages/cli#media-treatment) for normal authoring; see
[Color Grading](/guides/color-grading) and [Media Effects](/guides/media-effects)
for the workflow and current SDR/HDR boundary.
## Relative timing
A non-numeric `data-start` refers to the end of another clip in the same
composition:
```html
<video
id="intro"
data-start="0"
data-duration="4"
data-track-index="0"
src="./intro.mp4"
muted
playsinline
></video>
<section
id="result"
class="clip"
data-start="intro + 0.5"
data-duration="3"
data-track-index="0"
>
Result
</section>
```
Supported forms are `clip-id`, `clip-id + seconds`, and
`clip-id - seconds`. References stay within one composition, must resolve to a
known duration, and cannot form a cycle. Put intentional overlaps on different
tracks.
## Nested compositions
The host declares a fixed timeline window:
```html
<div
id="pricing-scene"
data-composition-id="pricing"
data-composition-src="./compositions/pricing.html"
data-start="4"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
The host `data-composition-id` must match the ID inside the source file and its
timeline registry key. The host `data-duration` controls how long the nested
composition remains visible. A shorter inner timeline holds its final state;
a shorter host duration cuts the slot.
A nested composition file transports its live markup through `<template>`.
Styles and scripts needed by that composition must be inside the template:
```html
<template>
<style>
#root {
position: absolute;
inset: 0;
}
</style>
<div
id="root"
data-composition-id="pricing"
data-width="1920"
data-height="1080"
>
<!-- scene content -->
</div>
<script>
window.__timelines = window.__timelines || {};
const timeline = gsap.timeline({ paused: true });
// scene animation
window.__timelines.pricing = timeline;
</script>
</template>
```
HyperFrames seeks nested timelines independently. Do not add a child timeline
manually to the parent GSAP timeline.
## Variables
Declare the schema with `data-composition-variables`, then pass values through a
render or a nested host:
```html
<html
data-composition-variables='[
{"id":"title","type":"string","label":"Title","default":"Hello"}
]'
>
```
```html
<div
data-composition-id="pricing"
data-composition-src="./compositions/pricing.html"
data-variable-values='{"title":"Built for teams"}'
data-start="0"
data-duration="6"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Use `data-var-text`, `data-var-src`, or CSS `var(--variableId)` for direct
bindings. Use `getVariables()` when the value affects logic.
## Animation contract
A composition using GSAP must:
- create one finite timeline with `{ paused: true }`;
- register it synchronously on `window.__timelines`;
- use the same key as `data-composition-id`;
- avoid wall-clock state, unseeded randomness, and infinite repeats.
HyperFrames controls seeking. Composition code describes how the visual state
looks at a given time.
## Validate
```bash
npx hyperframes lint
npx hyperframes check
```
`lint` checks the static contract. `check` opens the project in a browser and
checks runtime behavior, layout, motion, and contrast. Watch representative
snapshots and the finished render as the final visual gate.