/* global React */ const { useState, useEffect, useRef } = React; // Reveal-on-scroll: gives element class "is-in" once 12% visible. // Fallback: if already in viewport on mount (or IO doesn't fire), apply immediately. function useReveal() { const ref = useRef(null); useEffect(() => { if (!ref.current) return; const el = ref.current; // Immediate check: if already in viewport, reveal now. const inView = () => { const r = el.getBoundingClientRect(); return r.top < (window.innerHeight || document.documentElement.clientHeight) * 0.95 && r.bottom > 0; }; if (inView()) { // Defer one frame so the entry transition still plays. requestAnimationFrame(() => el.classList.add("is-in")); return; } if (typeof IntersectionObserver === "undefined") { el.classList.add("is-in"); return; } const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { el.classList.add("is-in"); io.disconnect(); } }); }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }); io.observe(el); // Safety net: poll on scroll too in case IO is throttled in this frame. const onScroll = () => { if (inView()) { el.classList.add("is-in"); io.disconnect(); window.removeEventListener("scroll", onScroll); } }; window.addEventListener("scroll", onScroll, { passive: true }); // Final fallback after 3s — reveal regardless. const t = setTimeout(() => el.classList.add("is-in"), 3000); return () => { io.disconnect(); window.removeEventListener("scroll", onScroll); clearTimeout(t); }; }, []); return ref; } function CountUp({ end, suffix = "", prefix = "", duration = 1800 }) { const [val, setVal] = useState(0); const ref = useRef(null); useEffect(() => { if (!ref.current) return; const el = ref.current; let raf; let started = false; const start = () => { if (started) return; started = true; const t0 = performance.now(); const tick = (now) => { const k = Math.min(1, (now - t0) / duration); const eased = 1 - Math.pow(1 - k, 3); setVal(Math.round(end * eased)); if (k < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); }; const inView = () => { const r = el.getBoundingClientRect(); return r.top < (window.innerHeight || 800) * 0.95 && r.bottom > 0; }; if (inView()) { start(); return () => cancelAnimationFrame(raf); } let io; if (typeof IntersectionObserver !== "undefined") { io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { start(); io.disconnect(); } }); }, { threshold: 0.5 }); io.observe(el); } const onScroll = () => { if (inView()) { start(); if (io) io.disconnect(); window.removeEventListener("scroll", onScroll); } }; window.addEventListener("scroll", onScroll, { passive: true }); const t = setTimeout(start, 2500); return () => { if (io) io.disconnect(); window.removeEventListener("scroll", onScroll); clearTimeout(t); cancelAnimationFrame(raf); }; }, [end, duration]); return {prefix}{val.toLocaleString("pt-BR")}{suffix}; } function Eyebrow({ children }) { return {children}; } function Container({ children, w = 1140, style }) { return
{children}
; } Object.assign(window, { useReveal, CountUp, Eyebrow, Container });