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