Skill Tracer
Skills › HyperFrames › hyperframes › format.md

format.md

159 lines · skills/hyperframes/references/format.md at a595755

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Composition format
2
3The detail behind the contract in `SKILL.md`. Full references: `npx hyperframes docs compositions`, `docs data-attributes`, `docs troubleshooting`.
4

Composition format

The detail behind the contract in SKILL.md. Full references: npx hyperframes docs compositions, docs data-attributes, docs troubleshooting.

Raw lines 1 to 4
5## Shape of a project
6
7```html
8<!-- index.html: a normal document; declares variables, mounts scenes -->
9<html
10 data-composition-variables='[{"id":"headline","type":"string","label":"Headline","default":"…"}]'
12 <body>
13 <div
14 id="root"
15 data-composition-id="main"
16 data-start="0"
17 data-duration="12"
18 data-width="1920"
19 data-height="1080"
20 >
21 <div
22 id="intro"
23 data-composition-id="intro"
24 data-composition-src="compositions/intro.html"
25 data-start="0"
26 data-duration="4"
27 data-track-index="1"
28 data-width="1920"
29 data-height="1080"
30 ></div>
31 </div>
32 <script>
33 window.__timelines["main"] = gsap.timeline({ paused: true });
34 </script>
35 </body>
36</html>
37```
39```html
40<!-- compositions/intro.html: everything inside <template> -->
41<template>
42 <style>
43 #root {
44 position: absolute;
45 inset: 0;
46 }
47 </style>
48 <div id="root" data-composition-id="intro" data-width="1920" data-height="1080">
49 <h1 id="headline" class="clip" data-start="0.4" data-duration="3" data-var-text="headline">
50 …
51 </h1>
52 </div>
53 <script>
54 const tl = gsap.timeline({ paused: true });
55 tl.fromTo(
56 "#headline",
57 { opacity: 0, filter: "blur(8px)" },
58 { opacity: 1, filter: "blur(0px)", duration: 0.18, ease: "power4.out" },
59 0.4,
60 );
61 window.__timelines["intro"] = tl;
62 </script>
63</template>
64```
66- Give the host, the scene root and the timeline key the same id. A host id that differs from its scene's makes each render wait 45 s per scene, then show static frames. Styles and scripts outside `<template>` are dropped.
67- Size the scene root with `inset: 0`, never hard-coded pixels; the canvas is `data-width` × `data-height`.
68- Keep ids unique across the assembled page (prefix them: `#intro-title`). A scene's timeline can't reach elements outside its file.
69- Use `fromTo`, not `from`, inside a scene: the host re-seeks the scene each time it shows, and `from` records its start state once, so it drifts on seek-back.
70- Never `add()` a scene's timeline to the root's; the runtime drives each scene itself, and nesting seeks it twice.

Shape of a project

<!-- index.html: a normal document; declares variables, mounts scenes -->
<html
  data-composition-variables='[{"id":"headline","type":"string","label":"Headline","default":"…"}]'
>
  <body>
    <div
      id="root"
      data-composition-id="main"
      data-start="0"
      data-duration="12"
      data-width="1920"
      data-height="1080"
    >
      <div
        id="intro"
        data-composition-id="intro"
        data-composition-src="compositions/intro.html"
        data-start="0"
        data-duration="4"
        data-track-index="1"
        data-width="1920"
        data-height="1080"
      ></div>
    </div>
    <script>
      window.__timelines["main"] = gsap.timeline({ paused: true });
    </script>
  </body>
</html>
<!-- compositions/intro.html: everything inside <template> -->
<template>
  <style>
    #root {
      position: absolute;
      inset: 0;
    }
  </style>
  <div id="root" data-composition-id="intro" data-width="1920" data-height="1080">
    <h1 id="headline" class="clip" data-start="0.4" data-duration="3" data-var-text="headline">
      …
    </h1>
  </div>
  <script>
    const tl = gsap.timeline({ paused: true });
    tl.fromTo(
      "#headline",
      { opacity: 0, filter: "blur(8px)" },
      { opacity: 1, filter: "blur(0px)", duration: 0.18, ease: "power4.out" },
      0.4,
    );
    window.__timelines["intro"] = tl;
  </script>
</template>
  • Give the host, the scene root and the timeline key the same id. A host id that differs from its scene's makes each render wait 45 s per scene, then show static frames. Styles and scripts outside <template> are dropped.
  • Size the scene root with inset: 0, never hard-coded pixels; the canvas is data-width × data-height.
  • Keep ids unique across the assembled page (prefix them: #intro-title). A scene's timeline can't reach elements outside its file.
  • Use fromTo, not from, inside a scene: the host re-seeks the scene each time it shows, and from records its start state once, so it drifts on seek-back.
  • Never add() a scene's timeline to the root's; the runtime drives each scene itself, and nesting seeks it twice.
Raw lines 5 to 71
72## Attributes you'll use most
74| Attribute | Meaning |
75| ---------------------------------------------- | --------------------------------------------------------------------------------------------------- |
76| `data-start`, `data-duration` | Seconds, local to the file that declares the element |
77| `data-track-index` | Studio lane only; paint order is CSS `z-index` |
78| `data-track-kind` | `graphics` or `captions` on scene hosts (video and audio come from the tag) |
79| `data-composition-src`, `data-variable-values` | Mount a scene file; per-mount variable values (a JSON object) |
80| `data-media-start` | Offset into the source media (a trim) |
81| `data-playback-rate` | Constant speed, 0.1–10 |
82| `data-has-audio="true"` | A `<video>` keeps its own sound; a timed `<video>` with neither this nor `muted` fails lint |
83| `data-volume`, `data-fade-in`, `data-fade-out` | Gain (1 = 0 dB, up to 3.98 = +12 dB) and edge fades; envelopes use `data-automation` (`/media-use`) |
84| `data-link`, `data-sync-origin` | A video and audio edited as one (see Linked clips) |
85| `data-hidden` | Hidden in preview and render; reversible |
86| `data-no-timeline` | On the root of a still composition: no timeline to wait for |

Attributes you'll use most

AttributeMeaning
data-start, data-durationSeconds, local to the file that declares the element
data-track-indexStudio lane only; paint order is CSS z-index
data-track-kindgraphics or captions on scene hosts (video and audio come from the tag)
data-composition-src, data-variable-valuesMount a scene file; per-mount variable values (a JSON object)
data-media-startOffset into the source media (a trim)
data-playback-rateConstant speed, 0.1–10
data-has-audio="true"A <video> keeps its own sound; a timed <video> with neither this nor muted fails lint
data-volume, data-fade-in, data-fade-outGain (1 = 0 dB, up to 3.98 = +12 dB) and edge fades; envelopes use data-automation (/media-use)
data-link, data-sync-originA video and audio edited as one (see Linked clips)
data-hiddenHidden in preview and render; reversible
data-no-timelineOn the root of a still composition: no timeline to wait for
Raw lines 72 to 87
Names a skill media-use :83
88## Variables
90- Declare an array of `{id, type, label, default}`: the video's own on `index.html`'s `<html>`, a scene or block file's on that file's `<html>` (or root element), where they stay inside each mount. Types: `string` (`placeholder`, `maxLength`), `number` (`min`, `max`, `step`, `unit`), `color`, `boolean`, `enum` (`options: [{value, label}]`).
91- Bind without script: `data-var-text="id"`, `data-var-src="id"` (keep a real fallback `src` on media with audio), `var(--id)` in CSS; for logic, `window.__hyperframes.getVariables()` once at init.
92- Values are an object keyed by id. A host's `data-variable-values` sets one mount; Studio's Variables panel and batch renders set only the root's variables, so to vary a block, change its host's values. Variables can't change the root's `data-duration`.

Variables

  • Declare an array of {id, type, label, default}: the video's own on index.html's <html>, a scene or block file's on that file's <html> (or root element), where they stay inside each mount. Types: string (placeholder, maxLength), number (min, max, step, unit), color, boolean, enum (options: [{value, label}]).
  • Bind without script: data-var-text="id", data-var-src="id" (keep a real fallback src on media with audio), var(--id) in CSS; for logic, window.__hyperframes.getVariables() once at init.
  • Values are an object keyed by id. A host's data-variable-values sets one mount; Studio's Variables panel and batch renders set only the root's variables, so to vary a block, change its host's values. Variables can't change the root's data-duration.
Raw lines 88 to 93
94## Fonts
96- Bundled, so they resolve by name: Inter, Montserrat, Outfit, Nunito, Oswald, League Gothic, Archivo Black, Space Mono, IBM Plex Mono, JetBrains Mono, EB Garamond, Playfair Display, Source Code Pro, Noto Sans JP, Roboto, Open Sans, Lato, Poppins. Common system fonts (Helvetica, Arial, Times, Menlo and the like) are swapped for the nearest of these.
97- The bundle only has Latin text at a few weights per family (Inter: 400, 700, 900). The compiler still fetches each of these families from Google Fonts for the other weights and scripts, and caches the result in `~/.cache/hyperframes/fonts`.
98- Any other family needs `@font-face` in the file that uses it, or lint fails `font_family_without_font_face`: `src: url('assets/fonts/x.woff2')` for a file in the project (a capture's own fonts, or one you download there), A `local('Exact Name')`-only face draws the font installed on this machine; a cloud render has no copy of it (lint warns `font_face_local_only`), so ship the file instead when the video may render in the cloud.

Fonts

  • Bundled, so they resolve by name: Inter, Montserrat, Outfit, Nunito, Oswald, League Gothic, Archivo Black, Space Mono, IBM Plex Mono, JetBrains Mono, EB Garamond, Playfair Display, Source Code Pro, Noto Sans JP, Roboto, Open Sans, Lato, Poppins. Common system fonts (Helvetica, Arial, Times, Menlo and the like) are swapped for the nearest of these.
  • The bundle only has Latin text at a few weights per family (Inter: 400, 700, 900). The compiler still fetches each of these families from Google Fonts for the other weights and scripts, and caches the result in ~/.cache/hyperframes/fonts.
  • Any other family needs @font-face in the file that uses it, or lint fails font_family_without_font_face: src: url('assets/fonts/x.woff2') for a file in the project (a capture's own fonts, or one you download there), A local('Exact Name')-only face draws the font installed on this machine; a cloud render has no copy of it (lint warns font_face_local_only), so ship the file instead when the video may render in the cloud.
Raw lines 94 to 99
100## Timing and the timeline
102- A clip shows over `[start, start + duration)`: an end state landing exactly on its end is never rendered.
103- Only a composition's root timeline is paused; timelines nested inside it are not. Never `tl.play()`.
104- A scene's clock starts at 0 at its host's `data-start`; media `data-start` is local to its composition. `<video>` / `<audio>` without `data-duration` play their full length; an `<img>` without one lasts 3 s.
105- A host's `data-duration` is the scene's slot: a shorter scene timeline holds its last frame, and a slot shorter than the video goes blank when it ends.
106- Root children with `data-start` are laid out full-frame by the runtime; an untimed full-bleed background needs `position: absolute; inset: 0` or it collapses to nothing.
107- A fill on the composition root is dropped when the video has shader transitions or HDR media; put it on a full-bleed child.
108- `data-start="intro + 0.5"` starts after `intro` ends (spaces required). A typo, a missing id or a cycle silently resolves to 0, so snapshot what you time this way.
109- A clip nested inside another timed element counts its `data-start` from the composition, not from that element, and shows only while that element shows. `timeline` lists it under its parent and warns `never visible` when the windows miss (`lint`: `nested_clip_never_visible`).
110- `npx hyperframes timeline --json` lists every clip by kind with `absStart`, `absEnd`, `file`, `src`, `playbackRate`, `nested`, `host` and `durationSource`: read it instead of opening every file. It reads one level of nesting (a deeper scene shows `children=unread`); `laneError` means a `data-automation` or `data-fx-chain` that doesn't parse.

Timing and the timeline

  • A clip shows over [start, start + duration): an end state landing exactly on its end is never rendered.
  • Only a composition's root timeline is paused; timelines nested inside it are not. Never tl.play().
  • A scene's clock starts at 0 at its host's data-start; media data-start is local to its composition. <video> / <audio> without data-duration play their full length; an <img> without one lasts 3 s.
  • A host's data-duration is the scene's slot: a shorter scene timeline holds its last frame, and a slot shorter than the video goes blank when it ends.
  • Root children with data-start are laid out full-frame by the runtime; an untimed full-bleed background needs position: absolute; inset: 0 or it collapses to nothing.
  • A fill on the composition root is dropped when the video has shader transitions or HDR media; put it on a full-bleed child.
  • data-start="intro + 0.5" starts after intro ends (spaces required). A typo, a missing id or a cycle silently resolves to 0, so snapshot what you time this way.
  • A clip nested inside another timed element counts its data-start from the composition, not from that element, and shows only while that element shows. timeline lists it under its parent and warns never visible when the windows miss (lint: nested_clip_never_visible).
  • npx hyperframes timeline --json lists every clip by kind with absStart, absEnd, file, src, playbackRate, nested, host and durationSource: read it instead of opening every file. It reads one level of nesting (a deeper scene shows children=unread); laneError means a data-automation or data-fx-chain that doesn't parse.
Raw lines 100 to 111
112## Adding media
114Write what a Studio drop writes, so the clip behaves like one the person dropped:
116- `id`, `class="clip"`, `data-start` (the playhead or the requested time, never a silent 0) and `data-track-index`. Video with sound: `playsinline data-has-audio="true"`; silent footage: `muted playsinline`. Audio: `data-volume="1"`.
117- Images and video fill the frame: `position: absolute; left: 0; top: 0`, width and height equal to the canvas, `object-fit: contain`.
118- `z-index` is the number of top-level clips already in that file plus one.
119- Raise the root's `data-duration` to cover the new clip's end, or it never plays.

Adding media

Write what a Studio drop writes, so the clip behaves like one the person dropped:

  • id, class="clip", data-start (the playhead or the requested time, never a silent 0) and data-track-index. Video with sound: playsinline data-has-audio="true"; silent footage: muted playsinline. Audio: data-volume="1".
  • Images and video fill the frame: position: absolute; left: 0; top: 0, width and height equal to the canvas, object-fit: contain.
  • z-index is the number of top-level clips already in that file plus one.
  • Raise the root's data-duration to cover the new clip's end, or it never plays.
Raw lines 112 to 120
121## Linked clips
123Studio's Detach audio writes a muted `<video>` and an `<audio>` of the same file sharing `data-link` and `data-sync-origin`; Studio moves, trims, splits and deletes them as one.
125- Prefer one `<video data-has-audio="true">`; link only when the sound needs its own track, volume or effects.
126- Every member keeps the same `data-start`, `data-duration`, `data-media-start` and `data-playback-rate`; retime them all together.
127- Unlink by removing `data-link` from every member. Leave `data-sync-origin` alone while they are still one source.

Linked clips

Studio's Detach audio writes a muted <video> and an <audio> of the same file sharing data-link and data-sync-origin; Studio moves, trims, splits and deletes them as one.

  • Prefer one <video data-has-audio="true">; link only when the sound needs its own track, volume or effects.
  • Every member keeps the same data-start, data-duration, data-media-start and data-playback-rate; retime them all together.
  • Unlink by removing data-link from every member. Leave data-sync-origin alone while they are still one source.
Raw lines 121 to 128
129## Layout audit opt-outs
131`check` measures boxes at sampled times; `overflow: hidden` does not silence a finding. Use the narrowest opt-out:
133| Attribute | Silences |
134| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
135| `data-layout-allow-overflow` | Overflow on the element and every descendant, including text-clipping checks: keep it on the smallest decorative wrapper |
136| `data-layout-bleed="true"` | One intentional crop of primary text |
137| `data-layout-allow-overlap` | Deliberate text layering, on that block only (not inherited) |
138| `data-layout-allow-occlusion` | An element meant to cover text |
139| `data-layout-allow-caption-zone` | Intentional copy in the caption band |
140| `data-layout-ignore` | A decorative element never audited |

Layout audit opt-outs

check measures boxes at sampled times; overflow: hidden does not silence a finding. Use the narrowest opt-out:

AttributeSilences
data-layout-allow-overflowOverflow on the element and every descendant, including text-clipping checks: keep it on the smallest decorative wrapper
data-layout-bleed="true"One intentional crop of primary text
data-layout-allow-overlapDeliberate text layering, on that block only (not inherited)
data-layout-allow-occlusionAn element meant to cover text
data-layout-allow-caption-zoneIntentional copy in the caption band
data-layout-ignoreA decorative element never audited
Raw lines 129 to 141
142## Lint errors you'll meet first
144| Rule | Fix |
145| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
146| `video_missing_muted` | A timed `<video>` gets `data-has-audio="true"` or `muted` |
147| `gsap_css_transform_conflict` | Set the start inside the tween (`gsap.fromTo`) instead of a CSS `transform` |
148| `gsap_animates_clip_element` | Animate a child of the `.clip`, or its `opacity` |
149| `video_nested_in_timed_element` | Time the `<video>`; its wrapper stays untimed and gives it no timing |
150| `media_missing_id`, `media_crossorigin_breaks_preview` | Give every `<audio>` an `id`; drop `crossorigin` |
151| `gsap_timeline_registered_before_async_build` | Register the timeline at the end of the async callback |
152| `standalone_composition_wrapped_in_template` | Only scene files use `<template>` |
153| `subcomposition_linked_style_names_root_id` | A linked sheet cannot style a scene's root by id: use an inline `<style>`, or select a child |
154| `subcomposition_root_styled_by_class` | Style a scene's root as `#root` |
155| `subcomposition_blanks_before_host` | Make the scene's slot span the time it should show |
156| `clip_ends_past_root_duration` | Raise the root's `data-duration` |
157| `linked_clips_out_of_sync`, `linked_clip_orphan` | Retime every linked member together; unlink from all of them |
158| `sweep_static` | A 3 s+ video with no visible change: spread the motion, or mark a still composition `data-no-timeline` |
159| `missing_data_no_timeline` | A root or host with no timeline: add `data-no-timeline`, or every render waits 45 s |

Lint errors you'll meet first

RuleFix
video_missing_mutedA timed <video> gets data-has-audio="true" or muted
gsap_css_transform_conflictSet the start inside the tween (gsap.fromTo) instead of a CSS transform
gsap_animates_clip_elementAnimate a child of the .clip, or its opacity
video_nested_in_timed_elementTime the <video>; its wrapper stays untimed and gives it no timing
media_missing_id, media_crossorigin_breaks_previewGive every <audio> an id; drop crossorigin
gsap_timeline_registered_before_async_buildRegister the timeline at the end of the async callback
standalone_composition_wrapped_in_templateOnly scene files use <template>
subcomposition_linked_style_names_root_idA linked sheet cannot style a scene's root by id: use an inline <style>, or select a child
subcomposition_root_styled_by_classStyle a scene's root as #root
subcomposition_blanks_before_hostMake the scene's slot span the time it should show
clip_ends_past_root_durationRaise the root's data-duration
linked_clips_out_of_sync, linked_clip_orphanRetime every linked member together; unlink from all of them
sweep_staticA 3 s+ video with no visible change: spread the motion, or mark a still composition data-no-timeline
missing_data_no_timelineA root or host with no timeline: add data-no-timeline, or every render waits 45 s
Raw lines 142 to 159