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:
drjones
2026-04-07 21:35:52 -07:00
parent 52432dccfa
commit 78a071ba02
162 changed files with 21692 additions and 39 deletions

View 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;