--- name: particles-and-effects description: How to add production-value FX — confetti, sparkles, bokeh, light leaks, dust, smoke, glow, lens flare, film grain, chromatic aberration, vignette, camera shake — to FlatRender Remotion templates, in both 2D (SVG/CSS) and 3D (@remotion/three). Use when a template needs atmosphere, finishing texture, particle systems, or a celebratory/cinematic hit. Every effect is a deterministic function of useCurrentFrame() — never Math.random. --- # Particles & effects for Remotion Project: `services/remotion/` (Remotion 4 + `@remotion/three`, R3F v9, `gl="angle"`). Effects are the **8th finishing layer** — the thing that separates "made in a tool" from "made by a studio." A flat, ungrainy, perfectly-locked frame reads as AI/template. Imperfect-by-design wins. ## The one non-negotiable rule Render is headless Chrome sampling frames out of order, in parallel. **Every particle position, every grain offset, every flicker MUST derive from `useCurrentFrame()`.** Never `Math.random()`, `Date.now()`, `useFrame` (R3F), `useState`, or `useEffect` motion. Use `rand(seed)` from `src/lib/anim.ts` for stable per-index pseudo-randomness, and `rand(i + frame)`-style offsets when you want it to *move*. Re-render twice → identical bytes, or it's wrong. Helpers you build on: - `anim.ts` — `rand(i)` (deterministic 0..1), `hexToRgba(hex,a)`, `mixHex(a,b,t)`. - `aspect.ts` — `useLayout()` → `isWide/isSquare/isTall`, `vmin(n)`, `unit`, and `pick(wide,square,tall)`. **Scale particle COUNT and SIZE per aspect** — a tall 9:16 needs fewer, bigger sparkles than a wide 16:9. - `branding.ts` — `colorSchema` props are `accentColor / secondaryColor / backgroundColor / textColor`. FX color comes from these so the studio recolors them. - `three-kit.tsx` — `StudioEnv`, `StudioLights`, `StudioFloor`, `StudioEffects` (bloom+DOF+vignette), `Confetti3D`. ## 2D vs 3D — pick per effect - **2D (SVG/CSS)** is the default: cheap, crisp, no WebGL. Confetti, sparkles, grain, light leaks, vignette, aberration, camera shake — all better/cheaper in 2D as an `` overlay, even on top of a 3D scene. - **3D (@remotion/three)** when the effect must respond to scene lighting/depth: volumetric bloom, real bokeh/DOF, `emissive` glow that bloom picks up, 3D confetti with perspective. Let `StudioEffects` do bloom/DOF/vignette in ONE component — don't re-roll them. - Persian text NEVER goes in 3D — keep it as a 2D overlay above ``. ## Effect → recipe table | Effect | Layer | Core technique | Determinism | |---|---|---|---| | **Confetti (2D)** | overlay | N ``/``, `rand(i)` for x/rot/color; `y` = `(frame*speed + rand(i)*span) % span` | `rand(i)` seed | | **Confetti (3D)** | scene | reuse `Confetti3D` from three-kit | built-in | | **Sparkles / shine** | overlay | 4-point star SVG, twinkle `opacity = abs(sin((frame+rand(i)*60)/12))`, scale pulse | `rand(i)` | | **Bokeh** | bg | big blurred radial-gradient circles drifting on `sin(frame/period)`, low opacity, `mix-blend:screen` | per-circle seed | | **Light leaks** | overlay | warm radial/linear gradient sweeping across via `interpolate(frame,...)` translate, `mix-blend:screen` | frame | | **Dust motes** | overlay | tiny dim dots, slow upward drift + lateral `sin`, `rand` size/speed | `rand(i)` | | **Smoke / fog** | bg/3D | 2D: layered blurred blobs drifting+scaling; 3D: stacked transparent planes | frame | | **Glow** | any | 2D `filter:drop-shadow(0 0 Npx accent)` / `textShadow`; 3D `emissive`+`emissiveIntensity`, `toneMapped={false}`, let bloom bloom it | static | | **Lens flare** | overlay | bright core + chromatic ring sprites along a line from a light point, opacity by angle/frame | frame | | **Film grain** | top | SVG `feTurbulence` with per-frame `seed`, `mix-blend:overlay`, low opacity — MUST animate or it looks frozen | frame | | **Chromatic aberration** | top | duplicate layer, offset R/B channels ±1–3px, strongest at impact frames | frame | | **Vignette** | top | `boxShadow: inset 0 0 vmin(600) rgba(0,0,0,.6)` or `StudioEffects` in 3D | static | | **Camera shake** | root | translate whole frame by `rand(frame)`-driven jitter, decaying after an impact | `rand(frame)` | ## Deterministic particle field (the pattern to memorize) ```tsx const frame = useCurrentFrame(); const { vmin, pick } = useLayout(); const count = pick(60, 48, 36); // fewer on tall {Array.from({ length: count }).map((_, i) => { const x = rand(i) * 100; // % of width const drift = Math.sin((frame + rand(i + 9) * 200) / 40) * 3; const fall = (frame * (0.3 + rand(i + 1) * 0.5) + rand(i + 5) * 120) % 120; const twinkle = Math.abs(Math.sin((frame + rand(i + 2) * 60) / 12)); return
; })} ``` Notice: `rand(i)` = stable identity per particle; `frame` = motion; `% span` = seamless wrap; aspect drives count via `pick`. ## Animated film grain (SVG — the cheapest authenticity layer) ```tsx ``` `seed={frame % 100}` is what makes it crawl. Keep opacity 0.05–0.12. For paper/vignette use `mix-blend:multiply` instead. ## Chromatic aberration & impact-driven FX Aberration should be **strongest at impacts** (a hard cut, the hero reveal, a confetti burst) and near-zero otherwise: ```tsx const ab = interpolate(frame, [hit - 2, hit, hit + 8], [0, vmin(4), 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }); ``` Render the content twice, offset the red copy `translateX(-ab)` `mix-blend:screen` and the blue copy `translateX(+ab)`. Same `interpolate` curve also drives a one-shot camera-shake amplitude — things calm down fast. ## Camera shake (subtle continuous + impact) ```tsx // continuous "frame alive" drift — tiny, always on const driftX = Math.sin(frame / 50) * vmin(3) + (rand(frame) - 0.5) * vmin(1); // impact shake — decays const amp = interpolate(frame, [hit, hit + 12], [vmin(14), 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }); const shake = (rand(frame * 7) - 0.5) * amp; // apply to a root ``` A locked, perfectly-still frame reads amateur. A *tiny* always-on drift makes it feel hand-held and alive — keep it under ~`vmin(4)` or it's distracting. ## 3D glow & bloom Make a material glow into bloom: ``, then mount ``. For sparkly metal confetti raise `metalness`. Drive every transform off `useCurrentFrame()` (deterministic under ANGLE), rotation = `linear` (mechanical), entrances = `spring` with high mass. ## Reusable components — make these, don't inline Put shared FX in `src/lib/fx.tsx` so every template gets the same texture: - `` — animated `feTurbulence`. - `` — inset boxShadow. - `` — burst (spring spread) vs rain (continuous fall) modes. - `` — twinkling 4-point stars. - `` + `` — bg/overlay atmosphere. - ` ` — finishing pair, wrap the whole comp. Each takes `colorSchema` colors so the studio picker recolors the FX, and reads `useLayout()` for per-aspect count/size. ## Restraint — FX amplify a hero, they are not the show - One celebratory burst on the **hero moment**, not raining the whole video. Often **silence before** + confetti + sparkle SFX on the same frame (see `../remotion-sound-effects/SKILL.md`). - Finishing texture (grain, vignette, drift) is *subtle and always-on*; spectacle (confetti, flare, big aberration) is *brief and on a beat*. - Don't stack 6 effects at full strength — that reads as a tool preset. Grain at 0.08, vignette at 0.5, aberration only at impacts. - All FX color from `colorSchema`; pass a user's garish hex through `mixHex(hex, background, 0.2)` so it doesn't blow out. ## Pre-ship checklist - [ ] Zero `Math.random` / `Date.now` / `useFrame` — only `rand()` + `frame`. Re-render twice → identical. - [ ] Grain is *animated* (per-frame seed), not frozen. - [ ] Particle count & size scale per aspect via `pick`/`vmin` — verified in 16:9, 1:1, 9:16; particles stay in the safe zone, never crop Persian text. - [ ] Every `interpolate` has `extrapolateLeft/Right: "clamp"` — no drift, no negative opacity. - [ ] Spectacle FX land on a beat / the hero; texture FX are subtle & continuous. - [ ] FX colors read from `colorSchema`; a continuous camera drift keeps the frame alive. - [ ] 3D glow uses `emissive`+`toneMapped={false}` + `StudioEffects` (not hand-rolled bloom). Related: `../remotion-design-styles/SKILL.md`, `../remotion-character-design/SKILL.md`, `../remotion-aspect-ratios/SKILL.md`, `../remotion-template-composition/SKILL.md`, `../remotion-sound-effects/SKILL.md`, `../remotion-music-picker/SKILL.md`, `../remotion-svg-colors/SKILL.md`, `../persian-fonts/SKILL.md`, `../remotion-template-catalog/SKILL.md`, `../flatrender-template-seo/SKILL.md`.