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
This commit is contained in:
135
components/ParticleBackground.tsx
Normal file
135
components/ParticleBackground.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
"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;
|
||||
Reference in New Issue
Block a user