QuantumAI / frontend /src /components /ParticleCanvas.js
Abhroneel's picture
Stage 2
ada223c
Raw
History Blame Contribute Delete
3.27 kB
import React, { useEffect, useRef } from 'react';
const COLORS = [
[59, 142, 255],
[0, 229, 255],
[155, 109, 255],
[0, 255, 178],
];
export default function ParticleCanvas() {
const canvasRef = useRef(null);
const animRef = useRef(null);
const particlesRef = useRef([]);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
let W, H;
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
}
function mkParticle() {
return {
x: Math.random() * W,
y: Math.random() * H,
vx: (Math.random() - 0.5) * 0.25,
vy: (Math.random() - 0.5) * 0.25,
r: Math.random() * 1.8 + 0.6,
opacity: Math.random() * 0.55 + 0.2,
pulse: Math.random() * Math.PI * 2,
speed: Math.random() * 0.018 + 0.006,
colorIdx: Math.floor(Math.random() * COLORS.length),
};
}
resize();
window.addEventListener('resize', resize);
particlesRef.current = Array.from({ length: 100 }, mkParticle);
function draw() {
ctx.clearRect(0, 0, W, H);
const pts = particlesRef.current;
pts.forEach(p => {
p.x += p.vx;
p.y += p.vy;
p.pulse += p.speed;
const op = p.opacity * (0.55 + 0.45 * Math.sin(p.pulse));
if (p.x < 0) p.x = W;
if (p.x > W) p.x = 0;
if (p.y < 0) p.y = H;
if (p.y > H) p.y = 0;
const [r, g, b] = COLORS[p.colorIdx];
const halo = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r * 5);
halo.addColorStop(0, `rgba(${r},${g},${b},${op * 0.55})`);
halo.addColorStop(1, `rgba(${r},${g},${b},0)`);
ctx.beginPath();
ctx.arc(p.x, p.y, p.r * 5, 0, Math.PI * 2);
ctx.fillStyle = halo;
ctx.fill();
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r},${g},${b},${Math.min(op * 1.6, 1)})`;
ctx.fill();
});
for (let i = 0; i < pts.length; i++) {
for (let j = i + 1; j < pts.length; j++) {
const dx = pts[i].x - pts[j].x;
const dy = pts[i].y - pts[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 140) {
const t = 1 - dist / 140;
const [r1, g1, b1] = COLORS[pts[i].colorIdx];
const [r2, g2, b2] = COLORS[pts[j].colorIdx];
const lg = ctx.createLinearGradient(pts[i].x, pts[i].y, pts[j].x, pts[j].y);
lg.addColorStop(0, `rgba(${r1},${g1},${b1},${t * 0.22})`);
lg.addColorStop(1, `rgba(${r2},${g2},${b2},${t * 0.22})`);
ctx.beginPath();
ctx.moveTo(pts[i].x, pts[i].y);
ctx.lineTo(pts[j].x, pts[j].y);
ctx.strokeStyle = lg;
ctx.lineWidth = t * 1.4;
ctx.stroke();
}
}
}
animRef.current = requestAnimationFrame(draw);
}
draw();
return () => {
cancelAnimationFrame(animRef.current);
window.removeEventListener('resize', resize);
};
}, []);
return (
<canvas
ref={canvasRef}
style={{ position: 'fixed', inset: 0, pointerEvents: 'none', zIndex: 0, opacity: 0.78 }}
/>
);
}