HyperFrames Core
Activated Cloud✓ Officialactivated/hyperframes-core
Free · Apache-2.0
About
The HyperFrames composition contract: how to build one renderable HTML-to-video project. Covers root and clip data attributes, the clip class, tracks, sub-compositions, variables, framework-owned media, deterministic rendering, editing recipes and validation. Read it before you write or edit composition HTML. Not for motion recipes (hyperframes-animation), zoom and camera keyframes (hyperframes-keyframes), CLI commands (hyperframes-cli), the end-to-end workflow and brief (hyperframes) or timeline layout conventions (hyperframes-studio).
Documentation
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 for building one HyperFrames project: the same time always gives the same frame, and lint and check finish with zero findings. The body is the build guide; per-topic detail lives in references/, read on demand.
When to use
- "Make a 15 second title card / promo / explainer in HyperFrames": before you write the first line of composition HTML.
- "Add this clip, image or music", "cut, trim, reorder, slow down, freeze, crossfade or swap this file".
- "Split this into scenes", "reuse this scene with different text", "make the title a variable".
lintorcheckreports a code you do not recognise, or a render comes out blank, frozen, silent or unstyled.- Opening an existing HyperFrames project to change it.
Neighbours, loaded with skill_view when the task needs them: hyperframes (the end-to-end workflow: brief, storyboard, production and review), hyperframes-animation (motion rules, blueprints, runtime adapters), hyperframes-keyframes (zooms, punch-ins, camera moves), hyperframes-cli (commands and render flags), hyperframes-studio (timeline layout and safe zones), hyperframes-audio (mixing placed audio), media-use (sourcing voice, music, images).
What you need
- Your computer set up: run the check in
references/computer-setup.mdfirst, every job. Node 22+ and FFmpeg are not installed by default, and installs outside/home/uservanish when the computer is rebuilt. Every CLI call in this skill assumes this line first:export PATH="$HOME/.local/node/bin:$PATH" HYPERFRAMES_NO_TELEMETRY=1 HYPERFRAMES_SKIP_SKILLS=1 - The project in
~/Desktop/<your name> - Work space/<job>/<project>/, scaffolded withnpx hyperframes init <project> --non-interactive(add--tailwindonly for Tailwind work). - Canvas size (
1920x1080,1080x1920,1080x1080) and length in seconds from the brief. If the brief does not say, ask withclarify. - Media already inside
assets/: voice fromtext_to_speech, pictures fromimage_generate, music and SFX only with a licence recorded inassets/LICENSES.md.
File in references/ |
Read it to |
|---|---|
minimal-composition.md |
start from the smallest renderable composition skeleton |
composition-patterns.md |
choose monolithic or modular; structure a modular index.html; pick a sub-composition archetype |
data-attributes.md |
look up any data-* (root, clip, sub-composition host, legacy aliases); use class="clip" |
tracks-and-clips.md |
see what data-track-index does and does not control, z-index, relative clip timing, npx hyperframes timeline |
creator-editing-recipes.md |
copy cut, trim, reorder, retime, freeze, camera, mask, crossfade and audio recipes, with their limits |
sub-compositions.md |
wire a sub-composition (host attributes, <template>, per-instance variables) and animate inside it |
variables-and-media.md |
declare variables; place <video> and <audio>, set volume, trim |
determinism-rules.md |
build a seekable timeline; determinism bans; layout and text fit |
full-screen-motion.md |
author full-frame motion with a shared background |
tailwind.md |
work in a Tailwind v4 project (init --tailwind; its runtime contract differs from Studio's v3) |
computer-setup.md |
install and check Node, FFmpeg and the render browser |
For animation runtime specifics (GSAP API, Lottie, Three.js and the rest), use hyperframes-animation and its references/adapters/<runtime>.md.
Rules
Two root forms (not interchangeable)
- Standalone (top-level
index.html): the root<div data-composition-id="…">sits directly in<body>, with no<template>wrapper. Wrapping a standalone root hides all content, andlintrejects it (standalone_composition_wrapped_in_template, error). - Sub-composition (loaded via
data-composition-src): wrap the root in<template>. This is the shape to write: the loader also accepts a plain full document and falls back to its<body>, but the templated form is what the examples and tooling assume. - Warning, transport rule: for a templated sub-composition the assembler drops the file's own
<head><style>and<script>(thehasTemplategate inpackages/core/src/compiler/compositionAssembly.ts, in the open-source HyperFrames repository), so put<style>and<script>inside the template.<link>is hoisted either way. - Warning, host-id convention: give the host slot, the inner template root and the
window.__timelines["<id>"]key the same id. A different local id is supported (the assembler falls back to the first root in the file), but the mismatch is silent and can leave a scene frozen at render (Pitfall 2 inreferences/sub-compositions.md), so match them unless you have a reason not to.
File shape, host wiring and the pre-render checklist: references/sub-compositions.md.
Root must be sized (silent layout bug)
The standalone root authors width and height: 100%. Canvas size is data-width and data-height; the runtime stamps those pixels onto the composition root. Do not hardcode 1920px or 1080px on #root. Skeleton: references/minimal-composition.md.
One paused timeline
Each composition registers exactly one gsap.timeline({ paused: true }) at window.__timelines["<id>"], keyed by the root data-composition-id. Building it inside an async callback (document.fonts.ready) is supported; what matters is that you register only after the build completes. Render length is the root's data-duration, not the timeline's length: a timeline that runs past it is cut off, and one that ends early holds its last frame. Omit the root data-duration and the length is inferred instead (timeline, media window, or adapter). You do not need window.__timelines = window.__timelines || {}: the runtime creates the registry before your inline scripts run, and lint no longer asks for it. Do not nest sub-timelines into the host by hand; the runtime auto-nests registered child timelines. Full contract, including non-GSAP runtimes: references/determinism-rules.md and the adapters in hyperframes-animation.
First-pass lint gotchas (a guaranteed first build failure)
lint does catch these, but only after the fact. Write them right the first time:
- Never pair a CSS initial
transformwith a GSAP tween on the same property: the CSS value and the tween's start fight, andlintrejects it withgsap_css_transform_conflict. Set the initial state inside the tween withgsap.fromTo(el, { x: -40 }, { x: 0 })instead of a CSStransform: translateX(-40px). - Never put
crossoriginon<video>or<audio>.lintrejects it unconditionally withmedia_crossorigin_breaks_preview(error), including for canvas, WebGL and WebAudio readback. There is no suppression. - Never give a
<video data-start>an ancestor that also carriesdata-start.lintrejects it withvideo_nested_in_timed_element(error). Time the wrapper or the video, not both. - Every
<audio>needs anid.lintrejects it withmedia_missing_id, and an id-less<audio>is never picked up by the mixer, so the render is silent. - Never tween a
.clipwithautoAlphaorvisibility:lintrejects it withgsap_animates_clip_element. Animate a child instead. - A named CSS
font-familyneeds an in-file@font-facepointing at a font file shipped in the project, orlintfiresfont_family_without_font_face. Exempt (CLI 0.8.111): the families the CLI bundles and substitutes itself (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) and common system names it maps onto them, soArialorHelveticarenders as Inter andGeorgiaas EB Garamond. For anything else, ship the file (references/computer-setup.md, section 9). - Sub-composition
#rootuseswidthandheight: 100%(orinset: 0), not hardcoded1920pxand1080px. Canvas size isdata-widthanddata-height. - A root-level clip holding block-level children (a
<section class="clip">with an<h1>inside) warnsnested_structure_needs_subcomposition, an error inside Studio. Make the visible element the clip, or move the scene into a sub-composition (references/composition-patterns.md).
A lint error also switches off the layout and contrast audits: check then reports 0 sample(s) and 0/0 text checks, which reads like a clean file but means nothing ran. Clear lint errors before you trust those numbers.
Non-negotiables (silent bugs automated gates may miss)
- No render-time clocks, unseeded
Math.random, network or input state.repeat: -1only under a finite rootdata-duration(export clips to it); otherwise use a finite count. Seereferences/determinism-rules.md. - Never tween
display,visibilityorautoAlphaon a.clipelement. The framework owns clip visibility, andlintrejects it (gsap_animates_clip_element). Animate a child instead. - No
<br>in body text; transformed elements must be block-level and sized; pulsing absolute decoratives need clearance at their peak. Seereferences/determinism-rules.md. <video>and<audio>are found by a flat document query, so the framework seeks and decodes them at any nesting depth (including inside a sub-composition<template>or a wrapper). One hard limit:linterrors if a<video data-start>sits inside another plain element that also hasdata-start, and the failure is real (wrong source frames, then the clip vanishes mid-slot), so put the timing on the wrapper or on the video, never both. Sub-composition hosts are exempt: media inside a sub-composition renders correctly. The other caveat is about timelines, not placement: a sub-composition timeline cannot animate host-root elements. Seereferences/variables-and-media.md.- Keep every
idunique across the assembled page (prefix sub-composition ids with the composition id,#<id>-hero) so your own#idCSS andgetElementByIdcalls resolve. Frame injection no longer depends on it: the compiler stamps a document-uniquedata-hf-render-idon everyvideo[src],audio[src]andimg[src]. Media that uses<source>children instead of asrcattribute is not stamped, so unique ids still matter there. Seereferences/composition-patterns.md. - A full-screen fill on the composition root is fine on a normal render. It is dropped only on the layered-composite path (HDR content, or a composition using shader transitions), where the engine forces every composition root transparent so the layer beneath shows through. With shader transitions or HDR media, put the fill on a full-bleed child (
position: absolute; inset: 0). Seereferences/full-screen-motion.md.
Method
- Set up and plan. Run the check in
references/computer-setup.md, scaffold the project, and usetodofor anything with more than two scenes. Fix the canvas, length and scene list before writing HTML. - Pick the architecture (
references/composition-patterns.md). One scene with simple content: monolithic, the visible element as the clip (references/minimal-composition.md). Anything with several scenes or structured scenes: modular, with a thinindex.html(scene slots, root-level<audio>, a near-empty root timeline) and onecompositions/<scene-id>.htmlper scene. - Write the root. Standalone form,
id="root",data-composition-id,data-width,data-height,data-duration; CSSposition: relative; width: 100%; height: 100%; overflow: hidden. - Build the static end state first, in HTML and CSS with flex, grid, padding and
max-width(layout contract inreferences/determinism-rules.md). Animate from and to it later. - Lay out clips in time (
references/data-attributes.md,references/tracks-and-clips.md). Each clip getsid,data-start,data-duration(required fordivand hosts; images default to 3 s; media to its length), andclass="clip"on visible elements. The window is half-open[start, start + duration): back-to-back clips useb.start = a.start + a.duration, and an animation's end state lands slightly before the clip ends.data-track-indexis a display lane only; layering is CSSz-index. Relative starts (data-start="intro + 2") need spaces round the operator and fail silently to 0, so snapshot them. - Wire sub-compositions (
references/sub-compositions.md): hostdata-composition-idequals the file's root id and its timeline key;<style>and<script>inside<template>; root styled by#root; ids prefixed with the scene id; entrances withgsap.fromTo. The host'sdata-durationis the scene's visible window. - Place media (
references/variables-and-media.md): a<video>with sound isplaysinline data-has-audio="true", a silent onemuted playsinline; every media element has anid; nocrossorigin; never callplay(), pause or seek. Continuous music sits at the root on a high track (10). Volume over time is adata-automationlane, never also a tween. Cuts, trims, speed, freezes and swaps:references/creator-editing-recipes.md. - Declare variables when the owner wants reusable text, colours or images: declarations on
<html>,data-var-text,data-var-srcandvar(--id)bindings,data-variable-valuesper instance,--variablesat render. Variables cannot change the rootdata-duration. - Animate: one paused timeline per composition, registered at the end of the build, keyed by its composition id. Motion choices come from
hyperframes-animation; zooms and camera moves fromhyperframes-keyframes. - Validate.
npx hyperframes lintuntil it reports 0 errors and you have fixed or can justify every warning; thennpx hyperframes checkuntil it reports 0 findings across lint, runtime, layout, motion and contrast. Then snapshot the middle of every scene and look:
RunSNAP=$(mktemp -d); npx hyperframes snapshot --at 1.5,9,15 --describe false -o "$SNAP" && ls "$SNAP" ffmpeg -v error -pattern_type glob -i "$SNAP/*.png" -vf "scale=480:-1,tile=3x2" -frames:v 1 "$SNAP/sheet.png"vision_analyzeonsheet.png(one contact sheet, not many single frames): styled text, the right scene at each time, nothing blank or cut off. Studio preview (npx hyperframes preview) is your own tool only: start it withterminal(background=true), look withbrowser_navigateandbrowser_vision, and never hand the owner a localhost URL. - Review, then render. Send the owner a review package: the key-frame PNGs and, for motion, a draft (
npx hyperframes render . --quality draft --output draft.mp4interminal(background=true, notify_on_complete=true)), attached in your reply asMEDIA:/absolute/path/to/file, and ask "render the final, or what changes?" withclarifyor in the reply. Render--quality deliveryonly after the owner says yes, or straight away when the owner said "just make it". Render flags and failures:hyperframes-cli.
Editing an existing composition
- Read the files first. Preserve unrelated timing, tracks, IDs, variables and media paths.
- To know what is on a project's timeline (tracks, clips, starts, ends, what plays), run
npx hyperframes timeline(or--json) instead of readingindex.htmland every sub-composition file. - Match existing composition IDs and timeline keys.
- Adding a clip: set its
data-startanddata-durationintentionally against the clips around it.data-track-indexis a Studio display lane, not a timing constraint, so it does not need to be free. - A clip that ends past the root
data-durationis cut off: extend the rootdata-durationto the clip's end in the same edit (lintwarnsclip_ends_past_root_duration). data-hiddenon any composition element hides it in BOTH preview and render, overriding its time window; it is non-destructive and reversible, and Studio's timeline eye icon toggles it.- Adding a sub-composition: verify its internal
data-composition-idbefore wiring the host. - When the owner says "this element", identify it from the owner's words, a frame you send, or
npx hyperframes timeline --json, and confirm withclarifyif it is ambiguous.
Output
- The project folder:
index.html,compositions/*.html,assets/(withLICENSES.mdfor any music or SFX), passinglintandcheck. - For review: key frames (PNG) and a draft MP4, attached with
MEDIA:/absolute/path, plus the question. - The final MP4 attached with
MEDIA:/absolute/path/to/final.mp4(never only a path), with its path, duration, resolution, fps and size fromffprobe -v error -show_entries format=duration,size:stream=width,height,r_frame_rate -of default=nw=1 final.mp4.show_cardmay carry the summary; copy the file to~/Desktop/Teamwhen teammates need it. - A short note: what you built or changed (clip ids and times), and any lint warning you deliberately left, with the reason.
Checks before you finish
lint: 0 errors.check: 0 findings, and its sample counts are not0 sample(s)or0/0 text checks.- Every
window.__timelineskey equals its root'sdata-composition-id; every host id equals its file's root id. - The root
data-durationcovers every clip's end. - Every
<video>and<audio>has anid; every timed<video>hasmutedordata-has-audio="true"; none hascrossorigin. - Projects with sub-compositions or reference-timed clips: snapshots at each scene's midpoint seen with
vision_analyze. - After a render: read the summary's second line (see Pitfalls); the
ffprobeduration matches the rootdata-duration; a video that should have sound has an audio stream (ffprobe -v error -select_streams a -show_entries stream=codec_name -of csv=p=0 final.mp4prints a codec). - Any preview server you started is stopped, and quality-check frames are in a temporary folder, not the project.
Pitfalls
- Centring with CSS
transform: translate(-50%,-50%)on a node you then move with GSAPxory. Lint:gsap_css_transform_conflict. Centre with flex, grid orinset, and usefromToorxPercentandyPercent. - Adding a scene-exit
tl.set(..., {visibility:"hidden"}). The runtime already hides timed clips. Opacity fades on inner nodes (oropacityon the.clip) are enough. Caption hard-kills are a different rule. - A timeline key that differs from the root
data-composition-id.window.__timelines["id"]must match it; with two or more timelines a mismatch freezes the render at t = 0. - Not reading the render summary. After
render, read the summary's second line:beginframeorscreenshotcapture, GPU mode, stage timings.screenshotwithsoftware gpuon Linux is the slow path, which is what a computer without a GPU gets: budget the time and render in the background. - Trusting
checkwhilelinthas an error. The audits did not run. <style>in the<head>of a sub-composition. It is dropped; the scene renders as unstyled text in the top-left.- Ending an animation exactly at
data-duration. That frame is never shown; land it slightly before. data-start="intro-0.5". Without spaces it is an id lookup that silently resolves to 0.- A volume tween and a volume lane on one track. The lane wins and the tween is ignored (
audio_volume_double_automation). - Naming a font you have not shipped, or expecting
Arialto look like Arial. Ship the file or pick a bundled family. - Handing the owner a localhost URL or a bare path. The owner cannot open either; attach the file with
MEDIA:.
Versions
Listed from the source repository.
Reviews
No reviews yet. Be the first.
