/* Custom cursor + scroll reveal hooks + utilities */ const useScrollReveal = (ref, { threshold = 0.2, once = true } = {}) => { const [seen, setSeen] = React.useState(false); React.useEffect(() => { if (!ref.current) return; const el = ref.current; const io = new IntersectionObserver( (entries) => { entries.forEach((e) => { if (e.isIntersecting) { setSeen(true); if (once) io.unobserve(el); } else if (!once) { setSeen(false); } }); }, { threshold, rootMargin: "0px 0px -10% 0px" } ); io.observe(el); return () => io.disconnect(); }, []); return seen; }; const Reveal = ({ children, as: Tag = "div", className = "", stagger = false, threshold = 0.15, ...rest }) => { const ref = React.useRef(null); const seen = useScrollReveal(ref, { threshold }); const cls = stagger ? "fade-up-stagger" : "fade-up"; return ( {children} ); }; /* No-op cursor (system default everywhere) */ const Cursor = () => null; /* Parallax-y element: translates Y based on scroll position relative to its center */ const useParallax = (ref, speed = 0.15) => { React.useEffect(() => { if (!ref.current) return; const el = ref.current; let raf = 0; const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { const rect = el.getBoundingClientRect(); const center = rect.top + rect.height / 2; const offset = (window.innerHeight / 2 - center) * speed; el.style.transform = `translate3d(0, ${offset}px, 0)`; }); }; window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); return () => { window.removeEventListener("scroll", onScroll); cancelAnimationFrame(raf); }; }, [speed]); }; Object.assign(window, { useScrollReveal, Reveal, Cursor, useParallax });