/* global React */
const { useState, useEffect, useRef, useCallback } = React;
/* ============ Hook: reveal on scroll ============ */
function useReveal(options = {}) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
el.classList.add("in");
io.unobserve(el);
}
});
},
{ threshold: options.threshold ?? 0.15, rootMargin: options.rootMargin ?? "0px 0px -8% 0px" }
);
io.observe(el);
return () => io.disconnect();
}, []);
return ref;
}
/* ============ Hook: count up ============ */
function useCountUp(target, duration = 1600) {
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((entries) => {
entries.forEach((e) => {
if (e.isIntersecting && !started.current) {
started.current = true;
const start = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 3);
setVal(Math.round(eased * target));
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
});
}, { threshold: 0.4 });
io.observe(el);
return () => io.disconnect();
}, [target, duration]);
return [val, ref];
}
/* ============ Icons (line, custom) ============ */
const I = {
arrow: (s = 16) => (
),
check: (s = 16) => (
),
plus: (s = 16) => (
),
minus: (s = 16) => (
),
stethoscope: (
),
shield: (
),
book: (
),
pulse: (
),
clipboard: (
),
mail: (
),
pin: (
),
whatsapp: (
),
phone: (
),
clock: (
),
};
/* ============ Decorative circles SVG ============ */
function DecoCircles({ size = 600, opacity = 0.5, color = "#0d8c72" }) {
return (
);
}
window.SLCommon = { useReveal, useCountUp, I, DecoCircles };