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.
-`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.
- **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 `<AbsoluteFill>` 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 `<ThreeCanvas>`.
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
// apply to a root <AbsoluteFill style={{ transform: `translate(${driftX+shake}px, ${...}px)` }}>
```
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: `<meshStandardMaterial emissive={accentColor} emissiveIntensity={2} toneMapped={false} />`, then mount `<StudioEffects bloom={0.9} />`. 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:
- 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.