/* 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;