// ============ Icons ============
const Icon = ({ name, size = 22, stroke = "currentColor", sw = 1.6, style }) => {
const c = { width:size, height:size, viewBox:"0 0 24 24", fill:"none", stroke, strokeWidth:sw, strokeLinecap:"round", strokeLinejoin:"round", style };
switch(name){
case "bolt": return ();
case "chat": return ();
case "calendar": return ();
case "clock": return ();
case "whatsapp": return ();
case "instagram": return ();
case "spark": return ();
case "shield": return ();
case "heart": return ();
case "target": return ();
case "brain": return ();
case "plug": return ();
case "graph": return ();
case "play": return ();
case "arrow": return ();
case "check": return ();
case "plus": return ();
case "mail": return ();
case "pin": return ();
case "phone": return ();
case "globe": return ();
case "users": return ();
case "star": return ();
case "infinity": return ();
case "logo": return (
);
default: return null;
}
};
// ============ Reveal hook (IntersectionObserver) ============
function useRevealRoot() {
React.useEffect(() => {
const els = Array.from(document.querySelectorAll("[data-reveal]"));
const io = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting){ e.target.classList.add("in"); io.unobserve(e.target);} });
}, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
els.forEach(el => io.observe(el));
return () => io.disconnect();
});
}
// ============ Lightweight smooth scroll (Lenis-style) ============
function useSmoothScroll() {
React.useEffect(() => {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let target = window.scrollY, current = window.scrollY, raf;
let active = false;
const ease = 0.085;
const onWheel = (e) => {
// let inner scrollers handle their own wheel
if (e.target.closest("[data-native-scroll]")) return;
e.preventDefault();
target = Math.max(0, Math.min(target + e.deltaY, document.body.scrollHeight - window.innerHeight));
if (!active){ active = true; loop(); }
};
const loop = () => {
current += (target - current) * ease;
if (Math.abs(target - current) < 0.4){ current = target; active = false; window.scrollTo(0, current); return; }
window.scrollTo(0, current);
raf = requestAnimationFrame(loop);
};
const onResize = () => { target = current = window.scrollY; };
window.addEventListener("wheel", onWheel, { passive:false });
window.addEventListener("resize", onResize);
return () => { window.removeEventListener("wheel", onWheel); window.removeEventListener("resize", onResize); cancelAnimationFrame(raf); };
}, []);
}
// parallax helper: returns ref; element translates on scroll
function useParallax(speed = 0.15) {
const ref = React.useRef(null);
React.useEffect(() => {
let raf;
const update = () => {
const el = ref.current; if (!el) return;
const r = el.getBoundingClientRect();
const center = r.top + r.height/2 - window.innerHeight/2;
el.style.transform = `translateY(${center * -speed}px)`;
raf = requestAnimationFrame(update);
};
raf = requestAnimationFrame(update);
return () => cancelAnimationFrame(raf);
}, [speed]);
return ref;
}
Object.assign(window, { Icon, useRevealRoot, useSmoothScroll, useParallax });