'use client' import React, { useEffect, useRef } from 'react' import { motion, AnimatePresence } from 'motion/react' interface GhostCursorProps { isActive: boolean containerRef: React.RefObject targetId?: string | null } export function GhostCursor({ isActive, containerRef, targetId }: GhostCursorProps) { const positionRef = useRef({ x: 0, y: 0 }) const visibleRef = useRef(false) const elRef = useRef(null) const rafRef = useRef(null) const targetRef = useRef({ x: 0, y: 0 }) const initializedRef = useRef(false) const targetIdRef = useRef(targetId) targetIdRef.current = targetId const isActiveRef = useRef(isActive) isActiveRef.current = isActive useEffect(() => { if (!isActive) { visibleRef.current = false initializedRef.current = false if (elRef.current) elRef.current.style.opacity = '0' if (rafRef.current) cancelAnimationFrame(rafRef.current) return } let angle = Math.random() * Math.PI * 2 const init = () => { const container = containerRef.current if (!container) { rafRef.current = requestAnimationFrame(init); return } const rect = container.getBoundingClientRect() if (rect.width === 0 || rect.height === 0) { rafRef.current = requestAnimationFrame(init); return } const cx = rect.width / 2 const cy = rect.height / 2 targetRef.current = { x: cx + (Math.random() - 0.5) * 200, y: cy + (Math.random() - 0.5) * 200 } positionRef.current = { ...targetRef.current } initializedRef.current = true visibleRef.current = true if (elRef.current) { elRef.current.style.opacity = '1' elRef.current.style.transform = `translate(${positionRef.current.x}px, ${positionRef.current.y}px)` } } rafRef.current = requestAnimationFrame(init) const tick = () => { if (!isActiveRef.current || !initializedRef.current) { rafRef.current = requestAnimationFrame(tick) return } const container = containerRef.current if (!container) { rafRef.current = requestAnimationFrame(tick); return } const containerRect = container.getBoundingClientRect() if (containerRect.width === 0 || containerRect.height === 0) { rafRef.current = requestAnimationFrame(tick); return } const cx = containerRect.width / 2 const cy = containerRect.height / 2 let tx = targetRef.current.x let ty = targetRef.current.y const currentTargetId = targetIdRef.current if (currentTargetId) { const nodeElement = container.querySelector(`[data-id="${currentTargetId}"]`) as HTMLElement | null if (nodeElement) { const nodeRect = nodeElement.getBoundingClientRect() tx = nodeRect.left - containerRect.left + nodeRect.width / 2 ty = nodeRect.top - containerRect.top + nodeRect.height / 2 } } const prev = positionRef.current const dx = tx - prev.x const dy = ty - prev.y const dist = Math.sqrt(dx * dx + dy * dy) if (!currentTargetId && dist < 20) { angle = Math.random() * Math.PI * 2 const radius = 150 + Math.random() * 200 tx = cx + Math.cos(angle) * radius ty = cy + Math.sin(angle) * radius targetRef.current = { x: tx, y: ty } } const speed = currentTargetId ? 0.12 : 0.04 let newX = prev.x + (tx - prev.x) * speed let newY = prev.y + (ty - prev.y) * speed if (isNaN(newX)) newX = cx if (isNaN(newY)) newY = cy positionRef.current = { x: newX, y: newY } if (elRef.current) { elRef.current.style.transform = `translate(${newX}px, ${newY}px)` } rafRef.current = requestAnimationFrame(tick) } rafRef.current = requestAnimationFrame(tick) return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current) } }, [isActive, containerRef]) return (
AI ✦
) }