'use client' import { useEffect, useRef } from 'react' interface Spore { x: number y: number vx: number vy: number life: number maxLife: number size: number } export function CursorEffect() { const canvasRef = useRef(null) const sporesRef = useRef([]) const mouseRef = useRef({ x: 0, y: 0 }) const frameRef = useRef(0) useEffect(() => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext('2d') if (!ctx) return const resize = () => { canvas.width = window.innerWidth canvas.height = window.innerHeight } resize() window.addEventListener('resize', resize) const handleMouseMove = (e: MouseEvent) => { const dx = e.clientX - mouseRef.current.x const dy = e.clientY - mouseRef.current.y const speed = Math.sqrt(dx * dx + dy * dy) mouseRef.current = { x: e.clientX, y: e.clientY } // Spawn spores based on movement speed if (speed > 3) { const count = Math.min(Math.floor(speed / 8), 3) for (let i = 0; i < count; i++) { const angle = Math.atan2(dy, dx) + Math.PI + (Math.random() - 0.5) * 1.5 const sporeSpeed = 0.5 + Math.random() * 1.5 sporesRef.current.push({ x: e.clientX + (Math.random() - 0.5) * 10, y: e.clientY + (Math.random() - 0.5) * 10, vx: Math.cos(angle) * sporeSpeed, vy: Math.sin(angle) * sporeSpeed, life: 1, maxLife: 80 + Math.random() * 60, size: 1.5 + Math.random() * 2.5, }) } } } window.addEventListener('mousemove', handleMouseMove) const animate = () => { // Clear canvas completely each frame - no lingering ctx.clearRect(0, 0, canvas.width, canvas.height) const spores = sporesRef.current // Update and draw spores sporesRef.current = spores.filter(spore => { // Drift toward other nearby spores (flocking) let avgX = 0, avgY = 0, count = 0 for (const other of spores) { if (other === spore) continue const dx = other.x - spore.x const dy = other.y - spore.y const dist = Math.sqrt(dx * dx + dy * dy) if (dist < 80 && dist > 5) { avgX += dx / dist avgY += dy / dist count++ // Draw faint connection lines if (dist < 50 && spore.life > 0.3 && other.life > 0.3) { const alpha = Math.min(spore.life, other.life) * 0.15 * (1 - dist / 50) ctx.beginPath() ctx.moveTo(spore.x, spore.y) ctx.lineTo(other.x, other.y) ctx.strokeStyle = `rgba(180, 160, 140, ${alpha})` ctx.lineWidth = 0.5 ctx.stroke() } } } // Apply gentle flocking if (count > 0) { spore.vx += (avgX / count) * 0.02 spore.vy += (avgY / count) * 0.02 } // Add slight upward drift spore.vy -= 0.01 // Damping spore.vx *= 0.98 spore.vy *= 0.98 // Update position spore.x += spore.vx spore.y += spore.vy // Decay spore.life -= 1 / spore.maxLife // Draw spore if (spore.life > 0) { const alpha = spore.life * 0.7 const size = spore.size * (0.5 + spore.life * 0.5) ctx.beginPath() ctx.arc(spore.x, spore.y, size, 0, Math.PI * 2) ctx.fillStyle = `rgba(200, 175, 140, ${alpha})` ctx.fill() // Soft glow ctx.beginPath() ctx.arc(spore.x, spore.y, size * 2, 0, Math.PI * 2) ctx.fillStyle = `rgba(200, 175, 140, ${alpha * 0.2})` ctx.fill() } return spore.life > 0 }) // Limit spore count if (sporesRef.current.length > 150) { sporesRef.current = sporesRef.current.slice(-150) } frameRef.current = requestAnimationFrame(animate) } frameRef.current = requestAnimationFrame(animate) return () => { window.removeEventListener('resize', resize) window.removeEventListener('mousemove', handleMouseMove) cancelAnimationFrame(frameRef.current) } }, []) return ( ) }