// FOLGORE — Custom cursor system // Modes: dot+ring (default), trail (dotted comet), bracket (focus markers) // Magnetism on [data-magnetic], grow + label on [data-cursor-label] const { useEffect, useRef, useState } = React; function Cursor({ mode = 'ring', enabled = true }){ const dotRef = useRef(null); const ringRef = useRef(null); const labelRef = useRef(null); const trailsRef = useRef([]); const stateRef = useRef({ x: window.innerWidth/2, y: window.innerHeight/2, tx: window.innerWidth/2, ty: window.innerHeight/2, rx: window.innerWidth/2, ry: window.innerHeight/2, hover: false, label: '', size: 1, magnet: null, }); const [hidden, setHidden] = useState(false); // Set up trail dots useEffect(() => { if (!enabled) return; const onMove = (e) => { stateRef.current.tx = e.clientX; stateRef.current.ty = e.clientY; }; const onLeave = () => setHidden(true); const onEnter = () => setHidden(false); window.addEventListener('mousemove', onMove); document.addEventListener('mouseleave', onLeave); document.addEventListener('mouseenter', onEnter); return () => { window.removeEventListener('mousemove', onMove); document.removeEventListener('mouseleave', onLeave); document.removeEventListener('mouseenter', onEnter); }; }, [enabled]); // Hover detection — delegated useEffect(() => { if (!enabled) return; const onOver = (e) => { const target = e.target.closest('a, button, [data-cursor-label], [data-magnetic], [role="button"], input, textarea'); const s = stateRef.current; if (target){ s.hover = true; s.label = target.getAttribute('data-cursor-label') || ''; s.size = target.hasAttribute('data-cursor-grow') ? 3.6 : (s.label ? 2.4 : 1.6); s.magnet = target.hasAttribute('data-magnetic') ? target : null; } else { s.hover = false; s.label = ''; s.size = 1; s.magnet = null; } }; document.addEventListener('mouseover', onOver); document.addEventListener('mouseout', onOver); return () => { document.removeEventListener('mouseover', onOver); document.removeEventListener('mouseout', onOver); }; }, [enabled]); // Animation loop useEffect(() => { if (!enabled) return; let raf; const tick = () => { const s = stateRef.current; // Magnetic snap let targetX = s.tx, targetY = s.ty; if (s.magnet){ const r = s.magnet.getBoundingClientRect(); const cx = r.left + r.width/2; const cy = r.top + r.height/2; const dx = s.tx - cx; const dy = s.ty - cy; const dist = Math.hypot(dx,dy); const radius = Math.max(r.width, r.height) * 0.6; if (dist < radius){ const pull = 0.45 * (1 - dist/radius); targetX = s.tx - dx * pull; targetY = s.ty - dy * pull; } } // Dot follows fast s.x += (targetX - s.x) * 0.55; s.y += (targetY - s.y) * 0.55; // Ring follows slower s.rx += (targetX - s.rx) * 0.18; s.ry += (targetY - s.ry) * 0.18; const dot = dotRef.current, ring = ringRef.current, label = labelRef.current; if (dot){ dot.style.transform = `translate3d(${s.x - 4}px, ${s.y - 4}px, 0)`; } if (ring){ ring.style.transform = `translate3d(${s.rx}px, ${s.ry}px, 0) translate(-50%, -50%) scale(${s.size})`; ring.style.opacity = s.hover ? 1 : 0.85; ring.style.borderColor = s.hover ? '#FAFAFA' : 'rgba(250,250,250,0.55)'; ring.style.background = s.hover && s.label ? 'rgba(250,250,250,0.92)' : 'transparent'; } if (label){ label.style.transform = `translate3d(${s.rx}px, ${s.ry}px, 0) translate(-50%, -50%)`; label.textContent = s.label; label.style.opacity = s.label ? 1 : 0; } // Trail const trails = trailsRef.current; if (trails.length){ // Each trail dot lerps toward the previous dot's position let px = s.x, py = s.y; for (let i=0; i cancelAnimationFrame(raf); }, [enabled, mode]); if (!enabled) return null; const trailCount = mode === 'trail' ? 10 : 0; return (