Files
hyperframes/skills/hyperframes-animation/rules/particle-burst.md
T
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

150 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: particle-burst
description: Deterministic particle / confetti events — a confetti pop that bursts up and drifts down (optionally instant-shrinking away), a dot burst from behind text, or a glyph dissolving to particles. Every particle's state is a pure ballistic function of timeline time from index-seeded values, so a scrub to any t shows the correct mid-flight frame.
metadata:
tags: particles, confetti, burst, dissolve, celebration, ballistic, deterministic, punctuation
---
# Particle Burst
Discrete flying particles as a one-shot event: a **confetti pop** that erupts upward and drifts back down on gravity, a **dot burst** radiating from behind a landing word, or a **glyph dissolve** where text breaks into particles that scatter and die. Particles are ephemeral garnish — born from a beat, fly, gone; they never become layout.
Boundaries: [css-marker-patterns.md](css-marker-patterns.md)'s burst mode is radiating **drawn lines** — a static accent, no flight. [press-release-spring.md](press-release-spring.md)'s release burst is **one blurred radial layer** faking an explosion — enough when a single glow pop will do. [center-outward-expansion.md](center-outward-expansion.md) moves **real layout elements** to final resting slots; particles have no destination, only physics and a death.
## How It Works
The whole event is **one driver tween and one formula**:
1. **Seeded setup** — a fixed pool of `PARTICLE_COUNT` small divs is created once at composition setup (a deterministic loop — setup-time generation is fine; per-frame DOM creation is not). Each particle `i` derives everything from a pure hash:
```js
// angle, speed, size, spin, color (palette[i % palette.length]) — all from prand(i * k)
const prand = (n) => {
const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;
return x - Math.floor(x); // 0..1, pure function of n
};
```
2. **Ballistic formula** — a proxy tween advances `T: 0 → 1` over `FLIGHT_DUR` with `ease: "none"`; `onUpdate` positions every particle as a **pure function of T**:
```
x(T) = vx · T·FLIGHT_DUR
y(T) = vy · T·FLIGHT_DUR + ½ · G · (T·FLIGHT_DUR)²
rot(T) = spin · T·FLIGHT_DUR
```
Gravity `G` supplies the rise-decelerate-fall arc for free. Because position is computed from `T` (never accumulated per frame), a seek to any moment renders the exact mid-flight state — this is what makes DOM particles seek-safe. The driver's `ease: "none"` is load-bearing: the physics lives in the formula; an eased driver warps gravity and the arc stops reading as thrown objects.
3. **Death** — an opacity tail inside the same formula (fade over the last `FADE_FRAC` of flight), or the confetti signature: a separate **instant-shrink** tween scaling the pool to 0 in a blink at flight end. Either way the particles end invisible and stay invisible.
## Recipe
```html
<!-- inside a standard scene clip (hyperframes-core) -->
<div class="burst-stage">
<div class="particle-field" id="particle-field"></div>
<div class="burst-hero" id="burst-hero">{heroWord}</div>
</div>
```
```css
/* .burst-stage: position: relative; display: grid; place-items: center.
.burst-hero: z-index: 2 — particles fly BEHIND the word. */
.particle-field {
position: absolute;
z-index: 1;
left: 50%;
top: 50%; /* the launch origin — offset to taste (e.g. the word's baseline) */
width: 0;
height: 0;
}
.particle {
position: absolute;
left: 0;
top: 0;
border-radius: 2px; /* confetti chip; 50% for dots */
opacity: 0; /* invisible until the event fires */
will-change: transform, opacity;
}
```
```js
// Setup: deterministic pool, generated ONCE.
const field = document.getElementById("particle-field");
const palette = ["{accentA}", "{accentB}", "{accentC}"]; // 3-5 brand tokens
const parts = [];
for (let i = 0; i < PARTICLE_COUNT; i++) {
const el = document.createElement("div");
el.className = "particle";
const size = SIZE_MIN + prand(i * 3 + 1) * (SIZE_MAX - SIZE_MIN);
el.style.width = `${size}px`;
el.style.height = `${size * 0.7}px`; // slightly oblong = confetti chip
el.style.background = palette[i % palette.length];
field.appendChild(el);
// Index-seeded launch parameters — the particle's whole life, fixed here.
const angle = -Math.PI / 2 + (prand(i * 5 + 2) * 2 - 1) * CONE; // upward cone
const speed = SPEED_MIN + prand(i * 7 + 3) * (SPEED_MAX - SPEED_MIN);
parts.push({
el,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed, // negative = up
spin: (prand(i * 11 + 4) * 2 - 1) * SPIN_MAX,
});
}
// Confetti pop — one driver, pure ballistic formula.
const drive = { T: 0 };
tl.fromTo(
drive,
{ T: 0 },
{
T: 1,
duration: FLIGHT_DUR,
ease: "none", // physics lives in the formula, not the ease
onUpdate: () => {
const t = drive.T * FLIGHT_DUR; // seconds of flight — pure function of T
const fade = Math.min(1, (1 - drive.T) / FADE_FRAC); // opacity tail
parts.forEach((p) => {
const x = p.vx * t;
const y = p.vy * t + 0.5 * G * t * t; // rise, stall, drift down
p.el.style.transform = `translate(${x}px, ${y}px) rotate(${p.spin * t}deg)`;
p.el.style.opacity = String(drive.T === 0 ? 0 : fade); // T===0 guard covers seeks before the event
});
},
},
BURST_AT,
);
```
## Variations
- **Confetti pop, then instant-shrink** — the playful signature: full burst, gravity drift, then every chip scales to 0 in a blink: `FADE_FRAC` near 0, plus `tl.to(".particle", { scale: 0, duration: SHRINK_DUR, ease: "power2.in" }, BURST_AT + FLIGHT_DUR - SHRINK_DUR)` with `SHRINK_DUR` 0.150.25s. Keep the whole event tiny relative to the subject — a garnish measured in a few dozen pixels, not a screen-filling cannon.
- **Dot burst behind a landing word** — radial instead of a cone: `angle = prand(i) * Math.PI * 2`, `G` near 0, short flight (0.40.7s), round dots (`border-radius: 50%`), pool z-indexed behind the word. Fire at the word's settle frame.
- **Glyph dissolve** — seed each particle's **origin** across the glyph block's box (`ox = (prand(i*13) - 0.5) * BLOCK_W`, same for `oy`, added inside the transform), gentle outward drift with low `G`; text fades out over the first ~30% of flight while particles fade in from its silhouette. Color every particle `{textColor}` so the swarm reads as the text's own material. (True per-pixel dissolves are Canvas-2D territory — `techniques.md`; this DOM version sells it up to ~40 particles.)
- **Two-stage burst (pop + stragglers)** — split the pool: 70% on the main driver, 30% on a second driver ~0.12s later with lower speeds; the split is index-derived (`i % 10 < 3`). Same formula, two windows.
## Values
| token | range | notes |
| --------------------- | -------------------------------------------- | ------------------------------------------------------------------------------- | --- | ----------------------- |
| PARTICLE_COUNT | 1018 pop/dots; 2440 dissolve | **cap ~40** — per-frame style writes; past that, seek perf and register degrade |
| G | 9001600 px/s² confetti; 0200 dots/dissolve | natural fall vs drift |
| SPEED_MIN / SPEED_MAX | 250700 px/s | per-particle via `prand`, never uniform |
| CONE | 0.350.8 rad (~2045°) | wider = splash, narrower = fountain |
| FLIGHT_DUR | 0.71.4s | arc should peak ~3545% of flight: check ` | vy | / G ≈ 0.4 × FLIGHT_DUR` |
| SIZE_MIN / SIZE_MAX | 514px chips; 48px dots | on a 1080p frame |
| SPIN_MAX | 180720 deg/s confetti; 0 dots | tumble |
| FADE_FRAC | 0.20.35 | near 0 when using instant-shrink |
| BURST_AT | on a cause | the word's settle, a click, a lockup completing — an uncaused burst is noise |
## Critical Constraints
- **Position is a pure function of time, driver ease `"none"`** — `x(T)`, `y(T)`, `rot(T)` computed from the driver value every frame, never accumulated (`+=`) per tick (accumulation breaks the moment the renderer seeks); gravity is the ease — an eased driver bends the parabola.
- **Fixed pool, no per-frame DOM** — all particles exist after setup with `opacity: 0`; the event only writes `transform` / `opacity`. **`PARTICLE_COUNT ≤ ~40`** — per-frame style writes scale linearly; keep the event cheap.
- **Particles start AND end at `opacity: 0`** — the `drive.T === 0` guard covers seeks to before the event; the tail/shrink covers after. A chip frozen mid-air at driver end is a bug every subsequent frame.
- **Particles are punctuation** — one event per beat, fired on a cause, small relative to the subject, dead before the next beat; z-ordered behind or around the word it celebrates, never over it. A persistent particle system is a background, and that's not this rule.
## See also
`spring-pop-entrance` (confetti fires on the hero's settle frame) · `kinetic-beat-slam` (one beat earns the confetti payoff) · `press-release-spring` (single-layer glow alternative, or compose both) · `css-marker-patterns` (drawn-line burst when the accent should feel hand-annotated) · `scale-swap-transition` (glyph dissolve covers the exit).