/* 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 }) => (
);
// ── 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 });