Files
heygen-com--hyperframes/skills/remotion-to-hyperframes/references/timing.md
T
wehub-resource-sync 85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:58:35 +08:00

6.1 KiB
Raw Blame History

Timing translation: interpolate, spring, easing

The single highest-leverage reference. Easings and timings are what readers notice; getting them wrong costs more SSIM than any other translation choice. Empirically validated against tiers T1T3.

Conversion: frames → seconds

HF's timeline is in seconds. Remotion is frame-based. Always:

time_seconds = frame / fps

So at fps=30:

  • frame 15 → 0.5 s
  • frame 30 → 1.0 s
  • frame 90 → 3.0 s

Do this conversion once when translating, not at runtime.

interpolate — linear

const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });

Translates to:

gsap.to(target, { opacity: 1, duration: 1.0, ease: "none" }, 0);
// fromTo if the property starts at 0 and CSS doesn't already set it
gsap.fromTo(target, { opacity: 0 }, { opacity: 1, duration: 1.0, ease: "none" }, 0);

ease: "none" matches Remotion's default linear interpolation. CSS sets the from value if your initial state is in CSS; otherwise use fromTo.

extrapolateLeft/extrapolateRight defaults to "extend" in Remotion but "clamp" is what the agent will see most often. GSAP doesn't extend — values hold at the start and end of the tween. So for clamp, GSAP matches; for extend, you'd need to extend the input range manually before emitting.

interpolate — multi-segment

const opacity = interpolate(frame, [0, 15, 75, 90], [0, 1, 1, 0]);

Three keyframed tweens at offsets [0]/fps, [1]/fps, [2]/fps:

const tl = gsap.timeline({ paused: true });
tl.to(target, { opacity: 1, duration: 0.5, ease: "none" }, 0);
tl.to(target, { opacity: 1, duration: 2.0, ease: "none" }, 0.5);
tl.to(target, { opacity: 0, duration: 0.5, ease: "none" }, 2.5);

Validated in T1 — mean SSIM 0.974 against Remotion baseline.

spring → GSAP back.out

Remotion's spring() is the most lossy translation. The mapping is approximate but close enough that real-world compositions hold ≥ 0.92 SSIM (T2: 0.985, T3: 0.953).

Remotion spring config GSAP equivalent Validated in
{damping: 12, stiffness: 100, mass: 1} (snappy) back.out(1.4) over ~0.7 s T2, T3 (TitleScene)
{damping: 14, stiffness: 90, mass: 1} (calmer) back.out(1.2) over ~0.7 s T3 (StatCard)
{damping: 8, stiffness: 200} (very bouncy) back.out(2.0) or elastic.out(1, 0.5) over ~0.6 s not validated; budget ~0.05 SSIM
{overshootClamping: true} power3.out over ~0.6 s (no overshoot) not validated

Rule of thumb: back.out(N) overshoot ratio ≈ (stiffness / damping^2) * 1.4. For damping:12, stiffness:100 that gives 1.4 * 100/144 = 0.97, which is close to the validated 1.4 (the formula is rough; tune by visual). Default duration is ~0.7 s for the typical config.

When the spring's delay/from/to are non-default, scale the duration proportionally.

interpolate with custom easing

import { Easing } from "remotion";
interpolate(frame, [0, 30], [0, 1], { easing: Easing.out(Easing.cubic) });
Remotion GSAP
Easing.in(Easing.linear) ease: "none"
Easing.out(Easing.cubic) ease: "power3.out"
Easing.inOut(Easing.cubic) ease: "power3.inOut"
Easing.out(Easing.poly(N)) ease: "power<N>.out" (N=2 quad, 3 cubic, 4 quart, 5 quint)
Easing.bezier(a,b,c,d) CustomEase.create("c", "M0,0 C${a},${b} ${c},${d} 1,1") (requires CustomEase plugin)
Easing.elastic(bounciness) ease: "elastic.out(${bounciness}, 0.3)"
Easing.bounce ease: "bounce.out"
Easing.back(overshoot) ease: "back.out(${overshoot * 1.7})" (Remotion's overshoot scale differs)

interpolate driving non-numeric properties

const color = interpolateColors(frame, [0, 30], ["#ff0000", "#0000ff"]);

GSAP does color tweens natively:

gsap.to(target, { color: "#0000ff", duration: 1.0, ease: "none" }, 0);

Same for backgroundColor, borderColor. The from value is read from CSS or the inline style.

Custom count-up / number tweens

When Remotion uses a frame-driven number ramp (Math.round(value * eased)):

const t = interpolate(frame, [0, 45], [0, 1]);
const eased = 1 - (1 - t) ** 3; // cubic ease-out
const value = Math.round(target * eased);
return <div>{value.toLocaleString()}</div>;

GSAP equivalent — tween a counter object, write textContent on update:

const counter = { v: 0 };
tl.to(
  counter,
  {
    v: target,
    duration: 1.5,
    ease: "power3.out",
    onUpdate: () => {
      el.textContent = Math.round(counter.v).toLocaleString();
    },
  },
  0,
);

power3.out matches 1 - (1-t)^3 exactly. Validated in T3 (mean SSIM 0.953). Per-frame digit mismatches occur on sub-frame timing offsets but final values converge — no SSIM impact above the noise floor.

Stagger via per-instance prop

When custom subcomponents take a delayInFrames prop:

<StatCard delayInFrames={i * 12} value={...} />

Translate to GSAP timeline offsets:

cards.forEach((card, i) => {
  const start = base + i * (12 / fps); // i * 0.4s at fps=30
  tl.to(card, { ... }, start);
});

Validated in T3 — three StatCards staggered at 0.0/0.4/0.8 s.