See QUICK_START.md for the 3-step setup.
The project visualizes a dynamic system of colored waves with particles that emit, interact, and get absorbed back into emitters. It's built on a high-performance canvas renderer with sophisticated physics.
Core Components:
- WaveParticles - Main canvas renderer handling all animations
- ParticleEmitter - Visual representation of particle emitters
- ParticleBackground - Container managing the full system
- FPSMonitor - Performance measurement
Global State (React Context):
ParticleSystemContext- Manages particle emission, collisions, and absorptionuseTheme- Manages dark/light theme
Local State (React Hooks):
useWaves- Wave state and updatesuseParticles- Particle generation and management- Canvas refs and animation frame references
WaveParticles Component
├── Clear canvas with background
├── Draw wave interference patterns
├── Draw distortion fields around emitters
├── Draw absorption burst effects (ripples, shockwaves)
├── Draw emitted particles with evolution effects
└── Draw each wave with integrated particles
- Create a new drawing function in
components/WaveParticles.tsx:
const drawMyEffect = useCallback((ctx: CanvasRenderingContext2D, config: WaveParticleConfig) => {
// Your effect code
}, [dependencies]);- Call it in the animation loop:
const animate = () => {
// ... existing code
drawMyEffect(ctx, waveConfig);
// ... more code
};- Add it to the useEffect dependencies array.
Edit context/ParticleSystemContext.tsx:
- Add new properties to
EmittedParticleinterface - Update particle creation in
createParticleForEmitter - Update physics in
applyEmitterGravityorapplyParticleCollisions - Render changes in
WaveParticlescomponent
Edit config/wave.config.ts:
export const DEFAULT_WAVE_CONFIG: WaveParticleConfig = {
lines: [
{
verticalPosition: new MutableRef(20), // % from top
frequency: 0.05, // Wave frequency
amplitude: 30, // Wave height
speed: 2, // Animation speed
hue: 180, // Color (0-360)
opacity: 0.8,
// ... more properties
},
],
// ... other config
};✅ Object Pooling - Particles reuse allocated memory ✅ Canvas Batching - Draw calls grouped by composite mode ✅ Spatial Partitioning - Collision detection uses proximity ✅ Refs for Animation - Avoids re-renders on every frame ✅ Callback Memoization - useCallback prevents recreation
Use the FPSMonitor component (visible in top-right corner):
- Green = 60 FPS (optimal)
- Yellow = 30-60 FPS (acceptable)
- Red = <30 FPS (needs optimization)
- Reduce particle count:
config/wave.config.ts→emittedParticles.emission.rate - Reduce visual effects: Disable specific drawing functions (distortion, interference)
- Simplify waves: Reduce number of wave lines or complexity
- Profile: Check which function takes the most time in DevTools Performance tab
# 1. Start dev server
npm run dev
# 2. Make changes to components/utils/config
# Changes auto-reload (Vite HMR)
# 3. Test build
npm run build
# 4. Preview production build
npm run previewIssue: Changes don't reflect
- Solution: Hard refresh (Cmd+Shift+R or Ctrl+Shift+R)
Issue: Build succeeds but doesn't work when deployed
- Solution: Check for environment-specific code or path issues. Run
npm run previewfirst.
Issue: Performance degrades over time
- Solution: Check browser DevTools → Memory tab for leaks. Likely refs not properly cleaned up.
// 1. React imports
import React, { useRef, useCallback } from 'react';
// 2. Local imports
import { useParticleSystem } from '@/context/ParticleSystemContext';
import { WaveConfig } from '@/types/wave.types';
import { drawWave } from '@/utils/wave.utils';
// 3. Constants/config
import { ANIMATION_CONFIG } from '@/config';- Components: PascalCase (
WaveParticles.tsx) - Hooks: camelCase with
useprefix (useWaves.ts) - Utils: camelCase (
wave.utils.ts) - Types: PascalCase interfaces (
WaveParticleConfig) - Constants: UPPER_SNAKE_CASE (
ANIMATION_SPEED)
Document complex logic with comments:
// Why: Ripple effect pushes particles outward from absorption point
// When: Triggered when particle is absorbed by emitter
// Impact: Creates energy cascade visual effect
for (const nearbyParticle of particles) {
const pushForce = (1 - distance / 60) * 0.5;
nearbyParticle.vx += (dx / distance) * pushForce;
}Add temporary debug logs with [v0] prefix:
console.log('[v0] Particle count:', particles.length);
console.log('[v0] FPS:', fps);Remove these before committing (search for [v0] to find them).
- Install React DevTools Chrome extension
- Open DevTools → React tab
- Select components to inspect props/state
- Use "Highlight updates when components render" to find unnecessary re-renders
Enable visual debugging in WaveParticles.tsx:
// Draw grid to verify coordinates
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
for (let x = 0; x < canvas.width; x += 50) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}npm run buildCreates /dist folder with:
- Optimized JavaScript bundles
- Minified CSS
- Static assets
# One-time setup
vercel
# Deploy (automatic on push)
git pushNetlify:
npm run build
# Drag /dist to NetlifyGitHub Pages:
# Update package.json: "homepage": "https://username.github.io/wave-particle-system"
npm run build
# Commit /dist and pushLocal Server:
npm install -g serve
serve -s dist- Check existing code patterns in similar files
- Search codebase for similar functionality
- Review type definitions in
/typesfor API contracts - Check debug logs in browser console
- Profile performance in DevTools Performance tab