Spaces:
Runtime error
Runtime error
| /** | |
| * TwoQuarks Quantum Field - Ultra Blue Particle System | |
| * High-performance canvas animation with WebGL-style particles | |
| */ | |
| class QuantumField { | |
| constructor(canvasId) { | |
| this.canvas = document.getElementById(canvasId); | |
| if (!this.canvas) { | |
| console.warn(`Canvas ${canvasId} not found`); | |
| return; | |
| } | |
| this.ctx = this.canvas.getContext('2d', { alpha: false }); | |
| this.particles = []; | |
| this.connections = []; | |
| this.mouse = { x: null, y: null, radius: 150 }; | |
| // Ultra Blue color palette | |
| this.colors = { | |
| primary: 'rgba(39, 80, 146, 0.8)', // #3B82F6 | |
| bright: 'rgba(96, 165, 250, 0.6)', // #60A5FA | |
| deep: 'rgba(30, 64, 175, 0.9)', // #1E40AF | |
| glow: 'rgba(59, 130, 246, 0.15)', | |
| connection: 'rgba(59, 130, 246, 0.12)' | |
| }; | |
| // Configuration | |
| this.config = { | |
| particleCount: 120, | |
| particleSpeed: 0.3, | |
| connectionDistance: 140, | |
| mouseInteraction: true, | |
| mouseRepelForce: 0.8, | |
| particleSize: { min: 1, max: 3 }, | |
| glowEnabled: true | |
| }; | |
| this.init(); | |
| } | |
| init() { | |
| this.resize(); | |
| this.createParticles(); | |
| this.setupEventListeners(); | |
| this.animate(); | |
| } | |
| resize() { | |
| this.canvas.width = window.innerWidth; | |
| this.canvas.height = window.innerHeight; | |
| } | |
| createParticles() { | |
| this.particles = []; | |
| const { particleCount, particleSpeed, particleSize } = this.config; | |
| for (let i = 0; i < particleCount; i++) { | |
| this.particles.push({ | |
| x: Math.random() * this.canvas.width, | |
| y: Math.random() * this.canvas.height, | |
| vx: (Math.random() - 0.5) * particleSpeed, | |
| vy: (Math.random() - 0.5) * particleSpeed, | |
| size: Math.random() * (particleSize.max - particleSize.min) + particleSize.min, | |
| color: this.getRandomColor(), | |
| brightness: Math.random() * 0.5 + 0.5 | |
| }); | |
| } | |
| } | |
| getRandomColor() { | |
| const colorOptions = [ | |
| this.colors.primary, | |
| this.colors.bright, | |
| this.colors.deep | |
| ]; | |
| return colorOptions[Math.floor(Math.random() * colorOptions.length)]; | |
| } | |
| setupEventListeners() { | |
| window.addEventListener('resize', () => this.resize()); | |
| if (this.config.mouseInteraction) { | |
| window.addEventListener('mousemove', (e) => { | |
| this.mouse.x = e.clientX; | |
| this.mouse.y = e.clientY; | |
| }); | |
| window.addEventListener('mouseleave', () => { | |
| this.mouse.x = null; | |
| this.mouse.y = null; | |
| }); | |
| } | |
| } | |
| updateParticles() { | |
| const { width, height } = this.canvas; | |
| const { mouseRepelForce } = this.config; | |
| this.particles.forEach(p => { | |
| // Mouse interaction - repel effect | |
| if (this.mouse.x !== null && this.mouse.y !== null) { | |
| const dx = p.x - this.mouse.x; | |
| const dy = p.y - this.mouse.y; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < this.mouse.radius) { | |
| const force = (this.mouse.radius - dist) / this.mouse.radius; | |
| const angle = Math.atan2(dy, dx); | |
| p.vx += Math.cos(angle) * force * mouseRepelForce; | |
| p.vy += Math.sin(angle) * force * mouseRepelForce; | |
| } | |
| } | |
| // Update position | |
| p.x += p.vx; | |
| p.y += p.vy; | |
| // Damping | |
| p.vx *= 0.99; | |
| p.vy *= 0.99; | |
| // Boundary wrapping | |
| if (p.x < 0) p.x = width; | |
| if (p.x > width) p.x = 0; | |
| if (p.y < 0) p.y = height; | |
| if (p.y > height) p.y = 0; | |
| // Subtle brightness pulsing | |
| p.brightness += (Math.random() - 0.5) * 0.02; | |
| p.brightness = Math.max(0.3, Math.min(1, p.brightness)); | |
| }); | |
| } | |
| drawConnections() { | |
| const { connectionDistance } = this.config; | |
| this.ctx.strokeStyle = this.colors.connection; | |
| this.ctx.lineWidth = 0.5; | |
| for (let i = 0; i < this.particles.length; i++) { | |
| for (let j = i + 1; j < this.particles.length; j++) { | |
| const dx = this.particles[i].x - this.particles[j].x; | |
| const dy = this.particles[i].y - this.particles[j].y; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < connectionDistance) { | |
| const opacity = (1 - dist / connectionDistance) * 0.3; | |
| this.ctx.strokeStyle = `rgba(29, 80, 126, ${opacity})`; | |
| this.ctx.beginPath(); | |
| this.ctx.moveTo(this.particles[i].x, this.particles[i].y); | |
| this.ctx.lineTo(this.particles[j].x, this.particles[j].y); | |
| this.ctx.stroke(); | |
| } | |
| } | |
| } | |
| } | |
| drawParticles() { | |
| this.particles.forEach(p => { | |
| // Glow effect | |
| if (this.config.glowEnabled) { | |
| const gradient = this.ctx.createRadialGradient( | |
| p.x, p.y, 0, | |
| p.x, p.y, p.size * 4 | |
| ); | |
| gradient.addColorStop(0, p.color.replace(/[\d.]+\)$/g, `${p.brightness})`)); | |
| gradient.addColorStop(1, p.color.replace(/[\d.]+\)$/g, '0)')); | |
| this.ctx.fillStyle = gradient; | |
| this.ctx.beginPath(); | |
| this.ctx.arc(p.x, p.y, p.size * 4, 0, Math.PI * 2); | |
| this.ctx.fill(); | |
| } | |
| // Core particle | |
| this.ctx.fillStyle = p.color.replace(/[\d.]+\)$/g, `${p.brightness})`); | |
| this.ctx.beginPath(); | |
| this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); | |
| this.ctx.fill(); | |
| }); | |
| } | |
| animate() { | |
| // Clear with black background | |
| this.ctx.fillStyle = '#000000'; | |
| this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); | |
| this.updateParticles(); | |
| this.drawConnections(); | |
| this.drawParticles(); | |
| requestAnimationFrame(() => this.animate()); | |
| } | |
| } | |
| // Auto-initialize when DOM is ready | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new QuantumField('quantumField'); | |
| }); | |
| } else { | |
| new QuantumField('quantumField'); | |
| } | |
| // Export for potential external use | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = QuantumField; | |
| } |