'use client'; import React, { useEffect, useRef, useState } from "react"; export default function Background({ children, }: Readonly<{ children: React.ReactNode; }>) { const canvasRef = useRef(null); const animationRef = useRef(0); const particlesRef = useRef([]); const [dimensions, setDimensions] = useState<{ width: number; height: number }>({ width: 0, height: 0, }); // Particle configuration const config = { particleCount: 400, maxDistance: 120, particleSpeed: 0.5, particleSize: 2, lineOpacity: 0.6, particleOpacity: 0.8, colors: { particles: "#f5304c", // blue-400 connections: "#f5304c", // blue-500 }, }; // Particle class class Particle { x: number; y: number; vx: number; vy: number; size: number; constructor(width: number, height: number) { this.x = Math.random() * width; this.y = Math.random() * height; this.vx = (Math.random() - 0.5) * config.particleSpeed; this.vy = (Math.random() - 0.5) * config.particleSpeed; this.size = config.particleSize + Math.random() * 2; } update(width: number, height: number) { this.x += this.vx; this.y += this.vy; // Bounce off edges if (this.x < 0 || this.x > width) { this.vx = -this.vx; this.x = Math.max(0, Math.min(width, this.x)); } if (this.y < 0 || this.y > height) { this.vy = -this.vy; this.y = Math.max(0, Math.min(height, this.y)); } } draw(ctx: CanvasRenderingContext2D) { ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fillStyle = `${config.colors.particles}${Math.round( config.particleOpacity * 255 ) .toString(16) .padStart(2, "0")}`; ctx.fill(); } } // Initialize particles const initParticles = (width: number, height: number) => { particlesRef.current = []; for (let i = 0; i < config.particleCount; i++) { particlesRef.current.push(new Particle(width, height)); } }; // Draw connections between nearby particles const drawConnections = (ctx: CanvasRenderingContext2D, particles: Particle[]) => { 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 < config.maxDistance) { const opacity = 1 - distance / config.maxDistance; ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = `${config.colors.connections}${Math.round( opacity * config.lineOpacity * 255 ) .toString(16) .padStart(2, "0")}`; ctx.lineWidth = 1; ctx.stroke(); } } } }; // Animation loop const animate = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; const { width, height } = dimensions; // Clear canvas ctx.clearRect(0, 0, width, height); // Update and draw particles particlesRef.current.forEach((particle) => { particle.update(width, height); particle.draw(ctx); }); // Draw connections drawConnections(ctx, particlesRef.current); animationRef.current = requestAnimationFrame(animate); }; // Handle window resize const handleResize = () => { const canvas = canvasRef.current; if (!canvas || !canvas.parentElement) return; const rect = canvas.parentElement.getBoundingClientRect(); const width = rect.width; const height = rect.height; const dpr = window.devicePixelRatio || 1; canvas.width = width * dpr; canvas.height = height * dpr; canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; const ctx = canvas.getContext("2d"); if (ctx) { ctx.setTransform(1, 0, 0, 1, 0, 0); // reset scaling before re-scaling ctx.scale(dpr, dpr); } setDimensions({ width, height }); }; useEffect(() => { handleResize(); window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, []); useEffect(() => { if (dimensions.width && dimensions.height) { initParticles(dimensions.width, dimensions.height); animate(); } return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [dimensions]); return (
{children}
); };