* 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.
8.7 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| depth-of-field-blur | Selective-focus rack-focus — pull the eye to a focal element by GSAP-tweening filter blur (+ a small opacity dim) on the off-focus layers while the focal one stays sharp. Drive blur via a `--dof` CSS var; finite tweens, no CSS transition, deterministic. Covers single focal pull, rack-focus between two depth planes, and blur-the-cluster-while-pushing-in. |
|
Depth-of-Field Blur (Selective Focus / Rack Focus)
Pulls the eye to one focal element by blurring (and slightly dimming) everything around it while the focal layer stays sharp — the camera's depth-of-field falling off the background, or a rack-focus shifting which plane is in focus. filter and opacity are paint-only, so both tween seek-safe. This is the backing rule for the focus-falloff beat the blueprints reach for: outer nodes blurring during a push-in (constellation-hub), rack-focus across a parallax card stack (cursor-ui-demo), non-highlighted cards dimming to spotlight a hero metric (dataviz-countup).
How It Works
Every layer carries a --dof custom property (px of blur), read by filter: blur(var(--dof)), plus its own opacity. A GSAP tween advances each layer's --dof from 0 to its target blur and its opacity from 1 to a dim level over the focus-shift window. The focal layer's --dof stays 0. Per-layer targets derive from data-depth / index, so the falloff is identical on every seek.
Three mechanics, same primitive:
- Focal pull — one window: off-focus layers go sharp(0) → blurred while the focal layer holds at 0. The eye is pulled to the only thing still crisp.
- Rack focus — two adjacent windows on the same property: plane A's blur ramps 0 → max at the same position plane B's ramps max → 0. State continuity matters exactly as in
press-release-spring: A's resting blur after the rack must equal what B held before it — author both as tweens on the same--dofat the same position so the hand-off is seamless. - Blur-the-cluster-while-pushing-in — the DoF tween runs at the SAME timeline position as a camera push-in (
multi-phase-camera/coordinate-target-zoom): "the world recedes" and "we push in" read as one move.
Recipe
<div class="world" id="world">
<!-- Focal layer — stays sharp -->
<div class="layer focal" id="focal">{FocalLabel}</div>
<!-- Off-focus layers — blur + dim; data-depth orders near→far -->
<div class="layer ctx" data-depth="1">{Context A}</div>
<div class="layer ctx" data-depth="2">{Context B}</div>
<div class="layer ctx" data-depth="3">{Context C}</div>
</div>
.world {
/* single wrapper so a concurrent camera push-in transforms everything
together; DoF is independent of the camera */
position: relative;
width: 100%;
height: 100%;
transform-origin: 50% 50%;
}
.layer {
--dof: 0px; /* px of blur; filter reads it — starts sharp */
filter: blur(var(--dof));
will-change: filter; /* promotes the layer so per-frame re-rasterization is cheap */
}
.focal {
z-index: 2; /* sharp layer must sit ABOVE the blurred ones, or its crisp
edges read as bleeding into the haze */
}
.ctx {
z-index: 1;
}
// Mechanic 1 — FOCAL PULL. Blur scales with data-depth so far planes blur
// more than near ones; the focal layer (--dof: 0, opacity: 1) is untouched.
gsap.utils.toArray(".ctx").forEach((el) => {
const depth = Number(el.dataset.depth) || 1;
tl.to(
el,
{
"--dof": `${BLUR_PER_DEPTH * depth}px`,
opacity: DIM_LEVEL, // dim, not gone
duration: FOCUS_DUR,
ease: "power2.inOut",
},
FOCUS_START,
);
});
Variations
- Rack focus between two depth planes —
gsap.setplane B pre-blurred BEFORE the rack (no pop), then two tweens sharingRACK_START+RACK_DUR: A →MAX_BLUR+DIM_LEVEL, B →0px+1. Shared window makes them cross at the midpoint. - Blur the cluster while pushing in — run the focal-pull tweens at the same position + duration as a camera tween on
#world(scale/x/y,power2.inOut). Camera transforms the world; DoF tweens the layers — independent property channels, no conflict. - Spotlight a hero metric in a card grid —
gsap.utils.toArray(".card:not(.hero)")all defocus (GRID_BLUR+DIM_LEVEL) on one shared window; heroes are skipped. - Refocus / settle — if the beat resolves back to "everything visible" (or hands off to a crossfade needing a clean outgoing frame), ramp all
--dofback to0px/ opacity 1 over the tail (REFOCUS_START + REFOCUS_DUR ≤ DURATION). - Bounded focus-breathing on the focal layer (optional) — a finite
ease:"none"driver writesMath.max(0, Math.sin(p)) * FOCAL_BREATH_PXinto the focal--dofduring a hold. Keep it ≤ ~0.6px or it reads as "still focusing"; default to omitting it.
Values
| token | range | notes |
|---|---|---|
| BLUR_PER_DEPTH | 3–6 px per depth step | a 3-plane stack tops out ~9–18 px; low = gentle DoF, high = tilt-shift falloff |
| MAX_BLUR | 8 soft → 16 default → 24 heavy px | terminal blur for a fully-defocused plane; above ~24 px on a big surface, shrink/group the layer instead |
| GRID_BLUR | 6–12 px | pushes cards back without losing the grid's shape |
| DIM_LEVEL | 0.4 strong → 0.55 default → 0.7 subtle | rarely below 0.35 — fully dark reads as "removed," not "defocused" |
| FOCUS_DUR | 0.5–1.2 s | a rack/pull is a deliberate move, not a snap; shorter = snap focus, longer = languid |
| RACK_START / RACK_DUR | shared by both planes | gsap.set the pre-blurred plane BEFORE RACK_START |
| FOCAL_BREATH_PX | ≤ 0.6 px, period 2–3 s | barely-there nicety |
| FOCAL vs CTX sizing | context smaller / grouped | small context layers let a modest radius still read as "out of focus" — and blur cheaply |
Tokens: dark {bgGradient} so the sharp focal layer reads as lit and forward; heavy display {font} weight — blurred copy needs it to stay shape-legible.
Critical Constraints
- Tween the
--dofvariable on the timeline — readingfilter: blur(var(--dof))keeps the blur on the HF seek clock. - Blur the SMALL / GROUPED layers, not the giant one. Filter cost scales with radius × pixel area; a 20 px blur on a full-frame background is the worst case. Keep per-layer radius ≤ ~24 px on large surfaces and lean on the
opacitydim to do the push-back work — dim + modest blur reads more like real DoF than blur cranked to the max. will-change: filteron every layer whose blur animates (drop it after settle if the layer also does heavy transform work).- Focal layer stays genuinely sharp —
--dof: 0, untouched (or breathing ≤ 0.6 px). Any visible blur on the focal element kills the "this is the thing" read. - State continuity on a rack — the outgoing plane starts at the blur the incoming plane was holding, and vice-versa; adjacent tweens on the same
--dofat the same position. - DoF is independent of the camera — blur the layers, transform
.worldfor the push-in; don't fake DoF with the camera transform or vice-versa. - Settle sharp before a hand-off — refocus to
--dof: 0in the tail if the next beat is a crossfade/push; handing off mid-defocus reads as "the render glitched." - Sharp focal layer above blurred layers (
z-index).
See also
multi-phase-camera.md (the push-in this rule's falloff accompanies) · coordinate-target-zoom.md (zoom onto the focal core — the constellation-hub hook) · viewport-change.md (pan + rack across a tilted card plane) · counting-dynamic-scale.md (hero metric counts up sharp — the dataviz-countup spotlight) · 3d-page-scroll.md (the parallax stack to rack between) · sine-wave-loop.md (post-rack idle; keep both amplitudes tiny).