// Reusable primitives: CTAButton, SectionHeading, useReveal observer, Reveal wrapper.
const { useEffect, useRef, useState, useCallback } = React;
/* ---------- Reveal-on-scroll hook ----------
Scroll-driven (more reliable inside preview iframes than IntersectionObserver,
which can stay silent when the host iframe isn't intersecting its parent doc).
Uses CSS animations triggered by `.in` class; everything stays visible if JS
fails or the safety timer expires. */
function useReveal() {
useEffect(() => {
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) return;
// Elements already in the initial viewport: mark as "already revealed" so
// they do NOT animate (avoids a visible-flash → opacity:0 → animate flash).
const vh = window.innerHeight || document.documentElement.clientHeight;
const initialTrigger = vh + 100;
document.querySelectorAll('[data-reveal]').forEach((el) => {
const r = el.getBoundingClientRect();
if (r.top < initialTrigger) {
el.dataset.revealSeen = '1'; // skip animating this one
}
});
let raf = 0;
const reveal = () => {
raf = 0;
const vh2 = window.innerHeight || document.documentElement.clientHeight;
const triggerY = vh2 - 60;
document.querySelectorAll('[data-reveal]:not(.in)').forEach((el) => {
if (el.dataset.revealSeen === '1') return;
const r = el.getBoundingClientRect();
if (r.top < triggerY && r.bottom > 0) {
const delay = el.dataset.revealDelay || '0';
el.style.animationDelay = delay + 'ms';
el.classList.add('in');
}
});
};
const onScroll = () => {
if (raf) return;
raf = requestAnimationFrame(reveal);
};
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll, { passive: true });
return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
if (raf) cancelAnimationFrame(raf);
};
}, []);
}
/* ---------- CTA Button ---------- */
function CTAButton({
children, variant = 'primary', size = 'md', icon = true,
href = '#contato', onClick, disabled, className = '', ariaLabel,
target, rel,
}) {
const base = "inline-flex items-center justify-center gap-2 rounded-full font-medium tracking-tight transition-all duration-300 lift focus:outline-none focus:ring-2 focus:ring-accent-500/40 focus:ring-offset-2 focus:ring-offset-ink-800";
const sizes = {
sm: "text-sm px-5 py-2.5",
md: "text-[15px] px-7 py-3.5",
lg: "text-base px-8 py-4",
xl: "text-base md:text-lg px-9 py-5",
};
const variants = {
primary: "bg-brand-mint hover:bg-brand-mint2 text-brand-navy shadow-[0_10px_30px_-12px_rgba(52,229,198,0.5)] hover:shadow-[0_18px_44px_-12px_rgba(52,229,198,0.6)]",
secondary: "border border-white/20 hover:border-brand-mint/50 text-fog-100 hover:bg-white/[0.04]",
ghost: "text-fog-100 hover:bg-white/[0.05]",
};
const klass = [base, sizes[size], variants[variant], disabled ? 'btn-cta-disabled' : '', className].join(' ');
const inner = (
{children}
); } /* ---------- Subtle decorative divider ---------- */ function SectionDivider() { return ; } /* ---------- Brand mark (text logo) ---------- Mirrors the "IA Vertical for Business" wordmark in text form: Fraunces serif, italic mint "Vertical", with a small uppercase subtitle below. Size scales from the `size` prop. */ function BrandMark({ size = 'md', subtitle = 'Mentoria VIP para Empresários', className = '' }) { const sizes = { sm: { title: 'text-[15px]', sub: 'text-[9px] tracking-[0.18em]' }, md: { title: 'text-[17px] sm:text-[18px]', sub: 'text-[9.5px] sm:text-[10px] tracking-[0.18em]' }, lg: { title: 'text-[22px]', sub: 'text-[11px] tracking-[0.2em]' }, }; const s = sizes[size] || sizes.md; return (