import { useEffect, useRef} from "react" import { motion } from "framer-motion" const ParticleBackground = () => { const canvasRef = useRef(null) const particlesRef = useRef([]) const mouseRef = useRef({ x: 0, y: 0 }) useEffect(() => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext("2d") let animationFrameId const resizeCanvas = () => { const { innerWidth, innerHeight } = window canvas.width = innerWidth canvas.height = innerHeight particlesRef.current.forEach((particle) => { if (particle.x > innerWidth) particle.x = innerWidth * 0.9 if (particle.y > innerHeight) particle.y = innerHeight * 0.9 if (particle.x < 0) particle.x = innerWidth * 0.1 if (particle.y < 0) particle.y = innerHeight * 0.1 particle.baseX = Math.min(particle.baseX, innerWidth * 0.9) particle.baseY = Math.min(particle.baseY, innerHeight * 0.9) }) } resizeCanvas() let resizeTimeout const debouncedResize = () => { clearTimeout(resizeTimeout) resizeTimeout = setTimeout(resizeCanvas, 100) } window.addEventListener("resize", debouncedResize) const handleMouseMove = (e) => { mouseRef.current = { x: e.clientX, y: e.clientY } } window.addEventListener("mousemove", handleMouseMove) class Particle { constructor(type = "default") { this.type = type this.reset() this.setupByType() } reset() { this.x = Math.random() * canvas.width this.y = Math.random() * canvas.height this.baseX = this.x this.baseY = this.y this.density = Math.random() * 30 + 1 this.life = 1 this.maxLife = Math.random() * 100 + 50 } setupByType() { const isMobile = window.innerWidth <= 768 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) const speedMultiplier = isMobile ? 0.3 : 1 switch (this.type) { case "cosmic": this.size = Math.random() * 4 + 2 this.speedX = (Math.random() * 0.3 - 0.15) * speedMultiplier this.speedY = (Math.random() * 0.3 - 0.15) * speedMultiplier this.color = this.getCosmicColor() this.pulseSpeed = Math.random() * 0.015 + 0.008 this.pulse = 0 break case "nebula": this.size = Math.random() * 8 + 4 this.speedX = (Math.random() * 0.15 - 0.075) * speedMultiplier this.speedY = (Math.random() * 0.15 - 0.075) * speedMultiplier this.color = this.getNebulaColor() this.opacity = Math.random() * 0.3 + 0.1 break case "star": this.size = Math.random() * 2 + 1 this.speedX = (Math.random() * 0.2 - 0.1) * speedMultiplier this.speedY = (Math.random() * 0.2 - 0.1) * speedMultiplier this.color = this.getStarColor() this.twinkle = Math.random() * Math.PI * 2 this.twinkleSpeed = Math.random() * 0.03 + 0.015 break case "energy": this.size = Math.random() * 3 + 1 this.speedX = (Math.random() * 0.6 - 0.3) * speedMultiplier this.speedY = (Math.random() * 0.6 - 0.3) * speedMultiplier this.color = this.getEnergyColor() this.trail = [] this.trailLength = 5 break default: this.size = Math.random() * 3 + 1 this.speedX = (Math.random() * 1.2 - 0.6) * speedMultiplier this.speedY = (Math.random() * 1.2 - 0.6) * speedMultiplier this.color = this.getDefaultColor() this.opacity = Math.random() * 0.5 + 0.2 } } getCosmicColor() { const colors = [ `rgba(102, 126, 234, ${Math.random() * 0.8 + 0.2})`, `rgba(246, 211, 101, ${Math.random() * 0.8 + 0.2})`, `rgba(255, 154, 158, ${Math.random() * 0.8 + 0.2})`, `rgba(168, 237, 234, ${Math.random() * 0.8 + 0.2})`, `rgba(254, 207, 239, ${Math.random() * 0.8 + 0.2})`, ] return colors[Math.floor(Math.random() * colors.length)] } getNebulaColor() { const colors = [ `rgba(120, 119, 198, ${Math.random() * 0.4 + 0.1})`, `rgba(255, 119, 198, ${Math.random() * 0.4 + 0.1})`, `rgba(120, 219, 255, ${Math.random() * 0.4 + 0.1})`, ] return colors[Math.floor(Math.random() * colors.length)] } getStarColor() { const colors = [ `rgba(255, 255, 255, ${Math.random() * 0.9 + 0.1})`, `rgba(255, 248, 220, ${Math.random() * 0.9 + 0.1})`, `rgba(173, 216, 230, ${Math.random() * 0.9 + 0.1})`, ] return colors[Math.floor(Math.random() * colors.length)] } getEnergyColor() { const colors = [ `rgba(0, 255, 255, ${Math.random() * 0.8 + 0.2})`, `rgba(255, 0, 255, ${Math.random() * 0.8 + 0.2})`, `rgba(255, 255, 0, ${Math.random() * 0.8 + 0.2})`, ] return colors[Math.floor(Math.random() * colors.length)] } getDefaultColor() { const colors = [ `rgba(102, 126, 234, ${Math.random() * 0.6 + 0.2})`, `rgba(246, 211, 101, ${Math.random() * 0.6 + 0.2})`, `rgba(255, 154, 158, ${Math.random() * 0.6 + 0.2})`, ] return colors[Math.floor(Math.random() * colors.length)] } update() { const mouse = mouseRef.current const dx = mouse.x - this.x const dy = mouse.y - this.y const distance = Math.sqrt(dx * dx + dy * dy) if (distance > 0 && distance < 150 && window.innerWidth > 768) { const forceDirectionX = dx / distance const forceDirectionY = dy / distance const maxDistance = 150 const force = (maxDistance - distance) / maxDistance const directionX = forceDirectionX * force * this.density * 0.3 const directionY = forceDirectionY * force * this.density * 0.3 this.x -= directionX this.y -= directionY } else { if (this.x !== this.baseX) { const dx = this.x - this.baseX this.x -= dx / 15 } if (this.y !== this.baseY) { const dy = this.y - this.baseY this.y -= dy / 15 } } switch (this.type) { case "cosmic": this.pulse += this.pulseSpeed this.x += this.speedX this.y += this.speedY break case "nebula": this.x += this.speedX this.y += this.speedY this.opacity = Math.sin(Date.now() * 0.001 + this.x * 0.01) * 0.2 + 0.3 break case "star": this.twinkle += this.twinkleSpeed this.x += this.speedX this.y += this.speedY break case "energy": this.trail.push({ x: this.x, y: this.y }) if (this.trail.length > this.trailLength) { this.trail.shift() } this.x += this.speedX this.y += this.speedY break default: this.x += this.speedX this.y += this.speedY } if (this.x > canvas.width) this.x = 0 if (this.x < 0) this.x = canvas.width if (this.y > canvas.height) this.y = 0 if (this.y < 0) this.y = canvas.height this.life++ if (this.life > this.maxLife) { this.reset() this.setupByType() } } draw() { ctx.save() switch (this.type) { case "cosmic": this.drawCosmic() break case "nebula": this.drawNebula() break case "star": this.drawStar() break case "energy": this.drawEnergy() break default: this.drawDefault() } ctx.restore() } drawCosmic() { const pulseSize = this.size + Math.sin(this.pulse) * 2 const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, pulseSize * 3) gradient.addColorStop(0, this.color) gradient.addColorStop(1, "transparent") ctx.fillStyle = gradient ctx.beginPath() ctx.arc(this.x, this.y, pulseSize * 3, 0, Math.PI * 2) ctx.fill() ctx.fillStyle = this.color ctx.beginPath() ctx.arc(this.x, this.y, pulseSize, 0, Math.PI * 2) ctx.fill() } drawNebula() { ctx.globalAlpha = this.opacity const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 4) gradient.addColorStop(0, this.color) gradient.addColorStop(0.5, this.color.replace(/[\d.]+\)$/g, "0.1)")) gradient.addColorStop(1, "transparent") ctx.fillStyle = gradient ctx.beginPath() ctx.arc(this.x, this.y, this.size * 4, 0, Math.PI * 2) ctx.fill() ctx.globalAlpha = 1 } drawStar() { const twinkleOpacity = Math.sin(this.twinkle) * 0.5 + 0.5 ctx.globalAlpha = twinkleOpacity ctx.strokeStyle = this.color ctx.lineWidth = 1 ctx.beginPath() ctx.moveTo(this.x - this.size * 2, this.y) ctx.lineTo(this.x + this.size * 2, this.y) ctx.moveTo(this.x, this.y - this.size * 2) ctx.lineTo(this.x, this.y + this.size * 2) ctx.stroke() ctx.fillStyle = this.color ctx.beginPath() ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2) ctx.fill() ctx.globalAlpha = 1 } drawEnergy() { if (this.trail.length > 1) { ctx.strokeStyle = this.color ctx.lineWidth = 2 ctx.beginPath() ctx.moveTo(this.trail[0].x, this.trail[0].y) for (let i = 1; i < this.trail.length; i++) { ctx.globalAlpha = i / this.trail.length ctx.lineTo(this.trail[i].x, this.trail[i].y) } ctx.stroke() } ctx.globalAlpha = 1 ctx.fillStyle = this.color ctx.beginPath() ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2) ctx.fill() const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 3) gradient.addColorStop(0, this.color) gradient.addColorStop(1, "transparent") ctx.fillStyle = gradient ctx.beginPath() ctx.arc(this.x, this.y, this.size * 3, 0, Math.PI * 2) ctx.fill() } drawDefault() { ctx.globalAlpha = this.opacity const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 2) gradient.addColorStop(0, this.color) gradient.addColorStop(1, "transparent") ctx.fillStyle = gradient ctx.beginPath() ctx.arc(this.x, this.y, this.size * 2, 0, Math.PI * 2) ctx.fill() ctx.fillStyle = this.color ctx.beginPath() ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2) ctx.fill() ctx.globalAlpha = 1 } } const createParticles = () => { const particles = [] const particleCount = Math.min(150, Math.floor((canvas.width * canvas.height) / 15000)) const types = [ { type: "cosmic", count: Math.floor(particleCount * 0.3) }, { type: "nebula", count: Math.floor(particleCount * 0.2) }, { type: "star", count: Math.floor(particleCount * 0.25) }, { type: "energy", count: Math.floor(particleCount * 0.15) }, { type: "default", count: Math.floor(particleCount * 0.1) }, ] types.forEach(({ type, count }) => { for (let i = 0; i < count; i++) { particles.push(new Particle(type)) } }) return particles } particlesRef.current = createParticles() const drawConnections = () => { const particles = particlesRef.current 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 < 120) { ctx.globalAlpha = ((120 - distance) / 120) * 0.1 ctx.strokeStyle = "#667eea" ctx.lineWidth = 1 ctx.beginPath() ctx.moveTo(particles[i].x, particles[i].y) ctx.lineTo(particles[j].x, particles[j].y) ctx.stroke() } } } ctx.globalAlpha = 1 } const animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height) const bgGradient = ctx.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, Math.max(canvas.width, canvas.height), ) bgGradient.addColorStop(0, "rgba(26, 26, 46, 0.1)") bgGradient.addColorStop(0.5, "rgba(22, 33, 62, 0.05)") bgGradient.addColorStop(1, "rgba(15, 52, 96, 0.02)") ctx.fillStyle = bgGradient ctx.fillRect(0, 0, canvas.width, canvas.height) particlesRef.current.forEach((particle) => { particle.update() particle.draw() }) drawConnections() animationFrameId = requestAnimationFrame(animate) } animate() return () => { window.removeEventListener("resize", debouncedResize) window.removeEventListener("mousemove", handleMouseMove) cancelAnimationFrame(animationFrameId) clearTimeout(resizeTimeout) } }, []) return ( <> {}