/* global React */ /* ────────────────────────────────────────────────────────────── tech-core.jsx — icons, atoms, hooks ────────────────────────────────────────────────────────────── */ const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React; // ── Icons (Lucide-style, 1.75 stroke, currentColor) ────────────── const ICON_PATHS = { arrowRight: <>, play: , spark: , refresh: <>, layers: <>, code: <>, split: <>, gauge: <>, map: <>, shield: , flask: <>, bug: <>, msg: , phone: <>, sync: <>, cpu: <>, target: <>, pen: <>, user: <>, eye: <>, check: , x: <>, menu: <>, globe: <>, bolt: , chevDown: , dollar: <>, clock: <>, building: <>, wave: <>, }; const Icon = ({ name, size = 20, style, ...rest }) => ( {ICON_PATHS[name]} ); // ── Button ─────────────────────────────────────────────────────── const Btn = ({ variant = 'primary', size = 'md', icon, iconRight, children, as = 'button', ...rest }) => { const Tag = as; return ( {icon && } {children} {iconRight && } ); }; // ── Reveal-on-scroll: one shared IntersectionObserver ──────────── let _io; function _ensureIO() { if (_io) return _io; _io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('in'); _io.unobserve(e.target); } }); }, { threshold: 0.14, rootMargin: '0px 0px -8% 0px' }); return _io; } const Reveal = ({ children, delay = 0, as = 'div', className = '', style, ...rest }) => { const ref = useRefC(null); useEffectC(() => { const el = ref.current; if (!el) return; _ensureIO().observe(el); }, []); const Tag = as; return {children}; }; // ── Count-up when scrolled into view ───────────────────────────── function useCountUp(target, { duration = 1400, decimals = 0 } = {}) { const ref = useRefC(null); const [val, setVal] = useStateC(0); useEffectC(() => { const el = ref.current; if (!el) return; let raf, started = false; const obs = new IntersectionObserver((ents) => { ents.forEach((e) => { if (e.isIntersecting && !started) { started = true; const t0 = performance.now(); const tick = (now) => { const p = Math.min(1, (now - t0) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(target * eased); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); obs.unobserve(el); } }); }, { threshold: 0.4 }); obs.observe(el); return () => { cancelAnimationFrame(raf); obs.disconnect(); }; }, [target]); const display = decimals ? val.toFixed(decimals) : Math.round(val).toLocaleString(); return [ref, display]; } Object.assign(window, { Icon, Btn, Reveal, useCountUp });