You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4af8802
Browse filesBrowse the repository at this point in the historyBrowse files
feat(skills): spring registers, bakeSpring for the CSS/WAAPI lanes, follow-through + inertia-chain
Extends adapters/gsap-easing-and-stagger.md → Spring Eases on top of the existing springEase:
named registers as a feel-word lookup (snappy / heavy-settle for entrances; bouncy / wobbly as a
separate impact-recovery table, with the "< 0.55 — Don't" row amended to name that exception),
the greppable duration-is-an-output criterion, and bakeSpring() emitting a CSS linear() easing for
the css and waapi adapters with measured parity. Adds two rules — follow-through (a velocity-
continuous ring composed into any ease that arrives with speed) and inertia-chain (followers replay
the leader's identical tween, time-shifted by frames) — their rules-index rows, and one-line
pointers in spring-pop-entrance.md, css-animations.md and waapi.md.
Copy file name to clipboardExpand all lines: skills/hyperframes-animation/adapters/css-animations.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -100,6 +100,7 @@ Use CSS custom properties to avoid duplicating keyframes:
100
100
- Decorative loops with a known repeat count.
101
101
- Mask, glow, shimmer, grain, and subtle parallax layers.
102
102
- Simple one-element entrances where a full JS timeline would be excessive.
103
+
- A physical spring settle without GSAP: bake the closed-form spring into a `linear()` timing function with `bakeSpring()` (`gsap-easing-and-stagger.md` → Spring Eases → bakeSpring); the duration is still the helper's.
|**1.0 (default)**| none (monotone) | The house settle — the exact curve `power3.out` approximates. Product / enterprise / serious tone. |
122
-
| 0.80–0.85 |~1–1.5% | "Alive, not bouncy" — the iOS system default register. The overshoot is felt, not seen. |
123
-
| 0.60–0.70 |~5–10% | Explicitly-playful ONLY (same rule as `back.out`, which this replaces — a spring's second-order settle reads physical where `back` reads cartoon). |
|**1.0 (default)**| none (monotone) | The house settle — the exact curve `power3.out` approximates. Product / enterprise / serious tone. |
122
+
| 0.80–0.85 |~1–1.5% | "Alive, not bouncy" — the iOS system default register. The overshoot is felt, not seen. |
123
+
| 0.60–0.70 |~5–10% | Explicitly-playful ONLY (same rule as `back.out`, which this replaces — a spring's second-order settle reads physical where `back` reads cartoon). |
124
+
| < 0.55 | > 12% | Don't — as an entrance. Cartoon-wobble territory for an arrival. The one sanctioned home for this band is impact **recovery** (Recovery Registers below), never the travel itself.|
-**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.
136
136
-**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.
137
137
138
+
### Named Registers
139
+
140
+
Feel words over the same helper — a lookup table, not a second API. Each register is a `{ response, dampingFraction }` pair that shipped through a deterministic reference build (24 s reel, 2026-09-08, running this helper: every spring tween consuming the helper's duration verbatim, double-render bit-identical). The durations below are what `springEase` returns for the pair; take them from the helper, never from this table.
141
+
142
+
```javascript
143
+
constSPRING_REGISTERS= {
144
+
// Entrance / settle voices — arrivals, lockups, hero landings (ζ ≥ 0.8 per the doctrine above).
`bouncy` and `wobbly` sit inside the "< 0.55 — Don't" band on purpose. They are **deformation-recovery** voices — the spring-back of a body after it lands and squashes, a control after release, the settle-back of a chain of followers after the leader's arrival — not entrance eases. A recovery starts _at_ the contact frame and moves the element by a small fraction of the arrival travel — a few percent of a large element's height, up to about its own height for a chip or badge — never the travel itself, so a 16–40% overshoot of that small displacement reads as material (rubber, jelly, drag), where the same overshoot on the arrival travel reads as cartoon.
|`bouncy`| 0.40 | 0.50 |~16% | ≈ 0.81s | soft-body landing recovery, a released press, a chain's settle-back move |
174
+
|`wobbly`| 0.50 | 0.28 |~40% | ≈ 1.90s | rubber / jelly tier — the wobble _is_ the material read; rare, deliberate |
175
+
176
+
- The arrival keeps the entrance doctrine (ζ ≥ 0.8, or `power3.out`); only the post-contact recovery may go `bouncy` / `wobbly`.
177
+
- Recovery goes on a transform or a deformation proxy that was just displaced — never on `opacity`, never on the arrival travel.
178
+
- Longer flight in a chain or trail comes from `response`, not from the duration (next section).
179
+
180
+
### Duration Is an Output — the Greppable Criterion
181
+
182
+
`springEase` returns the settle time and the tween consumes it verbatim, so the audit is mechanical: a spring tween's `duration:` is `<spring>.duration` with **no arithmetic on it**.
183
+
184
+
```bash
185
+
grep -nE 'duration\s*\*|\*\s*[A-Za-z_.]*duration' index.html # spring tweens: zero hits
186
+
```
187
+
188
+
The `css` / `waapi` lanes need milliseconds: cast once through a helper (`ms(s.duration)`, next section) so a unit conversion never reads as arithmetic on a spring duration. A longer or shorter flight comes from `response` — `springEase({ ...SPRING_REGISTERS.bouncy, response: 0.4 * 1.4 })` — the same normalized curve over a physics-derived settle. A stretched duration draws the identical pixels (it re-times the same curve) and is still the anti-pattern the grep catches: it hides the physics parameter from the reader and from the next edit, and it is the first thing to drift when a beat gets re-timed.
189
+
190
+
### bakeSpring — the Same Spring in the CSS-Keyframes and WAAPI Lanes
191
+
192
+
`@keyframes` and `element.animate()` can't take a function ease, but both accept CSS `linear()` — a piecewise-linear easing with explicit stops. Bake the spring into one at setup; the curve is then a pure function of the animation's own time, so the `css` and `waapi` adapters seek it like any other keyframe animation (`css-animations.md`, `waapi.md`).
193
+
194
+
```javascript
195
+
// Curvature-adaptive sampling: stops cluster where the curve bends (the overshoot lobes).
196
+
functionbakeSpring(spring, { maxPts =75 } = {}) {
197
+
constdense=400;
198
+
constpts= [[0, 0]];
199
+
constcurv= [];
200
+
for (let i =1; i < dense; i++) {
201
+
consty0=spring.ease((i -1) / dense);
202
+
consty1=spring.ease(i / dense);
203
+
consty2=spring.ease((i +1) / dense);
204
+
curv.push(Math.abs(y2 -2* y1 + y0)); // second difference ≈ local curvature
Parity against the analytic ease is a function of travel: the bake error is a fraction of the curve, so it grows with the distance the element moves. Computed against this file's `springEase` (script sampling of the bake at 4000 points, 2026-09-08):
239
+
240
+
| register | points at the default cap | worst error, 1000 px travel | at 220 px | with `maxPts: 200`|
In the render itself (one composition, the `wobbly` register at `maxPts: 200`, the GSAP analytic ease beside the CSS-keyframes and WAAPI lanes on the same travel, 1-px edge measurement on every frame of the tween): both baked lanes stay within **1 px** of the analytic chip over 220 px of travel and within **2 px** over 900 px.
248
+
249
+
Rule of thumb: keep `|baked − analytic| × travel ≤ 2 px`. The default cap holds `snappy`, `heavy-settle` and `bouncy` to that tolerance up to roughly 650 px of travel (the two entrance registers to about 800 px); the `wobbly` register's lobes want `maxPts: 200` for the same tolerance at 1000 px. Raise `maxPts` rather than accepting a visible step.
- Lightweight DOM motion where CSS keyframes are too rigid and GSAP is unnecessary.
70
70
- Generated animations from structured data.
71
71
- Simple timelines that can be represented as keyframes, delays, and offsets.
72
+
- A physical spring settle as the `easing` string: bake the closed-form spring into `linear()` with `bakeSpring()` (`gsap-easing-and-stagger.md` → Spring Eases → bakeSpring); pass the helper's duration.
Copy file name to clipboardExpand all lines: skills/hyperframes-animation/rules-index.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -93,6 +93,8 @@ A rule's own **Critical Constraints** section lists only what is SPECIFIC to tha
93
93
<card-morph-anchorpath="rules/card-morph-anchor.md">Container morphs apparent size + corner radius + surface treatment between two shots, then fades to reveal the real target underneath. HyperFrames substitutes uniform `scale` for the forbidden `width`/`height` tween, plus paint-only `borderRadius`/`background`/`boxShadow`. Tags: morph, anchor, transition, border-radius, container, shape, handoff</card-morph-anchor>
94
94
<theme-crossfade-morphpath="rules/theme-crossfade-morph.md">Whole-theme in-place morph under a fixed anchor — background, typography, radii, icons, chrome and logos blend simultaneously (~0.3s) through N pre-styled skins while one anchor element never moves. Stacked complete layers + opacity-only crossfade, anchor rendered once on top (or per-layer at identical geometry); static camera. Single container instead → `card-morph-anchor`. Tags: theme, skin, crossfade, morph, anchor, reskin, cycle, ui</theme-crossfade-morph>
95
95
<spring-pop-entrancepath="rules/spring-pop-entrance.md">The canonical ENTRANCE pop — an element (or staggered group) arrives by springing `scale: 0 → 1` with `back.out` overshoot, `fromTo` so it's correct at t=0 under seek. Single hero, staggered group (≤500ms cap), overshoot tuned by personality. Distinct from `press-release-spring` (a click/press reaction). Tags: spring, entrance, pop, scale-in, overshoot, stagger, arrival</spring-pop-entrance>
96
+
<follow-throughpath="rules/follow-through.md">Velocity-continuous ring-out composed INTO any ease that arrives with speed (`none`, an `in` ease, a slam) — measure the base curve's arrival velocity numerically, extend the window by a decaying-sine tail proportional to it, so a fast arrival over-rings and a slow one barely stirs. One tween, one pure composed ease; does nothing on `out` eases by design. Tags: follow-through, overshoot, ring-out, impact, secondary-motion, composed-ease</follow-through>
97
+
<inertia-chainpath="rules/inertia-chain.md">Follow-the-leader secondary motion — every follower runs the leader's IDENTICAL tween time-shifted by `i × DELTA` (≈ 2 frames) on the master timeline, so a stack or trail reads as one body with drag; the tip settles last. The slide-in runs an entrance register; only a post-contact settle-back may use a recovery register, whose overshoot ripples down the chain. Longer-travel followers scale `response`, never the duration. Distinct from an entrance stagger. Tags: inertia, chain, drag, follow-the-leader, trail, stack, secondary-motion</inertia-chain>
96
98
<motion-blur-streakpath="rules/motion-blur-streak.md">Fake directional velocity blur on a fast entrance / camera push-through — blur peaks at max speed, resolves to 0 at the settle. Two paths: SVG `feGaussianBlur` stdDeviation on the motion axis (proxy-tweened), or a deterministic echo/ghost trail that collapses into the lead. Entrances / mid-shot only. Per-frame-driven form for an element riding a baked track: velocity-gated text-free silhouette ghosts (or a rotate/blur/counter-rotate filter sandwich when a true blur is the look), streak axis from `atan2` of the track delta. Tags: motion-blur, streak, velocity, ghost, echo, fast, driven, tracked</motion-blur-streak>
97
99
<waterfall-entrypath="rules/waterfall-entry.md">Staggered ARRIVAL cascade — words/elements whip in from below, each starting before the previous settles, an accelerating wave that resolves composed. Title cards, segment openers, list intros. Binary 0→1 opacity via `tl.set` — never fade an arrival. Tags: entrance, cascade, stagger, kinetic-text, title-card, arrival, waterfall</waterfall-entry>
98
100
<particle-burstpath="rules/particle-burst.md">Deterministic particle / confetti events — confetti pop that bursts up and drifts down on gravity (optional instant-shrink), dot burst from behind text, glyph dissolve to particles. Fixed pool, index-seeded launch values, one `ease: "none"` driver whose onUpdate computes each particle as a pure ballistic function of time — scrub-safe mid-flight, ≤ ~40 particles. Tags: particles, confetti, burst, dissolve, ballistic, deterministic, punctuation</particle-burst>
0 commit comments