Skip to content

Commit 01fa31d

Browse files
committed
feat(skills): motion-blur-streak — per-frame-driven form for elements riding a baked track
Extends rules/motion-blur-streak.md with a Per-Frame-Driven Carve-In: when an element is driven from a baked track (tracked insert, shake-matched title) there is no tween window to share, so the envelope becomes a per-frame function of the track delta — velocity-gated text-free silhouette ghosts (Path B, the default for driven subtrees) or a rotate/blur/counter-rotate filter sandwich (Path A), streak axis from atan2 of the delta. Adds the Values rows, the constraint, and updates the rules-index row.
1 parent d4756f5 commit 01fa31d

3 files changed

Lines changed: 60 additions & 18 deletions

File tree

‎skills-manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"files": 29
2323
},
2424
"hyperframes-animation": {
25-
"hash": "2a567656b9291f5a",
25+
"hash": "628e777508b81418",
2626
"files": 122
2727
},
2828
"hyperframes-audio": {

‎skills/hyperframes-animation/rules-index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ A rule's own **Critical Constraints** section lists only what is SPECIFIC to tha
9393
<card-morph-anchor path="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>
9494
<theme-crossfade-morph path="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>
9595
<spring-pop-entrance path="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-
<motion-blur-streak path="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. Tags: motion-blur, streak, velocity, ghost, echo, fast</motion-blur-streak>
96+
<motion-blur-streak path="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>
9797
<waterfall-entry path="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>
9898
<particle-burst path="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>
9999
<nudge-curve path="rules/nudge-curve.md">Slow-fast-slow three-phase group slide (power3.in ramp → linear burst → power4.out tail, 10/65/25 distance, tail ≥3× ramp-in) to reposition a composed group and reveal content during the burst. Tags: slide, reposition, group-motion, nudge, slow-fast-slow</nudge-curve>

‎skills/hyperframes-animation/rules/motion-blur-streak.md‎

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
name: motion-blur-streak
3-
description: Fake directional velocity blur on a fast entrance or camera push-through — blur peaks at max speed and resolves to 0 at the settle, so the element streaks in then snaps sharp. Two paths — SVG feGaussianBlur on the motion axis, or an echo/ghost trail that collapses into the lead.
3+
description: Fake directional velocity blur on a fast entrance or camera push-through — blur peaks at max speed and resolves to 0 at the settle, so the element streaks in then snaps sharp. Two paths — SVG feGaussianBlur on the motion axis, or an echo/ghost trail that collapses into the lead. Plus a per-frame-driven form for an element riding a baked track (tracked insert, shake-matched title): velocity-gated text-free silhouette ghosts (or a rotate/blur/counter-rotate filter sandwich when a true blur is the look), streak axis from the track delta.
44
metadata:
5-
tags: motion-blur, velocity, streak, entrance, fly-in, ghost, echo, svg-filter, kinetic, camera, snap
5+
tags: motion-blur, velocity, streak, entrance, fly-in, ghost, echo, svg-filter, kinetic, camera, snap, driven, tracked, baked-track
66
---
77

88
# Motion-Blur Streak
@@ -12,7 +12,9 @@ Real motion blur isn't available to a seeked renderer (it integrates over shutte
1212
- **(A) Directional SVG blur** — inline `<feGaussianBlur stdDeviation="X 0">` (X on the motion axis, 0 across it), tweened via a proxy. Cleanest; a true directional smear.
1313
- **(B) Echo / ghost trail** — 2–4 duplicates at decreasing opacity, offset backward along the motion vector, collapsing into the lead as it settles. No filter cost; a stylized "speed-line" trail.
1414

15-
**Entrances and mid-shot moves only — never a mid-composition exit.** A blurred element fleeing off-frame mid-composition reads as a glitch; a hard exit between scenes is the transition's job (`../transitions/overview.md`). One sanctioned scope extension: the envelope may ride the **camera wrapper** during a travel leg — see the Camera-Travel Carve-Out.
15+
Both paths assume a **tween window** — an element driven per frame from a baked track has none; that form is the Per-Frame-Driven Carve-In below.
16+
17+
**Entrances and mid-shot moves only — never a mid-composition exit.** A blurred element fleeing off-frame mid-composition reads as a glitch; a hard exit between scenes is the transition's job (`../transitions/overview.md`). Two sanctioned scope extensions: the envelope may ride the **camera wrapper** during a travel leg (Camera-Travel Carve-Out), or become a per-frame function of a baked track for a **driven element** (Per-Frame-Driven Carve-In).
1618

1719
## How It Works
1820

@@ -100,31 +102,71 @@ tl.to(blurProxy, { v: PEAK_BLUR, duration: 0.12, ease: "power3.in", onUpdate: wr
100102
tl.to(blurProxy, { v: 0, duration: 0.35, ease: "power4.out", onUpdate: writeBlur }, WHIP_AT + 0.22);
101103
```
102104

105+
## Per-Frame-Driven Carve-In (baked tracks)
106+
107+
When the element is not tweened but **driven** — a tracked insert or callout riding a baked per-frame track, a title matched to camera shake — there is no move window to share. The envelope becomes a per-frame function of the track's own delta, computed in the same `onUpdate` that applies the position:
108+
109+
```js
110+
// inside the frame-lookup driver (an ease:"none" proxy → frames[i]); TRACK.x / TRACK.y are baked arrays
111+
const j = Math.max(1, i); // frame 0 borrows the frame-1 delta so a seek to t=0 has a defined state
112+
const dx = TRACK.x[j] - TRACK.x[j - 1];
113+
const dy = TRACK.y[j] - TRACK.y[j - 1];
114+
const speed = Math.hypot(dx, dy); // px per frame
115+
const angle = (Math.atan2(dy, dx) * 180) / Math.PI; // the streak axis IS the travel direction
116+
117+
// Path B, driven form (the default here): text-free silhouette clones trail the lead along −delta.
118+
const base = speed > TRAIL_GATE ? Math.min(TRAIL_MAX, TRAIL_K * speed) : 0;
119+
const leadOpacity = parseFloat(gsap.getProperty(lead, "opacity")); // slaved: a fading lead fades its trail
120+
ghost1.style.transform = `translate(${-dx * 0.25}px, ${-dy * 0.25}px)`;
121+
ghost2.style.transform = `translate(${-dx * 0.5}px, ${-dy * 0.5}px)`;
122+
ghost1.style.opacity = String(base * leadOpacity);
123+
ghost2.style.opacity = String(base * 0.5 * leadOpacity);
124+
125+
// Path A, driven form: rotate a filter wrapper onto the travel axis, blur along X only, counter-rotate the content.
126+
const sigma = speed > TRAIL_GATE ? Math.min(SIGMA_MAX, 0.5 * speed) : 0;
127+
blurWrap.style.transform = `rotate(${angle}deg)`;
128+
lead.style.transform = `rotate(${-angle}deg)`;
129+
blurNode.setAttribute("stdDeviation", `${sigma} 0`);
130+
```
131+
132+
Three deltas from the tweened recipe:
133+
134+
- **Ghosts are text-free silhouettes** — the lead's box, border, radius and fill with **no content** (`aria-hidden="true"`, `data-layout-allow-overlap`). A 25–50%-opacity clone reads as shape; a clone carrying the text reads as a duplicate element, and content-free clones give the layout and contrast checkers nothing to flag.
135+
- **Velocity-gated, not window-shaped** — the trail exists only above `TRAIL_GATE` px/frame and scales with speed (`min(TRAIL_MAX, TRAIL_K × speed)`): a slow drift shows nothing, a whip shows a 180°-shutter smear. It is slaved to the lead's own opacity so an entrance or exit fade takes the trail with it.
136+
- **Axis from the baked delta** — `atan2(dy, dx)` per frame; the streak follows the track around corners with nothing to seed at setup.
137+
138+
**Which path for a driven subtree.** Path B is the default: it costs no filter raster (a full filter region re-rasterizes every frame under per-frame drive), its clones carry no text so the layout and contrast checkers see nothing new, and it degrades to nothing when the track is still. Path A in the driven form is legitimate when a true blur is the look. Both forms measured deterministic on the reference host (2026-09-08: single-card 120-frame and three-card 450-frame drives of each path, hardware-GL and software lanes, every double render bit-identical) — but a per-frame-driven filter is the first thing to re-measure on a new host: keep the double-render frame-hash comparison (`--format png-sequence` twice) in the pipeline rather than trusting one clean run. One earlier build (2026-08) observed 1-LSB `feGaussianBlur` jitter under per-frame load and shipped Path B for that reason.
139+
103140
## Values
104141

105-
| token | range | notes |
106-
| ------------------ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
107-
| MOVE_EASE | `expo.out` / `power4.out` (default) / `power3.out` | `out`-family ONLY — `in`/`inOut` puts peak speed in the wrong place; position and blur share it |
108-
| MOVE_DUR | 0.25–0.6s | over ~0.7s reads as a focus pull, not velocity |
109-
| ENTER_FROM_X/Y | 40–120% of the element's own dimension | enough runway for the streak to read |
110-
| PEAK_BLUR | 8–30 (default 18) | >30 erases the glyph at the start; ~18–20 cap at wrapper level |
111-
| SCALE_FROM | 1.3–2.5 | push-through variation |
112-
| N (ghosts) | 2–4 | >4 reads as strobe, not streak |
113-
| ECHO_STEP_PX | 12–40px | `N × step ≲ ENTER_FROM` so the furthest ghost starts inside the runway |
114-
| GHOST_BASE_OPACITY | 0.3–0.6 | opaque ghosts read as duplicate elements |
115-
| CARD_STAGGER | 0.05–0.12s | one assembling wave, not separate arrivals |
142+
| token | range | notes |
143+
| ------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
144+
| MOVE_EASE | `expo.out` / `power4.out` (default) / `power3.out` | `out`-family ONLY — `in`/`inOut` puts peak speed in the wrong place; position and blur share it |
145+
| MOVE_DUR | 0.25–0.6s | over ~0.7s reads as a focus pull, not velocity |
146+
| ENTER_FROM_X/Y | 40–120% of the element's own dimension | enough runway for the streak to read |
147+
| PEAK_BLUR | 8–30 (default 18) | >30 erases the glyph at the start; ~18–20 cap at wrapper level |
148+
| SCALE_FROM | 1.3–2.5 | push-through variation |
149+
| N (ghosts) | 2–4 | >4 reads as strobe, not streak |
150+
| ECHO_STEP_PX | 12–40px | `N × step ≲ ENTER_FROM` so the furthest ghost starts inside the runway |
151+
| GHOST_BASE_OPACITY | 0.3–0.6 | opaque ghosts read as duplicate elements |
152+
| CARD_STAGGER | 0.05–0.12s | one assembling wave, not separate arrivals |
153+
| TRAIL_GATE | 3–6 px/frame (default 4) | driven form: below it the track counts as still — no trail, no blur |
154+
| TRAIL_K · TRAIL_MAX | 0.05 · 0.32 | driven form: ghost opacity `min(TRAIL_MAX, TRAIL_K × speed)` — hits the cap at ~6 px/frame |
155+
| TRAIL_OFFSETS | 0.25 / 0.5 of the per-frame delta | two clones = a discrete 180° shutter; a third at 0.75 only for a long whip |
156+
| SIGMA_MAX | 8–12 | driven Path A: `σ = min(SIGMA_MAX, 0.5 × speed)` along the travel axis |
116157

117158
## Critical Constraints
118159

119160
- Blur peaks at peak speed and resolves to 0 at the settle — share the ease and window between position and envelope. A blur that lingers after the stop reads as a focus pull.
120-
- Entrances / mid-shot arrivals only — never a mid-composition exit; wrapper-level use only per the carve-out.
161+
- Entrances / mid-shot arrivals only — never a mid-composition exit; wrapper-level use only per the Camera-Travel Carve-Out, per-frame-driven use only per the Per-Frame-Driven Carve-In.
121162
- Seed `stdDeviation` at setup: at `PEAK_BLUR` for the entrance shape, at 0 for a whip / `inOut` leg.
122163
- Generous filter region (`x="-50%" y="-50%" width="200%" height="200%"`) or the smear clips at the element's box edge.
123164
- Directional axis: `"X 0"` horizontal, `"0 Y"` vertical, `"B B"` only for a depth/scale move — symmetric blur on a sideways move looks like defocus.
124165
- Dwell ≥1 s sharp after the snap; a streak landing at the last beat reads as "flashed and gone".
125166
- Heavy element on a solid field — thin type (< ~120px / 800 weight) or a busy backdrop swallows the smear.
126167
- `overflow: hidden` on the scene — the smear / furthest ghost extends past the resting position during travel.
168+
- Driven form: clones are text-free silhouettes (`aria-hidden`, `data-layout-allow-overlap`), opacity slaved to the lead, axis from the baked delta; nothing is seeded at setup except the structural frame-0 state.
127169

128170
## See also
129171

130-
`kinetic-beat-slam` (streak as one beat's entrance) · `center-outward-expansion` (grid streak-in) · `scale-swap-transition` (same-footprint morph — not an arrival) · `nudge-curve` (the whip sweep's position half) · `3d-camera-flight` / `viewport-change` (the carve-out's wrappers).
172+
`kinetic-beat-slam` (streak as one beat's entrance) · `center-outward-expansion` (grid streak-in) · `scale-swap-transition` (same-footprint morph — not an arrival) · `nudge-curve` (the whip sweep's position half) · `3d-camera-flight` / `viewport-change` (the Camera-Travel Carve-Out's wrappers).

0 commit comments

Comments
 (0)