- name
- hyperframes-core
- description
- HyperFrames HTML composition contract. Use for composition structure, data attributes, clips, tracks, sub-compositions, variables, media playback, deterministic render rules, and validation of minimal renderable projects.
# HyperFrames Core
HyperFrames renders video from HTML. A composition is an HTML file whose DOM declares timing with `data-*` attributes, whose animation runtime is seekable, and whose media playback is owned by the framework.
This skill is the **technical contract**. Other concerns live in sibling skills:
- `hyperframes-animation` — atomic motion rules + scene blueprints + per-runtime adapters (GSAP, Lottie, Three, Anime.js, CSS, WAAPI, TypeGPU)
- `hyperframes-creative` — palettes, typography, narration, beat planning, audio-reactive
- `hyperframes-media` — TTS, Whisper transcribe, background removal, captions
- `hyperframes-registry` — install blocks and components (`hyperframes add`)
- `hyperframes-cli` — dev loop commands (lint / validate / preview / render)
For **Tailwind v4 projects** (`hyperframes init --tailwind`), see `references/tailwind.md` — the browser-runtime contract is distinct from Studio's Tailwind v3 setup.
## Routing
| Want to… | Read |
| ------------------------------------------------------------------------------------------ | ------------------------------------ |
| See a minimal renderable composition | `references/minimal-composition.md` |
| Decide between monolithic single-file and modular sub-comp architecture | `references/composition-patterns.md` |
| Structure a modular `index.html` (orchestrator + slots + root audio) | `references/composition-patterns.md` |
| Pick a sub-comp archetype (content / driver-only / multi-scene merge / audio-reactive) | `references/composition-patterns.md` |
| Look up a `data-*` attribute (root, clip, sub-comp host, legacy aliases) | `references/data-attributes.md` |
| Use `class="clip"` correctly | `references/data-attributes.md` |
| Pick a `data-track-index`; same-track overlap; track-index vs CSS `z-index` | `references/tracks-and-clips.md` |
| Time a clip relative to another (`data-start="intro + 2"`, crossfade overlap, chains) | `references/tracks-and-clips.md` |
| Wire a sub-composition (host attributes, `<template>` wrapper, per-instance variables) | `references/sub-compositions.md` |
| Animate inside a sub-composition (`gsap.fromTo` over `gsap.from`, seek-back behavior) | `references/sub-compositions.md` |
| Declare variables (types, extra options, defaults, `--strict-variables` in CI) | `references/variables-and-media.md` |
| Place `<video>` / `<audio>`, set volume, trim with `data-media-start` | `references/variables-and-media.md` |
| Build a seekable timeline (paused, sync construction, `gsap.set` later-scene trap) | `references/determinism-rules.md` |
| Avoid non-deterministic state (clocks, `Math.random`, `repeat: -1`, finite repeat formula) | `references/determinism-rules.md` |
| Know what can / cannot be animated (visual-property allowlist; not `display`/`visibility`) | `references/determinism-rules.md` |
| Fit text and prevent overflow (`fitTextFontSize` signature, `<br>` rule, layout contract) | `references/determinism-rules.md` |
| Author full-frame motion with shared backgrounds | `references/full-screen-motion.md` |
| Work in a Tailwind v4 project (`init --tailwind`) | `references/tailwind.md` |
For animation runtime specifics (GSAP API, Lottie, Three.js, etc.) go to `hyperframes-animation` → `adapters/<runtime>.md`.
## Composition Structure
Two root forms; they are **not** interchangeable.
- **Standalone** (the top-level `index.html`) — root `<div data-composition-id="…">` sits directly in `<body>`. **No `<template>` wrapper.** Wrapping a standalone root in `<template>` hides all content from the browser and breaks rendering.
- **Sub-composition** (a file loaded via `data-composition-src`) — root `<div data-composition-id="…">` **must** be wrapped in `<template>`. Without the wrapper the runtime cannot extract and mount it.
> ⚠ Sub-composition transport rule: the runtime **only clones `<template>` contents** into the live DOM. Everything outside the template — including `<head>` and any `<style>`/`<script>`/`<link>` that lives in `<head>` — is discarded. Put `<style>` and `<script>` blocks **inside** `<template>`, not in `<head>`.
>
> ⚠ Host-id rule: in the host file, `data-composition-id` on the slot must **exactly equal** the inner template's `data-composition-id` **and** the `window.__timelines["<id>"]` key. Do not add `-mount` / `-slot` / `-host` suffixes.
See `references/sub-compositions.md` for the file shape, host wiring, pitfall examples, and the pre-render verification checklist.
### Root must be sized (silent layout bug)
The standalone root must establish an explicitly **sized box**, and every ancestor between it and a `height: 100%` element must have a resolved height. If the root or an intermediate wrapper has no height, a flex/`height:100%` content container collapses to ~0 and content piles into the **top-left corner** (often clipping the first glyph at x=0). `lint`, `validate`, and `inspect` do **not** catch this — `inspect` substitutes the authored `data-width`/`data-height` for a collapsed root and reports "0 layout issues."
```html
<body style="margin: 0">
<div
id="root"
data-composition-id="main"
data-width="1920"
data-height="1080"
data-duration="5"
style="position: relative; width: 1920px; height: 1080px; overflow: hidden"
>
<!-- Center robustly: position:absolute + inset:0 fills the sized root regardless of
intermediate wrappers; or use a flex container ONLY if its parent chain is sized. -->
<section
class="clip"
data-start="0"
data-duration="5"
data-track-index="1"
style="position: absolute; inset: 0; display: grid; place-items: center; padding: 120px 160px; box-sizing: border-box"
>
<h1>Title</h1>
</section>
</div>
</body>
```
Keep the `padding` (≥80px) on the centering container — it is the title-safe margin that stops large type touching the frame edge. See `references/minimal-composition.md`.
## Timeline Contract (GSAP default)
Every composition registers exactly one GSAP timeline.
- Create with `gsap.timeline({ paused: true })` — the player owns playback.
- Register at `window.__timelines["<composition-id>"]`; the key **must exactly match** the root's `data-composition-id`. Dot syntax (`window.__timelines.<id> = tl`) is equivalent when the id is a valid identifier; use brackets if the id contains `-`.
- Build the timeline **synchronously** during page load — not inside `async`, `setTimeout`, `Promise`, or event handlers. The renderer samples after page load completes; any deferred timeline construction misses the sample.
- Render duration comes from `data-duration` on the root, **not** from GSAP timeline length. Do not pad the timeline with empty tweens to set duration.
- For sub-compositions, do **not** manually nest sub-timelines into the host (`master.add(sub)`); the framework drives them independently.
For non-GSAP runtimes (Lottie / Three / WAAPI / CSS / Anime.js / TypeGPU), the equivalent contract lives in `hyperframes-animation/adapters/<runtime>.md`. See `references/determinism-rules.md` for the cross-runtime Animation Runtime Contract.
## Non-Negotiable Rules
These break the renderer — or produce **silent visual bugs that `lint`/`validate`/`inspect` do NOT catch** (rules 3, 7-8). (Synchronous timeline construction is covered above in **Timeline Contract**.)
1. No `Math.random()` / `Date.now()` / `performance.now()` driving visuals — use a seeded PRNG.
2. No `repeat: -1`. Use `repeat: Math.max(0, Math.floor(duration / cycleDuration) - 1)` — **`floor`, not `ceil`** (`ceil` overshoots `data-duration` and trips the `gsap_repeat_ceil_overshoot` lint; `max(0, …)` avoids a negative repeat = infinite).
3. `<video>`/`<audio>` must be a **direct child of the host root** (`index.html`) — never inside a sub-comp `<template>`, never inside a wrapper `<div>`; otherwise it is never decoded and renders blank/black (silent). The framework owns playback: no `video.play()` / `audio.play()` / `currentTime = …`. A sub-comp **cannot** drive host elements (selector or `querySelector`), so animate host media from the **main timeline** at global time. See `references/variables-and-media.md`.
4. No `gsap.set()` on clip elements from later scenes (they are not in the DOM yet). Use `tl.set(selector, vars, time)` at or after the clip's `data-start`.
5. No animating `display` / `visibility`. Animate `opacity` / transforms; the clip lifecycle handles show/hide.
6. No `<br>` in body text. Let text wrap via `max-width`.
7. Transformed elements must be block-level + sized. `transform`/`scaleX`/`scaleY` is a **no-op on an inline `<span>`**, and scaling an auto-width (0px) element shows nothing → invisible bars/fills. Give them `display: block`/`inline-block`/flex-item **and** a real `width`/`height` (e.g. `width: 100%` inside a sized parent).
8. Absolutely-positioned decoratives (badges, pills, tags) that **pulse or overshoot** (`yoyo` scale, `back.out`) need clearance at their **peak** size and must not straddle an `overflow: hidden` edge — else they overlap a neighbor or get clipped. Position for the largest frame, not the resting one.
## Editing Existing Compositions
- Read the actual files before editing.
- Preserve unrelated timing, tracks, IDs, variables, and media paths.
- Match existing composition IDs and timeline registry keys.
- When adding a clip, choose a non-overlapping `data-track-index` or intentionally adjust surrounding timing.
- When adding a sub-composition, verify its internal `data-composition-id` before wiring the host.
## Validation
Use `hyperframes-cli` for command details.
- [ ] `npx hyperframes lint` passes (0 errors)
- [ ] `npx hyperframes validate` passes (0 console errors)
- [ ] `npx hyperframes inspect` passes, or overflow is intentionally marked
- [ ] Projects with sub-compositions: `npx hyperframes snapshot --at <midpoints>` and eyeball each frame
Auf GitHub ansehen