mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 18:01:20 +08:00
docs: make the two hardest-reading pages readable, and draw the two core concepts
**Prompting.** These were the only two pages that stayed hard to read once the sentence counter was corrected for bullet lists. Capstone went from 46% of sentences over 28 words to 20%, and 53 semicolons to 42. Motion went from 37% to 15%, 26 semicolons to 4, and lost six table rows that were prose in a grid. Both keep every technique and every worked example — the length barely moved, the sentences did. **Concepts.** Compositions and Determinism explain how the whole thing works and showed the reader nothing. Each has one mermaid diagram now: how an HTML file with data-* timing becomes a numbered sequence of frames, and why frame 90 always comes out the same — what is locked before frame 0, and the three things that break it. Mermaid renders natively in Mintlify, so these cost no asset, no CDN upload and no regeneration. Verified in the browser rather than assumed; the pattern is the one proven on concepts/frame-adapters.
This commit is contained in:
+220
-46
@@ -1,76 +1,250 @@
|
||||
---
|
||||
title: "Understand compositions"
|
||||
sidebarTitle: "Compositions"
|
||||
description: "See how a HyperFrames project divides a video into timed, reusable parts."
|
||||
title: Compositions
|
||||
description: "The fundamental building block of a Hyperframes video."
|
||||
---
|
||||
|
||||
A composition is a piece of a HyperFrames project with its own size and timeline. The main composition is the finished sequence. Other compositions can be scenes, captions, title systems, or reusable visual parts inside it.
|
||||
A composition is an HTML file that describes a video. You write elements, give
|
||||
them `data-*` timing attributes, and the framework turns them into frames.
|
||||
|
||||
```text
|
||||
Main video
|
||||
├── Opening
|
||||
├── Product demo
|
||||
├── Customer quote
|
||||
└── Closing
|
||||
`index.html` is the top-level composition. It can hold other compositions
|
||||
inside it. There is no special "root" type — any composition can be imported
|
||||
into any other.
|
||||
|
||||
## How an HTML file becomes frames
|
||||
|
||||
Here is a whole video: a logo, then a video clip.
|
||||
|
||||
```html index.html
|
||||
<div id="root" data-composition-id="root"
|
||||
data-start="0" data-width="1920" data-height="1080">
|
||||
<img class="clip" src="assets/logo.png"
|
||||
data-start="0" data-duration="2" data-track-index="0" />
|
||||
<video class="clip" src="assets/video.mp4"
|
||||
data-start="2" data-duration="3" data-track-index="0"></video>
|
||||
</div>
|
||||
```
|
||||
|
||||
Each part is still HTML. Studio, the agent, and normal code tools all work on the same files.
|
||||
Two rules make that work. The outer element needs `data-composition-id`, and
|
||||
every timed element needs `class="clip"` so the runtime can hide it outside its
|
||||
own window of time.
|
||||
|
||||
## What a composition controls
|
||||
The clips add up to five seconds. At 30 frames per second — the default — that
|
||||
is 150 frames, numbered 0 to 149:
|
||||
|
||||
A composition brings three things together:
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["index.html<br/>img: start 0, duration 2<br/>video: start 2, duration 3"]
|
||||
A --> B["One timeline, 5 seconds<br/>30 fps = 150 frames"]
|
||||
B --> C["frames 0-59<br/>image on screen"]
|
||||
B --> D["frames 60-149<br/>video on screen, playing<br/>from its own second 0"]
|
||||
```
|
||||
|
||||
- **content** — text, shapes, images, video, audio, or another composition;
|
||||
- **time** — when each part starts and how long it remains;
|
||||
- **motion** — seekable animation tied to the same playhead.
|
||||
Nothing plays in real time during a render. The renderer asks for frame 0, then
|
||||
frame 1, and each answer is a single still picture. That is what makes
|
||||
[rendering deterministic](/concepts/determinism).
|
||||
|
||||
The main composition also defines the output frame and total duration.
|
||||
## What can go on the timeline
|
||||
|
||||
## When to create another composition
|
||||
A clip is any element with timing attributes on it:
|
||||
|
||||
Separate a part when it has a clear job and can be understood on its own.
|
||||
- `<video>` — video clips, B-roll, A-roll
|
||||
- `<img>` — stills and overlays
|
||||
- `<audio>` — music and sound effects
|
||||
- `<div data-composition-id="...">` — another composition nested inside this one
|
||||
|
||||
Good boundaries include:
|
||||
The [Data attributes](/concepts/data-attributes) page lists every timing
|
||||
attribute. The [HTML schema](/reference/html-schema) has the full contract.
|
||||
|
||||
- a complete scene;
|
||||
- a repeated title or caption treatment;
|
||||
- a product-demo sequence;
|
||||
- a visual reused in several places;
|
||||
- a complex section that is easier to review separately.
|
||||
## Put one composition inside another
|
||||
|
||||
Do not split every small element into its own file. Extra nesting makes a simple project harder to follow.
|
||||
You can either point at a separate file or write the nested composition inline.
|
||||
Use a separate file when you want to reuse it.
|
||||
|
||||
## How nesting works
|
||||
<Tabs>
|
||||
<Tab title="External file">
|
||||
`data-composition-src` names the file. The framework fetches it, pulls the
|
||||
content out of its `<template>` tag, mounts it, runs its scripts, and
|
||||
registers its timeline.
|
||||
|
||||
The main composition gives each nested composition a place on its timeline:
|
||||
Paths resolve from the **project root**, not from the file doing the
|
||||
referencing. So a composition nested one level deep still writes
|
||||
`compositions/foo.html`, never `../compositions/foo.html`.
|
||||
|
||||
```html
|
||||
```html index.html
|
||||
<div
|
||||
id="el-5"
|
||||
data-composition-id="intro-anim"
|
||||
data-composition-src="compositions/intro-anim.html"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-playback-start="0"
|
||||
data-track-index="3"
|
||||
></div>
|
||||
```
|
||||
|
||||
The file it points at wraps everything in a `<template>`:
|
||||
|
||||
```html compositions/intro-anim.html
|
||||
<template id="intro-anim-template">
|
||||
<div data-composition-id="intro-anim" data-width="1920" data-height="1080">
|
||||
<div class="title">Welcome!</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="intro-anim"] .title {
|
||||
font-size: 72px; color: white; text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
tl.from(".title", { opacity: 0, y: -50, duration: 1 });
|
||||
window.__timelines["intro-anim"] = tl;
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
`data-playback-start` picks which moment of the child timeline shows first.
|
||||
It defaults to `0`. Trimming or splitting from the left pushes this offset
|
||||
forward by the time elapsed multiplied by `data-playback-rate`, so the
|
||||
nested animation keeps going instead of jumping back to its beginning.
|
||||
</Tab>
|
||||
<Tab title="Inline">
|
||||
Write the nested composition straight into the parent. Simpler for
|
||||
something you only use once.
|
||||
|
||||
```html index.html
|
||||
<div id="root" data-composition-id="root"
|
||||
data-start="0" data-width="1920" data-height="1080">
|
||||
|
||||
<!-- Inline nested composition -->
|
||||
<div id="el-5" data-composition-id="intro-anim"
|
||||
data-start="0" data-track-index="3"
|
||||
data-width="1920" data-height="1080">
|
||||
<div class="title">Welcome!</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Timeline for the inline composition
|
||||
const introTl = gsap.timeline({ paused: true });
|
||||
introTl.from(".title", { opacity: 0, y: -50, duration: 1 });
|
||||
window.__timelines["intro-anim"] = introTl;
|
||||
</script>
|
||||
</div>
|
||||
```
|
||||
|
||||
No `<template>` tag and no `data-composition-src` here.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Where the files live
|
||||
|
||||
<Tree>
|
||||
<Tree.Folder name="project" defaultOpen>
|
||||
<Tree.File name="index.html" />
|
||||
<Tree.Folder name="compositions" defaultOpen>
|
||||
<Tree.File name="intro-anim.html" />
|
||||
<Tree.File name="caption-overlay.html" />
|
||||
<Tree.File name="outro-title.html" />
|
||||
</Tree.Folder>
|
||||
<Tree.Folder name="assets">
|
||||
<Tree.File name="video.mp4" />
|
||||
<Tree.File name="music.mp3" />
|
||||
<Tree.File name="logo.png" />
|
||||
</Tree.Folder>
|
||||
</Tree.Folder>
|
||||
</Tree>
|
||||
|
||||
## HTML sets the timing, scripts do the motion
|
||||
|
||||
Your HTML says what plays, when, and on which track, all through
|
||||
[data attributes](/concepts/data-attributes). Your scripts handle the creative
|
||||
part: effects, transitions, canvas, SVG, [GSAP](/guides/gsap-animation)
|
||||
animation.
|
||||
|
||||
<Warning>
|
||||
Never use a script to play, pause, or seek a media element, and never use one
|
||||
to show or hide a clip based on time. The framework already does that from the
|
||||
data attributes, and a script doing it too will fight the framework. See
|
||||
[Common Mistakes](/guides/common-mistakes) for what that looks like.
|
||||
</Warning>
|
||||
|
||||
## Reuse one composition with different content
|
||||
|
||||
One source file can appear several times in the same video, each copy carrying
|
||||
its own text and colors. HyperFrames does not wire `data-var-*` attributes into
|
||||
your DOM or CSS for you — you do it in three steps:
|
||||
|
||||
1. Declare each variable — id, type, default — on the sub-composition's root
|
||||
with `data-composition-variables`. That root is the `<html>` element in a
|
||||
full-document composition, or the `[data-composition-id]` element in a
|
||||
template or fragment.
|
||||
2. Pass each copy's values on its host element with `data-variable-values`.
|
||||
3. Read them inside the composition with
|
||||
`window.__hyperframes.getVariables()`, which layers the host's values over
|
||||
the declared defaults, one copy at a time.
|
||||
|
||||
```html index.html
|
||||
<div
|
||||
id="product-demo-host"
|
||||
data-composition-id="product-demo"
|
||||
data-composition-src="compositions/product-demo.html"
|
||||
data-start="4"
|
||||
data-duration="6"
|
||||
data-composition-id="card-pro"
|
||||
data-composition-src="compositions/card.html"
|
||||
data-start="0"
|
||||
data-track-index="1"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-variable-values='{"title":"Pro","color":"#ff4d4f"}'
|
||||
></div>
|
||||
<div
|
||||
data-composition-id="card-enterprise"
|
||||
data-composition-src="compositions/card.html"
|
||||
data-start="card-pro"
|
||||
data-track-index="1"
|
||||
data-variable-values='{"title":"Enterprise","color":"#22c55e"}'
|
||||
></div>
|
||||
```
|
||||
|
||||
`data-composition-src` paths resolve from the project root, even when the referencing composition is inside another folder.
|
||||
The second card's `data-start="card-pro"` means "start when that one ends".
|
||||
|
||||
This tells HyperFrames to mount `product-demo.html` at four seconds and show a six-second window of it. The nested source keeps its own content and animation.
|
||||
And the source file both cards share:
|
||||
|
||||
If the same source is mounted more than once, each instance can start at a different time or receive different [variables](/concepts/variables).
|
||||
```html compositions/card.html
|
||||
<html data-composition-variables='[
|
||||
{"id":"title","type":"string","label":"Title","default":"Fallback"},
|
||||
{"id":"color","type":"color","label":"Color","default":"#111827"}
|
||||
]'>
|
||||
<body>
|
||||
<div data-composition-id="card" data-width="1920" data-height="1080">
|
||||
<h1 class="title"></h1>
|
||||
|
||||
## Decide where to make a change
|
||||
<style>
|
||||
[data-composition-id="card"] .title { color: var(--card-color, #111827); }
|
||||
</style>
|
||||
|
||||
- Edit the **nested composition** when the scene itself should change everywhere it is used.
|
||||
- Edit the **main composition** when only its placement, duration, or relationship to other scenes should change.
|
||||
- Ask the **agent** when a revision changes the story or crosses several compositions.
|
||||
<script>
|
||||
const { title, color } = __hyperframes.getVariables();
|
||||
const root = document.querySelector('[data-composition-id="card"]');
|
||||
root.querySelector(".title").textContent = title;
|
||||
root.style.setProperty("--card-color", color);
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
In Studio, open or expand a nested scene when you need to work inside it, then use the breadcrumb to return to the parent sequence.
|
||||
[Variables](/concepts/variables) covers the types, the bindings that need no
|
||||
script, CLI overrides, and which value wins. If you are building tooling on
|
||||
`@hyperframes/core`, `extractCompositionMetadata()` reads the same
|
||||
`data-composition-variables` array — that is how Studio builds its editing UI.
|
||||
|
||||
## Continue
|
||||
## See every composition in a project
|
||||
|
||||
Use [Variables](/concepts/variables) when the design should stay fixed while approved content changes. Use the [HTML schema](/reference/html-schema) when you need exact element and attribute rules.
|
||||
```bash
|
||||
npx hyperframes compositions
|
||||
```
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Data attributes](/concepts/data-attributes)
|
||||
- [Deterministic rendering](/concepts/determinism)
|
||||
- [Variables](/concepts/variables)
|
||||
- [Animate with GSAP](/guides/gsap-animation)
|
||||
- [HTML schema reference](/reference/html-schema)
|
||||
- [Start from an example](/examples)
|
||||
|
||||
@@ -1,72 +1,97 @@
|
||||
---
|
||||
title: Deterministic Rendering
|
||||
description: "Make every frame depend on the playhead, not the wall clock."
|
||||
description: "Same input, identical output. Every time."
|
||||
---
|
||||
|
||||
HyperFrames renders by seeking to one frame at a time. A well-authored composition can therefore reproduce the same state whenever the renderer seeks to the same frame.
|
||||
The same [composition](/concepts/compositions) always produces the same video.
|
||||
That is the guarantee everything else rests on — it is why automated pipelines,
|
||||
CI tests, and AI-driven editing can be trusted.
|
||||
|
||||
This is what makes a slow animation safe to render: the renderer does not need to play it in real time, and it does not drop frames when a frame takes longer to produce.
|
||||
## Why the same frame always comes out the same
|
||||
|
||||
## The rendering model
|
||||
Rendering never plays your video. It asks for one frame at a time, and the
|
||||
answer to "what does frame 90 look like?" depends on exactly one thing that
|
||||
changes: the number 90.
|
||||
|
||||
For each output frame, HyperFrames:
|
||||
|
||||
1. converts the frame number to a time;
|
||||
2. seeks the registered animation adapter to that state;
|
||||
3. updates time-based media and composition state;
|
||||
4. captures the frame;
|
||||
5. sends the result to the encoder.
|
||||
|
||||
The preview and renderer use the same composition runtime. Preview performance can still depend on your computer, while the final render advances frame by frame.
|
||||
|
||||
## What your composition must control
|
||||
|
||||
The frame must be reproducible from its inputs and current time.
|
||||
|
||||
- Use a paused, registered timeline instead of animation driven by `requestAnimationFrame`.
|
||||
- Do not use `Date.now()` or the current system time.
|
||||
- Seed random values instead of calling unseeded `Math.random()`.
|
||||
- Keep assets local or make sure they are fully available before rendering.
|
||||
- Give the composition a finite duration, dimensions, and frame rate.
|
||||
- Make custom frame adapters safe to seek in any order.
|
||||
|
||||
Run both checks before a final render:
|
||||
|
||||
```bash
|
||||
npx hyperframes lint
|
||||
npx hyperframes check
|
||||
```mermaid
|
||||
flowchart TD
|
||||
F["Frame 90"] --> T
|
||||
L["Locked before frame 0:<br/>fps, width, height, variables"] --> T
|
||||
T["t = 90 / fps<br/>integer math, never a clock"] --> S["Every animation seeked<br/>to exactly t"]
|
||||
S --> P["The same pixels, every run"]
|
||||
S -.-> X["Breaks if a frame reads:<br/>Date.now, unseeded Math.random,<br/>or a mid-render fetch"]
|
||||
```
|
||||
|
||||
## What “repeatable” does not mean
|
||||
So the rules are short:
|
||||
|
||||
Seek-driven rendering controls time. It does not make different computers identical.
|
||||
- **No wall clock.** No `Date.now()`, no `requestAnimationFrame`, no system
|
||||
timers.
|
||||
- **No unseeded randomness.** `Math.random()` without a seed gives a different
|
||||
frame every run.
|
||||
- **No fetching mid-render.** Every asset loads before the first frame.
|
||||
- **Fixed output size.** `fps`, `width`, and `height` are locked before frame 0.
|
||||
- **A finite length.** Every composition has a known end.
|
||||
|
||||
Chrome versions, installed fonts, GPU behavior, operating systems, and encoder versions can produce small differences even when the composition is unchanged. If exact reproduction across machines matters, render in the same controlled environment:
|
||||
## What happens on each frame
|
||||
|
||||
<Steps>
|
||||
<Step title="Frame clock">
|
||||
The [engine](/packages/engine) works out the time with integer math:
|
||||
`time = floor(frame) / fps`. Real time is never consulted.
|
||||
</Step>
|
||||
<Step title="Seek">
|
||||
The [frame adapter](/concepts/frame-adapters) gets `seekFrame(frame)` and
|
||||
moves every animation, DOM change, and canvas draw to that exact moment. All
|
||||
[GSAP](/guides/gsap-animation) timelines are paused and seeked, never played.
|
||||
</Step>
|
||||
<Step title="Capture">
|
||||
Chrome's `HeadlessExperimental.beginFrame` grabs the pixels in one atomic
|
||||
operation. No half-painted frames.
|
||||
</Step>
|
||||
<Step title="Encode">
|
||||
FFmpeg turns the captured frames into the MP4 and mixes in the audio from
|
||||
your `<audio>` and `<video>` elements.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Every [frame adapter](/concepts/frame-adapters) follows these same rules. If you
|
||||
write your own, it must follow the
|
||||
[determinism contract](/concepts/frame-adapters#determinism-contract):
|
||||
`seekFrame(frame)` gives the same result for the same frame, seeks work in any
|
||||
order, nothing resolves after the frame is committed, and the lifecycle stays
|
||||
`init` → `seekFrame` (many times) → `destroy`.
|
||||
|
||||
## Rule out your machine as a variable
|
||||
|
||||
Fonts and Chrome versions differ between computers, so a local render can shift
|
||||
by a pixel from one machine to the next. Render in Docker when you need exact
|
||||
reproducibility:
|
||||
|
||||
```bash
|
||||
npx hyperframes render --docker --output output.mp4
|
||||
```
|
||||
|
||||
Docker reduces environment differences by using a controlled browser, font, and encoder setup. Keep the same assets and render settings as well.
|
||||
Docker pins the Chromium version, the font set, and the FFmpeg encoder, so the
|
||||
platform stops being an input. The [Rendering guide](/guides/rendering) covers
|
||||
every other option.
|
||||
|
||||
## For frame-adapter authors
|
||||
## Will the preview match the render?
|
||||
|
||||
A frame adapter must follow four rules:
|
||||
Every frame looks the same in both, because both run the same
|
||||
`hyperframe.runtime`, the [producer's](/packages/producer) seek behavior is the
|
||||
single source of truth, and the `__playerReady` and `__renderReady` gates hold
|
||||
capture until the composition is fully loaded.
|
||||
|
||||
- seeking the same frame twice returns the same state;
|
||||
- frames can be requested out of order;
|
||||
- no unfinished asynchronous work changes the committed frame later;
|
||||
- `destroy()` leaves no state behind for the next render.
|
||||
Speed is a different story. Preview plays in real time in your browser, so it is
|
||||
limited by your hardware. Render is seek-driven and takes one frame at a time,
|
||||
so it never drops a frame no matter how expensive that frame is. A composition
|
||||
that stutters in preview still renders perfectly — see
|
||||
[Performance](/guides/performance).
|
||||
|
||||
See [Frame adapters](/concepts/frame-adapters) for the interface and a complete example.
|
||||
## Related topics
|
||||
|
||||
## Continue
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Render a composition" icon="film" href="/guides/rendering">
|
||||
Choose local, Docker, batch, or cloud rendering.
|
||||
</Card>
|
||||
<Card title="Improve render performance" icon="gauge-high" href="/guides/performance">
|
||||
Understand preview speed, capture cost, and render time.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
- [Frame adapters](/concepts/frame-adapters)
|
||||
- [Render to MP4](/guides/rendering)
|
||||
- [Compositions](/concepts/compositions)
|
||||
- [@hyperframes/producer](/packages/producer)
|
||||
- [Common mistakes that break determinism](/guides/common-mistakes)
|
||||
|
||||
+78
-52
@@ -1,36 +1,54 @@
|
||||
---
|
||||
title: Capstone — every technique, one journey
|
||||
description: "One prompt, one continuous camera move, every technique in this guide — the viewer rides the timeline itself from typed markup to rendered MP4."
|
||||
description: "One prompt, one unbroken camera move, and every technique in this guide joined into a single 62-second film."
|
||||
---
|
||||
|
||||
import { DocsVideo } from "/snippets/docs-video.jsx";
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Capstone Timeline Default"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/capstone-timeline-default.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/capstone-timeline-default.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*"The Timeline" — 1920x1080, ~62 seconds, one prompt, one unbroken camera move, unedited.*
|
||||
|
||||
The prompt targeted roughly 60 seconds; the delivered render runs 1:02 because the final lockup holds for a two-second breath.
|
||||
One prompt made this. Nothing was cut or stitched together afterward. The prompt asked for about 60 seconds. The render came back at 1:02, because the closing lockup holds for a two-second breath.
|
||||
|
||||
If you read the guide in order, you have already seen every piece of this film. Each region was cut out and embedded in the chapter that teaches its technique — the typed opening in [Code animations](/prompting/code-blocks), the chart and map in [Data and maps](/prompting/data-and-maps), the footage pipeline in [Media and audio](/prompting/media-and-audio), the glass and mural in [VFX](/prompting/vfx-and-liquid-glass) and [Generated artwork](/prompting/generated-artwork), the beat grid in [Music](/prompting/music-and-slideshows), the 3D coil in [Runtimes and 3D](/prompting/runtimes-and-3d), the continuous camera in [Motion](/prompting/motion), the seeded finale in [Rendering and output](/prompting/rendering-and-output). This page is where the pieces become one composition, and the prompt below is the glue: every paragraph of it is a chapter of this guide restated as a binding clause.
|
||||
If you read this guide in order, you have already seen every part of this film. Each region was pulled out and embedded in the chapter that teaches it. The table further down says which chapter each part came from. This page is where the pieces become one composition.
|
||||
|
||||
The concept is the product: the viewer travels along one literal timeline — the thing HyperFrames is built on — from the first typed character to the finished MP4. Everything in it is true: real catalog counts, real cloud regions, real product surfaces, footage staged as the material the tool processes. And the film has no scenes in the slideshow sense — it is one continuous camera journey through one wide world, which is the chapter's biggest lesson.
|
||||
## The idea
|
||||
|
||||
## The cinematography contract
|
||||
The concept is the product. The viewer travels along one literal timeline — the thing HyperFrames is built on — from the first typed character to the finished MP4.
|
||||
|
||||
Two earlier builds of this capstone passed every gate and still failed review — first as sparse frames floating in space, then as a well-dressed slideshow: discrete scenes, hard cuts, motion confined inside each frame. What fixed it wasn't more animation; it was a **cinematography contract** written into the prompt:
|
||||
Everything in the film is true. Real catalog counts. Real cloud regions. Real product surfaces. The footage appears as material the tool is processing, not as a testimonial.
|
||||
|
||||
- **One continuous camera, dwell-and-sweep.** The whole film is a single wide world the camera dollies across in one unbroken move — but the rhythm matters as much as the continuity: the camera SWEEPS between regions, then eases to a genuine full stop for a couple of seconds while each region's main point lands (the count completing, the kicker stamping, the spoken keywords), then accelerates away. The dwell is what gives the sweep meaning — without the rest, emphasis dies mid-motion. During a dwell the world keeps resolving; the camera rests, the film never freezes. Scene changes happen by *arriving*: the next region is already visible at the frame edge before the camera reaches it, and the previous one exits by parallax, not by fade.
|
||||
- **Persistent elements that tell the story.** Three things never leave frame: the **wire** (a single continuous line that is the timeline itself — it becomes the chart baseline, the map arc, the waveform, the 3D coil), the **ruler** (a frame-ruler chrome strip whose playhead tracks the film's real playback time), and the **protagonist** — the `<div class="clip">` typed in the opening, which rides the wire through every region and is the thing that finally renders at the end.
|
||||
- **Anti-slideshow negatives.** No element cluster that fades in centered, sits, and fades out; no region that reads as a static slide the camera happens to pass; no motion that is just idling in place.
|
||||
And the film has no scenes. There is one wide world and one camera moving through it. That is the biggest lesson on this page.
|
||||
|
||||
Like every rule in this film, it only exists because it was demanded in writing — the linter can verify a timeline, but only the prompt can demand cinematography.
|
||||
## Why the first two builds failed
|
||||
|
||||
## The full prompt, verbatim
|
||||
Two earlier builds of this capstone passed every check and still failed review.
|
||||
|
||||
This is the full prompt, nothing paraphrased or trimmed. Several of its clauses — the chip's hard bind to the wire's path geometry, the mural's pinned/iris-revealed/fade-out behavior, and the Depth region's 3D contract (the coil winding out of and back onto the wire, the chip passing through the loops and behind the form with true depth occlusion instead of floating over the geometry) — were added after frame review caught the builder drifting on exactly those points, and the film was patched to comply: the same catch-and-tighten loop [Iterating](/prompting/iterating) teaches, applied to the prompt itself so the next run can't regress.
|
||||
The first looked like sparse frames floating in space. The second was a well-dressed slideshow: separate scenes, hard cuts, motion trapped inside each frame.
|
||||
|
||||
{/* VISUAL: three stills side by side — the sparse-frames build, the slideshow build, and the approved continuous-camera build — so the reader can see the difference the contract made. */}
|
||||
|
||||
More animation was not the fix. What fixed it was a **cinematography contract** written into the prompt. Three rules:
|
||||
|
||||
**One camera, dwell and sweep.** The camera dollies across one wide world in a single unbroken move. The rhythm matters as much as the continuity. The camera sweeps between regions, then eases to a genuine full stop. It rests for a couple of seconds while the region's main point lands: the count completing, the kicker stamping, the spoken keywords. Then it accelerates away. The dwell is what gives the sweep meaning. Without the rest, emphasis dies mid-motion. The world keeps resolving during a dwell, so the camera rests but the film never freezes.
|
||||
|
||||
**Regions change by arriving, not cutting.** The next region is already visible at the edge of frame before the camera reaches it. The previous one leaves by parallax, not by fading out.
|
||||
|
||||
**No slideshow tells.** Nothing fades in centered, sits, and fades out. No region reads as a static slide the camera happens to pass. No motion that is just idling in place.
|
||||
|
||||
Three things also never leave frame. The **wire** is one continuous line, and it is the timeline itself. It becomes the chart baseline, then the map arc, then the waveform, then the 3D coil. The **ruler** is a chrome strip across the top. Its playhead tracks the film's real playback time. The **protagonist** is the `<div class="clip">` typed in the opening. It rides the wire through every region, and it is the thing that finally renders.
|
||||
|
||||
None of this happens on its own. The linter can check that a timeline is valid. Only the prompt can ask for cinematography.
|
||||
|
||||
## The prompt, word for word
|
||||
|
||||
Below is the whole prompt, nothing paraphrased or trimmed. It is long on purpose. This is what a film-scale ask looks like when every clause has a job. It is written at [High-fidelity looks](/prompting/visual-specs)' level of detail: exact hex values, exact timings, named eases, copy quoted word for word.
|
||||
|
||||
Three clauses were added after the fact. Frame review caught the builder drifting. So the prompt was tightened and the film was patched to match:
|
||||
|
||||
- The chip must bind to the wire's actual path geometry. It cannot just float near the line.
|
||||
- The mural must stay pinned in place, be revealed by the iris, and exit by fading out.
|
||||
- In the Depth region the coil must wind out of the wire and back onto it. The chip must pass behind the 3D form with real depth occlusion, not float over it.
|
||||
|
||||
That is the same catch-and-tighten loop [Iterating](/prompting/iterating) teaches, applied to the prompt itself so the next run cannot regress.
|
||||
|
||||
> /general-video Make a ~60-second 1920x1080 promo film for HyperFrames — the open-source framework that turns hand-authored HTML and GSAP animation into deterministic, frame-accurate video. The film's concept IS the product: the viewer travels along one literal timeline — the thing HyperFrames is built on — from the first typed character to the finished MP4. Everything true, nothing invented: real catalog counts, real cloud regions, real product surfaces.
|
||||
>
|
||||
@@ -87,57 +105,65 @@ This is the full prompt, nothing paraphrased or trimmed. Several of its clauses
|
||||
>
|
||||
> **Negatives:** no hard cuts outside the two sanctioned seams; no static regions; no shaking or idle-in-place motion passed off as energy; no hue beyond mint (cyan only inside the one CTA gradient) — and per the brand file, no purple-blue SaaS gradients ever; no stock footage or photography; no fictional stats, projects, or testimonials — every number and label must be true of HyperFrames today; no real OS/browser chrome; no unseeded randomness; none of the film is silent.
|
||||
|
||||
## Walking the prompt, chapter by chapter
|
||||
|
||||
Every region pins a different technique, and every technique has a chapter:
|
||||
## Which chapter taught which region
|
||||
|
||||
| Region | What it demonstrates | Chapter that teaches it |
|
||||
| --- | --- | --- |
|
||||
| Type (0:00–0:07) | Per-character code typing; the underline that becomes the wire | [Code animations](/prompting/code-blocks), [Prompt anatomy](/prompting/anatomy) |
|
||||
| Mount (0:07–0:14) | The timeline's own vocabulary — tracks, clips, `data-*` labels | [Prompt anatomy](/prompting/anatomy) |
|
||||
| Measure (0:14–0:21) | `data-chart` block; the wire as chart baseline (matched motion) | [Data and maps](/prompting/data-and-maps), [Transitions](/prompting/transitions) |
|
||||
| Everywhere (0:21–0:28) | `world-map` block; real Lambda / Cloud Run regions | [Data and maps](/prompting/data-and-maps), [Rendering and output](/prompting/rendering-and-output) |
|
||||
| Material (0:28–0:39) | Generated clip → HEVC proxy → background removed *before* the speech → the line's keywords slam in word-synced as display text BEHIND the cutout (silhouette occlusion) → lower-third behind the subject | [Media and audio](/prompting/media-and-audio), [Caption styles](/prompting/captions-catalog), [Overlays and lower thirds](/prompting/overlays-and-lower-thirds) |
|
||||
| Surface (0:39–0:45) | `sdf-iris` shader lens, frosted glass, palette-neutral generated mural | [Transitions](/prompting/transitions), [VFX and liquid glass](/prompting/vfx-and-liquid-glass), [When to generate artwork](/prompting/generated-artwork) |
|
||||
| Rhythm (0:45–0:52) | Cuts on a real analyzed beat grid (`hyperframes beats`) | [Music videos and slideshows](/prompting/music-and-slideshows) |
|
||||
| Depth (0:52–0:56) | Real Three.js via the frame adapter, wire as 3D coil; the chip joins the scene to thread the loops and pass behind the form (true depth occlusion) | [Runtimes and 3D](/prompting/runtimes-and-3d) |
|
||||
| Render (0:56–1:02) | Seeded (42) two-frame-hold confetti — determinism as the punchline | [Motion that reads premium](/prompting/motion) (rule 8) |
|
||||
| Type (0:00–0:07) | Per-character code typing. The underline that becomes the wire. | [Code animations](/prompting/code-blocks), [Prompt anatomy](/prompting/anatomy) |
|
||||
| Mount (0:07–0:14) | The timeline's own vocabulary: tracks, clips, `data-*` labels | [Prompt anatomy](/prompting/anatomy) |
|
||||
| Measure (0:14–0:21) | The `data-chart` block. The wire as a chart baseline, by matched motion. | [Data and maps](/prompting/data-and-maps), [Transitions](/prompting/transitions) |
|
||||
| Everywhere (0:21–0:28) | The `world-map` block. Real Lambda and Cloud Run regions. | [Data and maps](/prompting/data-and-maps), [Rendering and output](/prompting/rendering-and-output) |
|
||||
| Material (0:28–0:39) | The footage pipeline, in order. Generated clip, HEVC proxy, background removed *before* the speech, the speaker's own keywords slamming in behind the cutout, lower third behind the subject. | [Media and audio](/prompting/media-and-audio), [Caption styles](/prompting/captions-catalog), [Overlays and lower thirds](/prompting/overlays-and-lower-thirds) |
|
||||
| Surface (0:39–0:45) | The `sdf-iris` shader lens, frosted glass, a palette-neutral generated mural. | [Transitions](/prompting/transitions), [VFX and liquid glass](/prompting/vfx-and-liquid-glass), [When to generate artwork](/prompting/generated-artwork) |
|
||||
| Rhythm (0:45–0:52) | Cuts on a real analyzed beat grid from `hyperframes beats`. | [Music videos and slideshows](/prompting/music-and-slideshows) |
|
||||
| Depth (0:52–0:56) | Real Three.js via the frame adapter. The wire as a 3D coil, with the chip threading the loops and passing behind the form. | [Runtimes and 3D](/prompting/runtimes-and-3d) |
|
||||
| Render (0:56–1:02) | Seeded confetti, two-frame holds. Determinism as the punchline. | [Motion that reads premium](/prompting/motion) (rule 8) |
|
||||
|
||||
The camera itself is [Motion that reads premium](/prompting/motion)'s "camera as actor" rule taken to its logical end — the camera isn't *in* the film, it *is* the film. The persistent wire/ruler/protagonist are [Storyboards](/prompting/storyboards)' callback device stretched across the whole runtime instead of two frames. And the whole thing is written in [High-fidelity looks](/prompting/visual-specs)' density: exact hexes, exact timings, named eases, quoted copy.
|
||||
Three more chapters run through the whole film instead of one region.
|
||||
|
||||
One more chapter runs through every pixel: the film's look isn't described in the prompt at all — the prompt points at a **`frame.md` brand file** (the real HyperFrames design system: near-black ground, greyscale hierarchy, mint as the single go/active accent, ABC Solar Display lockups, TT Norms Pro Mono for machine text, one sanctioned mint→cyan gradient on the CTA) and says *obey it exactly*. That's [Design systems and brand](/prompting/design-systems)'s whole thesis — a source of brand truth beats any adjective — demonstrated at film scale.
|
||||
The camera is [Motion that reads premium](/prompting/motion)'s "camera as actor" rule taken to its limit. The camera is not in the film. It is the film.
|
||||
|
||||
### Why one file, still
|
||||
The wire, the ruler, and the chip are [Storyboards](/prompting/storyboards)' callback device stretched across the full runtime instead of across two frames.
|
||||
|
||||
HyperFrames scopes `data-composition-variables` **per composition file**, so the single-file constraint from the earlier capstones carries over — one `index.html`, one variable scope, which is what lets one `--variables` flag re-skin the whole journey. The new wrinkle: the world is one wide scene space traversed by a camera transform, while regions stay timed `class="clip"` sections synchronized to the camera's arrival — the persistent layers (wire, ruler, chip) are the only elements alive for all 62 seconds, so capture stays efficient without breaking the illusion of one continuous space. See [Variables and templating](/prompting/variables-and-templating).
|
||||
And the look is never described in the prompt at all. The prompt points at a `frame.md` brand file and says obey it exactly. That file holds the real HyperFrames design system. Near-black ground, greyscale text hierarchy, mint as the single go/active accent. ABC Solar Display for the lockups, TT Norms Pro Mono for machine text. One sanctioned mint-to-cyan gradient, on the closing CTA and nowhere else. A source of brand truth beats any adjective. That is [Design systems and brand](/prompting/design-systems)' whole thesis, shown at film scale.
|
||||
|
||||
## The re-skin: one flag, the whole journey
|
||||
### Why it is one file
|
||||
|
||||
HyperFrames reads `data-composition-variables` per composition file. One file means one variable scope, and one variable scope is what lets a single `--variables` flag re-skin the whole journey. So the single-file rule from the earlier capstones still applies here: one `index.html`.
|
||||
|
||||
The new wrinkle is the world. It is one wide scene space that a camera transform travels across. The regions are still timed `class="clip"` sections, synced to the moment the camera arrives, and the framework shows and hides each one by its own time window. Only the wire, the ruler, and the chip are alive for all 62 seconds. See [Variables and templating](/prompting/variables-and-templating).
|
||||
|
||||
## Change two colors, re-skin the film
|
||||
|
||||
```bash
|
||||
hyperframes render --variables '{"ground":"#0d1420","ink":"#c8ff3d"}' --strict-variables
|
||||
```
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Capstone Timeline Reskin"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/capstone-timeline-reskin.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*The same composition, navy/acid-green — the wire, ruler, chart, map, glass, and even the generated mural all follow the override.*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/capstone-timeline-reskin.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*The same composition, navy and acid green — the wire, ruler, chart, map, glass, and even the generated mural all follow the override.*
|
||||
|
||||
The mural trick from the previous capstone carries over: the artwork is generated **palette-neutral** (grayscale) and duotoned live by CSS layers driven by the `ground`/`ink` variables, so even the raster re-skins. Keep color in CSS and shapes in the raster whenever a template frame needs to survive a re-skin.
|
||||
The mural trick from the previous capstone carries over. The artwork is generated in grayscale, then duotoned live by CSS layers driven by the `ground` and `ink` variables. Even the raster re-skins. Keep color in CSS and shapes in the raster whenever a template frame needs to survive a re-skin.
|
||||
|
||||
## How everything ties together
|
||||
## What the prompt actually adds
|
||||
|
||||
Read the prompt again with the guide behind you and it stops looking like one enormous ask. Region by region it is material you already have: the typed opening is [Code animations](/prompting/code-blocks), the Mount paragraph is [Prompt anatomy](/prompting/anatomy)'s framework vocabulary staged as scenery, the chart and map paragraphs are [Data and maps](/prompting/data-and-maps), the Material paragraph is [Media and audio](/prompting/media-and-audio) + [Caption styles](/prompting/captions-catalog) + [Overlays](/prompting/overlays-and-lower-thirds) run in sequence, Surface is [Transitions](/prompting/transitions) + [VFX](/prompting/vfx-and-liquid-glass) + [Generated artwork](/prompting/generated-artwork), Rhythm is [Music](/prompting/music-and-slideshows), Depth is [Runtimes and 3D](/prompting/runtimes-and-3d), and the finale is [Motion](/prompting/motion)'s rule 8. None of those clauses is new — they are the chapters, restated as requirements.
|
||||
Read the prompt again with the guide behind you and it stops looking like one enormous ask. Region by region it is material you already have. None of those clauses is new. They are the chapters, restated as requirements.
|
||||
|
||||
What IS new — the part you could not have written after any single chapter — is the connective tissue, and that is exactly what the rest of the prompt spends its words on:
|
||||
What is new is the connective tissue, and that is where most of the prompt's words go.
|
||||
|
||||
- **The cinematography contract** fuses nine demos into one space: one camera, arrivals instead of cuts, dwell-and-sweep pacing. Remove it and you get a playlist of chapter examples; with it, the same nine pieces are a film.
|
||||
- **The persistent elements do the handoffs.** The wire is the trick that makes each region *become* the next — the same line is the typed underline, the chart baseline, the map arc, the waveform, the 3D coil. The chip is a callback threaded through all nine techniques; the ruler makes the film's own medium visible.
|
||||
- **One brand file governs every region** ([Design systems](/prompting/design-systems)) — so nine techniques drawn from a dozen chapters land looking like one designer made them.
|
||||
- **One variable scope re-skins all of it** ([Variables and templating](/prompting/variables-and-templating)) — the single-file constraint exists so the chart, the map, the glass, the mural, and the confetti all answer to the same two variables in one `--variables` flag.
|
||||
- **One soundtrack paces all of it** ([Media and audio](/prompting/media-and-audio)) — the VO cues every region's reveals ([pace reveals to the narration](/prompting/media-and-audio#pace-reveals-to-the-narration)), the same BGM that beds the narration feeds the Rhythm region's beat analysis, and one SFX plan rides the visual hits — keystrokes at the open, a whoosh per handoff, ticks on counters and beats, one riser into the render ([sound effects](/prompting/media-and-audio#sound-effects)).
|
||||
- **The cinematography contract** fuses nine demos into one space. Remove it and you get a playlist of chapter examples. Keep it and the same nine pieces are a film.
|
||||
- **The persistent elements do the handoffs.** The wire is the trick that makes each region *become* the next. The same line is the typed underline, the chart baseline, the map arc, the waveform, and the 3D coil. The chip is a callback threaded through all nine techniques. The ruler makes the film's own medium visible.
|
||||
- **One brand file governs every region.** Nine techniques drawn from a dozen chapters land looking like one designer made them.
|
||||
- **One variable scope re-skins all of it.** The chart, the map, the glass, the mural, and the confetti all answer to the same two variables.
|
||||
- **One soundtrack paces all of it** ([Media and audio](/prompting/media-and-audio)). The narration cues every region's reveals. The same BGM that beds the narration feeds the Rhythm region's beat analysis. One SFX plan rides the visual hits: keystrokes at the open, a whoosh per handoff, ticks on counters and beats, one riser into the render.
|
||||
|
||||
That's the method this guide has been building toward: learn each technique in isolation, then write the prompt whose job is not to invent the pieces but to bind them — shared space, shared spine, shared brand, shared variables, shared clock. A chart, a map, typed markup, processed footage, 3D, glass, a shader seam, a beat grid, seeded confetti: every one traceable to a chapter you've already read. The film is what the glue makes of them — and the glue, like everything else here, is just words in the prompt.
|
||||
That is the method this guide has been building toward. Learn each technique on its own. Then write the prompt whose job is not to invent the pieces but to bind them: shared space, shared spine, shared brand, shared variables, shared clock.
|
||||
|
||||
*Next: [Rules and anti-patterns](/prompting/rules-and-anti-patterns) — keep this cheat sheet open while you build.*
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Fix a render that came back wrong](/prompting/iterating)
|
||||
- [Rules and anti-patterns](/prompting/rules-and-anti-patterns)
|
||||
- [Motion that reads premium](/prompting/motion)
|
||||
- [Design systems and brand](/prompting/design-systems)
|
||||
- [Variables and templating](/prompting/variables-and-templating)
|
||||
|
||||
+139
-123
@@ -1,58 +1,66 @@
|
||||
---
|
||||
title: Motion that reads premium
|
||||
description: "Eight motion-grammar rules from frame-by-frame study of professional work — nothing stops, the camera acts, action overlaps, imperfection stays reproducible."
|
||||
description: "Eight rules for motion that reads as premium: nothing stops, the camera acts, action overlaps, and imperfection stays reproducible."
|
||||
---
|
||||
|
||||
import { DocsVideo } from "/snippets/docs-video.jsx";
|
||||
You can already specify a frame: exact copy, exact colors, exact layout, exact timing. This page makes it move.
|
||||
|
||||
By now you can specify a frame precisely — exact copy, exact colors, exact layout, exact timing. This level makes it move. But "make it move" is the wrong instruction, and it is worth understanding why before you write a single motion clause.
|
||||
But "make it move" is the wrong instruction. Here is why, and what to write instead.
|
||||
|
||||
## Motion is a claim, not a coat of paint
|
||||
## Every movement makes a claim
|
||||
|
||||
**Every movement says something about the thing that moves.** A number that breathes says *this figure is live, it is still counting*. A card that flies in from the right says *it came from somewhere, there is more offscreen*. Overshoot says *this has mass and it arrived eagerly*. A slow, heavy entrance says *this matters*. A jitter says *this is unstable*. You do not get to opt out of this: motion is read as meaning whether or not you intended meaning, so the only question is whether what it says is true.
|
||||
Movement says something about the thing that moves. A number that breathes says *this figure is live, it is still counting*. A card that flies in from the right says *it came from somewhere, there is more offscreen*. Overshoot says *this has mass and it arrived eagerly*. A slow, heavy entrance says *this matters*. A jitter says *this is unstable*.
|
||||
|
||||
That reframes the useful question. Not *"does this feel too static?"* but **"what is this movement saying, and do I mean it?"** A stat card that breathes because the number is live is telling the truth. The same card breathing because the frame looked empty is telling the viewer a live-data story about a static figure — and viewers feel the mismatch even when they can't name it.
|
||||
You cannot opt out. Viewers read motion as meaning whether you intended meaning or not. The only question is whether what it says is true.
|
||||
|
||||
The strongest motion goes further and works as a metaphor for the content itself. A deployment pipeline should *flow*. A build should *assemble*. A countdown should *tick*, discretely, the way time does. A route should *draw* in the direction of travel. When the movement and the meaning are the same gesture, you stop needing to explain the graphic.
|
||||
So the question is not *"does this feel too static?"* It is **"what is this movement saying, and do I mean it?"** A stat card that breathes because the number is live tells the truth. The same card breathing because the frame looked empty tells a live-data story about a static figure, and viewers feel the mismatch even when they cannot name it.
|
||||
|
||||
**The failure this prevents is the common one: adding amplitude when a piece feels dead.** More motion is not more meaning. Four decorative movements are worse than one motivated movement — they compete for the eye, none of them says anything, and the result reads as busy rather than alive. If a shot feels lifeless, the fix is usually to find the one thing that genuinely *is* changing and let that carry the frame, not to set everything else wobbling.
|
||||
The strongest motion goes further and works as a metaphor for the content. A deployment pipeline should *flow*. A build should *assemble*. A countdown should *tick*, in discrete steps, the way time does. A route should *draw* in the direction of travel. When the movement and the meaning are the same gesture, the graphic stops needing an explanation.
|
||||
|
||||
A practical test: name what each movement means, in words, in a sentence that ends. *"The counter breathes because it's still receiving data."* *"The cards stagger because they arrive over the wire, not all at once."* If you can't finish the sentence, cut the movement.
|
||||
### The mistake this prevents
|
||||
|
||||
Four jobs cover almost everything a movement can honestly be doing:
|
||||
When a piece feels dead, people add amplitude. More motion is not more meaning. Four decorative movements are worse than one motivated movement. They compete for the eye, none of them says anything, and the result reads busy rather than alive.
|
||||
|
||||
| The job | What it looks like |
|
||||
| --- | --- |
|
||||
| **Direct attention** | Motion pulls the eye to the thing that matters this second — a count landing, a line igniting |
|
||||
| **Carry continuity** | Motion ties one moment to the next so the piece reads as one space, not a sequence of slides |
|
||||
| **Show change** | Motion *is* the information — a value rising, a diff resolving, a route drawing |
|
||||
| **Express character** | Motion carries the brand's temperament — snappy and confident, or calm and editorial |
|
||||
If a shot feels lifeless, find the one thing that genuinely *is* changing and let that carry the frame. Do not set everything else wobbling.
|
||||
|
||||
If a movement is doing none of those, it's decoration, and decoration is what makes a video read as cheap no matter how smooth the easing is. (Product-UI motion frameworks name a fifth job, *feedback* — confirming a user's action. Video has no user input, so it doesn't apply here.)
|
||||
Here is the test. Name what each movement means, in a sentence that ends. *"The counter breathes because it's still receiving data."* If you cannot finish the sentence, cut the movement.
|
||||
|
||||
## The grammar
|
||||
### What a movement can honestly be doing
|
||||
|
||||
Once a movement has something to say, this is how to make it read well. These rules govern *execution*; none of them is a reason to add a movement that has no job:
|
||||
Four jobs cover almost everything.
|
||||
|
||||
1. **Nothing ever fully stops.** Every "hold" carries ambient idle motion — a 1-2% breathing scale, slow drift, a shimmer. Never write "holds motionless"; write "settles into a gentle ambient idle." A frozen final second is the single biggest cheap-motion tell.
|
||||
2. **The camera is an actor.** Give each scene one continuous camera move — a 4-8% push-in, a slow orbit, parallax between layered planes — easing gently and never *decaying* to a dead stop at the end of a scene (compute the ease over a window slightly longer than the render). But the reason isn't that stillness looks cheap: a move is what carries the **spatial relationship** between two shots. A cut or a cross-fade throws that away, so two views of one system read as two unrelated pictures. Move to get somewhere and the audience infers one continuous space for free. Beware the corollary — translating the scene and translating the camera are the *same transform*, so "the content slid past a fixed frame" is not a static alternative, it is the same shot. A deliberate mid-film rest is a different thing, and it is sanctioned: see [avoiding the slideshow](#avoiding-the-slideshow).
|
||||
3. **Overlapping action.** No two elements share a start or end time. Entrances stagger at irregular offsets; the next element begins while the last is still settling. The mechanism is a ratio: **each offset should be clearly shorter than the animation it offsets** — a small offset-to-duration ratio reads fluid, a large one reads deliberate, and an offset longer than the animation stops being a stagger and becomes a queue. Delay the *supporting* elements, never the focal one; a focal element that waits reads as lag. And stagger what is **happening**, not what is merely **present**: things that arrive in sequence should animate in sequence, but a set of things that already existed before the shot started should appear together — staggering those just makes the scene take longer to assemble.
|
||||
4. **Compound properties only when they tell one story.** Every animation changes at least one property — position, scale, opacity, rotation. Combining them works when they say the same thing (a confirmation sliding up *and* fading in both read as "arriving") and confuses when they don't (a sheet that rises *and* rotates is two unrelated claims about one object). Pick the properties that serve the movement's job and drop the rest. Direction follows from the same logic: entrances ease **out** (fast in, gentle settle — the object arrives), exits ease **in** (gentle start, decisive departure), on-screen moves ease **in-out**, and impacts — a stamp, a slam — ease **in**, because an impact is an acceleration.
|
||||
5. **Overshoot and follow-through.** Overshoot is a claim about mass: a thing with weight passes its target and settles back, and the parts it drags — a shadow, a trailing panel, the housing it lands in — resolve a beat *after* it does, not with it. That one-frame lag is what sells the weight; perfectly synchronized "follow-through" is just a bigger single object. Apply it to **transforms only**. A number is not a physical object, so a counter must never fly past its value and fall back — that renders a figure that was never true, and on a real metric it's a lie with a frame count.
|
||||
6. **Depth planes.** Move each plane at a rate that matches its distance — far background at a fraction of the camera's rate, content at full rate, foreground at several times. The cue that does the most work isn't blur, it's **occlusion**: one large, heavily-blurred foreground element that actually crosses in front of your content proves there is space between them in a way no amount of blur on an element that never overlaps can. Prefer one such element over two — a second stops reading as the room and starts reading as decoration.
|
||||
7. **Match pacing to genre.** Showreel-style cuts run 1.5-4 seconds per idea; a stretched 8-second version of a 2-second idea feels slow no matter how it's animated.
|
||||
8. **Handmade imperfection stays reproducible.** A stop-motion or hand-animated feel wants irregular timing, not machine-smooth eases — but true unseeded randomness breaks the render: HyperFrames must produce the identical frame every time it seeks to the same point, and `Math.random()` can't promise that. Seed a PRNG once at composition start instead, and step discrete elements between held positions (a "two-frame hold": land, hold two frames, jump to the next offset) rather than tweening continuously between them. The irregularity is real — it just comes from a seed, not from chance.
|
||||
- **Direct attention.** Motion pulls the eye to the thing that matters this second — a count landing, a line igniting.
|
||||
- **Carry continuity.** Motion ties one moment to the next, so the piece reads as one space instead of a stack of slides.
|
||||
- **Show change.** Motion *is* the information — a value rising, a diff resolving, a route drawing.
|
||||
- **Express character.** Motion carries the brand's temperament, snappy and confident or calm and editorial.
|
||||
|
||||
A movement doing none of those is decoration. Decoration is what makes a video read as cheap, no matter how smooth the easing is.
|
||||
|
||||
Product-UI motion frameworks name a fifth job, *feedback*, which confirms a user's action. Video has no user input, so it does not apply here.
|
||||
|
||||
## Each rule, shown
|
||||
## The eight rules
|
||||
|
||||
One A/B per rule, and the prompt that produces each half. Both halves come from the same base composition — same content, palette, layout and timing — so the two prompts below are the *only* difference between them. That's the point: you can see what the words buy.
|
||||
Once a movement has something to say, these rules make it read well. They govern *execution*. None of them is a reason to add a movement that has no job.
|
||||
|
||||
Each subject is chosen so the rule is *unavoidable* given the content: a live feed that freezes reads as a broken connection, a pipeline wider than the frame forces the camera to move, a split-flap board cannot stop dead because it has mass. The values are pushed far enough to be legible in a six-second loop, but every movement in the applied half can finish the sentence "this moves because…". If you find yourself copying a number here without being able to finish that sentence, you're copying the exaggeration and not the rule.
|
||||
1. Nothing ever fully stops.
|
||||
2. The camera is an actor.
|
||||
3. Overlapping action.
|
||||
4. Compound properties only when they tell one story.
|
||||
5. Overshoot and follow-through.
|
||||
6. Depth planes.
|
||||
7. Match pacing to genre.
|
||||
8. Handmade imperfection stays reproducible.
|
||||
|
||||
Each rule below comes with one A/B pair and the prompt that produces each half. Both halves come from the same base composition — same content, palette, layout and timing. The two prompts are the only difference, so you can see what the words buy.
|
||||
|
||||
Each subject makes its rule *unavoidable*. A live feed that freezes reads as a broken connection. A pipeline wider than the frame forces the camera to move. A split-flap board cannot stop dead, because it has mass.
|
||||
|
||||
The values are pushed far enough to read in a six-second loop. Even so, every movement in the applied half can finish the sentence "this moves because…". Copy a number here without finishing that sentence and you copied the exaggeration, not the rule.
|
||||
|
||||
### Rule 1 — Nothing ever fully stops
|
||||
|
||||
Every "hold" carries a little ambient idle motion — a 1-2% breathing scale, a slow drift, a shimmer. Never write "holds motionless." Write "settles into a gentle ambient idle." A frozen final second is the single biggest cheap-motion tell.
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> 6-second 1920x1080 live telemetry panel: an `ARR · LIVE` eyebrow with a status dot, `PROD · US-EAST-1` opposite, `$4.200M` at display scale with a delta chip, a sparkline of the reading history beneath, and a footer row reading `LAST SYNC` and `READINGS`. The panel rises in and lands by 1.9s, then holds motionless to the end — the dot stops, the figure freezes, the sparkline stops extending, the footer stops updating.
|
||||
@@ -61,17 +69,21 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> 6-second 1920x1080 live telemetry panel: an `ARR · LIVE` eyebrow with a status dot, `PROD · US-EAST-1` opposite, `$4.200M` at display scale with a delta chip, a sparkline of the reading history beneath, and a footer row reading `LAST SYNC` and `READINGS`. The panel rises in and lands by 1.9s, then stays alive because the feed is — a new reading arrives every 300ms and each one moves exactly what it should: the last digits change, the sparkline extends one segment with its leading dot, `LAST SYNC` restamps, `READINGS` increments. The status dot keeps a 1s heartbeat because the connection is up. Nothing else moves. Fix the sparkline's y-scale up front so an arriving reading never rescales the axis.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule1 Nothing Stops"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule1-nothing-stops.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*Left `WITHOUT`, right `WITH` — from 2.0s the left's 120 remaining frames share a single hash; all 180 of the right's are distinct. Every movement on the right answers "because": the dot beats because the connection is up, the digits change because a reading arrived, the sparkline extends because that reading is now history. That's the test — not "is there enough motion" but "can each thing that moves say why".*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule1-nothing-stops.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH`. From 2.0s the left half's remaining 120 frames all share a single hash. All 180 frames on the right are distinct. Every movement on the right answers "because": the dot beats because the connection is up, the digits change because a reading arrived, the sparkline extends because that reading is now history.*
|
||||
|
||||
*The clearest artifact is the footer. The frozen half reads `LAST SYNC 09:41:21.75 · READINGS 22` while the live one reads `09:41:23.95 · 30`. A still frame of the left half looks like a perfectly good stat card; the moment it sits next to a running one you can read exactly how long the feed has been dead. That is what a frozen hold communicates to a viewer, whether or not you meant it.*
|
||||
*The footer is the clearest tell. The frozen half reads `LAST SYNC 09:41:21.75 · READINGS 22`. The live one reads `09:41:23.95 · 30`. A still frame of the left half looks like a perfectly good stat card. Sit it next to a running one and you can read exactly how long the feed has been dead. That is what a frozen hold tells a viewer, whether you meant it or not.*
|
||||
|
||||
### Rule 2 — The camera is an actor
|
||||
|
||||
Give each scene one continuous camera move. A 4-8% push-in, a slow orbit, or parallax — nearer layers travelling further than distant ones. Ease it gently, and never let it *decay* to a dead stop at the end of a scene. Compute the ease over a window slightly longer than the render.
|
||||
|
||||
The reason is not that stillness looks cheap. A move is what carries the **spatial relationship** between two shots. A cut or a cross-fade throws that away, so two views of one system read as two unrelated pictures. Move to get somewhere and the audience infers one continuous space for free.
|
||||
|
||||
Beware the corollary. Translating the scene and translating the camera are the *same transform*. "The content slid past a fixed frame" is not a static alternative. It is the same shot.
|
||||
|
||||
A deliberate mid-film rest is a different thing, and it is sanctioned. See [avoiding the slideshow](#avoiding-the-slideshow).
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> …an isometric deploy pipeline of five stages, each with its own accent colour — `SOURCE` mint, `BUILD` cyan, `TEST` violet, `DEPLOY` amber, `EDGE` rose — bolted along a rail with support posts and numbered floor bays. The block at each stage and its card wear the same colour. **The camera never moves.** Stack all five stages on the same centred spot and cross-fade between them: the current card fades out as the next fades in already coloured, and the block beneath it changes to that colour. No wire and no work in transit. Nothing in the frame changes position at any point.
|
||||
@@ -80,19 +92,21 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> …an isometric deploy pipeline of five stages, each with its own accent colour — `SOURCE` mint, `BUILD` cyan, `TEST` violet, `DEPLOY` amber, `EDGE` rose — bolted along a rail with support posts and numbered floor bays. The block at each stage and its card wear the same colour. Lay the stages out where they really are, 640px apart across 2.5 frame widths, and **move the camera** across the whole run in one continuous linear pass, `ease: none`. The floor and rail travel with the camera 1:1 because they are the same space; distant structures track at a fifth. A packet of work runs the wire and ignites each stage in its colour as it reaches it, so a stage is revealed by arriving at it rather than by appearing.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule2 Camera Actor V2"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule2-camera-actor-v2.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*Left `WITHOUT`, right `WITH` — same five stages, same colours, same six seconds. On the left the block never leaves the centre of frame: five stages, one place. On the right you travel to each one, and neighbours stay visible on the way, so the five read as five points along a single run.*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule2-camera-actor-v2.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH` — same five stages, same colours, same six seconds. On the left the block never leaves the centre of frame: five stages, one place. On the right you travel to each one, and neighbours stay visible on the way, so the five read as five points along a single run. Watch the amber `DEPLOY` block still sitting behind frame-left as the camera settles on rose `EDGE`. That adjacency is information, and a dissolve does not carry it.*
|
||||
|
||||
*The rule is usually stated as "give every scene a camera move," which undersells it. The real claim is about what a **cut throws away**. A move carries the spatial relationship between two shots; a cross-fade carries none, so the colour simply becomes a different colour where it stood. Watch the amber `DEPLOY` block still sitting behind frame-left as the camera settles on rose `EDGE` — that adjacency is information, and it does not survive a dissolve.*
|
||||
|
||||
*There is a trap in this one worth knowing: **translating the scene and translating the camera are the same transform.** Earlier versions of this demo had the locked half slide the content past a fixed frame, then step it, then freeze the backdrop behind it — and every one looked identical to the pan, because it was. Measured, 3% of pixels differed. Only refusing to move at all produces the 30–47% here. If your "static" comparison still moves the content, you have built the same shot twice.*
|
||||
*One trap is worth knowing. Earlier versions of this demo had the locked half slide the content past a fixed frame, then step it, then freeze the backdrop. Every one looked identical to the pan, because it was — measured, 3% of pixels differed. Only refusing to move at all produces the 30–47% you see here.*
|
||||
|
||||
### Rule 3 — Overlapping action
|
||||
|
||||
No two elements share a start time or an end time. Stagger entrances at irregular offsets. Begin the next element while the last is still settling.
|
||||
|
||||
The mechanism is a ratio. **Each offset should be clearly shorter than the animation it offsets.** A small offset-to-duration ratio reads fluid. A large one reads deliberate. An offset longer than the animation stops being a stagger and becomes a queue.
|
||||
|
||||
Delay the *supporting* elements, never the focal one. A focal element that waits reads as lag.
|
||||
|
||||
Stagger what is **happening**, not what is merely **present**. Things that arrive in sequence should animate in sequence. A set of things that already existed before the shot started should appear together. Staggering those just makes the scene take longer to assemble.
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> …`Rolling out to every region` over a dotted world map with twelve edge regions marked and a live `n / 12` tally. A progressive rollout takes each region live: the region's core ignites, its dot field turns mint, and the tally counts. All twelve ignite at the same moment, 0.5s each.
|
||||
@@ -101,17 +115,19 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> …`Rolling out to every region` over a dotted world map with twelve edge regions marked and a live `n / 12` tally. A progressive rollout spreads outward from `us-east-1`, reaching each region in order of its distance from the origin, 0.09s apart: a link draws from the nearest already-live region and lands exactly as that region's core ignites, its dot field turns mint, a ping carries onward and an ACK dash returns up the link. Each region's 0.5s ignite is still running when the next three begin.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule3 Overlapping Action"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule3-overlapping-action.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule3-overlapping-action.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH` — twelve distinct ignition frames on the right, 0.088s apart, against a single frame on the left. At peak, all twelve regions are animating at once.*
|
||||
|
||||
*Stagger here isn't a styling preference, it's the difference between a true statement and a false one. A rollout propagates; twelve regions cannot go live on the same frame, and the simultaneous version says something about the system that isn't true. Watch the tally: the left jumps `0 → 12` in one frame and the map gives you no way to tell where the rollout started, because there is no order to read. Note also what is **not** staggered — the twelve region markers all appear on the same frame in both halves, because the regions already existed. Stagger what is happening, not what is merely present.*
|
||||
*Stagger here is not a styling preference. A rollout propagates, so twelve regions cannot go live on the same frame. The simultaneous version says something untrue about the system. Watch the tally: the left jumps `0 → 12` in one frame, and the map gives you no way to tell where the rollout started.*
|
||||
|
||||
*Note what is **not** staggered. The twelve region markers all appear on the same frame in both halves, because the regions already existed.*
|
||||
|
||||
### Rule 4 — Compound properties only when they tell one story
|
||||
|
||||
Every animation changes at least one property — position, scale, opacity, rotation. Combining them works when they say the same thing. A confirmation that slides up *and* fades in reads as one idea: this is arriving. A sheet that rises *and* rotates makes two unrelated claims about one object. Pick the properties that serve the movement's job and drop the rest.
|
||||
|
||||
Direction follows the same logic. Entrances ease **out** — fast in, gentle settle, because the object arrives. Exits ease **in** — gentle start, decisive departure. On-screen moves ease **in-out**. Impacts like a stamp or a slam ease **in**, because an impact is an acceleration.
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> …an itemized coffee-shop receipt; a payment-confirmation sheet rises 470px over 1.2s to cover its lower half while simultaneously rotating in from 18°, scaling up from 0.8, and un-blurring from 14px. The receipt dims behind it. The sequence plays twice.
|
||||
@@ -120,15 +136,15 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> …an itemized coffee-shop receipt; a payment-confirmation sheet rises 470px over 1.2s to cover its lower half while fading up from 0. Rotation, scale and blur are never touched. The receipt dims behind it. The sequence plays twice.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule4 Compound Properties"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule4-compound-properties.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*Left `WITHOUT`, right `WITH` — identical travel (470px), identical window (0.25s → 1.45s), identical ease. The only difference is how many properties ride along. Both halves are pixel-identical at rest, so everything you see mid-flight is the cost of the extra three. On the left, "Payment confirmed" is an unreadable tilted smear for most of its arrival; on the right it is legible the whole way up. Two properties agreeing on one claim — this is arriving — beat four properties each making a different one.*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule4-compound-properties.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH` — identical travel (470px), identical window (0.25s → 1.45s), identical ease. The only difference is how many properties ride along. Both halves are pixel-identical at rest, so everything you see mid-flight is the cost of the extra three. On the left, "Payment confirmed" is an unreadable tilted smear for most of its arrival. On the right it is legible the whole way up. Two properties agreeing on one claim beat four properties each making a different one.*
|
||||
|
||||
### Rule 5 — Overshoot and follow-through
|
||||
|
||||
Overshoot is a claim about mass. A thing with weight passes its target and settles back. The parts it drags — a shadow, a trailing panel, the housing it lands in — resolve a beat *after* it does, not with it. That one-frame lag is what sells the weight. Perfectly synchronized "follow-through" is just a bigger single object.
|
||||
|
||||
Apply it to **transforms only**. A number is not a physical object. A counter must never fly past its value and fall back. That renders a figure that was never true, and on a real metric it is a lie with a frame count.
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> …a bolted airport departure board, `HF 402 · SAN FRANCISCO`, its split-flap gate cells riffling to **42**. The flap plates drop 34px and rotate from −16° straight to their final position and stop. Linear throughout, no overshoot, and the cast shadow moves locked to its plate. The sequence plays twice.
|
||||
@@ -137,15 +153,15 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> …a bolted airport departure board, `HF 402 · SAN FRANCISCO`, its split-flap gate cells riffling to **42**. The flap plates drop 34px and rotate from −16° on `back.out(5)`, swinging past their rest position and settling back; the housing recoils ~7px when they land, and each plate's cast shadow reaches its own peak one frame later than the plate that casts it. Transforms only — the gate number never overshoots **42**. The sequence plays twice.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule5 Overshoot"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule5-overshoot.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*Left `WITHOUT`, right `WITH` — the right's plates swing 1.51× past their travel before settling; every transform on the left has peak == final. The shadow's lag is measured at 0.033s, exactly one frame at 30fps. Note the one thing that does **not** overshoot on either side: the number itself tops out at 42 and stays there. Overshoot is a claim about mass, and a value has none — a counter that flies to 47 and falls back is reporting a number that was never true.*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule5-overshoot.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH` — the right's plates swing 1.51× past their travel before settling, while every transform on the left has peak == final. The shadow's lag is measured at 0.033s, exactly one frame at 30fps. Note the one thing that does **not** overshoot on either side: the number itself tops out at 42 and stays there. A counter that flies to 47 and falls back is reporting a number that was never true.*
|
||||
|
||||
### Rule 6 — Depth planes
|
||||
|
||||
Move each plane at a rate that matches its distance. Far background at a fraction of the camera's rate, content at full rate, foreground at several times.
|
||||
|
||||
The cue that does the most work is not blur. It is **occlusion** — one thing passing in front of another. A single large, heavily blurred foreground element that actually crosses in front of your content proves there is space between them. No amount of blur on an element that never overlaps can do that. Prefer one such element. A second stops reading as the room and starts reading as decoration.
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> …a render-farm corridor: CSS-3D floor and ceiling converging on a single vanishing point, a bank of server racks with blinking status LEDs, a distant rack row near the horizon, and one rack column close to the lens. `CLUSTER 04 · WEST / Every frame, in parallel.` on a camera-locked HUD panel. The camera trucks forward for the full 6s. Every plane drifts at the same rate and nothing is blurred.
|
||||
@@ -154,15 +170,13 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> …a render-farm corridor: CSS-3D floor and ceiling converging on a single vanishing point, a bank of server racks with blinking status LEDs, a distant rack row near the horizon, and one rack column close to the lens. `CLUSTER 04 · WEST / Every frame, in parallel.` on a camera-locked HUD panel. The camera trucks forward for the full 6s and each plane drifts at its own distance: the horizon row at a fifth of the room's rate, the room and its racks at full rate, and the near column sweeping past at six times — `blur(22px)`, close enough to cross in front of the headline. Haze the far plane slightly.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule6 Depth Planes"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule6-depth-planes.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*Left `WITHOUT`, right `WITH` — measured travel ratios on the right are exactly 0.20× / 1.00× / 6.00×; on the left all five planes displace an identical 240px with `filter: none`. The strongest cue isn't the blur, it's the occlusion: the near column crosses in front of the headline for about a third of the shot, which is only possible if it's in the room. One foreground element does this — a second would just be clutter with a blur on it.*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule6-depth-planes.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH` — measured travel ratios on the right are exactly 0.20× / 1.00× / 6.00×, while on the left all five planes displace an identical 240px with `filter: none`. The strongest cue is not the blur, it is the occlusion: the near column crosses in front of the headline for about a third of the shot, which is only possible if it is in the room. One foreground element does this. A second would just be clutter with a blur on it.*
|
||||
|
||||
### Rule 7 — Match pacing to genre
|
||||
|
||||
Showreel-style cuts run 1.5-4 seconds per idea. A stretched 8-second version of a 2-second idea feels slow, no matter how it is animated.
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> …a three-beat product showreel, each beat a titled panel with its own live evidence: `SEEK ANY FRAME` over a scrubbing timeline reading out frame 147, `RENDER ON LAMBDA` over six worker lanes filling their own frame ranges, `SHIP THE MP4` over a player with an encode bar. 2 seconds per beat, dissolving between them, filling the full 6s runtime. A rail under each half tracks time consumed and time spare.
|
||||
@@ -171,15 +185,15 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> …a three-beat product showreel, each beat a titled panel with its own live evidence: `SEEK ANY FRAME` over a scrubbing timeline reading out frame 147, `RENDER ON LAMBDA` over six worker lanes filling their own frame ranges, `SHIP THE MP4` over a player with an encode bar. Showreel pace — 1.1 seconds per beat, cutting hard rather than dissolving, all three delivered by 3.3s, then hold the last beat on a live idle. A rail under each half tracks time consumed and time spare.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule7 Pacing"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule7-pacing.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*Left `WITHOUT`, right `WITH` — same three ideas, same evidence, same 6 seconds. At 3.6s the left is still working through beat 2 with `SPARE 0.00`; the right has already delivered all three and banked `+0.33s`. The left is not badly animated, it is correctly animated at the wrong tempo — which is why this failure survives review so often. Pace is a genre decision you make before you touch an ease.*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule7-pacing.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH` — same three ideas, same evidence, same 6 seconds. At 3.6s the left is still working through beat 2 with `SPARE 0.00`. The right has already delivered all three and banked `+0.33s`. The left is not badly animated. It is correctly animated at the wrong tempo, which is why this failure survives review so often. Pace is a genre decision you make before you touch an ease.*
|
||||
|
||||
### Rule 8 — Handmade imperfection stays reproducible
|
||||
|
||||
A stop-motion or hand-animated feel wants irregular timing, not machine-smooth eases. But true unseeded randomness breaks the render. HyperFrames must produce the identical frame every time it seeks to the same point, and `Math.random()` cannot promise that.
|
||||
|
||||
Instead, seed a PRNG once at composition start. A PRNG is a pseudo-random number generator: a formula that returns the same "random" sequence every time from the same starting number. Then step discrete elements between held positions rather than tweening continuously between them. A "two-frame hold" means land, hold two frames, jump to the next offset. The irregularity is real. It just comes from a seed, not from chance.
|
||||
|
||||
❌ **Without**
|
||||
|
||||
> …a hanging paper mobile: five cut-paper shapes on fine visible wires from a wooden dowel, each with real paper grain, irregular hand-cut edges and its own layered drop shadow. They sway through a ±12° arc with smooth continuous tweens. A readout shows the current frame and how many distinct states the six seconds contain.
|
||||
@@ -188,51 +202,47 @@ Each subject is chosen so the rule is *unavoidable* given the content: a live fe
|
||||
|
||||
> …a hanging paper mobile: five cut-paper shapes on fine visible wires from a wooden dowel, each with real paper grain, irregular hand-cut edges and its own layered drop shadow. They sway through the same ±12° arc, but driven by a mulberry32 PRNG seeded once at composition start and stepped on a two-frame hold quantized to the integer frame index — so the cadence is stop-motion, not a tween, and every render is byte-identical. A readout shows the current frame and how many distinct states the six seconds contain.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Rule8 Seeded Handmade"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule8-seeded-handmade.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
*Left `WITHOUT`, right `WITH` — the right holds each pose for exactly two frames: 90 states across 180 frames, no irregular holds, while the left has 180 distinct ones. Rendered twice, the whole thing came back byte-identical both times. This is the rule that is easy to get wrong in the wrong direction — reaching for `Math.random()` to buy the same irregularity produces a composition that renders differently every time and can never be re-rendered or resumed.*
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-rule8-seeded-handmade.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Left `WITHOUT`, right `WITH` — the right holds each pose for exactly two frames: 90 states across 180 frames, no irregular holds, while the left has 180 distinct ones. Rendered twice, the whole thing came back byte-identical both times. This rule is easy to get wrong in the other direction. Reaching for `Math.random()` to buy the same irregularity produces a composition that renders differently every time and can never be re-rendered or resumed.*
|
||||
|
||||
[Handmade, still deterministic](#handmade-still-deterministic) below has a fuller prompt for this, plus the one trap that catches people.
|
||||
|
||||
## The grammar, measured
|
||||
|
||||
The same composition built twice — identical content, identical layout; the only variable is the motion treatment. The first build commits the anti-patterns above: simultaneous identical entrances, no camera, a frozen final 2.4 seconds. The second applies the eight rules.
|
||||
The same composition built twice. Identical content, identical layout, and the motion treatment as the only variable. The first build commits the anti-patterns above: simultaneous identical entrances, no camera, a frozen final 2.4 seconds. The second applies the eight rules.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Before"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-before.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-before.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Every anti-pattern: tiles enter together, nothing overlaps, the last seconds are a freeze-frame.*
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion After"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-after.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-after.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*The eight rules: staggered overshoot entrances, a continuous 5% push, ambient idle, a blurred foreground plane, sparkline draw-ins as secondary motion.*
|
||||
|
||||
The difference is measurable, not just visible: in the final second the frozen build has bit-identical consecutive frames, while the motion-grammar build changes every single frame — corroborated by the encoder (211KB vs 2.5MB for the same content). A frozen final second is the cheap-motion tell you can test for.
|
||||
The difference is measurable, not just visible. In the final second the frozen build has bit-identical consecutive frames, while the motion-grammar build changes every single frame. The encoder agrees: 211KB against 2.5MB for the same content. A frozen final second is the cheap-motion tell you can test for.
|
||||
|
||||
## Avoiding the slideshow
|
||||
|
||||
The eight rules govern motion *inside* a scene. Apply all eight, cut between scenes, and you can still end up with a slideshow: a sequence of well-animated cards that happen to play in order. It's the most common note on multi-scene work, and the rebuild passes on the [Level 7 film](/prompting/capstone) after its slideshow diagnosis are what taught us the two properties — neither fix was more animation.
|
||||
The eight rules govern motion *inside* a scene. Apply all eight, cut between scenes, and you can still end up with a slideshow — a run of well-animated cards that happen to play in order. It is the most common note on multi-scene work.
|
||||
|
||||
**What a slideshow actually is:** a run of scenes that are *independent* (nothing connects one to the next) and *uniform in energy* (nothing tells you which moment matters). So any fix has to supply two properties:
|
||||
A slideshow is two failures at once. The scenes are *independent*, so nothing connects one to the next. And they are *uniform in energy*, so nothing tells you which moment matters. Any fix has to supply two properties:
|
||||
|
||||
1. **Something crosses the boundary.** Consecutive scenes need a shared element, space, or motion the eye can follow through the transition — otherwise the viewer re-orients at every seam and reads slides.
|
||||
2. **The energy varies.** The piece needs quiet against which a hero moment can register. Uniform motion is as flat as no motion; emphasis is a *contrast* effect.
|
||||
1. **Something crosses the boundary.** Consecutive scenes need a shared element, space, or motion the eye can follow through the transition. Otherwise the viewer re-orients at every seam and reads slides.
|
||||
2. **The energy varies.** The piece needs quiet against which a hero moment can register. Uniform motion is as flat as no motion. Emphasis is a *contrast* effect.
|
||||
|
||||
Ask for those two properties, not for a specific device. Below is the device we used, then several others that satisfy the same two properties differently — pick whichever fits your piece.
|
||||
Ask for those two properties, not for a specific device. The rebuild passes on the [Level 7 film](/prompting/capstone), after its slideshow diagnosis, are what taught us this. Neither fix was more animation.
|
||||
|
||||
### One method: a continuous world-camera
|
||||
|
||||
The capstone treats the whole film as a single wide space the camera travels across, with three elements that never leave frame and a dwell-and-sweep rhythm on the camera itself.
|
||||
The capstone treats the whole film as a single wide space the camera travels across. Three elements never leave frame, and the camera keeps a dwell-and-sweep rhythm.
|
||||
|
||||
For property 1, "no hard cuts" was not enough on its own — the first rebuild honored it and still played as slides, because each region faded up centered, sat, and faded out while the camera politely waited. What worked was naming the things that survive the boundary: **persistent elements** (a wire that becomes the chart baseline, then the map arc, then the waveform, then the 3D coil; a frame-ruler; a protagonist chip), **arrivals instead of cuts** (the next region is already at the edge of frame before the camera reaches it; the previous exits by parallax), and **a negative that names the tell** (agents default to the slideshow because it is safe — forbid it explicitly).
|
||||
For property 1, "no hard cuts" was not enough on its own. The first rebuild honored it and still played as slides, because each region faded up centered, sat, and faded out while the camera politely waited. What worked was naming the things that survive the boundary:
|
||||
|
||||
For property 2, the second rebuild fixed continuity and broke emphasis: the camera never stopped, so every hero moment landed mid-sweep and got thrown away. The fix was a **dwell** — a genuine 1.5–2.5s full stop while the point lands. Two subtleties keep this consistent with the rules above. Against rule 1: during a dwell the **camera** rests, the **film** does not — counters keep ticking, labels keep stamping. Say that, or you trade a slideshow for a freeze-frame. Against rule 2: a dwell is a mid-film rest the piece resumes from, not the decayed settle that rule guards against — what rule 2 forbids is a camera that runs out of energy and dies on the last frame.
|
||||
- **Persistent elements.** A wire that becomes the chart baseline, then the map arc, then the waveform, then the 3D coil. Plus a frame-ruler and a protagonist chip.
|
||||
- **Arrivals instead of cuts.** The next region is already at the edge of frame before the camera reaches it, and the previous one exits by parallax.
|
||||
- **A negative that names the tell.** Agents default to the slideshow because it is safe, so forbid it explicitly.
|
||||
|
||||
For property 2, the second rebuild fixed continuity and broke emphasis. The camera never stopped, so every hero moment landed mid-sweep and got thrown away. The fix was a **dwell**: a genuine full stop of 1.5–2.5 seconds while the point lands.
|
||||
|
||||
Two details keep a dwell consistent with the rules above. During a dwell the **camera** rests but the **film** does not — counters keep ticking, labels keep stamping. Say that, or you trade a slideshow for a freeze-frame. And a dwell is a rest the piece resumes from, not the decayed settle rule 2 guards against.
|
||||
|
||||
<Note>
|
||||
**Capstone thread** — this is the method the [Level 7 film](/prompting/capstone) uses, and the reason it reads as a film instead of nine demos in a row.
|
||||
@@ -242,14 +252,10 @@ This is the clause in the [full capstone prompt](/prompting/capstone#the-full-pr
|
||||
|
||||
> ONE continuous camera journey with a dwell-and-sweep rhythm. The whole film is a single wide world (one large scene space) that the camera dollies across […] in one unbroken move with NO hard cuts anywhere except inside the two sanctioned seam moments named below. The rhythm: the camera SWEEPS between regions, then eases to a genuine REST at each region's hero moment — a full stop of roughly 1.5–2.5 seconds while the main point lands […] then accelerates away. The dwell is what gives the sweep meaning: without the rest, emphasis dies mid-motion. During a dwell the WORLD keeps resolving (counters tick, labels stamp, secondary motion continues) — the camera rests, the film never freezes.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Capstone Region Camera"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/capstone-region-camera.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/capstone-region-camera.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*That clause, rendered: accelerating out of one region, arriving in the next, stopping to let the beat land — no cut anywhere in the span.*
|
||||
|
||||
This method is expensive. One continuous space is more to author than a cut, and it only pays when the piece has a through-line worth following. For a 6-second stat card, cut.
|
||||
This method is expensive. One continuous space is more work to author than a cut, and it only pays when the piece has a through-line worth following. For a 6-second stat card, cut.
|
||||
|
||||
### Other methods worth trying
|
||||
|
||||
@@ -258,35 +264,45 @@ Each of these supplies both properties by different means. They also compose —
|
||||
| Method | How it crosses the boundary | Where the emphasis comes from |
|
||||
| --- | --- | --- |
|
||||
| **Match cuts** ([Transitions](/prompting/transitions)) | Cut freely, but on a shared shape, color, or motion vector, so the eye carries a form through the seam | Break the rhyme deliberately at the moment that matters |
|
||||
| **A hero prop** ([Storyboards](/prompting/storyboards)) | Cut the environment, keep one object continuously on screen (the product, a chart, a character) | Push in on the prop and still everything else around it |
|
||||
| **A persistent frame** | Scenes change *inside* a frame that never does — a HUD, a ruler, a browser shell, a border | Let the chrome fall away for one beat; its absence is the accent |
|
||||
| **Audio as the spine** ([Media and audio](/prompting/media-and-audio)) | One unbroken sentence or musical phrase crosses the cut; the ear ties what the eye didn't | A rest in the audio — a held note, a beat of silence before the number lands |
|
||||
| **A hero prop** ([Storyboards](/prompting/storyboards)) | Cut the environment, keep one object continuously on screen — the product, a chart, a character | Push in on the prop and still everything else around it |
|
||||
| **A persistent frame** | Scenes change *inside* a frame that never does — a HUD, a ruler, a browser shell, a border | Let the chrome fall away for one beat. Its absence is the accent |
|
||||
| **Audio as the spine** ([Media and audio](/prompting/media-and-audio)) | One unbroken sentence or musical phrase crosses the cut, so the ear ties what the eye did not | A rest in the audio — a held note, or a beat of silence before the number lands |
|
||||
| **Beat-locked cutting** | Cuts land on an analyzed grid, so the rhythm itself is the connective tissue ([Music](/prompting/music-and-slideshows)) | Hold through a downbeat, or drop a hit, so one card gets twice the space |
|
||||
| **Accumulation** ([Storyboards](/prompting/storyboards)) | Nothing ever exits — each scene adds to one canvas and the frame reflows to the new material | Everything but the newest element quiets to a low idle |
|
||||
| **Accumulation** ([Storyboards](/prompting/storyboards)) | Nothing ever exits. Each scene adds to one canvas and the frame reflows to the new material | Everything but the newest element quiets to a low idle |
|
||||
|
||||
Whichever you choose, put the two properties in the prompt in your own words, and name the tell you are forbidding. The device is negotiable; the properties are not.
|
||||
Whichever you choose, put the two properties in the prompt in your own words, and name the tell you are forbidding. The device is negotiable. The properties are not.
|
||||
|
||||
## Handmade, still deterministic
|
||||
|
||||
Rule 1's ambient idle is *continuous* — a slow, smooth breathing scale. Rule 8 is a different tool for a different job: when the brief wants something that reads as physically hand-animated (paper cutouts, stop-motion, a wobbly hand-drawn line), continuous easing is the wrong texture — it reads as digital no matter how organic the curve. What you want is irregular, but reproducible:
|
||||
Rule 1's ambient idle is *continuous* — a slow, smooth breathing scale. Rule 8 is a different tool for a different job. When the brief wants something that reads as physically hand-animated (paper cutouts, stop-motion, a wobbly hand-drawn line), continuous easing is the wrong texture. It reads as digital no matter how organic the curve. What you want is irregular, but reproducible:
|
||||
|
||||
> A paper-cutout garden scene: flowers, leaves, and a bee drifting on visible wires, like stop-motion. Seed a random offset per element at the start (same seed every render) so each leaf sways to a slightly different rhythm, but hold each position for exactly two frames before stepping to the next — discrete jumps, not a smooth tween. It should read as intentionally handmade, not glitchy.
|
||||
|
||||
<DocsVideo
|
||||
title="HyperFrames video: Motion Handmade"
|
||||
src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-handmade.mp4#t=0.1"
|
||||
loop
|
||||
/>
|
||||
<video controls muted loop playsinline preload="metadata" src="https://static.heygen.ai/hyperframes-oss/docs/images/prompting/motion-handmade.mp4#t=0.1" style={{ borderRadius: "0.5rem", marginTop: "0.75rem" }}></video>
|
||||
*Rendered from the prompt above, unedited — paper cutouts on visible wires, every position held for exactly two frames, and byte-identical across two separate renders.*
|
||||
|
||||
Name the seed and the hold length explicitly — "seeded" and "two-frame hold" are the two words that keep an agent from reaching for `Math.random()` and quietly breaking every re-render.
|
||||
Name the seed and the hold length explicitly. "Seeded" and "two-frame hold" are the two phrases that keep an agent from reaching for `Math.random()` and quietly breaking every re-render.
|
||||
|
||||
<Warning>
|
||||
**Ask for the hold in frames, not seconds.** Validating this prompt surfaced a real trap: quantizing the hold in the *time* domain (`Math.floor(t / 0.0667)`) produces irregular holds — a three-frame group here, a one-frame group there — because the renderer's seek times don't land on exact 1/30-second doubles, so the arithmetic drifts across step boundaries. Quantize on the integer frame index instead (`Math.floor(Math.round(t * fps) / 2)`), which tolerates any seek error under half a frame. The visible symptom is a stop-motion cadence that stutters unevenly instead of ticking, and it is worth naming in the prompt: "hold each position for exactly two frames, quantized on the frame index." Note also that a hold specified in frames is coupled to the composition's fps — re-rendering at a different frame rate changes the hold's duration.
|
||||
**Ask for the hold in frames, not seconds.** Validating this prompt surfaced a real trap. Quantizing the hold in the *time* domain (`Math.floor(t / 0.0667)`) produces irregular holds — a three-frame group here, a one-frame group there. That happens because the renderer's seek times do not land on exact 1/30-second doubles, so the arithmetic drifts across step boundaries. Quantize on the integer frame index instead (`Math.floor(Math.round(t * fps) / 2)`), which tolerates any seek error under half a frame. The visible symptom is a stop-motion cadence that stutters unevenly instead of ticking. It is worth naming in the prompt: "hold each position for exactly two frames, quantized on the frame index." Note also that a hold specified in frames is tied to the composition's fps, so re-rendering at a different frame rate changes the hold's duration.
|
||||
</Warning>
|
||||
|
||||
Ask for it, and seeking to frame 214 twice gives you the identical frame twice, exactly like every other HyperFrames render — the imperfection is designed in, not left to chance.
|
||||
Ask for it, and seeking to frame 214 twice gives you the identical frame twice, exactly like every other HyperFrames render. The imperfection is designed in, not left to chance.
|
||||
|
||||
Unseeded randomness is the obvious way to break cold-render correctness, but a handful of GSAP lint rules catch subtler seek-order mistakes — relative tweens colliding with another writer, `repeatRefresh` combined with a relative value, function-valued tween vars, DOM measurement inside timeline callbacks — see the [seek-order safety rows](/prompting/rules-and-anti-patterns#seek-order-safety) in the appendix if the linter ever flags one.
|
||||
Unseeded randomness is the obvious way to break a cold render. A handful of lint rules catch subtler seek-order mistakes in GSAP, the animation library HyperFrames drives:
|
||||
|
||||
- a relative tween colliding with another writer
|
||||
- `repeatRefresh` combined with a relative value
|
||||
- function-valued tween vars
|
||||
- DOM measurement inside timeline callbacks
|
||||
|
||||
See the [seek-order safety rows](/prompting/rules-and-anti-patterns#seek-order-safety) in the appendix if the linter ever flags one.
|
||||
|
||||
*Next: [Transitions](/prompting/transitions) — the same grammar applied to the cut between scenes, not just the motion inside one.*
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Transitions between scenes](/prompting/transitions)
|
||||
- [The Level 7 capstone film](/prompting/capstone)
|
||||
- [Rules and anti-patterns](/prompting/rules-and-anti-patterns)
|
||||
- [Plan scenes with a storyboard](/prompting/storyboards)
|
||||
|
||||
Reference in New Issue
Block a user