mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +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.
113 lines
8.7 KiB
Markdown
113 lines
8.7 KiB
Markdown
---
|
||
name: depth-of-field-blur
|
||
description: 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.
|
||
metadata:
|
||
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
|
||
|
||
```html
|
||
<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>
|
||
```
|
||
|
||
```css
|
||
.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;
|
||
}
|
||
```
|
||
|
||
```js
|
||
// 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](multi-phase-camera.md) (the push-in this rule's falloff accompanies) · [coordinate-target-zoom.md](coordinate-target-zoom.md) (zoom onto the focal core — the `constellation-hub` hook) · [viewport-change.md](viewport-change.md) (pan + rack across a tilted card plane) · [counting-dynamic-scale.md](counting-dynamic-scale.md) (hero metric counts up sharp — the `dataviz-countup` spotlight) · [3d-page-scroll.md](3d-page-scroll.md) (the parallax stack to rack between) · [sine-wave-loop.md](sine-wave-loop.md) (post-rack idle; keep both amplitudes tiny).
|