* fix(core): composition scripts start after web fonts are ready A scene script that measures text (offsetHeight, getBoundingClientRect) ran while the page parsed, often before its web font loaded, so it laid the scene out with fallback metrics. Renders of the same film differed run to run, and preview could differ from export. The compilers now give every body script a type the browser does not run (text/hf-after-fonts, or +module), through one helper in scriptRuns.ts called at the end of the bundler, the producer compile and the sub-composition preview. The Studio motion script is emitted the same way so it still runs after them. The runtime entry owns the wait: on DOMContentLoaded it forces a layout, waits for document.fonts.ready or FONT_WAIT_TIMEOUT_MS (5 s), then re-creates each script in its own place in parser order (classic in order, awaiting src loads; then module and defer scripts in insertion order), boots as before, and calls the DOMContentLoaded and load listeners the scripts added. On timeout the scripts run anyway and runtime_font_wait_timeout names the families still loading. Scene swaps and the composition loader use the same wait. * fix(core): a page whose runtime predates the font gate still runs its scripts A bundle can be served with a runtime loaded from HYPERFRAME_RUNTIME_URL. If that runtime is older than the font gate, nothing would run the deferred composition scripts and the film would be blank. Whenever the marking helper defers scripts, it also puts one small inline script first in the head. At DOMContentLoaded it runs the deferred scripts in parser order itself, without waiting for fonts and with a console warning, unless a runtime has claimed them. The gate's runtime claims them when it loads, before DOMContentLoaded, so the scripts run exactly once either way. * fix(studio-server): motion paths animate in Studio preview when gsap loads in the body The MotionPathPlugin tag Studio inserts after a body gsap script now carries that script's type, so the runtime runs it right after gsap once fonts are ready, instead of before gsap exists. * fix(cli): layout, motion-shot and validate wait for the composition scripts to run The three commands waited on the timeline registry, which exists as soon as the runtime file loads, or on a flat sleep. They now wait for the runtime's render-ready flag through one shared helper, keeping each command's timeout and proceeding as before when it passes. * fix(core): a script that wraps addEventListener keeps its wrapper after its scripts run While composition scripts run, the runtime patches addEventListener to hold load events that already passed. Putting the original back now happens only if the patch is still in place, so a library wrapper installed meanwhile survives. * docs: comments on script timing match scripts that wait for web fonts The early stub's flush and the player's iframe-load handler described page scripts as done by DOMContentLoaded and the runtime as ready before load. Composition scripts now wait for web fonts, so both can come later. Comment-only. * test(core): pin script order after an external script and the scene swap's font wait An inline composition script that follows an external one sees its global, and a scene swap runs the new scene's script only once web fonts are ready. * docs(producer): the early stub comment names what drains late font-gated work * fix(studio): motion paths add without a flash when gsap loads after web fonts Studio preloads MotionPathPlugin on iframe load. A body gsap now runs after web fonts, often after that load, so the preload retries once the runtime reports ready. * test(cli): pin the runtime wait before each command first samples the page * fix(studio): the motion path preload stops waiting once the preview runtime has booted A composition with no global gsap posts ready before the preload runs; the retry listener then never detached and kept each retired preview document alive. It now waits only while the runtime has not booted, and the tests pin the cleanup and the ready filter. * fix(studio): a preview frame that never boots is not kept alive by the motion path preload Only the latest preview waits for the ready message of its runtime; a newer load drops the older wait, so a crashed or missing composition frame can be collected. * style: comments added by the font gate move into names
@hyperframes/core
Types, parsers, generators, compiler, linter, runtime, and frame adapters for the Hyperframes video framework.
Install
npm install @hyperframes/core
Most users don't need to install core directly — the CLI, producer, and studio packages depend on it internally.
What's inside
| Module | Description |
|---|---|
| Types | TimelineElement, CompositionSpec, Asset, canvas dimensions, defaults |
| Parsers | parseHtml — extract timeline elements from HTML; parseGsapScript — parse GSAP animations |
| Generators | generateHyperframesHtml — produce valid Hyperframes HTML from a composition spec |
| Compiler | compileTimingAttrs — resolve data-start / data-duration into absolute times |
| Linter | lintHyperframeHtml — validate Hyperframes HTML (missing attributes, overlapping tracks, etc.) |
| Runtime | IIFE script injected into the browser — manages seek, media playback, and the window.__hf protocol |
| Frame Adapters | Pluggable animation drivers (GSAP, Lottie, CSS, or custom) |
Generated composition trust
Composition generators require trusted authors for code-bearing inputs. styles and
generateHyperframesStyles preserve authored CSS, which can load external resources.
animations may contain __raw: values that are emitted as JavaScript;
includeScripts: true includes executable timeline code. serializeGsapAnimations
also accepts raw preamble, postamble, and a code-bearing timelineVar. Never fill
these inputs with untrusted data. Attribute encoding and closing-tag containment
are not a sandbox; render untrusted compositions in an appropriately isolated
execution environment and never serve them on a privileged origin.
Text content retains the supported inline-formatting sanitizer contract. The clip parser intentionally flattens inner formatting to text, so parse/generate is not a lossless replacement for editing the source HTML.
Frame Adapters
A frame adapter tells the engine how to seek your animation to a specific frame:
import { createGSAPFrameAdapter } from "@hyperframes/core";
const adapter = createGSAPFrameAdapter({
getTimeline: () => gsap.timeline(),
compositionId: "my-video",
});
Implement FrameAdapter for custom animation runtimes:
import type { FrameAdapter } from "@hyperframes/core";
const myAdapter: FrameAdapter = {
id: "my-adapter",
getDurationFrames: () => 300,
seekFrame: (frame) => {
/* seek your animation */
},
};
Parsing and generating HTML
import { parseHtml, generateHyperframesHtml } from "@hyperframes/core";
const { elements, metadata } = parseHtml(htmlString);
const html = generateHyperframesHtml(spec);
Linting
import { lintHyperframeHtml } from "@hyperframes/core/lint";
const result = lintHyperframeHtml(htmlString);
// result.findings: { severity, message, elementId }[]
Documentation
Full documentation: hyperframes.heygen.com/packages/core
Related packages
@hyperframes/engine— rendering engine that drives the browser@hyperframes/producer— full render pipeline (capture + encode)hyperframes— CLI