85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
216 lines
12 KiB
Markdown
216 lines
12 KiB
Markdown
# Cut catalog — within-frame seams (worker-built)
|
||
|
||
> **A worker build-recipe (Step 5) — the sibling of `../hyperframes-animation/rules/`, not a second motion doc.** These are within-frame cuts the **frame worker builds INSIDE its own composition** (Z-scale + blur + opacity tweens, or per-word x-staggers, all on the frame's own paused GSAP timeline). They are **not** the between-frame transition: story owns that via `transition_in`, which the harness's injector stamps from a **separate registry vocabulary** (`crossfade` / `blur-crossfade` / `push-slide` / `zoom-through` / `squeeze`) — the catalog names here (**cut-the-curve / inverse-zoom / waterfall**) are **not** valid `transition_in` values. Use this catalog when a frame's shot sequence has an internal seam — a within-scene text/element swap, a **Scene-to-Scene** cut (a `Scene` is a time window WITHIN one frame, **not** a frame-to-frame boundary), or a text-to-text line change — and you want it to read as one continuous move instead of a hard slideshow cut. (`zoom-through` lives in both worlds: a whole-frame wrapper transition in the registry, an element-level Z-cut here — same idea, different scope.)
|
||
|
||
Four techniques that create depth and continuity:
|
||
|
||
1. **Zoom-Through** — within-scene text swaps, Z-axis, moving TOWARD the viewer
|
||
2. **Inverse Zoom-Through** — Z-axis swaps moving AWAY from the viewer
|
||
3. **Cut the Curve** — between-scene transitions on x/y
|
||
4. **Waterfall Cut** — word-by-word cut-the-curve with staggered exits and entries
|
||
|
||
All four are the same underlying principle: **cut at peak velocity, match direction and
|
||
speed on both sides of the cut.** The differences are axis, scope, and granularity.
|
||
|
||
**Choosing which at a seam:** for an UNFINISHED phrase (building one larger idea across
|
||
several visually distinct scenes that still approach the same point — multi-line text, a
|
||
run of consecutive cards) use **cut-the-curve** / **waterfall**. For a STATE CHANGE (turning
|
||
to a NEW part of the video — most often hook → context, between two distinct chapters) use
|
||
**zoom-through**, and **inverse zoom-through** for an arrival / payoff beat. Chain these so
|
||
the frame's internal seams feel like one camera moving through the content.
|
||
|
||
---
|
||
|
||
## Blur Logic (applies to all Z-axis variants)
|
||
|
||
Blur sells the speed at the cut, but it must scale with the SUBJECT SIZE:
|
||
|
||
| Subject | Peak blur | Why |
|
||
| ------------------------------------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| Text-scale (headline, line, word group) | **10px** | At 20px text smears into illegibility — the eye loses the word it was tracking and the cut reads as a glitch, not speed. At 20px letterforms go mushy mid-cut; 10px keeps them readable. |
|
||
| Full-frame surface (terminal window, card, screenshot) | **18–20px** | Big surfaces have edges and texture that survive heavy blur; lighter blur on a full-frame move reads as a rendering hiccup instead of motion. |
|
||
|
||
Both sides of a cut use the SAME peak blur — the value must match at the swap frame.
|
||
Apply blur to the WRAPPER, never to individual children.
|
||
|
||
---
|
||
|
||
## 1. Zoom-Through (forward)
|
||
|
||
### The Problem
|
||
|
||
Text enters, holds, exits. Then next text enters, holds, exits. Each text block is
|
||
independent — no depth, no continuity. The video feels like a slideshow.
|
||
|
||
### The Principle
|
||
|
||
A velocity-matched cut on the Z-axis. You **never see both texts at the same time.** The
|
||
outgoing text scales toward the viewer (accelerating), blur and opacity peak at the cut
|
||
point hiding a hard swap, and the incoming text continues scaling up from behind
|
||
(decelerating into the focal plane). One continuous forward motion, two different texts.
|
||
|
||
### The Three Phases
|
||
|
||
**Phase 1: Exit** — text accelerates forward (toward viewer)
|
||
|
||
- Scale: `1.0 -> 1.2`, Blur: `0px -> 10px` (text-scale; see Blur Logic), Opacity: `1.0 -> 0.15`
|
||
- Scale/blur easing: `power3.in` (steep acceleration)
|
||
- Opacity easing: `none` (linear — even dimming, separated from scale)
|
||
- Duration: 0.2s
|
||
|
||
**Phase 2: Hard cut** at peak velocity + peak blur
|
||
|
||
- Outgoing: `opacity: 0` (instant via `tl.set`)
|
||
- Incoming: `opacity: 0.15, scale: 0.75, blur: 10px` (instant via `tl.set`)
|
||
- All properties match at the cut: blur, opacity, and scale DIRECTION (both scaling up)
|
||
|
||
**Phase 3: Entry** — text continues forward (growing into focal plane)
|
||
|
||
- Scale: `0.75 -> 1.0`, Blur: `10px -> 0px`, Opacity: `0.15 -> 1.0`
|
||
- Easing: `expo.out` (steep initial burst matching exit velocity, long settle)
|
||
- Duration: 0.5s
|
||
|
||
### Why Opacity Must Be Separate on Exit
|
||
|
||
Scale uses `power3.in` but that keeps opacity near 1.0 for most of the tween. Splitting
|
||
opacity to its own tween with linear ease makes the dimming even. On entry, all properties
|
||
can share `expo.out`.
|
||
|
||
---
|
||
|
||
## 2. Inverse Zoom-Through (backward)
|
||
|
||
The mirror: the camera "pulls back" instead of pushing through. The outgoing element
|
||
RECEDES away from the viewer; the incoming element arrives OVERSIZED (as if it had been
|
||
just behind the camera) and retracts into the focal plane. Both move in the shrinking
|
||
direction — same-direction rule preserved, just reversed.
|
||
|
||
**When to use over the forward variant:** arrival beats. The incoming element lands with
|
||
presence because it comes from larger-than-frame — right for a payoff line ("That changes
|
||
today."), a giant reply, or a held end-state. Forward zoom-through reads as _progressing
|
||
through_ content; inverse reads as _arriving at_ content.
|
||
|
||
### The Three Phases
|
||
|
||
**Phase 1: Exit** — element recedes (away from viewer)
|
||
|
||
- Scale: `1.0 -> 0.8`, Blur: `0px -> 10px` (text-scale)
|
||
- Scale/blur easing: `power3.in`; Opacity: `1.0 -> 0.15` on `none` (separate tween)
|
||
- Duration: 0.2s
|
||
|
||
**Phase 2: Hard cut**
|
||
|
||
- Outgoing: `opacity: 0` via `tl.set`
|
||
- Incoming: `opacity: 0.15, scale: 1.25, blur: 10px` via `tl.set`
|
||
|
||
**Phase 3: Entry** — incoming retracts into place
|
||
|
||
- Scale: `1.25 -> 1.0`, Blur: `10px -> 0px`, Opacity: `0.15 -> 1.0`
|
||
- Easing: `expo.out`, Duration: 0.5s
|
||
|
||
---
|
||
|
||
## 3. Cut the Curve (Scene Transitions)
|
||
|
||
### The Principle
|
||
|
||
Use cut-the-curve for **all scene-to-scene transitions** on x and y axes. The outgoing
|
||
scene's hero element accelerates in one direction, the cut lands mid-motion, and the
|
||
incoming scene's hero element continues moving in the **same direction** and decelerates.
|
||
Nothing exits fully off-screen and nothing enters from fully off-screen — **speed plus
|
||
opacity fading trick the eye**; the partial moves are enough.
|
||
|
||
### Same Path, Same Direction
|
||
|
||
If Scene A's hero slides left, Scene B's hero enters from the right and continues sliding
|
||
left. Both move leftward. One continuous motion.
|
||
|
||
| Direction | Scene A exit | Scene B entry start | Scene B entry end |
|
||
| --------- | -------------- | ------------------- | ----------------- |
|
||
| Leftward | `x: 0 -> -230` | `x: +230` | `x: 0` |
|
||
| Rightward | `x: 0 -> +230` | `x: -230` | `x: 0` |
|
||
| Upward | `y: 0 -> -230` | `y: +230` | `y: 0` |
|
||
| Downward | `y: 0 -> +230` | `y: -230` | `y: 0` |
|
||
|
||
### Velocity matching via mirrored eases
|
||
|
||
The cleanest match: exit `power4.in` and entry `power4.out` with the SAME distance and
|
||
duration — mathematically the two halves of one `power4.inOut` composite, so the entering
|
||
element picks up at exactly the 50% point of the notional path at identical velocity
|
||
(e.g. 230px / 0.3s ≈ 3,070 px/s at the cut on both sides).
|
||
|
||
The fade trick: the exit's opacity completes at ~25–30% of its travel (fade duration
|
||
≈ 0.18–0.3s vs motion 0.3–0.34s) — the element vanishes while still visibly accelerating,
|
||
and nothing has to reach the frame edge. Entry fades IN fast from ~0.35 under its
|
||
deceleration. Time the LAST fading element to die right at the hard cut — gaps where
|
||
nothing is moving read as awkward dead air.
|
||
|
||
### Rules
|
||
|
||
- Use cut-the-curve for all scene transitions — it's the default, not an accent
|
||
- Same direction on both sides; mirrored `.in`/`.out` eases, same distance + duration
|
||
- Exit duration short (0.2–0.4s), entry duration >= exit duration
|
||
- Partial travel + fade, never full off-screen moves
|
||
|
||
---
|
||
|
||
## 4. Waterfall Cut (word-by-word cut-the-curve)
|
||
|
||
Cut-the-curve at WORD granularity — the strongest version of the leftward cut for
|
||
text-to-text seams. Each word of the outgoing line ramps out on its own pronounced curve;
|
||
each word of the incoming line cascades in mid-flight. The stagger turns the cut into a
|
||
wave the eye rides across the seam.
|
||
|
||
### Exit (per word)
|
||
|
||
- Motion: `x: 0 -> -230` over 0.34s on **power4.in** — a much more pronounced ramp than
|
||
the usual power2: the word barely creeps, then RIPS
|
||
- Fade: `opacity -> 0` over 0.18s (separate tween, `power1.in`) — completes when the word
|
||
is only ~25–30% into its travel
|
||
- Stagger: reading order, ~0.022s per word, timed so the LAST word finishes fading right
|
||
at the hard cut
|
||
|
||
### Entry (per word)
|
||
|
||
- `fromTo x: +230 -> 0, opacity: 0.35 -> 1` over 0.3s on **power4.out** — the mirrored
|
||
back half of the composite; every word ignites already moving at matched velocity
|
||
- Waterfall stagger with SHRINKING gaps (start 0.05s, multiply by ~0.84 per word) so the
|
||
cascade accelerates across the line — the cascade should speed up word over word, not run
|
||
at a flat per-word delay
|
||
- Pre-set all words to `x: +230, opacity: 0` at build time — `immediateRender: false`
|
||
alone leaves un-started words sitting visible at rest during the stagger window
|
||
|
||
### Whole-line variant
|
||
|
||
A single-line beat (e.g. a big intro line) exits as one group with the same pronounced
|
||
ramp, but stretch its fade to ~0.3s ending ~0.02s before the cut — a lone element that
|
||
fades early leaves dead air that a word cascade would have covered.
|
||
|
||
---
|
||
|
||
## Choosing a Variant
|
||
|
||
| | Zoom-Through | Inverse Zoom | Cut the Curve | Waterfall Cut |
|
||
| -------------- | --------------------------- | --------------------------- | ----------------- | ------------------------- |
|
||
| Scope | Within-scene text swap | Arrival/payoff beat | Between scenes | Text-to-text seam |
|
||
| Axis | Z, toward viewer | Z, away from viewer | X / Y | X, per-word |
|
||
| Peak blur | 10px text / 20px full-frame | 10px text / 20px full-frame | none required | none (fade does the work) |
|
||
| Opacity at cut | 0.15 | 0.15 | exit faded by cut | last word dies at cut |
|
||
| Feel | progressing through | arriving at | carried sideways | a wave across the seam |
|
||
|
||
---
|
||
|
||
## Anti-Patterns
|
||
|
||
| Don't | Why | Instead |
|
||
| ---------------------------------------- | ------------------------------------------- | ------------------------------------------------------ |
|
||
| Two texts visible during a zoom-through | Overlapping text breaks the Z-axis illusion | Hard cut at blur peak, one text at a time |
|
||
| 20px blur on text-scale subjects | Letterforms smear; reads as a glitch | 10px for text, 18–20px only full-frame |
|
||
| Elements on different paths across a cut | Eye tracks one direction, cut goes another | Same property, same direction |
|
||
| Mismatched blur/opacity at the swap | Visible flash or brightness jump | Identical values at the cut frame |
|
||
| Gentle easing on entry (`power2.out`) | Entry velocity feels slower than exit | Mirror the exit: `power4.out` / `expo.out` |
|
||
| Full off-screen exits / entries | Wastes time and breaks the speed illusion | Partial travel + early fade |
|
||
| Lone element fading long before its cut | Dead air at the seam | Fade ends ~0.02s before the cut, or use a word cascade |
|
||
| Zoom-through on body text | Small text at 0.75 scale is unreadable | Only headlines and short phrases |
|
||
| Scene cuts without cut-the-curve | Static cuts feel like a slideshow | Cut-the-curve is the default |
|