"use client" import { useEffect, useRef } from "react" interface Trail { points: { x: number; y: number }[] color: string opacity: number createdAt: number } export function CursorTrails() { const canvasRef = useRef(null) const trailsRef = useRef([]) const mouseRef = useRef({ x: 0, y: 0, lastX: 0, lastY: 0 }) const animationRef = useRef() 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 colors = [ "rgba(0, 255, 255, 0.6)", // Cyan "rgba(255, 0, 255, 0.6)", // Magenta "rgba(138, 43, 226, 0.6)", // Purple "rgba(255, 140, 0, 0.6)", // Orange "rgba(20, 20, 30, 0.8)", // Near-black "rgba(0, 200, 200, 0.6)", // Teal "rgba(255, 100, 50, 0.6)", // Orange-red ] let colorIndex = 0 let frameCount = 0 const handleMouseMove = (e: MouseEvent) => { mouseRef.current.lastX = mouseRef.current.x mouseRef.current.lastY = mouseRef.current.y mouseRef.current.x = e.clientX mouseRef.current.y = e.clientY const dx = mouseRef.current.x - mouseRef.current.lastX const dy = mouseRef.current.y - mouseRef.current.lastY const distance = Math.sqrt(dx * dx + dy * dy) if (distance > 5 && frameCount % 2 === 0) { const points: { x: number; y: number }[] = [] const steps = 20 // Create a perpendicular vector for sine wave oscillation const perpX = -dy / distance const perpY = dx / distance // Random frequency and amplitude for varied squiggles const frequency = 0.3 + Math.random() * 0.5 const amplitude = 15 + Math.random() * 25 for (let i = 0; i <= steps; i++) { const t = i / steps // Base position along the line const baseX = mouseRef.current.lastX + dx * t const baseY = mouseRef.current.lastY + dy * t // Add sine wave oscillation perpendicular to movement direction const wave = Math.sin(t * Math.PI * frequency * 4) * amplitude * (1 - t * 0.3) points.push({ x: baseX + perpX * wave, y: baseY + perpY * wave, }) } trailsRef.current.push({ points, color: colors[colorIndex], opacity: 1, createdAt: Date.now(), }) colorIndex = (colorIndex + 1) % colors.length } frameCount++ } const animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height) const now = Date.now() trailsRef.current = trailsRef.current.filter((trail) => { const age = now - trail.createdAt const maxAge = 2000 if (age > maxAge) return false trail.opacity = 1 - age / maxAge ctx.beginPath() ctx.strokeStyle = trail.color.replace(/[\d.]+\)$/, `${trail.opacity * 0.6})`) ctx.lineWidth = 2 + trail.opacity * 2 ctx.lineCap = "round" ctx.lineJoin = "round" if (trail.points.length > 0) { ctx.moveTo(trail.points[0].x, trail.points[0].y) for (let i = 1; i < trail.points.length - 1; i++) { const xc = (trail.points[i].x + trail.points[i + 1].x) / 2 const yc = (trail.points[i].y + trail.points[i + 1].y) / 2 ctx.quadraticCurveTo(trail.points[i].x, trail.points[i].y, xc, yc) } if (trail.points.length > 1) { const last = trail.points[trail.points.length - 1] ctx.lineTo(last.x, last.y) } } // Add glow effect ctx.shadowBlur = 15 * trail.opacity ctx.shadowColor = trail.color ctx.stroke() ctx.shadowBlur = 0 return true }) animationRef.current = requestAnimationFrame(animate) } window.addEventListener("mousemove", handleMouseMove) animate() return () => { window.removeEventListener("mousemove", handleMouseMove) window.removeEventListener("resize", resize) if (animationRef.current) { cancelAnimationFrame(animationRef.current) } } }, []) return ( ) }