/* Hooks compartidos del prototipo. */ (function () { const { useEffect, useRef, useState } = React; // Revela elementos .reveal al entrar en viewport (IntersectionObserver). // Llamar una vez a nivel de página; observa todos los .reveal:not(.in). function useRevealAll(deps = []) { useEffect(() => { const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const els = Array.from(document.querySelectorAll(".reveal:not(.in)")); if (reduce) { els.forEach(e => e.classList.add("in")); return; } const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }); els.forEach(e => io.observe(e)); return () => io.disconnect(); }, deps); } // Parallax por movimiento de ratón. Devuelve ref al contenedor y posición normalizada (-1..1). function useMouseParallax() { const ref = useRef(null); const [p, setP] = useState({ x: 0, y: 0 }); useEffect(() => { const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reduce) return; const el = ref.current; if (!el) return; let raf = null; const onMove = (e) => { const r = el.getBoundingClientRect(); const x = ((e.clientX - r.left) / r.width - 0.5) * 2; const y = ((e.clientY - r.top) / r.height - 0.5) * 2; if (raf) cancelAnimationFrame(raf); raf = requestAnimationFrame(() => setP({ x, y })); }; const onLeave = () => setP({ x: 0, y: 0 }); el.addEventListener("mousemove", onMove); el.addEventListener("mouseleave", onLeave); return () => { el.removeEventListener("mousemove", onMove); el.removeEventListener("mouseleave", onLeave); }; }, []); return [ref, p]; } window.useRevealAll = useRevealAll; window.useMouseParallax = useMouseParallax; })();