diff --git a/server.js b/server.js index eb472c5..3610f54 100644 --- a/server.js +++ b/server.js @@ -181,7 +181,8 @@ app.get('/', (req, res) => { color: #eee; margin: 0; } - .container { text-align: center; } + .container { text-align: center; position: relative; z-index: 10; } + #network { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } .count { font-size: 8rem; font-weight: bold; color: #4ade80; transition: color 0.2s; } .label { font-size: 1.5rem; color: #9ca3af; margin-top: 1rem; } .footer { margin-top: 2rem; font-size: 0.9rem; color: #4b5563; } @@ -196,6 +197,7 @@ app.get('/', (req, res) => { +
${count}
Active Nodes
@@ -211,12 +213,96 @@ app.get('/', (req, res) => { const countEl = document.getElementById('count'); const directEl = document.getElementById('direct'); + // Particle System + const canvas = document.getElementById('network'); + const ctx = canvas.getContext('2d'); + let particles = []; + + function resize() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + } + window.addEventListener('resize', resize); + resize(); + + class Particle { + constructor() { + this.x = Math.random() * canvas.width; + this.y = Math.random() * canvas.height; + this.vx = (Math.random() - 0.5) * 1; + this.vy = (Math.random() - 0.5) * 1; + this.size = 3; + } + + update() { + this.x += this.vx; + this.y += this.vy; + + if (this.x < 0 || this.x > canvas.width) this.vx *= -1; + if (this.y < 0 || this.y > canvas.height) this.vy *= -1; + } + + draw() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fillStyle = '#4ade80'; + ctx.fill(); + } + } + + function updateParticles(count) { + const currentCount = particles.length; + if (count > currentCount) { + for (let i = 0; i < count - currentCount; i++) { + particles.push(new Particle()); + } + } else if (count < currentCount) { + particles.splice(count, currentCount - count); + } + } + + // Initialize with server-rendered count + updateParticles(${count}); + + function animate() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Draw connections + ctx.strokeStyle = 'rgba(74, 222, 128, 0.15)'; + ctx.lineWidth = 1; + 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 < 150) { + ctx.beginPath(); + ctx.moveTo(particles[i].x, particles[i].y); + ctx.lineTo(particles[j].x, particles[j].y); + ctx.stroke(); + } + } + } + + particles.forEach(p => { + p.update(); + p.draw(); + }); + + requestAnimationFrame(animate); + } + + animate(); + // Use Server-Sent Events for realtime updates const evtSource = new EventSource("/events"); evtSource.onmessage = (event) => { const data = JSON.parse(event.data); + updateParticles(data.count); + // Only update and animate if changed if (countEl.innerText != data.count) { countEl.innerText = data.count;