// Reveal, Eyebrow, useCountUp, ScrollToTop, useHashRoute. (function () { const { useState, useEffect, useRef, useCallback } = React; function Reveal({ children, delay = 0, as: Tag = 'div', className = '', style, ...rest }) { const ref = useRef(null); const [shown, setShown] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setShown(true); io.disconnect(); } }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }); io.observe(el); return () => io.disconnect(); }, []); return ( {children} ); } function useCountUp(target, duration = 900) { const ref = useRef(null); const [val, setVal] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { const start = performance.now(); const tick = (t) => { const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(Math.round(eased * target)); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); io.disconnect(); } }, { threshold: 0.5 }); io.observe(el); return () => io.disconnect(); }, [target, duration]); return [ref, val]; } function Eyebrow({ children, color = 'var(--color-green)', style }) { return (
{children}
); } // Simple hash router. Returns [route, setRoute]. function useHashRoute() { const get = () => { const h = window.location.hash.replace(/^#/, ''); return h || '/'; }; const [route, setRoute] = useState(get()); useEffect(() => { const onHash = () => setRoute(get()); window.addEventListener('hashchange', onHash); return () => window.removeEventListener('hashchange', onHash); }, []); const go = useCallback((path) => { if (window.location.hash !== '#' + path) { window.location.hash = path; } // Scroll to top on every navigation (covers same-page clicks too). window.scrollTo({ top: 0, left: 0, behavior: 'instant' in window ? 'instant' : 'auto' }); }, []); // Auto scroll-to-top whenever route changes. useEffect(() => { window.scrollTo({ top: 0, left: 0, behavior: 'instant' in window ? 'instant' : 'auto' }); }, [route]); return [route, go]; } // — anchor that updates hash without reload. function Link({ to, onClick, children, ...rest }) { const handle = (e) => { e.preventDefault(); if (onClick) onClick(e); window.location.hash = to; window.scrollTo({ top: 0, left: 0, behavior: 'instant' in window ? 'instant' : 'auto' }); }; return ( {children} ); } window.GR = Object.assign(window.GR || {}, { Reveal, useCountUp, Eyebrow, useHashRoute, Link, }); })();