/* components.jsx — shared primitives, icons, neutral mockups */ const { useState, useEffect, useRef, useCallback } = React; /* ---------- Scroll reveal ---------- */ function Reveal({ children, delay = 0, as = "div", className = "", once = false, style, ...rest }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reduce) { el.style.opacity = "1"; el.style.transform = "none"; return; } const cs = getComputedStyle(el); const vRx = parseFloat(cs.getPropertyValue("--rx")); const vRy = cs.getPropertyValue("--ry").trim(); const RX = isNaN(vRx) ? 0 : vRx; const RY = vRy === "" ? 30 : parseFloat(vRy) || 0; const easeOut = (p) => 1 - Math.pow(1 - p, 3); let raf = 0, t1 = 0, t2 = 0, poll = 0, state = ""; const clearAll = () => { if (raf) cancelAnimationFrame(raf); raf = 0; clearTimeout(t1); clearTimeout(t2); }; const setHidden = () => { el.style.opacity = "0"; el.style.transform = "translate3d(" + RX + "px," + RY + "px,0)"; }; const finalize = () => { if (raf) cancelAnimationFrame(raf); raf = 0; el.style.opacity = "1"; el.style.transform = "none"; if (poll) { clearInterval(poll); poll = 0; } }; const animateIn = () => { if (state === "in") return; state = "in"; clearAll(); setHidden(); let start = null; const run = (now) => { if (start == null) start = now; const p = Math.min((now - start) / 720, 1); const e = easeOut(p); el.style.opacity = String(e); el.style.transform = "translate3d(" + RX * (1 - e) + "px," + RY * (1 - e) + "px,0)"; if (p < 1) raf = requestAnimationFrame(run); else finalize(); }; // stagger, then animate t1 = setTimeout(() => { raf = requestAnimationFrame(run); }, delay); // SAFETY: timers fire even when rAF/animations are throttled/frozen — // guarantees the element reaches its visible end-state regardless. t2 = setTimeout(finalize, delay + 820); }; const animateOut = (dir) => { if (once || state === "out") return; state = "out"; clearAll(); const oy = dir < 0 ? -14 : 14; const from = parseFloat(el.style.opacity || "1"); const from0 = isNaN(from) ? 1 : from; let start = null; const run = (now) => { if (start == null) start = now; const p = Math.min((now - start) / 400, 1); const e = easeOut(p); el.style.opacity = String(from0 * (1 - e)); el.style.transform = "translate3d(0," + oy * e + "px,0)"; if (p < 1) raf = requestAnimationFrame(run); else el.style.opacity = "0"; }; raf = requestAnimationFrame(run); // a frozen exit harmlessly leaves the element visible — no forced hide }; const evaluate = () => { const vh = window.innerHeight || 0; const r = el.getBoundingClientRect(); const visible = r.top < vh * 0.9 && r.bottom > vh * 0.12; if (visible) animateIn(); else if (state === "in") { if (r.bottom <= vh * 0.12) animateOut(-1); else if (r.top >= vh * 0.9) animateOut(1); } }; const io = new IntersectionObserver(() => evaluate(), { threshold: [0, 0.12, 0.5, 0.9] }); io.observe(el); // Call evaluate() directly (NOT wrapped in rAF, which is throttled when the // frame isn't painting) so scroll always re-evaluates. const onScroll = () => evaluate(); window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", onScroll); const its = [setTimeout(evaluate, 60), setTimeout(evaluate, 300), setTimeout(evaluate, 700)]; // low-frequency timer poll: guarantees state updates even if scroll/IO/rAF // are all throttled in a given environment. Stops once the element settles // visible (scroll + IO then handle any later exit/re-entry). poll = setInterval(evaluate, 400); // ultimate safety: never leave an on-screen element hidden const safe = setTimeout(() => { const r = el.getBoundingClientRect(); if (state !== "in" && r.top < (window.innerHeight || 0) && r.bottom > 0) finalize(); }, 2400); evaluate(); return () => { io.disconnect(); window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); if (raf) cancelAnimationFrame(raf); clearInterval(poll); clearTimeout(t1); clearTimeout(t2); clearTimeout(safe); its.forEach(clearTimeout); }; }, [once, delay]); const Tag = as; return ( {children} ); } /* ---------- Spotlight card (pointer-following glow) ---------- */ function SpotlightCard({ children, style = {}, className = "" }) { return (
{children}
); } /* ---------- Buttons ---------- */ function ArrowIcon() { return ( ); } function CTA({ children = "Quero desenvolver meu E‑Commerce", variant = "primary", big = false, small = false, onClick, withArrow = true }) { const sizeStyle = big ? { fontSize: "1.12rem", padding: "1.2em 2em" } : small ? { fontSize: ".94rem", padding: ".82em 1.45em", borderRadius: 13 } : undefined; return ( ); } /* ---------- Recurring inline CTA (used between sections) ---------- */ function jumpToContact() { document.getElementById("contato")?.scrollIntoView({ behavior: "smooth" }); } function SectionCTA({ note, label = "Quero desenvolver meu E‑Commerce", dark = false, mt = 64 }) { return ( {note &&

{note}

} {label}
); } /* ---------- Section header ---------- */ function SectionHead({ eyebrow, title, sub, center = false, max = 720, eyebrowColor }) { return (
{eyebrow && {eyebrow}{center && } } {title} {sub && {sub}}
); } /* ---------- Animated counter ---------- */ function Counter({ to, prefix = "", suffix = "", dur = 1800 }) { 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 reduce = matchMedia("(prefers-reduced-motion:reduce)").matches; if (reduce) {setVal(to);return;} const t0 = performance.now(); const tick = (t) => { const p = Math.min(1, (t - t0) / dur); const e2 = 1 - Math.pow(1 - p, 3); setVal(Math.round(to * e2)); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); } }); }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [to, dur]); return {prefix}{val.toLocaleString("pt-BR")}{suffix}; } /* ---------- Infinite marquee ---------- */ function Marquee({ items, speed = 38 }) { // Repete o conjunto base o suficiente para uma "metade" exceder a largura da // tela; depois duplica para o loop -50% emendar sem vão/piscar. const half = [...items, ...items, ...items, ...items]; const row = half.concat(half); return (
{row.map((it, i) =>
e.currentTarget.style.opacity = 1} onMouseLeave={(e) => e.currentTarget.style.opacity = .6}>
)}
); } /* Platform logos — official brand marks (white), rendered as images */ function PlatformLogo({ logo }) { return ( {logo.name}); } /* ---------- Neutral mockups (abstract, substituíveis) ---------- */ function bar(w, op = .14, h = 8, r = 4) { return { width: w, height: h, borderRadius: r, background: `rgba(255,252,252,${op})` }; } function BrowserMockup({ title = "operacao.exemplo" }) { return (
{title}
{[0, 1, 2].map((i) =>
)}
); } function DashboardMockup() { const pts = [22, 34, 30, 46, 40, 58, 52, 70, 64, 82]; const path = pts.map((p, i) => `${i / (pts.length - 1) * 100},${100 - p}`).join(" "); return (
+38%
{["7d", "30d", "12m"].map((t, i) => {t})}
{[["Pedidos", "1.2k"], ["Ticket", "R$ 312"], ["Conv.", "3.4%"]].map(([a, b]) =>
{a.toUpperCase()}
{b}
)}
); } function PhoneMockup() { return (
{[0, 1, 2, 3].map((i) =>
)}
); } Object.assign(window, { Reveal, CTA, SectionCTA, jumpToContact, ArrowIcon, SectionHead, Counter, Marquee, PlatformLogo, BrowserMockup, DashboardMockup, PhoneMockup, SpotlightCard });