Files
dark-lord/components/ParticleBackground.tsx
drjones 78a071ba02 Harden onion boot flow and deepen site surfaces
Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation.

Made-with: Cursor
2026-04-07 21:35:52 -07:00

135 lines
3.6 KiB
TypeScript

"use client";
import { useEffect, useRef } from "react";
const ParticleBackground = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Set canvas size
const resize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resize();
window.addEventListener("resize", resize);
// Particle system
const particles: Array<{
x: number;
y: number;
vx: number;
vy: number;
radius: number;
color: string;
}> = [];
const colors = ["#00ffff", "#9d00ff", "#ff00ff", "#00ff9d"];
// Create particles
for (let i = 0; i < 80; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.5,
vy: (Math.random() - 0.5) * 0.5,
radius: Math.random() * 2 + 0.5,
color: colors[Math.floor(Math.random() * colors.length)],
});
}
// Mouse interaction
let mouseX = canvas.width / 2;
let mouseY = canvas.height / 2;
const onMouseMove = (e: MouseEvent) => {
mouseX = e.clientX;
mouseY = e.clientY;
};
window.addEventListener("mousemove", onMouseMove);
// Animation loop
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw connecting lines
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.strokeStyle = `rgba(0, 224, 255, ${0.2 * (1 - distance / 150)})`;
ctx.lineWidth = 0.5;
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
// Update and draw particles
particles.forEach((p) => {
// Move particle
p.x += p.vx;
p.y += p.vy;
// Bounce off edges
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
// Attract to mouse
const dx = mouseX - p.x;
const dy = mouseY - p.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200) {
p.vx += dx * 0.0001;
p.vy += dy * 0.0001;
}
// Limit velocity
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
if (speed > 1) {
p.vx = (p.vx / speed) * 1;
p.vy = (p.vy / speed) * 1;
}
// Draw particle
ctx.beginPath();
const gradient = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.radius * 3);
gradient.addColorStop(0, p.color);
gradient.addColorStop(1, "transparent");
ctx.fillStyle = gradient;
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
ctx.fill();
});
requestAnimationFrame(animate);
};
const animationId = requestAnimationFrame(animate);
// Cleanup
return () => {
window.removeEventListener("resize", resize);
window.removeEventListener("mousemove", onMouseMove);
cancelAnimationFrame(animationId);
};
}, []);
return (
<canvas
ref={canvasRef}
className="fixed inset-0 -z-10 h-full w-full"
style={{ pointerEvents: "none" }}
/>
);
};
export default ParticleBackground;