Files
hyperframes/skills/hyperframes-animation/rules/depth-of-field-blur.md
T
WaterrrForever 853256403b feat(skills): c2v mining pass — 7 new blueprints, 10 new rules, compacted recipe corpus (#2680)
* 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.
2026-07-21 17:28:55 +08:00

8.7 KiB
Raw Blame History

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.
tags
blur, focus, depth-of-field, dof, rack-focus, filter, dim, spotlight, cinematic, push-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:

  1. 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.
  2. 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 --dof at the same position so the hand-off is seamless.
  3. 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 planesgsap.set plane B pre-blurred BEFORE the rack (no pop), then two tweens sharing RACK_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 gridgsap.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 --dof back to 0px / opacity 1 over the tail (REFOCUS_START + REFOCUS_DUR ≤ DURATION).
  • Bounded focus-breathing on the focal layer (optional) — a finite ease:"none" driver writes Math.max(0, Math.sin(p)) * FOCAL_BREATH_PX into the focal --dof during a hold. Keep it ≤ ~0.6px or it reads as "still focusing"; default to omitting it.

Values

token range notes
BLUR_PER_DEPTH 36 px per depth step a 3-plane stack tops out ~918 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 612 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.51.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 23 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 --dof variable on the timeline — reading filter: 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 opacity dim to do the push-back work — dim + modest blur reads more like real DoF than blur cranked to the max.
  • will-change: filter on 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 --dof at the same position.
  • DoF is independent of the camera — blur the layers, transform .world for the push-in; don't fake DoF with the camera transform or vice-versa.
  • Settle sharp before a hand-off — refocus to --dof: 0 in 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).