Files
AISkills/particles-and-effects/SKILL.md
T
Soroush Asadi 6cf6d8953f feat: design+motion R&D report and 6 professional craft skills
R&D brief (references/design-motion-rnd.md): 2024-2026 design/motion trends,
animating-anything craft, Iran-aware asset pipeline, masterpiece + platform playbook.

New craft skills: motion-design-principles, scene-transitions, kinetic-typography,
video-hooks, particles-and-effects, asset-sourcing — grounded in the Remotion stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 19:09:03 +03:30

9.3 KiB
Raw Blame History

name, description
name description
particles-and-effects 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.tsrand(i) (deterministic 0..1), hexToRgba(hex,a), mixHex(a,b,t).
  • aspect.tsuseLayout()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.tscolorSchema props are accentColor / secondaryColor / backgroundColor / textColor. FX color comes from these so the studio recolors them.
  • three-kit.tsxStudioEnv, 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 <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>.

Effect → recipe table

Effect Layer Core technique Determinism
Confetti (2D) overlay N <rect>/<path>, 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 ±13px, 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)

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 <div key={i} style={{
    position: "absolute", left: `${x + drift}%`, top: `${fall - 10}%`,
    width: vmin(6), height: vmin(6), opacity: twinkle,
    background: i % 2 ? accentColor : secondaryColor,
    transform: `rotate(${frame * 2 + rand(i) * 360}deg)`,
  }} />;
})}

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)

<svg style={{ position: "absolute", inset: 0, mixBlendMode: "overlay", opacity: 0.08 }}>
  <filter id="grain">
    <feTurbulence type="fractalNoise" baseFrequency="0.9"
      numOctaves="2" seed={frame % 100} stitchTiles="stitch" />
  </filter>
  <rect width="100%" height="100%" filter="url(#grain)" />
</svg>

seed={frame % 100} is what makes it crawl. Keep opacity 0.050.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:

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)

// 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 <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:

  • <GrainOverlay opacity? blend? /> — animated feTurbulence.
  • <Vignette strength? /> — inset boxShadow.
  • <Confetti2D colors count? burstFrame? /> — burst (spring spread) vs rain (continuous fall) modes.
  • <Sparkles colors count? area? /> — twinkling 4-point stars.
  • <Bokeh colors count? /> + <LightLeak color from to /> — bg/overlay atmosphere.
  • <Aberration amount /> <CameraShake amount /> — 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).
  • 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, remotion-character-design, remotion-aspect-ratios, remotion-template-composition, remotion-sound-effects, remotion-music-picker, remotion-svg-colors, persian-fonts, remotion-template-catalog, flatrender-template-seo.