/* global React */ /* Hook: reveal on scroll */ function useReveal() { const ref = React.useRef(null); React.useEffect(() => { const el = ref.current; if (!el) return; // immediate check: if already in viewport, reveal now (skip transition) const r = el.getBoundingClientRect(); if (r.top < window.innerHeight && r.bottom > 0) { el.style.transition = "none"; el.classList.add("in"); // force reflow then restore transition void el.offsetWidth; requestAnimationFrame(() => { el.style.transition = ""; }); return; } const io = new IntersectionObserver( (entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: "0px 0px -60px 0px" } ); io.observe(el); // fallback: ensure visible within 800ms even if IO fails const fallback = setTimeout(() => { if (el && !el.classList.contains("in")) el.classList.add("in"); }, 800); return () => { io.disconnect(); clearTimeout(fallback); }; }, []); return ref; } function Reveal({ as: Tag = "div", className = "", delay, scale, stagger, children, ...rest }) { const ref = useReveal(); const cls = [ scale ? "reveal-scale" : (stagger ? "reveal-stagger" : "reveal"), delay ? `delay-${delay}` : "", className, ].filter(Boolean).join(" "); return {children}; } /* Number counter on enter */ function CountUp({ to, prefix = "", suffix = "", duration = 1400, decimals = 0 }) { const ref = React.useRef(null); const [val, setVal] = React.useState(0); React.useEffect(() => { const el = ref.current; if (!el) return; 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); setVal(to * eased); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); io.unobserve(e.target); } }); }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [to, duration]); const formatted = decimals > 0 ? val.toFixed(decimals).replace(".", ",") : Math.round(val).toLocaleString("pt-BR"); return {prefix}{formatted}{suffix}; } /* Parallax orb */ function ParallaxOrb({ kind = "blue", style }) { const ref = React.useRef(null); React.useEffect(() => { const el = ref.current; if (!el) return; const onScroll = () => { const r = el.getBoundingClientRect(); const center = r.top + r.height / 2; const dist = (window.innerHeight / 2) - center; el.style.transform = `translate3d(0, ${dist * 0.15}px, 0)`; }; onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); return
; } window.LegadoMotion = { useReveal, Reveal, CountUp, ParallaxOrb }; Object.assign(window, { useReveal, Reveal, CountUp, ParallaxOrb });