mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
* 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.
150 lines
9.7 KiB
Markdown
150 lines
9.7 KiB
Markdown
---
|
||
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.15–0.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.4–0.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 | 10–18 pop/dots; 24–40 dissolve | **cap ~40** — per-frame style writes; past that, seek perf and register degrade |
|
||
| G | 900–1600 px/s² confetti; 0–200 dots/dissolve | natural fall vs drift |
|
||
| SPEED_MIN / SPEED_MAX | 250–700 px/s | per-particle via `prand`, never uniform |
|
||
| CONE | 0.35–0.8 rad (~20–45°) | wider = splash, narrower = fountain |
|
||
| FLIGHT_DUR | 0.7–1.4s | arc should peak ~35–45% of flight: check ` | vy | / G ≈ 0.4 × FLIGHT_DUR` |
|
||
| SIZE_MIN / SIZE_MAX | 5–14px chips; 4–8px dots | on a 1080p frame |
|
||
| SPIN_MAX | 180–720 deg/s confetti; 0 dots | tumble |
|
||
| FADE_FRAC | 0.2–0.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).
|