Skill 18 · Hyperframes Animation
Subchapter 18.91
rules/physics-press-reaction.mdMarkdown7 KBView on GitHub
Models a real click: a cursor approaches a button, lands, and both compress IN SYNC, then release together. Distinct from press-release-spring.md (no cursor — just a press happening); this rule is the COMBINED cursor + element behavior. A single drives both: press down compresses both to via , release springs both back to 1.0 with overshoot. The cursor translates to the button’s center BEFORE the press starts; after release it may move on or hold.
PRESS_INTENSITY1 - PRESS_INTENSITY<button class="btn" id="btn">{ctaCopy}</button>
<!-- Cursor at scene-root level so it translates freely; arrow TIP is the click
point, so transform-origin: 0 0 — scaling around the tip keeps it stable. -->
<svg class="cursor" id="cursor" style="pointer-events: none; transform-origin: 0 0">…</svg>gsap.set("#cursor", { x: CURSOR_START_X, y: CURSOR_START_Y }); // off-screen / far corner
// Phase 1 — approach
tl.to(
"#cursor",
{ x: BUTTON_CENTER_X, y: BUTTON_CENTER_Y, duration: APPROACH_DUR, ease: "power2.inOut" },
APPROACH_START,
);
// Phase 2 — coordinated press down: ONE targets array, same scale
tl.to(
["#btn", "#cursor"],
{ scale: 1 - PRESS_INTENSITY, duration: PRESS_DOWN_DUR, ease: "power1.in" },
PRESS_DOWN_AT,
);
// Phase 3 — release: both spring back together
tl.to(
["#btn", "#cursor"],
{ scale: 1, duration: RELEASE_DUR, ease: `back.out(${BOUNCE_FACTOR})` },
RELEASE_AT,
);
// Phase 4 — inner glow during press, resting shadow on release (contact confirmation)
tl.to(
"#btn",
{ boxShadow: "{btnPressedShadow}", duration: PRESS_DOWN_DUR, ease: "power1.in" },
PRESS_DOWN_AT,
);
tl.to(
"#btn",
{ boxShadow: "{btnRestingShadow}", duration: RELEASE_DUR, ease: "power2.out" },
RELEASE_AT,
);
// Cursor optionally exits after the press settles
tl.to(
"#cursor",
{ x: CURSOR_EXIT_X, y: CURSOR_EXIT_Y, duration: CURSOR_EXIT_DUR, ease: "power2.out" },
CURSOR_EXIT_AT,
);HOLD_DUR window between press-down and release: both scales stay at 1 - PRESS_INTENSITY, inner glow stays on. Suggests “thinking” or “loading.”{ p: 0 } proxy tweened to Math.PI * GLOW_PULSE_CYCLES * 2 on ease: "none", onUpdate writing boxShadow with alpha = GLOW_BASE_ALPHA + sin(p) * GLOW_PULSE_AMP. Suggests “processing.”| token | range / rule | notes |
|---|---|---|
| APPROACH_START | 0–0.3 s | long delays read as a dead frame |
| APPROACH_DUR | 0.7–1.3 s | faster = urgent, slower = deliberate |
| PRESS_DOWN_AT | = APPROACH_START + APPROACH_DUR | cursor arrives exactly as the press begins — avoids “tapping on air” |
| PRESS_DOWN_DUR | 0.1–0.25 s | |
| RELEASE_AT | > PRESS_DOWN_AT + PRESS_DOWN_DUR | optional 0.05–0.4 s hold (or HOLD_DUR 0.3–0.8 s) for “thinking” interactions |
| RELEASE_DUR | 0.4–0.7 s | long enough for the overshoot to settle |
| PRESS_INTENSITY | 0.05 subtle · 0.10 standard · 0.15 heavy | applied to both cursor and button via the single targets array |
| BOUNCE_FACTOR | 1.6 soft · 2.0 firm · 2.4 cartoony | |
| CURSOR_START / EXIT | off-screen or far corner | the approach must read as motion-in, not a teleport; exit ≥ RELEASE_AT + RELEASE_DUR |
| BUTTON_CENTER | measured | for place-items: center at 1920×1080: (960, 540) |
| BRAND_REVEAL_AT | < PRESS_DOWN_AT | context precedes interaction |
| glow pulse | 1–4 cycles; base α 0.15–0.3; amp 0.1–0.2 | GLOW_BASE_ALPHA − GLOW_PULSE_AMP ≥ 0 |
| CURSOR_SIZE | 48–96 px at 1080p |
back.out(BOUNCE_FACTOR) on the release, for both together — a linear release loses the tactile feel; release MUST come after press.transform-origin: 0 0 — the arrow’s tip is the click point; scale around the tip keeps it stable. pointer-events: none on the cursor.mouseenter / click events — HF is a render context; everything runs via the timeline.press-release-spring (the BUTTON-only press; this rule layers the cursor on top) · cursor-click-ripple (adds a ripple at the click point) · scale-swap-transition (the press TRIGGERS the swap).