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.
This commit is contained in:
WaterrrForever
2026-07-21 17:28:55 +08:00
committed by GitHub
parent 8f171433f9
commit 853256403b
68 changed files with 4911 additions and 8179 deletions
@@ -7,80 +7,24 @@ metadata:
# Cursor Click Ripple
An animated cursor moves to a target element, performs a click with visual depression, and emits expanding ripple rings from the click point.
An animated cursor moves to a target element, performs a click with visual depression, and emits expanding ripple rings from the click point. Three sequential phases on one timeline: **move** (eased translation to the target's center) → **click** (scale depression on cursor + target together, yoyo back) → **ripple** (13 staggered rings expand and fade from the click point). This is a _point event at one location_ — a sustained hold across space is [cursor-drag.md](cursor-drag.md).
## How It Works
Three sequential phases driven by a single GSAP timeline:
1. **Move**: eased cursor translation from entry point to the target element's center
2. **Click**: scale depression on both cursor and target (yoyo: shrink then return)
3. **Ripple**: expanding circles radiate outward from the click point with fade-out. 13 staggered rings amplify the click feedback
Use a GSAP timeline because the phase ordering (move → settle → click → ripples) is exactly what timelines express cleanly.
## HTML
## Recipe
```html
<div
class="scene"
id="cursor-click-scene"
data-composition-id="cursor-click-scene"
data-start="0"
data-duration="2"
data-track-index="0"
>
<button class="target-button">{ctaLabel}</button>
<div class="cursor">
<svg width="24" height="24" viewBox="0 0 24 24">
<path
d="M5 3L19 12L12 13L9 20L5 3Z"
fill="{cursorFill}"
stroke="{cursorStroke}"
stroke-width="1.5"
/>
</svg>
</div>
<!-- Ripple rings — centered on click target, hidden until trigger -->
<div class="ripple ripple-1"></div>
<div class="ripple ripple-2"></div>
<div class="ripple ripple-3"></div>
</div>
<button class="target-button">{ctaLabel}</button>
<div class="cursor"><!-- arrow SVG, positioned at the entry corner --></div>
<!-- Rings live in DOM from t=0 at the click-target CENTER, scale 0 + opacity 0 -->
<div class="ripple ripple-1"></div>
<div class="ripple ripple-2"></div>
<div class="ripple ripple-3"></div>
```
## CSS
Position cursor at the entry point. Button sits at its final position. Ripples are at the click-target center with `scale: 0` and `opacity: 0` so they hold invisible until the timeline trigger:
```css
.scene {
position: relative;
width: 100%;
height: 100%;
}
.target-button {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
/* ...button styling (background, color, font from project tokens) */
}
.cursor {
position: absolute;
left: 10%;
top: 80%; /* entry corner */
pointer-events: none;
z-index: 999;
}
.ripple {
position: absolute;
left: 50%;
top: 50%; /* click target center */
top: 50%; /* click-target center */
width: 100px;
height: 100px;
border-radius: 50%;
@@ -91,172 +35,70 @@ Position cursor at the entry point. Button sits at its final position. Ripples a
}
```
## GSAP Timeline
```js
// Phase 1 — Move: eased, not linear
tl.to(".cursor", { x: TARGET_X, y: TARGET_Y, duration: MOVE_DUR, ease: MOVE_EASE }, 0);
Build a paused timeline. Register it on `window.__timelines` with the same key as `data-composition-id` on the scene root. All tuning values are named constants — see How to Choose Values below.
// Phase 2 — Click: cursor + target depress together, then return
tl.to(
".cursor",
{ scale: CURSOR_PRESS_SCALE, duration: PRESS_DUR, ease: "power2.in", yoyo: true, repeat: 1 },
CLICK_AT,
);
tl.to(
".target-button",
{ scale: TARGET_PRESS_SCALE, duration: PRESS_DUR, ease: "power2.in", yoyo: true, repeat: 1 },
CLICK_AT,
);
```html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// MOVE_DUR, MOVE_EASE, CLICK_AT, PRESS_DUR, CURSOR_PRESS_SCALE, TARGET_PRESS_SCALE,
// RIPPLE_AT, RIPPLE_DUR, RIPPLE_SCALE, RIPPLE_STAGGER, RIPPLE_EASE
// — all named; values per How to Choose Values.
// Phase 1 — Move cursor to target center (eased, not linear)
tl.to(
".cursor",
{
x: TARGET_X,
y: TARGET_Y,
duration: MOVE_DUR,
ease: MOVE_EASE,
},
0,
);
// Phase 2 — Click: cursor + target depress together, then return
tl.to(
".cursor",
{
scale: CURSOR_PRESS_SCALE,
duration: PRESS_DUR,
ease: "power2.in",
yoyo: true,
repeat: 1,
},
CLICK_AT,
);
tl.to(
".target-button",
{
scale: TARGET_PRESS_SCALE,
duration: PRESS_DUR,
ease: "power2.in",
yoyo: true,
repeat: 1,
},
CLICK_AT,
);
// Phase 3 — Ripple burst, N rings staggered from the click point
tl.set([".ripple-1", ".ripple-2", ".ripple-3"], { opacity: 1 }, RIPPLE_AT);
tl.to(
[".ripple-1", ".ripple-2", ".ripple-3"],
{
scale: RIPPLE_SCALE,
opacity: 0,
duration: RIPPLE_DUR,
ease: RIPPLE_EASE,
stagger: RIPPLE_STAGGER,
immediateRender: false,
},
RIPPLE_AT,
);
window.__timelines["cursor-click-scene"] = tl;
</script>
// Phase 3 — Ripple burst, N rings staggered from the click point
tl.set([".ripple-1", ".ripple-2", ".ripple-3"], { opacity: 1 }, RIPPLE_AT);
tl.to(
[".ripple-1", ".ripple-2", ".ripple-3"],
{
scale: RIPPLE_SCALE,
opacity: 0,
duration: RIPPLE_DUR,
ease: RIPPLE_EASE,
stagger: RIPPLE_STAGGER,
immediateRender: false, // holds scale 0 / opacity 0 until the click moment
},
RIPPLE_AT,
);
```
## How to Choose Values
- **MOVE_DUR** — cursor travel time from entry to target, in seconds
- Range: 0.41.0 s
- Effects: short feels darting; long feels deliberate / "considered click"
- Constraints: must end before `CLICK_AT` — otherwise the click fires while the cursor is still moving and reads as a misclick
- Reference: ../../examples/cta-orbit-collapse.html uses 0.5 s
- **MOVE_EASE** — easing family for the move tween
- Discrete choice. Options:
- `power2.inOut` — symmetric, calm; good for "the user thoughtfully moves the cursor"
- `back.out(<n>)` — overshoot landing; good when the click target is a button you want the cursor to "settle onto" with a tiny visible recoil. Pair with a low overshoot coefficient (~1.21.4) — higher reads as cartoonish
- `power3.out` — fast start, soft landing; good for a "decisive" move
- Reference: ../../examples/cta-orbit-collapse.html uses `back.out(1.3)`
- **CLICK_AT** — time the click fires, in seconds
- Range: must be ≥ `MOVE_DUR` (cursor has settled); typically `MOVE_DUR + 0.00.3 s` of "decision pause"
- Effects: zero pause reads as autopilot; >0.3 s of pause reads as hesitation
- Reference: ../../examples/cta-orbit-collapse.html clicks 0.2 s after the cursor settles
- **PRESS_DUR** — half-duration of the depression (the yoyo runs twice this)
- Range: 0.060.12 s
- Effects: short feels crisp; long feels mushy
- Constraints: total press = `2 * PRESS_DUR`; must finish before the next scene phase needs the cursor / target back at normal scale
- Reference: ../../examples/cta-orbit-collapse.html uses 0.08 s
- **CURSOR_PRESS_SCALE / TARGET_PRESS_SCALE** — how far each compresses during the click
- Range: cursor 0.800.90; target 0.920.97
- Effects: smaller numbers = stronger "this click counts" feel; values close to 1 read as a gentle tap
- Constraints: cursor compresses MORE than the target — the cursor is the actor, the target is the recipient
- Reference: ../../examples/cta-orbit-collapse.html uses cursor 0.85 / target 0.95
- **RIPPLE_AT** — when the rings start expanding, in seconds
- Range: `CLICK_AT + 0.00.08 s`
- Effects: simultaneous with the press feels causal; slight delay feels acoustic ("the click happens, then the wave radiates")
- Reference: ../../examples/cta-orbit-collapse.html starts the ripple at `CLICK_AT` exactly
- **RIPPLE_DUR** — how long each ring takes to fully expand and fade
- Range: 0.51.0 s
- Effects: short rings feel sharp; long rings feel like a soft sonar
- Constraints: must complete before any phase that depends on the ring being gone (e.g. a screen wipe)
- Reference: ../../examples/cta-orbit-collapse.html uses 0.7 s
- **RIPPLE_SCALE** — final scale of each ring before it fades
- Range: 36
- Effects: 3 keeps the ring near the click site; 6 lets it sweep the surrounding area
- Constraints: if the ring would exit the visible frame before opacity reaches 0, lower the scale or shorten the duration
- Reference: ../../examples/cta-orbit-collapse.html uses 5
- **RIPPLE_STAGGER** — delay between consecutive rings
- Range: 0.060.12 s (or 0 for a single ring; see Variations)
- Effects: below ~0.06 s reads as one thick ring; above ~0.12 s reads as separate events
- Reference: ../../examples/cta-orbit-collapse.html uses a single ring (no stagger)
- **RIPPLE_EASE** — easing family for the expansion
- Discrete choice. Options:
- `power2.out` — fast start, soft tail; the standard "ping" feel
- `power3.out` — even sharper attack, longer tail
- `expo.out` — almost-instant expansion with a long quiet fade; reads as a strong, distant pulse
- Reference: ../../examples/cta-orbit-collapse.html uses `power2.out`
- **TARGET_X / TARGET_Y** — pixel offset of the click target from the cursor's CSS-laid origin
- These are layout-derived, not creative knobs — they must match the visual centroid of the actual click target. A 4 px miss reads as missing the button
- Reference: ../../examples/cta-orbit-collapse.html targets the white button at `CENTER_X + 130, CENTER_Y + 15`
## Variations
- **Single ring** — keep one `.ripple` element, drop the stagger; reads as more elegant when the rest of the scene is busy
- **Keyframed attack-decay** — replace the simple expand-and-fade with a `keyframes` block that ramps opacity 0 → peak → 0 across the duration; gives a clearer "energy radiates and dissipates" envelope (used in ../../examples/cta-orbit-collapse.html)
- **Multi-ring expanding pulse** — 3 rings with 0.08 s stagger feels richer when the click is the climactic moment of the scene
- **Single ring** — one `.ripple`, no stagger; more elegant when the rest of the scene is busy.
- **Keyframed attack-decay** — a `keyframes` block ramps opacity 0 → peak → 0 across the duration; a clearer "energy radiates and dissipates" envelope.
- **Multi-ring expanding pulse** — 3 rings at 0.08 s stagger when the click is the scene's climactic moment.
## Key Principles
## Values
- **Move before click**: trigger the click only after the move tween has settled — clicking mid-motion reads as unintentional
- **Synchronized depression**: cursor + target depress at the same `position` time with the same duration (and both yoyo back)
- **Ripple from click point**: ripples expand from the exact click location (the button's visual center), not from any element's bounding-box origin
- **Subtle scale**: cursor compresses more than the target — see `CURSOR_PRESS_SCALE` / `TARGET_PRESS_SCALE`
- **High z-index cursor**: cursor renders above all content for the entire sequence
| token | range | notes |
| --------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| MOVE_DUR | 0.41.0 s | short darts; long reads as a "considered click." Must end before CLICK_AT or it reads as a misclick |
| MOVE_EASE | discrete choice | `power2.inOut` calm · `power3.out` decisive · `back.out(1.21.4)` settles onto the button with a tiny recoil (higher reads cartoonish) |
| CLICK_AT | `MOVE_DUR + 00.3 s` | zero pause reads as autopilot; >0.3 s reads as hesitation |
| PRESS_DUR | 0.060.12 s (half; yoyo ×2) | short crisp, long mushy; must finish before the next phase needs normal scale |
| CURSOR / TARGET_PRESS_SCALE | 0.800.90 / 0.920.97 | cursor compresses MORE than the target — the cursor is the actor, the target the recipient |
| RIPPLE_AT | `CLICK_AT + 00.08 s` | simultaneous feels causal; slight delay feels acoustic |
| RIPPLE_DUR | 0.51.0 s | sharp ping vs soft sonar; must complete before anything that needs the ring gone |
| RIPPLE_SCALE | 36 | 3 stays near the click site; if the ring would exit the frame before fading, lower it |
| RIPPLE_STAGGER | 0.060.12 s (or 0) | below ~0.06 s reads as one thick ring; above ~0.12 s as separate events |
| RIPPLE_EASE | discrete choice | `power2.out` standard ping · `power3.out` sharper attack · `expo.out` strong distant pulse |
| TARGET_X / TARGET_Y | layout-derived | must match the target's visual centroid — a 4 px miss reads as missing the button |
Reference values: `../../examples/cta-orbit-collapse.html` — 0.5 s move on `back.out(1.3)`, click +0.2 s, press 0.08 s at 0.85/0.95, single ring to 5× over 0.7 s `power2.out`.
## Critical Constraints
- **Timeline must be paused**: `gsap.timeline({ paused: true })`. Never call `tl.play()` — HyperFrames seeks the timeline frame-by-frame deterministically
- **Registry key = `data-composition-id`**: `window.__timelines["<id>"]` must match the `data-composition-id` on the scene root exactly
- **`immediateRender: false` on the ripple expand**: holds the initial state (`scale: 0`, `opacity: 0`) until the click moment, otherwise the tween pre-renders and the rings appear at the wrong size at t=0
- **Finite duration**: verify `tl.duration()` matches the scene's `data-duration`
- **`pointer-events: none` on cursor + ripples**: they're purely visual; never block underlying interactivity (matters for hover-able exports)
- **No CSS transitions / animations**: all motion lives in the GSAP timeline so seek stays deterministic
- **Move before click** — trigger the click only after the move tween settles; clicking mid-motion reads as unintentional.
- **Rings live in DOM from t=0** at the click-target center with `scale: 0` + `opacity: 0` — never conditionally rendered; `immediateRender: false` on the expand so they hold invisible until the trigger.
- **Ripple from the click point** — the button's visual center, not any element's bounding-box origin.
- **Synchronized depression** — cursor + target depress at the same position with the same duration, and both yoyo back.
- **Cursor above all content** (high z-index) for the whole sequence; `pointer-events: none` on cursor + ripples.
## Combinations
## See also
- [orbit-3d-entry.md](orbit-3d-entry.md) — when the click is the pivot that collapses orbiting elements toward the cursor's target
- [center-outward-expansion.md](center-outward-expansion.md) — the click can be the trigger for an outward burst from the click point
- [press-release-spring.md](press-release-spring.md) for stronger physical feel on the target button
- [scale-swap-transition.md](scale-swap-transition.md) for the button's state change after click (button morphs into success state, next view, etc.)
## Pairs with HF skills
- `/hyperframes-animation` — timeline + tween API reference (eases, stagger, `immediateRender`, etc.)
- `/hyperframes-core` — composition wiring (`data-*` attributes, scene structure, registration contract)
- `/hyperframes-cli``hyperframes lint` to verify the registry key + duration match
`orbit-3d-entry` (click as the pivot that collapses orbiters) · `center-outward-expansion` (click triggers an outward burst) · `press-release-spring` (stronger physical feel on the target) · `scale-swap-transition` (the button's post-click state change).