mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 10:46:06 +00:00
* feat(skills): c2v mining pass over animation blueprints and rules Compacts ~45 existing animation rules/blueprints into tighter recipe form (net -3.4k lines) and adds 17 mined from the c2v corpus: - 7 blueprints: agent-progress-theater, camera-journey, fixed-anchor-cycle, panel-edit-live-sync, prompt-type-submit-generate, transcript-scroll-artifact-reveal, zoom-out-workspace-reveal - 10 rules: 3d-camera-flight, anchored-layout-expand, chart-scrub-readout, chromatic-glitch, control-target-sync, cursor-drag, gradient-text-sweep, multi-cursor-choreography, particle-burst, theme-crossfade-morph Both indexes updated. * feat(skills): sync product-launch script bank with mined blueprint roles The role->blueprint script bank in product-launch-video/story-design.md is kept 1:1 with blueprints-index role declarations, which the c2v mining pass expanded. Adds the 25 missing entries (script-shape descriptor + example lines + pattern): 13 for the 7 new blueprints, 12 for role widenings on 7 existing ones (cursor-ui-demo, dataviz-countup, titlecard-reveal, et al.), and states the 1:1 sync contract in the bank's intro. * docs(skills): cover constellation-hub scatter-drift variant in the script bank Review follow-up on #2680: the SOCIAL_PROOF constellation-hub entry patterned only the orbit shape; the c2v pass added a scatter-drift end-card variant with the opposite geometry (no hub, no ring). Adds an example line and extends the pattern so a scatter-drift beat's VO isn't steered toward the orbit shape.
131 lines
10 KiB
Markdown
131 lines
10 KiB
Markdown
---
|
||
name: motion-blur-streak
|
||
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.
|
||
metadata:
|
||
tags: motion-blur, velocity, streak, entrance, fly-in, ghost, echo, svg-filter, kinetic, camera, snap
|
||
---
|
||
|
||
# Motion-Blur Streak
|
||
|
||
Real motion blur isn't available to a seeked renderer (it integrates over shutter time), so this rule **fakes** it for a fast fly-in or hard camera push-through. The whole point is the _coupling_: the blur envelope rides the **same ease and window** as the position tween, so peak blur lands exactly on peak speed and the element is razor-sharp the instant it stops. Two paths:
|
||
|
||
- **(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.
|
||
- **(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.
|
||
|
||
**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.
|
||
|
||
## How It Works
|
||
|
||
A fast `out`-eased move front-loads velocity — fastest off the start, bleeding to zero at the settle. Map the blur/echo envelope onto that same curve: position travels from an off-frame / pushed-back start to rest over `MOVE_DUR`; in lockstep on the same window and ease the smear goes `PEAK_BLUR → 0` (A) or the ghosts collapse onto the lead (B). By the settle the element is fully crisp and dwells ≥1 s — the contrast between violent streak and still, sharp settle IS the effect. GSAP can't tween an SVG attribute directly: tween a plain `{ v }` proxy and write `setAttribute("stdDeviation", …)` in `onUpdate`, seeding it once at setup so a seek to t=0 shows the streaked start.
|
||
|
||
## Recipe
|
||
|
||
```html
|
||
<!-- inside a standard scene clip; overflow: hidden on the scene (the smear extends past rest) -->
|
||
<svg width="0" height="0" aria-hidden="true" style="position: absolute">
|
||
<filter id="streak" x="-50%" y="-50%" width="200%" height="200%">
|
||
<feGaussianBlur id="streak-blur" in="SourceGraphic" stdDeviation="0 0" />
|
||
</filter>
|
||
</svg>
|
||
<div class="streak-el" id="streak-el" style="filter: url(#streak)">{phrase}</div>
|
||
<!-- Path B instead: N-1 aria-hidden .streak-ghost duplicates BEHIND the lead, no filter -->
|
||
```
|
||
|
||
```js
|
||
// Path A — proxy-tweened directional blur.
|
||
const blurNode = document.getElementById("streak-blur");
|
||
const blurProxy = { v: PEAK_BLUR };
|
||
const writeBlur = () => blurNode.setAttribute("stdDeviation", `${blurProxy.v} 0`); // X axis only
|
||
writeBlur(); // seed frame 0 — a seek to t=0 must show the streaked start, not a sharp pre-frame
|
||
|
||
tl.fromTo(
|
||
"#streak-el",
|
||
{ x: ENTER_FROM_X, opacity: 0 },
|
||
{ x: 0, opacity: 1, duration: MOVE_DUR, ease: MOVE_EASE },
|
||
MOVE_START,
|
||
);
|
||
tl.to(blurProxy, { v: 0, duration: MOVE_DUR, ease: MOVE_EASE, onUpdate: writeBlur }, MOVE_START);
|
||
|
||
// Path B — ghosts on the SAME window/ease; per-ghost variation by index.
|
||
gsap.utils.toArray(".streak-ghost").forEach((g) => {
|
||
const i = Number(g.dataset.i); // 1..N-1, set in HTML
|
||
tl.fromTo(
|
||
g,
|
||
{ x: ENTER_FROM_X - i * ECHO_STEP_PX, opacity: GHOST_BASE_OPACITY / i },
|
||
{ x: 0, opacity: 0, duration: MOVE_DUR, ease: MOVE_EASE },
|
||
MOVE_START,
|
||
);
|
||
});
|
||
```
|
||
|
||
## Variations
|
||
|
||
- **Vertical streak** — swap axes: `y`, `stdDeviation="0 Y"`, vertical echo offsets.
|
||
- **Camera push-through** — `scale: SCALE_FROM → 1` with a symmetric `"B B"` envelope (depth-wise smear, not directional): the wordmark punches out of soft focus and snaps crisp at the lock.
|
||
- **Staggered grid streak-in** — each card streaks into its slot at `MOVE_START + i * CARD_STAGGER` with its own blur proxy / ghosts; sharp the instant it lands.
|
||
- **Hold-the-streak** — blur on a marginally slower curve than position (position `expo.out`, blur `power3.out`) so the last wisp resolves just after arrival. Sparingly; default is locked envelopes.
|
||
|
||
## Camera-Travel Carve-Out
|
||
|
||
The envelope is also sanctioned at **wrapper level**: on the `.world` / camera wrapper of a virtual-camera scene ([viewport-change.md](viewport-change.md), [multi-phase-camera.md](multi-phase-camera.md), [3d-camera-flight.md](3d-camera-flight.md)) during a **travel leg** — a dive, a whip sweep, a violent final push. This does **not** violate "never a mid-composition exit": the world never leaves frame — the camera travels _through_ it, and every leg ends with the world at rest, sharp, inside the frame. Each leg is an **arrival** at the next pose, so the entrance doctrine applies leg by leg. Three deltas from the element-level recipe:
|
||
|
||
- **Envelope follows the leg's ease.** An `out` leg (dive, final push) uses the base recipe unchanged. An `inOut` repositioning leg peaks mid-leg: split the envelope at the velocity peak — `0 → PEAK` on the in-half ease over the first half, `PEAK → 0` on the out-half over the second. Seed the proxy at **0** for these (the streaked state lives mid-leg, not at t=0; seed-at-`PEAK_BLUR` belongs to the entrance shape, where the first frame IS the fastest).
|
||
- **Filter placement.** 2D camera: `filter: url(#streak)` on the `.world` wrapper. 3D flight: on the **perspective stage** above the 3D context — a `filter` on a `preserve-3d` element flattens it and collapses every `translateZ`. Never per-element inside the world: one frame-wide envelope, not N desynced ones.
|
||
- **Full-frame blur is heavy** — cap `PEAK_BLUR` ~18–20 at wrapper level (vs 30 for one element); a brief whip may touch ~24. Axis rule as usual: `"X 0"` for a lateral whip/pan, `"B B"` for a dive/push.
|
||
|
||
### Whip sweep (named composition)
|
||
|
||
The heavily-blurred lateral whip that resolves into the next region — two rules on one window:
|
||
|
||
1. **Position** — [nudge-curve.md](nudge-curve.md)'s three-phase chain on the camera state, tuned burst-dominant (tail still ≥3× ramp-in in time).
|
||
2. **Blur** — `0 → PEAK` across the ramp-in, held at `PEAK` through the linear burst (constant velocity = constant smear), `PEAK → 0` across the tail.
|
||
|
||
Swap or reveal the next region's content DURING the burst — the smear masks the change; the `power4.out` tail lands it sharp. Reveal during the burst, read after the tail.
|
||
|
||
```js
|
||
tl.to(cam, { x: WHIP_X * 0.1, duration: 0.12, ease: "power3.in", onUpdate: applyCamera }, WHIP_AT);
|
||
tl.to(
|
||
cam,
|
||
{ x: WHIP_X * 0.75, duration: 0.1, ease: "none", onUpdate: applyCamera },
|
||
WHIP_AT + 0.12,
|
||
);
|
||
tl.to(
|
||
cam,
|
||
{ x: WHIP_X, duration: 0.35, ease: "power4.out", onUpdate: applyCamera },
|
||
WHIP_AT + 0.22,
|
||
);
|
||
|
||
tl.to(blurProxy, { v: PEAK_BLUR, duration: 0.12, ease: "power3.in", onUpdate: writeBlur }, WHIP_AT);
|
||
// blur holds at PEAK through the linear burst (no tween needed — value rests at PEAK)
|
||
tl.to(blurProxy, { v: 0, duration: 0.35, ease: "power4.out", onUpdate: writeBlur }, WHIP_AT + 0.22);
|
||
```
|
||
|
||
## Values
|
||
|
||
| token | range | notes |
|
||
| ------------------ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||
| 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 |
|
||
| MOVE_DUR | 0.25–0.6s | over ~0.7s reads as a focus pull, not velocity |
|
||
| ENTER_FROM_X/Y | 40–120% of the element's own dimension | enough runway for the streak to read |
|
||
| PEAK_BLUR | 8–30 (default 18) | >30 erases the glyph at the start; ~18–20 cap at wrapper level |
|
||
| SCALE_FROM | 1.3–2.5 | push-through variation |
|
||
| N (ghosts) | 2–4 | >4 reads as strobe, not streak |
|
||
| ECHO_STEP_PX | 12–40px | `N × step ≲ ENTER_FROM` so the furthest ghost starts inside the runway |
|
||
| GHOST_BASE_OPACITY | 0.3–0.6 | opaque ghosts read as duplicate elements |
|
||
| CARD_STAGGER | 0.05–0.12s | one assembling wave, not separate arrivals |
|
||
|
||
## Critical Constraints
|
||
|
||
- 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.
|
||
- Entrances / mid-shot arrivals only — never a mid-composition exit; wrapper-level use only per the carve-out.
|
||
- Seed `stdDeviation` at setup: at `PEAK_BLUR` for the entrance shape, at 0 for a whip / `inOut` leg.
|
||
- Generous filter region (`x="-50%" y="-50%" width="200%" height="200%"`) or the smear clips at the element's box edge.
|
||
- 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.
|
||
- Dwell ≥1 s sharp after the snap; a streak landing at the last beat reads as "flashed and gone".
|
||
- Heavy element on a solid field — thin type (< ~120px / 800 weight) or a busy backdrop swallows the smear.
|
||
- `overflow: hidden` on the scene — the smear / furthest ghost extends past the resting position during travel.
|
||
|
||
## See also
|
||
|
||
`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).
|