Files
hyperframes/skills/hyperframes-animation/rules/press-release-spring.md
WaterrrForever 853256403b 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.
2026-07-21 17:28:55 +08:00

6.9 KiB
Raw Permalink Blame History

name, description, metadata
name description metadata
press-release-spring Tactile button press with linear compression, spring-based elastic recovery, and layered visual feedback (shadow shrink + release burst + background glow).
tags
spring, press, interaction, button, physics, glow, burst, ui

Press-Release Spring Chain

Separates input (linear compression) from output (spring recovery) to create tactile feel: the overshoot is a natural byproduct of the spring config, not manually coded, with secondary motion (shadow shrink, release burst, background glow) layered on the same trigger frame. This is a reaction on an element already resting on screen — an arrival that springs in from nothing is spring-pop-entrance.md; add a visible cursor actor and it becomes physics-press-reaction.md.

Two phases split at the release:

  1. Press: linear ease → compression (scale: 1 → PRESS_SCALE, shadow shrinks). Linear, not spring — the dip must read as instant/tactile, not squishy.
  2. Release: back.out(BOUNCE_FACTOR) spring back to 1.0. Optional burst glow ring expands behind the button; optional environmental glow fades in.

State continuity is critical: the release tween's start value MUST equal the press tween's end value, or the spring snaps to a different position. GSAP threads this automatically when both tweens target the same property at adjacent positionsRELEASE_START = PRESS_START + PRESS_DUR; a gap or overlap breaks it.

Recipe

<div class="press-stage">
  <div class="bg-glow" id="bg-glow"></div>
  <!-- Burst sits BEHIND the button (z-index 1 vs 2), same footprint, blurred
       radial gradient, opacity 0. bg-glow is a full-stage radial at negative
       inset so it extends past the stage edges. -->
  <div class="burst" id="burst"></div>
  <button class="btn" id="btn">{buttonLabel}</button>
</div>
// Phase 1 — press (linear compression)
tl.to(
  "#btn",
  { scale: PRESS_SCALE, boxShadow: "{btnPressedShadow}", duration: PRESS_DUR, ease: "power1.in" },
  PRESS_START,
);

// Phase 2 — release (spring back; start scale == PRESS_SCALE by adjacency)
tl.to(
  "#btn",
  {
    scale: 1,
    boxShadow: "{btnRestShadow}",
    duration: RELEASE_DUR,
    ease: `back.out(${BOUNCE_FACTOR})`,
  },
  RELEASE_START,
);

// Phase 3 — burst glow pops behind the button, then fades
tl.fromTo(
  "#burst",
  { scale: 1, opacity: 0 },
  {
    scale: BURST_PEAK_SCALE,
    opacity: BURST_PEAK_OPACITY,
    duration: BURST_GROW_DUR,
    ease: "power2.out",
  },
  RELEASE_START,
);
tl.to("#burst", { opacity: 0, duration: BURST_FADE_DUR, ease: "power2.in" }, BURST_FADE_START);

// Phase 4 — environmental glow fades in after release
tl.to(
  "#bg-glow",
  { opacity: BG_GLOW_PEAK_OPACITY, duration: BG_GLOW_FADE_DUR, ease: "power2.out" },
  RELEASE_START,
);

Variations

  • Subtle press (status save / muted CTA): PRESS_SCALE ~0.96, BOUNCE_FACTOR ~1.4, burst scale/opacity reduced.
  • Dramatic press (hero CTA / "ship it"): PRESS_SCALE ~0.88, BOUNCE_FACTOR ~2.5, burst maxed.
  • Color shift during press — darken mid-press, return on release; interpolated backgroundColor at the same timeline positions as the scale tweens. Same state-continuity rule.
  • State change at release (approve / confirm) — instead of returning to the rest color, swap to {successColor} at RELEASE_START and pop a checkmark via a separate back.out(CHECK_BOUNCE) tween (1.42.0, firmer than the button's bounce — a punctuating "stamp"; pop 0.30.6 s) at the same position. The button is now terminal — no further presses expected.

Values

token range notes
button footprint ≥ 35% of canvas area a 320×68 button at 1080p is ~1% and the press reads as visually insignificant
PRESS_SCALE 0.88 dramatic · 0.92 default · 0.96 subtle never <0.85 (broken) or >0.98 (no perceptible dip)
PRESS_DUR 0.100.30 s shorter = snappier; must be shorter than RELEASE_DUR (input faster than spring recovery)
RELEASE_DUR 0.400.90 s shorter = tight pop; longer = loose, wobbly settle
BOUNCE_FACTOR 1.4 soft · 2.0 firm · 2.8 cartoony or elastic.out(amplitude, period) for a rubbery oscillation instead of one overshoot
RELEASE_START = PRESS_START + PRESS_DUR adjacency = automatic state continuity
BURST_PEAK_SCALE 3 subtle · 6 default · 8 max beyond ~8 the radial gradient pixelates visibly
BURST_PEAK_OPACITY 0.41.0 grow ≈ fade, 0.40.7 s each; blur 40100 px (hard ring → ambient haze)
BG_GLOW_PEAK_OPACITY 0.1 subtle · 0.25 default · 0.45 max higher washes the whole composition; fade-in 0.61.0 s; inset 300…−500 px at 1080p

Color tokens: pressed surface darker than rest; rest shadow large + diffuse, pressed small + tight (the button "sinks toward the surface"); burst gradient darker + more saturated than {btnBg} — same-color glow looks washed out; bg glow a low-opacity tint of the button's hue family.

Critical Constraints

  • State continuity — release start value exactly equals press end value; enforced by same-property adjacency at RELEASE_START = PRESS_START + PRESS_DUR.
  • Linear press, spring release — both spring → squishy; both linear → mechanical, no overshoot punch.
  • Anchor compression on center (transform-origin: 50% 50%) or the button collapses asymmetrically.
  • Burst behind, not in front — burst z-index: 1, button z-index: 2; in front it occludes the button at peak opacity.
  • Don't tween boxShadow and filter on the same element — they compete in the layout pipeline; shadow on the button, blur on the separate burst layer.
  • Climax dwell — after the burst peak + reveal, the composition must run ≥ 1 s more (≥ 2 s for dramatic variants); a reveal at t = DURATION 0.2 s reads as "flashed and gone."

See also

spring-pop-entrance (the ENTRANCE counterpart — arrival, not reaction) · physics-press-reaction (this press with a visible cursor actor) · cursor-click-ripple (the cursor click that triggers the press) · sine-wave-loop (idle micro-float BEFORE the press) · center-outward-expansion (badge burst synced to the release).