optional-skills/creative/hyperframes/references/composition.md
HTML structure, data attributes, timeline contract, and non-negotiable rules.
Standalone index.html — the top-level composition. Does NOT use <template>. Put the data-composition-id div directly in <body>.
<!doctype html>
<html>
<body>
<div
id="stage"
data-composition-id="root"
data-start="0"
data-duration="10"
data-width="1920"
data-height="1080"
>
<!-- clips go here -->
<video id="clip-1" data-start="0" data-duration="5" data-track-index="0" src="intro.mp4" muted playsinline></video>
<audio id="music" data-start="0" data-duration="10" data-track-index="2" data-volume="0.5" src="music.wav"></audio>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.from("#logo", { opacity: 0, y: 40, duration: 0.6 }, 2);
window.__timelines["root"] = tl;
</script>
</body>
</html>
Sub-compositions loaded via data-composition-src DO use <template>:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- content + scoped <style> + <script> with window.__timelines["my-comp"] -->
</div>
</template>
Load from the root: <div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="10" data-track-index="1"></div>
| Attribute | Required | Values |
|---|---|---|
id | Yes | Unique identifier |
data-start | Yes | Seconds, or clip ID reference ("el-1", "intro + 2") |
data-duration | Required for img/div/compositions | Seconds. Video/audio defaults to media duration. |
data-track-index | Yes | Integer. Same-track clips cannot overlap. |
data-media-start | No | Trim offset into source (seconds) |
data-volume | No | 0–1 (default 1) |
data-track-index controls timeline layout only — not visual layering. Use CSS z-index for layering.
| Attribute | Required | Values |
|---|---|---|
data-composition-id | Yes | Unique composition ID |
data-start | Yes | Start time (root composition: "0") |
data-duration | Yes | Takes precedence over GSAP timeline duration |
data-width / data-height | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
data-composition-src | No | Path to external HTML file |
{ paused: true } — the player controls playback.window.__timelines["<composition-id>"] = tl.data-duration, not from the GSAP timeline length.Math.random(), Date.now(), or time-based logic. Use a seeded PRNG (e.g. mulberry32) if you need pseudo-randomness.opacity, x, y, scale, rotation, color, backgroundColor, borderRadius, transforms. Never animate visibility, display, or call video.play()/audio.play().repeat: -1. Infinite-repeat tweens break the capture engine. Compute repeat: Math.ceil(duration / cycleDuration) - 1.async/await, setTimeout, or Promises. The capture engine reads window.__timelines synchronously after page load. Fonts are embedded by the compiler — no need to wait for load.<template> wrapper. Only sub-compositions use <template>.muted playsinline. Audio is always a separate <audio> element — even if it's the same source file..scene-content { width: 100%; height: 100%; padding: Npx; display: flex; flex-direction: column; gap: Npx; box-sizing: border-box }. Absolute-positioned content containers overflow. Reserve position: absolute for decoratives only.Multi-scene compositions MUST follow all of these:
gsap.from(...). No element may appear fully-formed.gsap.to() that animates opacity to 0, y offscreen, etc. The transition IS the exit. Outgoing scene content must be fully visible at the moment the transition starts.gsap.to(..., { opacity: 0 }) is allowed.font-family you want in CSS — the compiler embeds supported fonts automatically. Unsupported fonts produce a compiler warning.crossorigin="anonymous" to external media.window.__hyperframes.fitTextFontSize(text, { maxWidth, fontFamily, fontWeight }).index.html. Sub-compositions reference assets with ../.font-variant-numeric: tabular-nums on number columns. Avoid full-screen linear gradients on dark backgrounds (H.264 banding — use radial or solid + localized glow).t=0).window.__timelines registration.<audio>.data-layer (use data-track-index) or data-end (use data-duration).play/pause/seek on media — framework owns playback.data-composition-id.repeat: -1 on any timeline or tween.gsap.set() on elements from later scenes — they don't exist in the DOM at page load. Use tl.set(selector, vars, timePosition) inside the timeline at or after the clip's data-start. in content text — causes unwanted extra breaks when the text wraps naturally. Use max-width instead. Exception: short display titles (e.g., "THE\nIMMORTAL\nGAME") where each word is deliberately on its own line.