feat(skills): align easing docs with motion doctrine, add baked springEase (#1989)

* feat(skills): align easing docs with motion doctrine, add baked springEase

The easing adapter contradicted the workflow doctrine: it taught a
power2.out entrance default and pitched back/elastic as playful
defaults, while motion-language.md says power3 / smooth-beats-bouncy.
A worker following the adapter produced exactly the flat, cheap motion
users complain about.

- gsap-easing-and-stagger: power3.out becomes the documented house
  default; back/elastic/bounce capped as RARE playful-only; new
  "Spring Eases (baked physics, seek-safe)" section — closed-form
  damped-spring ease springEase(response, dampingFraction), measured
  damping ladder, response/duration table, craft notes
- gsap-timeline-and-labels: last leftover power2.out default -> power3
- spring-pop-entrance: exact-physics option (zeta=1) for the settle;
  playful variant now prefers spring zeta 0.6-0.7 over back.out
- motion-language x3 (product-launch-video, faceless-explainer,
  pr-to-video): doctrine "Smooth beats bouncy" wired to the baked
  springEase — real physics, same doctrine, not a license for bounce

Verified with node + GSAP 3.15: zeta=1 strictly monotone with zero
overshoot; scrambled out-of-order seeks return bit-identical values;
ease(0)=0 and ease(1)=1 exact; snippet re-extracted from the published
markdown and re-run.

* chore: format README.md (landed unformatted on main; unblocks the repo-wide Format check)
This commit is contained in:
WaterrrForever
2026-07-07 12:38:01 +08:00
committed by GitHub
parent a1abd57a60
commit 47276edcc6
7 changed files with 114 additions and 32 deletions
+4 -4
View File
@@ -6,7 +6,7 @@
"files": 144
},
"faceless-explainer": {
"hash": "868057217d0499e8",
"hash": "d147102be8cb2c6a",
"files": 18
},
"figma": {
@@ -22,7 +22,7 @@
"files": 1
},
"hyperframes-animation": {
"hash": "6323b750d67287e9",
"hash": "efec72bc46353a6a",
"files": 116
},
"hyperframes-cli": {
@@ -58,11 +58,11 @@
"files": 132
},
"pr-to-video": {
"hash": "0bc51f6ba74b2bfd",
"hash": "84d65fb2b1dfd782",
"files": 22
},
"product-launch-video": {
"hash": "40bfb0bf49ae17cd",
"hash": "47d206f75c193862",
"files": 20
},
"remotion-to-hyperframes": {
@@ -96,7 +96,7 @@ These four rules are the difference between a clip that reads as a serious expla
Elements should use **long-tail decel curves that let them settle smoothly. `power3` is enough in most cases.** No bouncy, no overshoot, no `back.out` / `bounce.out` / `elastic.out` as a default.
Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious motion-design shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle.
Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious motion-design shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle. (The exact form of that settle is a critically-damped spring; the worker has a baked, seek-safe `springEase` — ζ=1 — in `../hyperframes-animation/adapters/gsap-easing-and-stagger.md` → Spring Eases for when the settle is the hero. Real physics, same doctrine — not a license for bounce.)
## 2. Sequential reveal in the back ~50%, timed to the voiceover
@@ -6,54 +6,135 @@ Built-in eases: `power1`, `power2`, `power3`, `power4`, `back`, `bounce`, `circ`
Each has `.in`, `.out`, `.inOut` variants.
| Ease | Use for |
| -------------------------- | ----------------------------------------------------------------------- |
| `power1.out`, `power2.out` | Standard UI motion. Default for most entrances. |
| `power3.out`, `power4.out` | Punchier deceleration. Title cards, hero reveals. |
| `sine.inOut` | Long, slow, calm motion. Crossfades, ambient drift. |
| `back.out(1.7)` | Slight overshoot. Playful entrances. The arg controls overshoot amount. |
| `elastic.out(1, 0.3)` | Springy bounce. First arg = amplitude, second = period. |
| `expo.inOut` | Snappy, dramatic. Quick transitions between hero scenes. |
| `none` (linear) | Camera moves with timed counterpoint, mechanical motion. |
| Ease | Use for |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| `power1.out`, `power2.out` | Gentle motion for secondary elements (a caption fade, a small shift). NOT the entrance default. |
| `power3.out` (house default), `power4.out` | The standard long-tail settle. Entrances, title cards, hero reveals. |
| `sine.inOut` | Long, slow, calm motion. Crossfades, ambient drift. |
| `back.out(1.7)` | Overshoot then settle. RARE — explicitly-playful register only, never a default. |
| `elastic.out(1, 0.3)` | Springy bounce. Same playful-only rule; prefer a baked spring (see Spring Eases below). |
| `expo.inOut` | Snappy, dramatic. Quick transitions between hero scenes. |
| `none` (linear) | Camera moves with timed counterpoint, mechanical motion. |
Pick `.out` for entrances, `.in` for exits, `.inOut` for symmetric moves and continuous motion.
**Smooth beats bouncy** — the motion doctrine (`rules/spring-pop-entrance.md`, the workflows' `motion-language.md`): entrances default to `power3.out` or the baked critically-damped spring (see Spring Eases below); overshoot eases (`back` / `elastic` / `bounce`) are a rare, explicitly-playful register, never the house style.
## Easing Vocabulary (character & mood)
Easings are tone of voice: a video that only whispers is boring; one that varies between whisper, normal, and punch is engaging. Every composition should use at least 3 different easings — `power2.out` for everything produces flat, monotonous motion.
Easings are tone of voice: a video that only whispers is boring; one that varies between whisper, normal, and punch is engaging. A composition should draw on ~3 easing characters across its beats — but vary **within the smooth families by energy** (`sine` / `power1` calm → `power3` standard → `power4` / `expo` punch); don't reach for overshoot to add variety. Overshoot is a _register_ (explicitly playful), not a spice. One ease everywhere reads flat; bounce everywhere reads cheap — the second failure is worse.
The full palette by character (each family has `.in`, `.out`, `.inOut` variants):
| Family | Character | Typical use |
| -------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `power1``power4` | Gentle (1) to aggressive (4) acceleration curves | General purpose. power2 is the workhorse, power4 for dramatic snaps |
| `back(N)` | Overshoot then settle. N controls how far past the target (1=subtle, 4=wild) | Logo reveals, badge pops, card entrances. `back.out(2.5)` for playful, `back.out(1.2)` for elegant |
| `elastic(amp, freq)` | Spring bounce. amp=magnitude, freq=oscillation speed | Panel scatter, energetic drops, fun reveals |
| `bounce` | Ball-drop bouncing | Physical interactions, icons landing, score counters |
| `expo` | Extreme acceleration curve (much steeper than power4) | Premium/luxury reveals, dramatic entrances |
| `sine` | Smooth, organic, no hard edges | Ambient float, breathing, Ken Burns, anything that loops. `.inOut` for yoyo motion |
| `circ` | Circular acceleration (starts very fast, ends very gentle or vice versa) | Camera moves, scene transitions, orbital motion |
| `steps(N)` | Discrete N-step jumps, no interpolation | Typing effects, cursor blink, counter ticks, retro/digital aesthetics |
| Family | Character | Typical use |
| -------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `power1``power4` | Gentle (1) to aggressive (4) acceleration curves | General purpose. **power3 is the house workhorse**; power2 for gentle secondary motion, power4 for dramatic snaps |
| `back(N)` | Overshoot then settle. N controls how far past the target (1=subtle, 4=wild) | RARE — explicitly-playful register only, never a default. Keep N ≤ 2; prefer a baked spring at ζ 0.60.7 (physical settle, see Spring Eases) |
| `elastic(amp, freq)` | Spring bounce. amp=magnitude, freq=oscillation speed | RARE — same playful-only rule; the baked spring (below) is the physical version |
| `bounce` | Ball-drop bouncing | RARE — physical-comedy register only (something literally dropping) |
| `expo` | Extreme acceleration curve (much steeper than power4) | Premium/luxury reveals, dramatic entrances |
| `sine` | Smooth, organic, no hard edges | Ambient float, breathing, Ken Burns, anything that loops. `.inOut` for yoyo motion |
| `circ` | Circular acceleration (starts very fast, ends very gentle or vice versa) | Camera moves, scene transitions, orbital motion |
| `steps(N)` | Discrete N-step jumps, no interpolation | Typing effects, cursor blink, counter ticks, retro/digital aesthetics |
**Mood mapping:** Match easing character to the beat's emotional content. Smooth/organic easings (`sine`, `power1`) feel contemplative and drifting. Aggressive deceleration (`power4.out`, `expo.out`) feels snappy and confident. Spring overshoot (`back.out`) feels bouncy and physical. The storyboard's mood description should guide which character fits — not a formula.
**Mood mapping:** Match easing character to the beat's emotional content. Smooth/organic easings (`sine`, `power1`) feel contemplative and drifting. Aggressive deceleration (`power4.out`, `expo.out`) feels snappy and confident. Spring overshoot (`back.out`) feels bouncy and physical — but bouncy is a register, not an emphasis tool; reach for it only on explicitly-playful beats. The storyboard's mood description should guide which character fits — not a formula.
## Defaults
```javascript
const tl = gsap.timeline({
paused: true,
defaults: { duration: 0.6, ease: "power2.out" },
defaults: { duration: 0.6, ease: "power3.out" }, // the house settle — smooth beats bouncy
});
```
Or globally:
```javascript
gsap.defaults({ duration: 0.6, ease: "power2.out" });
gsap.defaults({ duration: 0.6, ease: "power3.out" });
```
Setting defaults at timeline scope is preferred — it documents the motion language of that composition in one place.
## Spring Eases (baked physics, seek-safe)
The "iOS feel" is a **damped spring's velocity curve**, not a bounce: a fast launch into a long asymptotic settle. Well-made system animations are critically damped or close to it — they barely overshoot, or don't at all. `power3.out` / `expo.out` approximate that curve; when you want the exact one — or a _physical_ overshoot for the rare playful register — bake the spring's closed-form solution into a function ease.
Why not a real-time spring library: an interactive spring is a stateful integrator (velocity accumulates frame to frame), which cannot be seeked deterministically — you'd have to simulate frames 0…N1 to render frame N. The closed form below is a **pure function of progress** — no state, nothing to desync, seek-safe by construction. This is also why interaction-lib spring solvers are banned in compositions.
```javascript
// springEase — a damped spring's exact position curve as a GSAP ease.
// response ≈ seconds one oscillation would take (0.30.6 for entrances)
// dampingFraction 1.0 = critically damped — smooth settle, NO overshoot (house default)
// 0.800.85 ≈ the iOS system register — ~11.5% overshoot, felt not seen
// 0.600.70 = explicitly playful — ~510% overshoot (rare; replaces back.out)
function springEase({ response = 0.5, dampingFraction = 1 } = {}) {
const w = (2 * Math.PI) / response; // undamped natural frequency
const z = dampingFraction;
let pos; // x(t): 0 → 1, starting at rest (v0 = 0)
if (z < 1) {
const wd = w * Math.sqrt(1 - z * z);
pos = (t) => 1 - Math.exp(-z * w * t) * (Math.cos(wd * t) + ((z * w) / wd) * Math.sin(wd * t));
} else if (z > 1) {
const wo = w * Math.sqrt(z * z - 1);
pos = (t) =>
1 - Math.exp(-z * w * t) * (Math.cosh(wo * t) + ((z * w) / wo) * Math.sinh(wo * t));
} else {
pos = (t) => 1 - Math.exp(-w * t) * (1 + w * t);
}
// Settle time: last moment the curve sits outside ±0.1% of target.
// Fixed-step scan, runs once at setup — deterministic (no Math.random / Date.now).
const EPS = 0.001;
const rate = z <= 1 ? z * w : (z - Math.sqrt(z * z - 1)) * w; // slowest decay mode
const SCAN = 12 / rate;
const N = 4800;
let T = SCAN;
for (let i = N; i >= 0; i--) {
const t = (i / N) * SCAN;
if (Math.abs(1 - pos(t)) > EPS) {
T = ((i + 1) / N) * SCAN;
break;
}
}
const xT = pos(T);
return {
duration: T, // use as the tween's duration — the settle time IS the physics
ease: (p) => pos(p * T) + p * (1 - xT), // normalized so ease(1) === 1 exactly
};
}
```
Usage — take **both** the ease and the duration from the helper (the settle time is part of the physics; overriding the duration just re-times the same curve, so tune speed via `response` instead):
```javascript
const settle = springEase({ response: 0.4 }); // critically damped → duration ≈ 0.59s
tl.fromTo(
"#hero",
{ scale: 0, opacity: 0 },
{ scale: 1, opacity: 1, duration: settle.duration, ease: settle.ease },
0.2,
);
```
| dampingFraction | overshoot | register |
| ----------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **1.0 (default)** | none (monotone) | The house settle — the exact curve `power3.out` approximates. Product / enterprise / serious tone. |
| 0.800.85 | ~11.5% | "Alive, not bouncy" — the iOS system default register. The overshoot is felt, not seen. |
| 0.600.70 | ~510% | Explicitly-playful ONLY (same rule as `back.out`, which this replaces — a spring's second-order settle reads physical where `back` reads cartoon). |
| < 0.55 | > 12% | Don't. Cartoon-wobble territory. |
| response | duration (ζ=1) | feel |
| --------- | -------------- | ------------------------------------------------------------ |
| 0.250.35 | 0.370.51s | tight snap — chips, small UI |
| 0.350.50 | 0.510.74s | standard entrance |
| 0.500.70 | 0.741.03s | weighted hero landing — check the `t ≤ 0.5s` visibility rule |
Craft notes:
- **ζ=1 vs `power3.out`**: the true spring front-loads harder (~67% vs ~58% travelled at quarter-time) and settles on a longer asymptotic tail; max shape difference ~11%. That long tail is the "premium" read — use it when the settle IS the shot (a wordmark landing, a final lockup).
- **At ζ<1, overshooting curves go on transforms only** — never on `opacity` (it would push past 1) or color. Split opacity onto its own `power2.out` tween at the same timeline position.
- **Doctrine unchanged**: ζ below ~0.8 is still the rare, explicitly-playful exception (`rules/spring-pop-entrance.md`). The default of this section is ζ=1 — real spring physics is not a license for bounce.
## Stagger
```javascript
@@ -7,7 +7,7 @@ HyperFrames is a seek-driven runtime. Build one paused timeline per composition,
```javascript
const tl = gsap.timeline({
paused: true,
defaults: { duration: 0.5, ease: "power2.out" },
defaults: { duration: 0.5, ease: "power3.out" },
});
tl.to(".a", { x: 100 }).to(".b", { y: 50 }).to(".c", { opacity: 0 });
@@ -175,7 +175,7 @@ tl.fromTo(
);
```
Keep `OVERSHOOT` modest even here (≤ ~2) — past that it reads as a cartoon wobble, not an arrival.
Keep `OVERSHOOT` modest even here (≤ ~2) — past that it reads as a cartoon wobble, not an arrival. Better still: the baked spring at `dampingFraction: 0.60.7` (`../adapters/gsap-easing-and-stagger.md` → Spring Eases) gives ~510% overshoot with a second-order settle that reads physical where `back.out` reads cartoon.
### Origin-anchored pop (callout springs from a pointer / source)
@@ -195,6 +195,7 @@ When a popped element then **holds** an ongoing slot (a constellation node, a pe
- **EASE** — the settle curve (the load-bearing decision)
- Default: **`power3.out`** — a smooth long-tail settle, no overshoot; the house style for product / enterprise / serious tone. Use `expo.out` for a punchier, faster-front arrival (still smooth).
- Exact-physics option: `springEase({ response: 0.4 })` (critically damped, ζ=1) from `../adapters/gsap-easing-and-stagger.md` → Spring Eases — the curve `power3.out` approximates, with a harder front and a longer settle tail; take `duration` from the helper. Use when the settle IS the shot (a wordmark landing, a final lockup).
- Playful exception only: `back.out(OVERSHOOT)` — see the Bouncy pop variation; reach for it only when a bounce is clearly the brand intent.
- **OVERSHOOT**`back.out(OVERSHOOT)` overshoot strength — **only used in the rare bouncy variant**; the smooth default has no overshoot dial
@@ -96,7 +96,7 @@ These four rules are the difference between a clip that reads as a serious code-
Elements should use **long-tail decel curves that let them settle smoothly. `power3` is enough in most cases.** No bouncy, no overshoot, no `back.out` / `bounce.out` / `elastic.out` as a default.
Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious motion-design shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle.
Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious motion-design shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle. (The exact form of that settle is a critically-damped spring; the worker has a baked, seek-safe `springEase` — ζ=1 — in `../hyperframes-animation/adapters/gsap-easing-and-stagger.md` → Spring Eases for when the settle is the hero. Real physics, same doctrine — not a license for bounce.)
## 2. Sequential reveal in the back ~50%, timed to the voiceover
@@ -96,7 +96,7 @@ These four rules are the difference between a clip that reads as a serious launc
Elements should use **long-tail decel curves that let them settle smoothly. `power3` is enough in most cases.** No bouncy, no overshoot, no `back.out` / `bounce.out` / `elastic.out` as a default.
Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious launch-video shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle.
Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious launch-video shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle. (The exact form of that settle is a critically-damped spring; the worker has a baked, seek-safe `springEase` — ζ=1 — in `../hyperframes-animation/adapters/gsap-easing-and-stagger.md` → Spring Eases for when the settle is the hero. Real physics, same doctrine — not a license for bounce.)
## 2. Sequential reveal in the back ~50%, timed to the voiceover