Skip to content

Commit 6ea13f2

Browse files
Initial commit: Particle system with emitters, gravity fields, and wave interactions
0 parents  commit 6ea13f2

File tree

146 files changed

+25639
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+25639
-0
lines changed

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?
25+
26+
# Environment
27+
.env
28+
.env.local
29+
.env.*.local

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Wave Particle System
2+
3+
A beautiful, high-performance particle and wave animation system built with React and Vite.
4+
5+
## 🚀 Quick Start
6+
7+
```bash
8+
npm install
9+
npm run dev
10+
# Opens at http://localhost:3001
11+
```
12+
13+
## 📚 Documentation
14+
15+
**All documentation has been organized in `/docs/`:**
16+
17+
- **[QUICK_START](/docs/QUICK_START.md)** - Get running in 3 steps
18+
- **[README](/docs/README.md)** - Complete project overview
19+
- **[DEVELOPMENT](/docs/DEVELOPMENT.md)** - Architecture & coding guide
20+
- **[PROJECT_STRUCTURE](/docs/PROJECT_STRUCTURE.md)** - File organization
21+
- **[MIGRATION_GUIDE](/docs/MIGRATION_GUIDE.md)** - Why we switched from Next.js
22+
- **[FEATURES_IMPLEMENTATION](/docs/FEATURES_IMPLEMENTATION.md)** - Feature checklist
23+
- **[DOCS_INDEX](/docs/DOCS_INDEX.md)** - All documentation index
24+
25+
## ✨ Features
26+
27+
- Wave animations with real-time particle effects
28+
- Interactive particle emitters with physics
29+
- 60+ FPS optimized rendering
30+
- Color-responsive wave harmonics
31+
- Distortion fields and visual effects
32+
- Complete TypeScript support
33+
34+
## 🎯 Next Steps
35+
36+
1. **Get Started**: `npm run dev`
37+
2. **Read Docs**: Start with `/docs/QUICK_START.md`
38+
3. **Deploy**: `npm run build` then upload `/dist`
39+
40+
## 📊 Project Status
41+
42+
- ✅ Production Ready
43+
- ✅ 0 Type Errors
44+
- ✅ 60+ FPS Performance
45+
- ✅ Comprehensive Documentation
46+
- ✅ Works Anywhere (Static Build)
47+
48+
See **[HOUSEKEEPING_SUMMARY.md](./docs/HOUSEKEEPING_SUMMARY.md)** for full details on what's been implemented and organized.

app/effects-showcase/page.tsx

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
'use client';
2+
3+
import React, { useState, useRef, useCallback } from 'react';
4+
import { WaveParticles } from '@/components/WaveParticles';
5+
import { ParticleEmitter } from '@/components/ParticleEmitter';
6+
import { useParticleSystem } from '@/context/ParticleSystemContext';
7+
import { ParticleSystemProvider } from '@/context/ParticleSystemContext';
8+
9+
/**
10+
* Effects Showcase - Interactive demonstration of particle physics
11+
* DOM elements emit particles that interact with the wave system
12+
*/
13+
function EffectsShowcaseContent() {
14+
const [activeTab, setActiveTab] = useState<'demo' | 'interactive'>('demo');
15+
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
16+
const [buttonGravityWells, setButtonGravityWells] = useState<
17+
Array<{ x: number; y: number; width: number; height: number; mass: number; strength: number }>
18+
>([]);
19+
const { emittedParticlesRef } = useParticleSystem();
20+
21+
// Update button positions on scroll and resize
22+
React.useEffect(() => {
23+
const updateButtonPositions = () => {
24+
const newWells = buttonRefs.current
25+
.filter((btn) => btn !== null)
26+
.map((btn) => {
27+
const rect = btn!.getBoundingClientRect();
28+
return {
29+
x: rect.left + rect.width / 2,
30+
y: rect.top + rect.height / 2,
31+
width: rect.width,
32+
height: rect.height,
33+
mass: 2 + Math.random() * 0.5, // Button mass
34+
strength: 0.12, // Gravity attraction strength
35+
};
36+
});
37+
setButtonGravityWells(newWells);
38+
};
39+
40+
window.addEventListener('scroll', updateButtonPositions, { passive: true });
41+
window.addEventListener('resize', updateButtonPositions, { passive: true });
42+
updateButtonPositions(); // Initial call
43+
44+
return () => {
45+
window.removeEventListener('scroll', updateButtonPositions);
46+
window.removeEventListener('resize', updateButtonPositions);
47+
};
48+
}, []);
49+
50+
// Function to emit particles from a button
51+
const emitParticlesFromButton = useCallback(
52+
(buttonElement: HTMLButtonElement | null, hue: number, count: number = 12) => {
53+
if (!buttonElement) return;
54+
55+
const rect = buttonElement.getBoundingClientRect();
56+
const centerX = rect.left + rect.width / 2;
57+
const centerY = rect.top + rect.height / 2;
58+
59+
console.log('[v0] Emitting particles from button at', centerX, centerY);
60+
61+
for (let i = 0; i < count; i++) {
62+
const angle = (i / count) * Math.PI * 2;
63+
const velocity = 2 + Math.random() * 2;
64+
const vx = Math.cos(angle) * velocity;
65+
const vy = Math.sin(angle) * velocity;
66+
67+
emittedParticlesRef.current.push({
68+
id: `button-${Date.now()}-${i}`,
69+
x: centerX,
70+
y: centerY,
71+
vx,
72+
vy,
73+
size: 1.5 + Math.random() * 0.5,
74+
hue: hue + (Math.random() - 0.5) * 40,
75+
opacity: 1,
76+
lifetime: 0,
77+
maxLifetime: 3000,
78+
glow: true,
79+
glowIntensity: 0.8,
80+
drag: 0.97,
81+
gravity: 0.05,
82+
mass: 1 + Math.random() * 0.5, // Particles have mass too
83+
age: 0,
84+
saturation: 85,
85+
baseSize: 1.5,
86+
});
87+
}
88+
},
89+
[emittedParticlesRef]
90+
);
91+
92+
const buttonConfigs = [
93+
{ label: 'Violet Burst', hue: 270 },
94+
{ label: 'Cyan Pulse', hue: 180 },
95+
{ label: 'Pink Wave', hue: 320 },
96+
{ label: 'Green Shimmer', hue: 120 },
97+
{ label: 'Blue Spark', hue: 210 },
98+
{ label: 'Orange Flame', hue: 30 },
99+
];
100+
101+
const InteractiveButton = ({ index, label, hue }: { index: number; label: string; hue: number }) => (
102+
<button
103+
ref={(el) => {
104+
if (el) buttonRefs.current[index] = el;
105+
}}
106+
onMouseEnter={(e) => {
107+
emitParticlesFromButton(e.currentTarget, hue, 6);
108+
}}
109+
onClick={(e) => {
110+
emitParticlesFromButton(e.currentTarget, hue, 12);
111+
}}
112+
className="px-6 py-3 rounded-lg font-semibold transition-all duration-150 hover:scale-105 active:scale-95"
113+
style={{
114+
background: `hsl(${hue}, 70%, 50%)`,
115+
color: 'white',
116+
boxShadow: `0 0 20px hsla(${hue}, 70%, 50%, 0.5)`,
117+
}}
118+
>
119+
{label}
120+
</button>
121+
);
122+
123+
return (
124+
<div className="w-full h-screen bg-black relative overflow-hidden">
125+
{/* Wave Particle System Background - Pass button gravity wells */}
126+
<WaveParticles
127+
showRogueParticles={true}
128+
domGravityWells={buttonGravityWells}
129+
/>
130+
131+
{/* Particle Emitters - Fixed color gravity wells */}
132+
<ParticleEmitter
133+
position={{ x: 5, y: 20 }}
134+
particlesPerBurst={1}
135+
burstInterval={1500}
136+
hue={348}
137+
emitterSize={16}
138+
active={true}
139+
gravityRadius={100}
140+
gravityStrength={0.08}
141+
showGravityField={false}
142+
/>
143+
<ParticleEmitter
144+
position={{ x: 95, y: 25 }}
145+
particlesPerBurst={1}
146+
burstInterval={1500}
147+
hue={204}
148+
emitterSize={16}
149+
active={true}
150+
gravityRadius={100}
151+
gravityStrength={0.08}
152+
showGravityField={false}
153+
/>
154+
<ParticleEmitter
155+
position={{ x: 50, y: 90 }}
156+
particlesPerBurst={1}
157+
burstInterval={1500}
158+
hue={180}
159+
emitterSize={16}
160+
active={true}
161+
gravityRadius={100}
162+
gravityStrength={0.08}
163+
showGravityField={false}
164+
/>
165+
166+
{/* Tab Navigation */}
167+
<div className="absolute top-6 left-6 z-20 flex gap-2">
168+
<button
169+
onClick={() => setActiveTab('demo')}
170+
className={`px-4 py-2 rounded font-semibold transition-all ${
171+
activeTab === 'demo'
172+
? 'bg-cyan-500 text-black'
173+
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
174+
}`}
175+
>
176+
Live Demo
177+
</button>
178+
<button
179+
onClick={() => setActiveTab('interactive')}
180+
className={`px-4 py-2 rounded font-semibold transition-all ${
181+
activeTab === 'interactive'
182+
? 'bg-cyan-500 text-black'
183+
: 'bg-slate-700 text-slate-300 hover:bg-slate-600'
184+
}`}
185+
>
186+
Interactive
187+
</button>
188+
</div>
189+
190+
{/* Demo Mode - Static Information */}
191+
{activeTab === 'demo' && (
192+
<div className="absolute inset-0 z-10 pointer-events-none flex flex-col items-center justify-center">
193+
<div className="text-center backdrop-blur-md bg-black/40 p-12 rounded-xl max-w-2xl">
194+
<h1 className="text-4xl font-bold text-white mb-4">Particle Effects System</h1>
195+
<p className="text-slate-300 mb-6">
196+
Watch as particles interact with wave lines, emitters, and each other through sophisticated physics simulation.
197+
</p>
198+
<div className="grid grid-cols-2 gap-6 text-left mb-8">
199+
<div>
200+
<h3 className="text-cyan-400 font-semibold mb-2">Gravity Wells</h3>
201+
<p className="text-sm text-slate-400">Fixed color emitters attract matching particles</p>
202+
</div>
203+
<div>
204+
<h3 className="text-cyan-400 font-semibold mb-2">Wave Gravity</h3>
205+
<p className="text-sm text-slate-400">Particles orbit along wave lines by color</p>
206+
</div>
207+
<div>
208+
<h3 className="text-cyan-400 font-semibold mb-2">Particle Collision</h3>
209+
<p className="text-sm text-slate-400">Same colors merge, different colors repel</p>
210+
</div>
211+
<div>
212+
<h3 className="text-cyan-400 font-semibold mb-2">Mass-Based Physics</h3>
213+
<p className="text-sm text-slate-400">Heavier particles resist gravity more</p>
214+
</div>
215+
</div>
216+
<button
217+
onClick={() => setActiveTab('interactive')}
218+
className="px-6 py-3 bg-cyan-500 hover:bg-cyan-600 text-black font-semibold rounded transition-colors pointer-events-auto"
219+
>
220+
Try Interactive Mode →
221+
</button>
222+
</div>
223+
</div>
224+
)}
225+
226+
{/* Interactive Mode - Clickable Elements */}
227+
{activeTab === 'interactive' && (
228+
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-12 pointer-events-auto">
229+
<div className="text-center">
230+
<h1 className="text-3xl font-bold text-white mb-2">Interactive Particle Effects</h1>
231+
<p className="text-slate-400">Hover and click elements to emit particles</p>
232+
</div>
233+
234+
{/* Button Grid */}
235+
<div className="flex flex-wrap justify-center gap-8 max-w-3xl">
236+
{buttonConfigs.map((config, index) => (
237+
<InteractiveButton key={index} index={index} label={config.label} hue={config.hue} />
238+
))}
239+
</div>
240+
241+
{/* Info Cards */}
242+
<div className="grid grid-cols-3 gap-6 max-w-4xl mt-8">
243+
<div className="bg-slate-900/50 backdrop-blur-md border border-cyan-500/30 p-6 rounded-lg">
244+
<h3 className="text-cyan-400 font-semibold mb-2">Hover Effect</h3>
245+
<p className="text-sm text-slate-400">Move over any button to emit particles</p>
246+
</div>
247+
<div className="bg-slate-900/50 backdrop-blur-md border border-cyan-500/30 p-6 rounded-lg">
248+
<h3 className="text-cyan-400 font-semibold mb-2">Click Effect</h3>
249+
<p className="text-sm text-slate-400">Click buttons to emit more particles with spread</p>
250+
</div>
251+
<div className="bg-slate-900/50 backdrop-blur-md border border-cyan-500/30 p-6 rounded-lg">
252+
<h3 className="text-cyan-400 font-semibold mb-2">Physics</h3>
253+
<p className="text-sm text-slate-400">Particles interact with waves and each other</p>
254+
</div>
255+
</div>
256+
257+
<button
258+
onClick={() => setActiveTab('demo')}
259+
className="text-slate-400 hover:text-slate-300 text-sm transition-colors"
260+
>
261+
← Back to Demo
262+
</button>
263+
</div>
264+
)}
265+
266+
{/* Navigation Link to Sandbox */}
267+
<div className="absolute bottom-8 right-8 z-10">
268+
<a
269+
href="/sandbox"
270+
className="px-6 py-2 bg-slate-700 hover:bg-slate-600 text-slate-300 font-semibold rounded transition-colors text-sm"
271+
>
272+
Sandbox Controls →
273+
</a>
274+
</div>
275+
</div>
276+
);
277+
}
278+
279+
export default function EffectsShowcasePage() {
280+
return (
281+
<ParticleSystemProvider>
282+
<EffectsShowcaseContent />
283+
</ParticleSystemProvider>
284+
);
285+
}

0 commit comments

Comments
 (0)