Why is my HyperFrames animation static?
If the render shows your elements frozen in their start state, HyperFrames almost certainly cannot find or seek your timeline. The timeline has to be paused and registered under the exact composition ID.
missing_data_no_timeline
Your composition looks right in the editor. The render shows every element frozen in the state it had before any animation started, or never visible at all. The renderer is seeking to a time and finding nothing to move.
The contract
HyperFrames does not play your animation. It asks for a single frame at a time, and moves every registered timeline to exactly that time. So a timeline has to satisfy three conditions:
- It is created paused.
- It is registered on
window.__timelines. - The registry key matches
data-composition-idon the root.
A minimal version that satisfies all three:
<div
id="root"
data-composition-id="intro"
data-start="0"
data-duration="3"
data-width="1920"
data-height="1080"
>
<h1 id="title" class="clip" data-start="0" data-duration="3" data-track-index="0">
HyperFrames
</h1>
</div>
<script>
const timeline = gsap.timeline({ paused: true });
timeline.fromTo(
"#title",
{ opacity: 0, y: 32 },
{ opacity: 1, y: 0, duration: 0.6, ease: "power3.out" },
0,
);
window.__timelines = window.__timelines || {};
window.__timelines.intro = timeline;
</script>
What goes wrong
The key does not match. The root is data-composition-id="my-video" and the registration is window.__timelines["intro"]. Nothing connects them. The docs put it plainly: my-video must match data-composition-id="my-video".
The timeline is not paused. A free-running timeline fights the renderer for control of the playhead.
Registration happens too late. If you build the timeline inside an async function, a fetch().then(), or any callback that runs after the page has loaded, the renderer polls for it, waits, and falls back. The rule is synchronous timeline construction: no async, await or fetch() while the timeline is being set up. See why that also makes every render slow.
The timeline never got built because the script threw. The first error in the log is the cause. See why the first render can be a still image.
There is no animation, but you did not say so. A composition that never registers a timeline still makes the producer wait for one. If the composition is static by design, declare it:
<div
data-composition-id="promo"
data-no-timeline
data-width="1920"
data-height="1080"
data-duration="6"
></div>
Nested timelines. If you combined paused scene timelines with .add(), the parent can report a duration of 0 and nothing will advance. See the nested paused timelines answer.
Two smaller rules that avoid the next bug
- Give important tweens an explicit position, and prefer
fromTo()when both endpoints matter. It stays reliable after backward or random seeks, which is what a render worker does. - Do not animate layout properties such as
top,left,widthorheightrepeatedly. Animate transforms and opacity.
Check it worked
npx hyperframes lint
npx hyperframes snapshot --at 0,0.6,2.9
Look at the first frame, the moving state and the end state. A timeline that looks correct during continuous browser playback can still fail when the renderer seeks straight to a frame, so the seeked snapshot is the real test.
Timelines you do not have to wire up

GenMotion ships the HyperFrames animation skills to its agent, so scenes are written against the rules in this answer (a paused timeline, registered under the composition ID) instead of guessed at. You describe the motion in plain language and scrub the result in the preview. If something does not move, you see it immediately and ask for it to be fixed.
Related answers
Why does every HyperFrames render take 45 seconds longer than it should?
A composition that never registers a GSAP timeline still makes the renderer wait for one, and gives up after a fixed 45 seconds. The cost is flat on every render. One attribute removes it.
Why does a HyperFrames render go black when I nest paused GSAP timelines?
Combining paused scene timelines with parent.add(child) makes the parent report a duration of 0 and leaves the children frozen at time zero. It is GSAP behaviour, not a regression, and lint and check do not catch it.
Why is an element invisible in my HyperFrames render but fine in preview?
A render worker seeks straight to a frame and restores the authored state, not whatever the preview last showed. Elements that start hidden need their visible end state stated explicitly, and a fromTo shows its from-state before it starts.
What are the HyperFrames determinism rules, in one checklist?
Same composition, same video, every time, as long as nothing in a frame reads the clock, an unseeded random number or the network. The rules, the reason for each, and the lint codes that enforce them.
Why is my first HyperFrames video a still image?
A composition script that throws leaves the timeline unbuilt, and older HyperFrames versions waited 45 seconds, wrote the MP4 anyway and exited 0. Here is why that happens and how current versions fail instead.
Sources
- HyperFrames: Animate with GSAP
- HyperFrames troubleshooting: animation is static
- HyperFrames rules and anti-patterns
This answer as plain Markdown, for agents: /answers/hyperframes-animation-is-static.md