The HyperFrames docs teach you the API; this file is about taste. The numbers below come from a work that actually shipped—treat them as defaults: deviating is fine, but it must be deliberate, not just an oversight.
Adapted from whaleyxbt/claude-motion's
motion-designskill (MIT, seeLICENSEin the same folder). The original targets Remotion; this version is rewritten for HyperFrames (HTML + GSAP) and adds Traditional Chinese typography rules. Sections marked "(new in this version)" are not from the original.
When used within the motion-explainer series workflow (new in this version)
motion-explainer(and -images、-video、soil-motion-deck) using the official faceless-explainer workflow, the following three things follow that workflow; this file's approach does not apply:
- Timing: beats come from
STORYBOARD.mdandaudio_meta.json(the timing of narration clauses), and each frame'sdata-start/data-durationis written byassemble-index.Do not create a separatetimeline.js/window.TL(Section 1). - Creating the project: use
npx hyperframes init(see motion-explainer Section 3), not the "workflow" section's approach. - Fonts and colors: determined by the project's
theme.json(default Source Han Sans Bold, three-color theme);theme.pyoverrides the fonts and palettes written in each frame. The font family suggestions in Sections 5 and 6 apply only when there is notheme.json.
The rest of the taste rules (easing, spring, reading dwell, composition, Chinese typography, accent color only for emphasis) apply as usual.
Division of labor with the official HyperFrames skills (new in this version)
HeyGen's official HyperFrames skills are also installed locally (hyperframes、hyperframes-core、hyperframes-animation etc.). The division of labor is as follows:
- Official skills govern "how to do it": project structure,
data-*attributes, API usage, workflow routing, ready-made registry components. When API usage conflicts with this file, the official one prevails. - This file governs "taste": easing, rhythm, dwell time, fonts, color, Chinese typography. When using the official
hyperframes-animationrules / blueprints, the timing and easing numbers still default to this file. - Checking: always go through
review-loop, which already includes the officialhyperframes check。
0. Hard rules of HyperFrames (new in this version)
Violating these makes the picture flicker, misalign, or differ on every output:
-
Timing is determined by HTML: each scene is a
class="clip"element, placed on the timeline withdata-start、data-duration. The root element must havedata-composition-idand an explicitdata-duration。 -
Motion is handled by GSAP:
gsap.timeline({ paused: true }), registered towindow.__timelines["<composition-id>"], the key name must equaldata-composition-id。 -
Give every important tween an explicit position (the third argument); when both ends matter, use
fromTo(), because output seeks around skipping. -
Only animate
transformandopacity, do not repeatedly animatetop/left/width/height。 -
Prohibited: CSS
transition/@keyframes、Date.now()、requestAnimationFrame、setTimeout, no seededMath.random(), resources fetched only midway through the output. -
Do not use script to play, pause, or seek audio/video, and do not use script to show or hide clips based on time. Leave all of that to data attributes. To move a video, move the outer wrapper element.
-
Always use a seeded function for randomness:
function rand(seed) { let t = (seed += 0x6D2B79F5); t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }
1. Time in one place
-
Every beat is a named point in time (seconds), grouped by scene (
s1、s2…), written intimeline.js:window.TL = { s1: { start: 0, title: 0.3, items: [1.2, 1.45, 1.7], exit: 3.6 }, s2: { start: 3.4, drawOn: 3.5, hook: 5.2, end: 8 }, };in
index.html, loaded with<script src="timeline.js"></script>before all composition scripts. -
Always take GSAP position parameters from
TL(TL.s1.title、TL.s1.exit + 0.05relative notation like this is fine); do not hardcode seconds in scenes. -
The scene wrapper's
data-start/data-durationmust match the scene boundaries ofTL. When retiming, first changetimeline.js, then update these two attributes to match. -
Place sound effects and narration with
<audio data-start="…">on the same set of beats. The review-loop screenshot times are also taken fromTL, so picture, sound, and checks move together. -
All visuals must be determined solely by time; see section 0.
2. Easing
The original's four curves are precisely rebuilt in GSAP with CustomEase . To load more gsap@3/dist/CustomEase.min.js, then register with gsap.registerPlugin(CustomEase) :
CustomEase.create("enter", "0.16,1,0.3,1"); // easeOut
CustomEase.create("exit", "0.6,0,0.9,0.35"); // easeIn
CustomEase.create("move", "0.65,0,0.35,1"); // easeInOut
CustomEase.create("land", "0.55,0,0.2,1.12"); // overshoot
| Curve | Use | Closest built-in name |
|---|---|---|
enter = bezier(0.16, 1, 0.3, 1) |
Entrances, reveals, any "arrival" | expo.out(nearly identical) |
exit = bezier(0.6, 0, 0.9, 0.35) |
Exits: fast, no dragging | approx. power3.in |
move = bezier(0.65, 0, 0.35, 1) |
Movement between two states (flip, dock, morph) | power2.inOut(identical) |
land = bezier(0.55, 0, 0.2, 1.12) |
Values that "settle" (slider climbing to a slot) | No good built-in substitute; use CustomEase |
ease: "none"(linear) only for continuous motion: rotation, drift, particles, second hand.
To make one animation follow another value (for example, subtitles following slider position), use the same tween to drive a proxy object, deriving in onUpdate ; do not give it another independent clock.
3. Spring
The original uses Remotion's physical springs; GSAP has no spring physics. The table below shows the approximations chosen for this version (added in this version, not the original's numbers). After output, use review-loop to confirm the feel:
| Feel | GSAP setting | Use for |
|---|---|---|
| Default settle | back.out(1.2),0.6 s |
General UI entrance |
| Heavy, steady | power3.out,0.8–1.0 s |
Large panels, flip back |
| Knob snapping | back.out(1.7),0.35 s |
Controls jumping to a slot |
| Pop | back.out(2.2),0.45 s |
Callouts, badges, success dots |
| Spring accent | elastic.out(1, 0.4),0.9 s |
The climax moment used only once per video |
Use springs for things with physicality (UI, objects); use Bezier curves for text and camera-like movement.
4. Rhythm
- Stagger: English words about 0.09 s; Chinese character by character about 0.04–0.05 s, word by word about 0.09 s (the Chinese part is new in this version); list items and labels about 0.2–0.25 s; rings and dots about 0.06 s.
- Text revealAbout 0.8 s, with a mask: outer layer
overflow: hidden, inner layeryPercent: 100 → 0, plus a bit of rotation (rotation: 3 → 0)。 - The exit is about half the entrance(0.35–0.55 s vs. 0.7–1.25 s); for the exit use the
exitcurve. - Scenes must overlap: the next scene starts drawing in before the previous one has fully left (e.g.
s2.drawOn4.02 s is earlier thans1.flipEnd4.4 s). A hard cut to blank looks like it's "loading." - TypingEach character takes about 0.035 s; when idle, the cursor blinks at about 2.4 Hz (use
stepsorsetscheduled on the timeline, don't use a timer). - Reading dwell: every state meant to be read must hold once it settles. At least 1 s for a short sentence; about 0.25 s more for each word beyond three; for Chinese, about 0.25 s more for each 4 additional characters (new in this version). The final closing line must be at least 2 s. The original 22-second version exists precisely because the 15-second version flashed its text too fast: when in doubt, hold longer and move less.
5. Fonts
- At most three type families: a display serif, a UI sans-serif, and a monospace (for labels, prompts, numbers).
- English (original): Instrument Serif / Inter / JetBrains Mono.
- Traditional Chinese (new in this version): Noto Serif TC / Noto Sans TC, with JetBrains Mono for numbers and labels. Put the font files in the project's
assets/fonts/, load them with@font-face, and don't rely on system fonts (they'll differ on another computer).
- English display type should be large (about 138 px on a 1080-wide canvas), with tighter tracking (
-0.02em),line-height: 1。 - Chinese display type (new in this version): about 96–120 px at 1080 wide, no more than about 12 characters per line, tracking from
0to0.02em(no negative tracking for Chinese),line-height: 1.15–1.25。 - one focal point per line: in English, use a single italic word in the accent color; Chinese has no true italics, so carry the emphasis with the accent color plus heavier weight (new in this version).
- Monospace labels: uppercase,
letter-spacing: 0.2–0.24em, color muted. They're just decoration; make anything the audience must read big. - Mobile test: at 360 px wide (the size actually seen in social feeds), the key message must still be legible. Check with a contact sheet.
6. Color
- (new in this version) Set the tone for the whole video withthree colors, decided by the user like a presentation template: background color
bg, primary colorfg(text and line art), accent coloraccent, written in the project'stheme.json, applied with themotion-explainerseries skill'stheme.py. Use the accent color only for "answers, conclusions, key numbers, section cards." - Define the palette as CSS variables in
:root(--bg、--ink、--muted、--accent…); don't let new color codes appear in scenes. - There's only one accent color, and it means something: the thing that changed, the answer, the winner. If everything is accent, nothing is.
- Glow is driven by state, not time (
glow = effort): light must tell the story.
7. Texture: nothing is completely still
- Film grain: overlay an SVG
feTurbulencefilter at about 0.09 opacity, changingseedevery two frames (scheduled on the timeline withset, seed computed from the frame number). - Vignette + dotted grid, drifting a few px per second:
radial-gradientvignette, with the grid using a single linear tween spanning the whole video. - Hand-drawn "line jitter" (
feTurbulence+feDisplacementMap, also changing the seed every two frames): use it for a draft-like, low-completion feel, and fade it out as quality improves. - Idle elements should breathe: a clock that ticks, a blinking cursor, a slow rotation.
8. Continuity and transitions
- Prefer letting objects persist over hard cuts. In the original, the cue bar, sparks, and slider travel across scenes and change roles; that is the difference between "designed" and "cobbled together." In HyperFrames, cross-scene objects go in the root composition, not inside any one scene.
- Every transition should be carried by an object on screen (five result dots merging into one, then becoming a halo). No stock wipe transitions.
- Use velocity: derive motion trails and squash from "the difference between this frame's value and the previous frame's," don't fake it.
- One climax moment per video (halo + impact). Build all the way up to it, don't use it up too early.
9. Anti-patterns
- Everything fading in at once, or everything using the same duration.
- Using linear easing on things that start and stop.
- Text still moving when it should be read.
- More than one focal point in a single frame.
- Content less than about 60 px from the frame edge (safe area), unless it's a deliberate crop.
- Frame 0 is blank or all black: when social platforms autoplay muted, frame 0 is the thumbnail.
- (New in this version) Controlling audio/video playback or clip visibility in script, using CSS animations, using unseeded randomness, see Section 0.
Workflow
- First write the beats into
timeline.js(a storyboard written in numbers), then confirm the rhythm with the user before starting. - Use
npx hyperframes init <專案資料夾>to create the project; each scene is a composition incompositions/, and shared components and cross-scene objects go inindex.html。 - After every meaningful change, run the
review-loopskill. - Handle audio only after the visual timing is locked: sound effects and music should match the beats of
<audio data-start>withTL. Leave the accent and emotion of Chinese narration for the user to judge by ear; don't substitute ASR or similarity scores (new in this version).