// V23 — Etapas. Terminal mostra processo do projeto como wizard de etapas. // Mail / envelope icon. const MailIcon = ({ size = 18, color = 'currentColor' }) => ( ); // WhatsApp icon (official-ish glyph). const WhatsAppIcon = ({ size = 16, color = 'currentColor' }) => ( ); // Icon-only email + WhatsApp buttons, side by side. const CTAButtons = ({ showWhatsApp }) => (
{showWhatsApp && ( )}
); // Internal CTA block — types the headline on view, then shows bouncing arrow + button. const CTABlock = ({ showWhatsApp }) => { const ref = React.useRef(null); const [started, setStarted] = React.useState(false); const [typed, setTyped] = React.useState(''); const TEXT = 'Pronto pra dar o\nprimeiro passo?'; React.useEffect(() => { if (!ref.current) return; const obs = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { setStarted(true); obs.disconnect(); } }, { threshold: 0.4 }); obs.observe(ref.current); return () => obs.disconnect(); }, []); React.useEffect(() => { if (!started || typed.length >= TEXT.length) return; const t = setTimeout(() => setTyped(TEXT.slice(0, typed.length + 1)), 60); return () => clearTimeout(t); }, [started, typed]); const done = typed.length >= TEXT.length; const parts = typed.split('\n'); // Show the icons only after "vamos conversar" has had time to appear. const [showButtons, setShowButtons] = React.useState(false); React.useEffect(() => { if (!done) return; const t = setTimeout(() => setShowButtons(true), 500); return () => clearTimeout(t); }, [done]); return (

{parts.map((p, i) => ( {i > 0 &&
} {p}
))}

{/* Bouncing scroll-style indicator */}
vamos conversar
); }; window.CTABlock = CTABlock; const V23Etapas = ({ eyebrow = '◇ Software house brasileira', headline, sub = 'Sites, apps, sistemas, automações e IA pra empresas que precisam de algo no ar, não no PowerPoint.', logo, footerLogo, navLinks, navCta, showCases = true, showServices = true, showSecondaryCTA = true, showWhatsApp = false, showGrid = true, typingCTA = false, autoSnapToCTA = false, fullScreenCTA = false, showHeroScrollHint = false, width = 1440, minHeight = 2400, } = {}) => { const heroRef = React.useRef(null); const ctaRef = React.useRef(null); const [showHint, setShowHint] = React.useState(false); // Show "role para continuar" hint 1s after hero enters view. React.useEffect(() => { if (!showHeroScrollHint) return; const hero = heroRef.current; if (!hero) return; let timer = null; const obs = new IntersectionObserver(([entry]) => { if (entry.isIntersecting && !timer) { timer = setTimeout(() => setShowHint(true), 1000); } }, { threshold: 0.5 }); obs.observe(hero); return () => { obs.disconnect(); if (timer) clearTimeout(timer); }; }, [showHeroScrollHint]); const scrollToCTA = (e) => { if (e) e.preventDefault(); ctaRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; // Auto-snap to CTA: when the hero has been in view for 1s, // any further scroll of 20px+ smooth-scrolls to the contact section. React.useEffect(() => { if (!autoSnapToCTA) return; const hero = heroRef.current; if (!hero) return; let timer = null; let armed = false; let triggered = false; let baseline = 0; const onScroll = () => { if (!armed || triggered) return; if (window.scrollY - baseline >= 20) { triggered = true; ctaRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }; const obs = new IntersectionObserver(([entry]) => { if (entry.isIntersecting && !timer && !triggered) { timer = setTimeout(() => { armed = true; baseline = window.scrollY; window.addEventListener('scroll', onScroll, { passive: true }); }, 1000); } }, { threshold: 0.5 }); obs.observe(hero); return () => { obs.disconnect(); if (timer) clearTimeout(timer); window.removeEventListener('scroll', onScroll); }; }, [autoSnapToCTA]); const defaultHeadline = ( <> Sem promessa
de revolução.
Só software que funciona. ); const steps = [ { n: 1, t: 'Conversa inicial', d: 'Conta seu desafio sem precisar de termo técnico', status: 'done' }, { n: 2, t: 'Proposta', d: 'Escopo, prazo e investimento em até 24h', status: 'done' }, { n: 3, t: 'Design', d: 'Você aprova antes da gente começar a construir', status: 'active' }, { n: 4, t: 'Construção', d: 'Acompanhamento semanal, sem caixa preta', status: 'todo' }, { n: 5, t: 'Entrega', d: 'No ar, com suporte e ajustes inclusos', status: 'todo' }, ]; return ( {/* Hero — split */}
{eyebrow}

{headline || defaultHeadline}

{sub}

Começar projeto {showSecondaryCTA && ( Falar com o time )}
{/* Terminal — etapas */}
{/* progress bar */}
Progresso 2 de 5 etapas
{steps.map((s, i) => ( ))}
{/* steps */}
{steps.map((s, i) => (
{s.status === 'done' ? '✓' : s.n}
{s.t} {s.status === 'active' && ( EM ANDAMENTO )}
{s.d}
))}
{/* Scroll hint — appears 1s after hero enters view */} {showHeroScrollHint && (
role para continuar
)}
{/* Services */} {showServices && (
{SERVICES_SHORT.map((s, i) => ( {s} ))}
)} {/* Cases */} {showCases && (

Projetos no ar.

{CASES.map((c, i) => (
{c.client}
{c.work}
))}
)} {/* CTA — full-screen wraps contact + footer so they fit in one viewport */} {fullScreenCTA ? (
{typingCTA ? ( ) : ( <>

Pronto pra dar o
primeiro passo?

)}
) : ( <>
{typingCTA ? ( ) : ( <>

Pronto pra dar o
primeiro passo?

)}
)}
); }; window.V23Etapas = V23Etapas;