Files
zooqueen 62bbc7766c skills: give each skill the directory its name declares
Every SKILL.md already declared the new name — name: frames, frames-cli,
frames-core, and so on — but the directories were never moved, so the tree
carried frames-* in content and hyperframes-* on disk. Everything that
resolves a skill by path missed: the core-skill list, the plugin marketplace
pin, and the docs the CLI reads at runtime. Eight tests failed on main because
the files they open do not exist.

The directories now match what the files say they are, and the guide the
claude-design contract reads takes the name that contract already used.
2026-08-13 18:17:24 -07:00

8.7 KiB
Raw Permalink 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 planes — gsap.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 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 --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 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 --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).