/* global React */ const { useState, useEffect, useRef, useMemo } = React; /* ---------- Icons ---------- */ const Icon = { ArrowRight: (p) => , ArrowUpRight: (p) => , Plus: (p) => , Phone: (p) => , Mail: (p) => , Clock: (p) => , Check: (p) => , Star: (p) => , WhatsApp: (p) => , Instagram: (p) => , Facebook: (p) => }; /* ---------- Brand Mark ---------- */ function BrandMark() { return ( Grupo Over Klin Servicios profesionales de limpieza ); } /* ---------- Placeholder Image ---------- */ function Placeholder({ caption, tag, style, className = "" }) { return (
{tag &&
{tag}
} {caption &&
{caption}
}
); } /* ---------- Reveal-on-scroll wrapper ---------- */ function Reveal({ children, delay = 0, as: As = "div", ...rest }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { setTimeout(() => el.classList.add("in"), delay); io.unobserve(el); } }); }, { threshold: 0.12 }); io.observe(el); return () => io.disconnect(); }, [delay]); return {children}; } /* ---------- Counter (animated number on view) ---------- */ function Counter({ value, suffix = "", duration = 1400 }) { const ref = useRef(null); const [n, setN] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; let raf; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { const start = performance.now(); const tick = (t) => { const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setN(Math.round(value * eased)); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); io.unobserve(el); } }); }, { threshold: 0.3 }); io.observe(el); return () => {io.disconnect();if (raf) cancelAnimationFrame(raf);}; }, [value, duration]); return {n}{suffix}; } Object.assign(window, { Icon, BrandMark, Placeholder, Reveal, Counter });