/* UI helpers: iconos, onda de flujo (motivo de marca), CountUp, reveal */ /* ---- Flow wave: motivo de marca recurrente ---- */ function FlowWave({ className, style, variant = "ribbon" }) { if (variant === "divider") { return ( ); } if (variant === "line") { return ( ); } // S-curve glyph derived from the logo wave return ( ); } /* ---- Icons ---- */ const Icon = { arrow: (p) => (), play: (p) => (), list: (p) => (), team: (p) => (), ship: (p) => (), check: (p) => (), x: (p) => (), spark: (p) => (), chip: (p) => (), radar: (p) => (), connect: (p) => (), board: (p) => (), code: (p) => (), rocket: (p) => (), chev: (p) => (), shield: (p) => (), eye: (p) => (), scale: (p) => (), info: (p) => (), clock: (p) => (), alert: (p) => (), }; /* ---- CountUp ---- */ function CountUp({ to, prefix = "", suffix = "", decimals = 0, dur = 1600, anim = 1 }) { const [val, setVal] = useState(0); const ref = useRef(null); const started = useRef(false); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((es) => { es.forEach((e) => { if (e.isIntersecting && !started.current) { started.current = true; if (anim <= 0) { setVal(to); return; } const t0 = performance.now(); const D = dur / Math.max(0.35, anim); const tick = (now) => { const p = Math.min(1, (now - t0) / D); const eased = 1 - Math.pow(1 - p, 3); setVal(to * eased); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); } }); }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [to, anim]); return {prefix}{val.toFixed(decimals)}{suffix}; } /* ---- global reveal observer ---- */ function useReveal(dep) { useEffect(() => { const els = document.querySelectorAll(".reveal:not(.in)"); const 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" }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, [dep]); } Object.assign(window, { FlowWave, Icon, CountUp, useReveal }); /* ---- Selector de idioma (lo maneja i18n.js) ---- */ function LangToggle() { return ; } window.LangToggle = LangToggle;