// Shared primitives and hooks
const { useState, useEffect, useRef, useMemo, createContext, useContext } = React;
// IntersectionObserver reveal hook
function useReveal() {
const ref = useRef(null);
const [shown, setShown] = useState(false);
useEffect(() => {
if (!ref.current) return;
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) { setShown(true); io.disconnect(); }
});
}, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' });
io.observe(ref.current);
return () => io.disconnect();
}, []);
return [ref, shown];
}
function FadeUp({ children, delay = 0, as = 'div', className = '', ...rest }) {
const [ref, shown] = useReveal();
const Cmp = as;
return (
{children}
);
}
// Count-up hook
function useCountUp(target, duration = 1600, start = false) {
const [val, setVal] = useState(0);
useEffect(() => {
if (!start) return;
const t0 = performance.now();
let raf;
const tick = (t) => {
const p = Math.min(1, (t - t0) / duration);
const eased = 1 - Math.pow(1 - p, 3);
setVal(target * eased);
if (p < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [target, duration, start]);
return val;
}
function ArrowIcon({ size = 16 }) {
return (
);
}
function Logo({ mode = 'light', onClick }) {
return (
);
}
Object.assign(window, { FadeUp, useReveal, useCountUp, ArrowIcon, Logo });