const canvas = document.getElementById('particleCanvas'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; // Configuration let config = { particleCount: 100, particleSize: 2, speed: 1, connectionDistance: 100, colorMode: 'rainbow', mouseEffect: 'attract', mouseRadius: 150 }; // Mouse position let mouse = { x: canvas.width / 2, y: canvas.height / 2 }; // Particles array let particles = []; // Color modes const colorModes = { rainbow: (particle, time) => { const hue = (time * 0.1 + particle.id * 10) % 360; return `hsl(${hue}, 70%, 50%)`; }, ocean: (particle, time) => { const hue = 180 + Math.sin(time * 0.001 + particle.id * 0.1) * 60; return `hsl(${hue}, 70%, 50%)`; }, fire: (particle, time) => { const hue = Math.sin(time * 0.001 + particle.id * 0.1) * 30; return `hsl(${hue}, 80%, 50%)`; }, monochrome: (particle, time) => { const brightness = 30 + Math.sin(time * 0.001 + particle.id * 0.1) * 20; return `hsl(0, 0%, ${brightness}%)`; }, neon: (particle, time) => { const colors = ['#ff00ff', '#00ffff', '#ffff00', '#ff00aa']; return colors[Math.floor((time * 0.001 + particle.id * 0.1) % colors.length)]; } }; // Particle class class Particle { constructor(id) { this.id = id; this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * config.speed; this.vy = (Math.random() - 0.5) * config.speed; this.size = config.particleSize; this.baseSize = config.particleSize; } update() { // Mouse interaction const dx = mouse.x - this.x; const dy = mouse.y - this.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < config.mouseRadius) { const force = (1 - distance / config.mouseRadius) * 0.5; if (config.mouseEffect === 'attract') { this.vx += dx * force * 0.01; this.vy += dy * force * 0.01; } else { this.vx -= dx * force * 0.01; this.vy -= dy * force * 0.01; } } // Update position this.x += this.vx * config.speed; this.y += this.vy * config.speed; // Bounce off walls if (this.x < 0 || this.x > canvas.width) { this.vx *= -0.9; this.x = Math.max(0, Math.min(canvas.width, this.x)); } if (this.y < 0 || this.y > canvas.height) { this.vy *= -0.9; this.y = Math.max(0, Math.min(canvas.height, this.y)); } // Apply friction this.vx *= 0.99; this.vy *= 0.99; // Size animation this.size = this.baseSize + Math.sin(Date.now() * 0.001 + this.id) * 0.5; } draw(time) { ctx.fillStyle = colorModes[config.colorMode](this, time); ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); // Glow effect ctx.shadowBlur = 10; ctx.shadowColor = colorModes[config.colorMode](this, time); ctx.fill(); ctx.shadowBlur = 0; } } // Initialize particles function initParticles() { particles = []; for (let i = 0; i < config.particleCount; i++) { particles.push(new Particle(i)); } } // Draw connections between particles function drawConnections() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < config.connectionDistance) { const opacity = 1 - distance / config.connectionDistance; ctx.strokeStyle = `rgba(100, 255, 218, ${opacity * 0.2})`; ctx.lineWidth = opacity * 0.5; ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.stroke(); } } } } // Animation loop function animate() { ctx.fillStyle = 'rgba(10, 10, 10, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); const time = Date.now(); // Update and draw particles particles.forEach(particle => { particle.update(); particle.draw(time); }); // Draw connections drawConnections(); requestAnimationFrame(animate); } // Event listeners window.addEventListener('resize', () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }); canvas.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; }); canvas.addEventListener('click', (e) => { // Create explosion effect for (let i = 0; i < 20; i++) { const angle = (Math.PI * 2 * i) / 20; const speed = 5 + Math.random() * 5; const particle = new Particle(particles.length); particle.x = e.clientX; particle.y = e.clientY; particle.vx = Math.cos(angle) * speed; particle.vy = Math.sin(angle) * speed; particle.baseSize = 3 + Math.random() * 2; particles.push(particle); } // Remove excess particles if (particles.length > config.particleCount * 2) { particles.splice(0, particles.length - config.particleCount); } }); // Keyboard controls document.addEventListener('keydown', (e) => { switch(e.key.toLowerCase()) { case ' ': e.preventDefault(); const modes = Object.keys(colorModes); const currentIndex = modes.indexOf(config.colorMode); config.colorMode = modes[(currentIndex + 1) % modes.length]; break; case '1': config.particleCount = 50; initParticles(); break; case '2': config.particleCount = 100; initParticles(); break; case '3': config.particleCount = 200; initParticles(); break; case '4': config.particleCount = 300; initParticles(); break; case '5': config.particleCount = 500; initParticles(); break; case 'q': config.speed = Math.max(0.1, config.speed - 0.1); break; case 'w': config.speed = Math.min(5, config.speed + 0.1); break; case 'a': config.particleSize = Math.max(1, config.particleSize - 0.5); particles.forEach(p => p.baseSize = config.particleSize); break; case 's': config.particleSize = Math.min(10, config.particleSize + 0.5); particles.forEach(p => p.baseSize = config.particleSize); break; case 'z': config.connectionDistance = Math.max(50, config.connectionDistance - 10); break; case 'x': config.connectionDistance = Math.min(300, config.connectionDistance + 10); break; case 'r': config = { particleCount: 100, particleSize: 2, speed: 1, connectionDistance: 100, colorMode: 'rainbow', mouseEffect: 'attract', mouseRadius: 150 }; initParticles(); break; case 'm': config.mouseEffect = config.mouseEffect === 'attract' ? 'repel' : 'attract'; break; } }); // Initialize and start initParticles(); animate();