mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(skills): remotion-to-hyperframes references (6/7)
Adds 11 progressively-disclosed reference files that the skill loads on
demand during translation. Total ~1500 LOC, every file under 200 lines
(skill-creator's progressive-disclosure budget).
api-map.md the comprehensive Remotion -> HF translation table
(the index; loaded at start of translation)
timing.md interpolate, spring (validated configs), easing,
count-up, stagger
sequencing.md Sequence, Series, Loop, Freeze, AbsoluteFill,
Composition root
media.md Audio, Video, Img, IFrame, OffthreadVideo,
staticFile, asset paths
transitions.md @remotion/transitions presentations -> manual GSAP
crossfades or HF shader-transitions
lottie.md @remotion/lottie -> HF lottie adapter (incl. AE
feature limitations note)
fonts.md Google Fonts loading, local @font-face, system
fallback noise floor
parameters.md Zod schemas, defaultProps, sync vs async
calculateMetadata
escape-hatch.md when to bow out + the runtime interop pattern
from PR #214
limitations.md known caveat patterns (volume ramps, Loop with
state, custom presentations, code-split components)
eval.md how to run the validation harness, threshold rule
of thumb, what the noise floor looks like
The references are evidence-driven rather than speculative: every spring
config, easing curve, and SSIM threshold is documented from the
validated T1/T2/T3 calibration runs (mean 0.974 / 0.985 / 0.953). The
escape-hatch boundaries match the lint blockers in PR 2 and the T4
fixtures in PR 5.
Replaces the placeholder .gitkeep from PR 1.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
# 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 T1–T3.
|
||||
|
||||
## 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
|
||||
|
||||
```tsx
|
||||
const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });
|
||||
```
|
||||
|
||||
Translates to:
|
||||
|
||||
```js
|
||||
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
|
||||
|
||||
```tsx
|
||||
const opacity = interpolate(frame, [0, 15, 75, 90], [0, 1, 1, 0]);
|
||||
```
|
||||
|
||||
Three keyframed tweens at offsets `[0]/fps`, `[1]/fps`, `[2]/fps`:
|
||||
|
||||
```js
|
||||
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
|
||||
|
||||
```tsx
|
||||
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
|
||||
|
||||
```tsx
|
||||
const color = interpolateColors(frame, [0, 30], ["#ff0000", "#0000ff"]);
|
||||
```
|
||||
|
||||
GSAP does color tweens natively:
|
||||
|
||||
```js
|
||||
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)`):
|
||||
|
||||
```tsx
|
||||
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:
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```tsx
|
||||
<StatCard delayInFrames={i * 12} value={...} />
|
||||
```
|
||||
|
||||
Translate to GSAP timeline offsets:
|
||||
|
||||
```js
|
||||
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.
|
||||
Reference in New Issue
Block a user