All skills
heygen-com avatar

/hyperframes-core

@13bee74
by HeyGenheygen-com/hyperframes55k stars
4,998

The HyperFrames composition contract — build one renderable project. Use for composition structure, the `data-*` timing attributes, `class="clip"`, tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Read before writing composition HTML.

Requires /hyperframes

Use this Skill: https://skilld.dev/gh/heygen-com/hyperframes/hyperframes-core

This session only. Nothing lands on disk.

referencescomposition-patterns.md

≈2.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Composition Patterns

How to architect a project: the index.html orchestrator at scale, and the common sub-composition archetypes. Pair with minimal-composition.md (single-file shape) and sub-compositions.md (mechanics of a sub-comp file).

Two Architectures

Monolithic (single file) Modular (sub-compositions)
Project layout index.html only index.html + compositions/<scene>.html per scene
Where scenes live Inline <section class="clip"> siblings under the root Each scene is a separate file wrapped in <template>
Timeline registration One timeline keyed at the root's data-composition-id Root timeline (often near-empty) + one timeline per sub-comp, each keyed by its id
Routing entry references/minimal-composition.md references/sub-compositions.md

Both architectures use the same runtime contract — data-* attributes + window.__timelines[id]. The choice is structural, not behavioral.

Modular Orchestrator Pattern

When using sub-compositions, index.html should be thin. Its job is to declare slots, lay them out in time, mount the audio track, and register a (usually empty) root timeline. All scene animation lives inside the sub-comps.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
    <style>
      body {
        margin: 0;
        background: #000;
      }
      #root {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      /* Sub-comp slots stretch to fill the root. */
      [data-composition-id="root"] > div[data-composition-src] {
        position: absolute;
        inset: 0;
      }
    </style>
  </head>
  <body>
    <div
      id="root"
      data-composition-id="root"
      data-width="1920"
      data-height="1080"
      data-duration="30"
    >
      <!-- Sequential scenes — each one a sub-composition slot. -->
      <div
        id="el-intro"
        data-composition-id="intro"
        data-composition-src="compositions/intro.html"
        data-start="0"
        data-duration="6"
        data-track-index="1"
      ></div>

      <div
        id="el-body"
        data-composition-id="body"
        data-composition-src="compositions/body.html"
        data-start="6"
        data-duration="18"
        data-track-index="1"
      ></div>

      <div
        id="el-outro"
        data-composition-id="outro"
        data-composition-src="compositions/outro.html"
        data-start="24"
        data-duration="6"
        data-track-index="1"
      ></div>

      <!-- Continuous audio at the root — survives scene cuts. -->
      <audio
        id="el-bgm"
        src="assets/bgm.mp3"
        data-start="0"
        data-duration="30"
        data-track-index="10"
        data-volume="0.6"
      ></audio>
    </div>

    <script>
      window.__timelines["root"] = gsap.timeline({ paused: true });
    </script>
  </body>
</html>

Key properties of this layout:

  • Visual scenes on the same data-track-index (e.g. 1), authored sequentially. For a cross-fade, overlap their times by the fade duration; giving the incoming scene its own track keeps Studio's timeline readable, but the render accepts an overlap either way.
  • Audio on a separate, higher track index (e.g. 10). Keeps the linter's overlap rules clear of any visual collisions.
  • Root timeline is near-empty. All animation lives in the sub-comps. A root-level fade-to-black at the very end is fine; do not stage a parallel animation track from the root.
  • Host slot ids use el-<name> or <scene-id>. The slot's data-composition-id must still equal the sub-comp's internal id (see sub-compositions.md).

Sub-Composition Archetypes

A. Content scene (default)

The sub-comp contains the scene's full DOM, scoped CSS, and timeline. This is the standard pattern in sub-compositions.md — most scenes are this.

B. Host media + main-timeline driver (one pattern for <video>/<audio>)

<video>/<audio> seek and decode at any nesting depth, so a scene-specific clip can live inside its scene's sub-comp with scene-local data-start and be driven by that sub-comp's own timeline. Use this host-media pattern instead when you want the media's motion authored on the main timeline: put the <video>/<audio> as a host-root sibling positioned over the scene's frame.

The reason to reach for it: a sub-comp timeline cannot drive host elements (a global selector or document.querySelector does not resolve across the boundary). So if the media lives at the host root, author its per-scene motion (scale/opacity/morph/tilt/breathing) on the main timeline in index.html, at global time = scene-local time + the scene slot's data-start.

<!-- index.html (host) -->
<div
  id="el-final"
  data-composition-id="final-anim"
  data-composition-src="compositions/final-anim.html"
  data-start="20"
  data-duration="6"
  data-track-index="1"
></div>

<!-- media is a DIRECT root child; sits over the sub-comp's frame -->
<video
  id="final-video"
  class="clip"
  src="assets/final.mp4"
  data-start="20"
  data-duration="6"
  data-track-index="2"
  muted
  playsinline
  style="position:absolute; left:360px; top:100px; width:1200px; height:680px; object-fit:cover; border-radius:24px;"
></video>

<script>
  // MAIN timeline drives the host video. Global time: scene starts at 20.
  const main = window.__timelines["main"];
  main.fromTo(
    "#final-video",
    { scale: 1.4, filter: "blur(14px)" },
    { scale: 1.0, filter: "blur(0px)", duration: 0.9, ease: "power3.out" },
    20,
  ); // = slot data-start (+ any scene-local offset)
</script>

<!-- compositions/final-anim.html — frame/shell only, no <video>, no host-element animation -->
<template>
  <div
    data-composition-id="final-anim"
    data-width="1920"
    data-height="1080"
    data-duration="6"
    style="position:absolute; inset:0; pointer-events:none;"
  >
    <script>
      const tl = gsap.timeline({ paused: true });
      // animate ONLY this sub-comp's own elements here (labels, frame, overlays)
      window.__timelines["final-anim"] = tl;
    </script>
  </div>
</template>

Caveats:

  • In this pattern the media is a host-root child, static in index.html, so the main timeline's selector resolves it. (Media nested in a sub-comp is also driven fine; it just can't be reached by the main timeline's selectors — drive it from the sub-comp's own timeline.)
  • Clip lifecycle owns the media element's visibility across its [data-start, data-start+data-duration] window. The main-timeline opacity/scale tweens compose with it fine; for an opacity reveal/crossfade prefer a host wrapper so you are not fighting the lifecycle on the media element itself.
  • Two media elements sharing the same src + data-start trigger duplicate_media_discovery_risk (benign — both still render).

C. Multi-scene merge

When several beat-level scenes share continuous state — a chat thread that grows, a persistent headline word that carries across the cut, a single canvas with internal phase changes — collapse them into one sub-comp and use internal phase divs rather than multiple sub-comp slots.

<!-- compositions/act2-merged.html -->
<template>
  <div data-composition-id="act2-merged" data-width="1920" data-height="1080" data-duration="9">
    <style>
      [data-composition-id="act2-merged"] .phase {
        position: absolute;
        inset: 0;
        opacity: 0;
      }
    </style>
    <div class="phase" id="phase-a">…</div>
    <div class="phase" id="phase-b">…</div>
    <div class="phase" id="phase-c">…</div>
    <script>
      const tl = gsap.timeline({ paused: true });
      tl.set("#phase-a", { opacity: 1 }, 0);
      tl.to("#phase-a", { opacity: 0, duration: 0.4 }, 3.0);
      tl.set("#phase-b", { opacity: 1 }, 3.0);
      // …
      window.__timelines["act2-merged"] = tl;
    </script>
  </div>
</template>

Reach for this over multiple sequential slots when scenes share DOM, share a canvas, or need to cross-fade with persistent elements (a headline that survives the cut between phases). Each phase is just a div inside the same sub-comp — the parent timeline never has to know about the internal phase boundaries.

D. Audio at root, reactive visual inside

Audio always lives at the host (index.html) as a root-level <audio> so playback survives scene cuts. A sub-comp that visualizes audio should read a pre-baked frequency curve at init, then sample the baked curve from its timeline — the visual must still be a deterministic function of tl.time(), not of audio.currentTime. See determinism-rules.md and hyperframes-creative for the authoring pattern.

Naming Conventions

Thing Convention Example
Sub-comp file compositions/<scene-id>.html compositions/act0-intro-bell.html
Sub-comp <template> id (optional) <scene-id>-template <template id="act0-intro-bell-template">
Sub-comp root data-composition-id <scene-id> (must match host slot) data-composition-id="act0-intro-bell"
Timeline registry key matches data-composition-id window.__timelines["act0-intro-bell"]
Host slot id el-<short> or <scene-id> id="el-intro", id="act0"
Element ids inside a sub-comp prefix with the scene id #act0-bell, #b1-tape
Audio at root data-track-index well above visual tracks 10 while visuals use 1

The -template suffix on <template> is conventional but not required — the runtime extracts contents from whichever <template> is in <body>, regardless of id. The prefix on inner element ids is the only safeguard against id collisions when multiple sub-comps are mounted into the same host page at once.

Source: SKILL.md on GitHub

No alerts2d3 checks · Risk SAFE
  • Gen Agent Trust Hub2d

    The hyperframes-core skill is a technical specification and documentation suite for the HyperFrames rendering framework. It provides guidelines, architectural patterns, and validation rules for building video compositions using HTML, CSS, and GSAP. The analysis found no malicious patterns, unauthorized data access, or suspicious command executions. The skill adheres to standard developer workflows, including the use of well-known CDNs for libraries and ecosystem-specific CLI tools for project validation.

  • Socket2d

    No alerts

  • Snyk2d

    Risk: LOW · No issues

Signed by skilld at 13bee74. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 12 hours ago.

Activeupdated 4 days ago

README badge

README badge for heygen-com/hyperframes/hyperframes-core