// landing-page.jsx — página inteira do Comissa (PT-BR) const { useEffect: __useEffect, useRef: __useRef, useState: __useState } = React; // IntersectionObserver reveal — mais presente (distância + leve escala/blur) function Reveal({ children, delay = 0, y = 42 }) { const ref = __useRef(null); const [shown, setShown] = __useState(false); __useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { setShown(true); io.unobserve(el); } }); }, { threshold: 0.12, rootMargin: '0px 0px -10% 0px' }); io.observe(el); return () => io.disconnect(); }, []); return (
{children}
); } // Barra de progresso de scroll (topo da página) function ScrollProgress() { const [w, setW] = __useState(0); __useEffect(() => { let raf = 0; const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { const h = document.documentElement.scrollHeight - window.innerHeight; setW(h > 0 ? Math.min(1, window.scrollY / h) : 0); }); }; window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); return () => { window.removeEventListener('scroll', onScroll); cancelAnimationFrame(raf); }; }, []); return (
); } function SectionHeader({ eyebrow, title, sub, align = 'left' }) { return (
{eyebrow &&
{eyebrow}
}

{title}

{sub &&

{sub}

}
); } // ── Marquee "vida de vendedor" ─────────────────────────────── function VendedorTicker() { const frases = [ 'Bateu a meta? Bebe uma.', 'Vendi! E agora? Já caiu na conta.', 'Cliente sumiu há 3 dias. Vai um WhatsApp?', 'Tua comissão tá ali.', 'Quanto eu ganhei hoje?', 'Hoje é dia de fechar.', 'O time bateu. Gerente paga o churrasco.', 'Sem planilha. Sem confusão.', ]; return (
{[...frases, ...frases, ...frases, ...frases].map((f, i) => ( {f} ))}
); } // ── Strip de logos de clientes ─────────────────────────────── function CustomerStrip() { return (
Vendedores e equipes que vendem todo dia, em todo lugar, usando Comissa.
{CUSTOMERS.map((c) => (
{c.icon && {c.icon}} {c.n}
))}
); } // ── Demo animado ───────────────────────────────────────────── function ProductDemo() { const STEPS = [ { k: 'create', title: 'Lance uma venda em 5 segundos', hint: '⌘K, digita "nova venda", pronto.' }, { k: 'assign', title: 'Comissão calculada na hora', hint: 'Regras por produto, vendedor e meta. Sem planilha.' }, { k: 'move', title: 'Bloqueie tempo pra follow-up', hint: 'Arrasta pra agenda. O Comissa lembra você.' }, { k: 'room', title: 'Veja o time vendendo, ao vivo', hint: 'Quem fechou, quanto, agora.' }, ]; const [step, setStep] = __useState(0); const trilho = __useRef(null); const [manual, setManual] = __useState(false); // As etapas andam com o scroll, não com um cronômetro. // // Com timer de 2,8s a cena trocava sozinha enquanto o visitante ainda lia a // etapa anterior — e trocava também quando ele nem estava olhando. Agora o // trilho tem altura de sobra, o painel fica preso no topo e cada trecho de // rolagem acende uma etapa: descer é o que faz o dia avançar. Clicar numa // etapa continua funcionando e desliga o scroll até o próximo movimento. __useEffect(() => { if (manual) return; const el = trilho.current; if (!el) return; let raf = 0; const calc = () => { const r = el.getBoundingClientRect(); const curso = Math.max(1, r.height - window.innerHeight * 0.92); const p = (-r.top) / curso; const i = Math.floor(p * STEPS.length); setStep(i < 0 ? 0 : i > STEPS.length - 1 ? STEPS.length - 1 : i); }; const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(calc); }; window.addEventListener('scroll', onScroll, { passive: true }); window.addEventListener('resize', onScroll); calc(); return () => { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); cancelAnimationFrame(raf); }; }, [manual, STEPS.length]); // Volta pro controle por scroll no próximo movimento depois de um clique. __useEffect(() => { if (!manual) return; const volta = () => setManual(false); const t = setTimeout(() => window.addEventListener('scroll', volta, { once: true, passive: true }), 1200); return () => { clearTimeout(t); window.removeEventListener('scroll', volta); }; }, [manual]); return (
{/* steps */}
Um dia no Comissa
{STEPS.map((s, idx) => { const active = idx === step; return ( ); })}
); } function DemoStage({ step }) { return (
{['#E05252', '#D97706', '#059669'].map((c, i) => ( ))} comissa.app · painel {{create: 'CMD + K', assign: 'AUTO', move: 'ARRASTAR', room: 'AO VIVO'}[step]}
{step === 'create' && } {step === 'assign' && } {step === 'move' && } {step === 'room' && }
); } // Cena 1 — paleta de comando "nova venda" function Scene_Create() { const target = 'Rolex Submariner · Pedro Marques · R$ 78.000'; const [text, setText] = __useState(''); // A cena não acabava: digitava e ficava com a paleta aberta pra sempre, sem // mostrar o que o Enter produz. Agora ela termina onde importa — a venda // caindo no painel, com a comissão já calculada. const [pronto, setPronto] = __useState(false); __useEffect(() => { setText(''); setPronto(false); let i = 0; const timers = []; const t = setInterval(() => { i++; setText(target.slice(0, i)); if (i >= target.length) { clearInterval(t); timers.push(setTimeout(() => setPronto(true), 620)); } }, 30); return () => { clearInterval(t); timers.forEach(clearTimeout); }; }, []); return (
{[ { i: , l: 'Registrar nova venda', s: 'Em "A negociar" · Maio' }, { i: , l: 'Novo cliente', s: 'no CRM' }, { i: , l: 'Definir meta', s: 'pra maio · R$ 100.000' }, ].map((r, i) => (
{r.i}
{r.l}
{r.s}
{i === 0 && }
))}
{/* O fim da história: a venda no painel e a comissão já feita. */}
Venda registrada e já no seu painel
VND-2415 · Rolex Submariner · R$ 78.000
+R$ 2.340
comissão 3%
); } // Cena 2 — comissão calculada function Scene_Comissao() { return (
COMISSÃO · VND-2415
Rolex Submariner · Pedro M.
{[ { l: 'Valor da venda', v: 'R$ 78.000', s: false }, { l: 'Regra (relógio)', v: '7%', s: false }, { l: 'Bônus meta atingida', v: '+R$ 800', s: false }, ].map((r, i) => (
{r.l} {r.v}
))}
Sua comissão
R$ 6.260
Cai via Pix em até 1 dia útil após o pagamento
); } // Cena 3 — agenda / follow-up function Scene_Agenda() { return (
Hoje · qui, 16 mai
{['9', '10', '11', '12', '13', '14', '15'].map((h) => (
{h}h
))}
FOLLOW-UP · 10h11h30
Ligar pro Pedro · proposta Rolex
); } // Cena 4 — time online (ranking ao vivo) function Scene_Time() { const time = [ { n: 'Marina S.', v: 32400, m: 0.86, hue: 14 }, { n: 'João P.', v: 28700, m: 0.72, hue: 30 }, { n: 'Carla O.', v: 19200, m: 0.48, hue: 42 }, { n: 'Bruno T.', v: 14300, m: 0.36, hue: 30 }, ]; return (
AO VIVO · TIME VENDAS · MAIO
{time.map((p, i) => (
#{i + 1} x[0]).join('')} hue={p.hue} size={28} />
{p.n}
R$ {p.v.toLocaleString('pt-BR')}
))}
); } // Background board nas cenas — versão de vendas function FauxBoard({ dim, highlight }) { const rows = [ { id: 'VND-2417', t: 'Curso Trading Pro · Ana Souza', state: 'todo', who: 'JS', tag: 'curso', valor: 'R$ 1.997' }, { id: 'VND-2415', t: 'Rolex Submariner · Pedro Marques', state: 'todo', who: 'MS', tag: 'relogio', valor: 'R$ 78.000' }, { id: 'VND-2412', t: 'Honda Civic · Loja Vista Verde', state: 'doing', who: 'CO', tag: 'carro', valor: 'R$ 142k' }, { id: 'VND-2402', t: 'Apto 2Q · Família Lima', state: 'doing', who: 'FC', tag: 'imovel', valor: 'R$ 580k' }, { id: 'VND-2381', t: 'iPhone 16 Pro · Mariana R.', state: 'review', who: 'JS', tag: 'eletro', valor: 'R$ 9,5k' }, ]; const stateAccent = { todo: '#94A3B4', doing: '#F59E0B', review: '#059669' }; return (
Minhas vendas {rows.length} abertas
{rows.map((r) => { const tag = TAG_COLORS[r.tag] || TAG_COLORS.curso; const isHi = highlight && r.id === highlight; return (
{r.id} {r.t} {r.tag} {r.valor} {isHi && ( )}
); })}
); } // ── Ranking da equipe (era VirtualOffice) ──────────────────── function RankingEquipe() { return (
Equipe

Quem vende mais?
Todo mundo sabe.

Ranking ao vivo, comissão de cada um, progresso pra meta da equipe. A competição saudável que faz o time vender, sem grupo de WhatsApp pra cobrar.

    {[ { i: , l: 'Metas individuais e da equipe, em tempo real' }, { i: , l: 'Comissão de cada vendedor, transparente' }, { i: , l: 'Bônus por meta batida, calculado automágico' }, { i: , l: 'Visão do gerente: pipeline de todo o time' }, ].map((x) => (
  • {x.i} {x.l}
  • ))}
); } function PodiumViz() { const podium = [ { n: 'Marina', ini: 'MS', hue: 14, v: 'R$ 32,4k', m: 86, pos: 1 }, { n: 'João', ini: 'JP', hue: 30, v: 'R$ 28,7k', m: 72, pos: 2 }, { n: 'Carla', ini: 'CO', hue: 42, v: 'R$ 19,2k', m: 48, pos: 3 }, ]; const heights = { 1: 200, 2: 150, 3: 110 }; const order = [2, 1, 3]; // visual order: silver, gold, bronze const barDelay = { 1: 156, 2: 0, 3: 312 }; const avatarDelay = { 1: 936, 2: 780, 3: 1092 }; const rootRef = __useRef(null); const [play, setPlay] = __useState(false); __useEffect(() => { const el = rootRef.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { setPlay(true); io.unobserve(el); } }); }, { threshold: 0.25 }); io.observe(el); return () => io.disconnect(); }, []); return (
Ranking · Maio
AO VIVO
{order.map((pos) => { const p = podium.find(x => x.pos === pos); const isFirst = pos === 1; const fallStart = -360; return (
{isFirst && ( )}
{p.n}
{p.v}
{pos}
{p.m}% META
); })}
); } // ── Feature grid (em PT, niche de vendas) ──────────────────── function FeatureGrid() { const cells = [ { title: 'Comissão inteligente', sub: 'Regras por produto, vendedor, faixa de meta e bônus. O Comissa calcula tudo na hora. Você só confere o número final.', icon: , preview: , span: 2, }, { title: 'Lembretes que não falham', sub: 'Cliente sumiu, follow-up agendado, comissão a cair. O Comissa cutuca você no momento certo.', icon: , preview: , span: 1, }, { title: 'Integrações que importam', sub: 'Pix, Hotmart, Kiwify, Mercado Pago, Asaas, Pagar.me. Pluga e pronto.', icon: , preview: , span: 1, }, { title: 'Relatórios ao vivo', sub: 'Gráficos, exportação pra PDF, e os números que importam pro fechamento do mês.', icon: , preview: , span: 2, }, ]; return (
{cells.map((c) => (
{c.icon}

{c.title}

{c.sub}

{c.preview}
))}
); } function ComissaoPreview() { return (
regra{' '} relógio{' '} = 7% + bônus meta > 80% = + R$ 800
{/* mini barra dividida composição da comissão */}
Comissão deste mês R$ 12.840
{[ { c: '#2563EB', w: 48, l: 'Relógio' }, { c: '#38BDF8', w: 26, l: 'Curso' }, { c: '#F59E0B', w: 14, l: 'Carro' }, { c: '#059669', w: 8, l: 'Bônus' }, { c: '#E5E7EB', w: 4, l: 'Outros' }, ].map((b) => (
))}
{[ { c: '#2563EB', l: 'Relógio · R$ 6.240' }, { c: '#38BDF8', l: 'Curso · R$ 3.340' }, { c: '#F59E0B', l: 'Carro · R$ 1.800' }, { c: '#059669', l: 'Bônus · R$ 980' }, { c: '#E5E7EB', l: 'Outros · R$ 480' }, ].map((b) => ( {b.l} ))}
); } function LembretesPreview() { const itens = [ { icon: , tone: '#25D366', bg: 'rgba(37,211,102,0.10)', t: 'Ana Souza sumiu há 3 dias', s: 'Mandar Whats lembrando do orçamento.', when: 'agora', }, { icon: , tone: '#F59E0B', bg: 'rgba(245,158,11,0.12)', t: 'Reunião com Pedro · Rolex', s: 'Confirmar entrega amanhã, 14:00.', when: '1 d', }, { icon: , tone: '#059669', bg: 'rgba(5,150,105,0.12)', t: 'Comissão de R$ 5.460', s: 'Cai no Pix em 2 dias úteis.', when: '2 d', }, ]; return (
{itens.map((x, i) => (
{x.icon}
{x.t}
{x.s}
{x.when}
))}
); } function IntegrationsPreview() { const integrations = [ { n: 'Pix', b: 'pix' }, { n: 'Mercado Pago', b: 'mercadopago' }, { n: 'Hotmart', b: 'hotmart' }, { n: 'Kiwify', b: 'kiwify' }, { n: 'Asaas', b: 'asaas' }, ]; return (
{integrations.map((x) => (
{x.n}
))}
); } function RelatoriosPreview() { // mini chart de vendas por dia (cada barra clicável) const data = [ { d: 1, h: 22, v: 2400, n: 1 }, { d: 2, h: 38, v: 4180, n: 2 }, { d: 3, h: 28, v: 3080, n: 2 }, { d: 4, h: 54, v: 5940, n: 3 }, { d: 5, h: 46, v: 5060, n: 2 }, { d: 6, h: 62, v: 6820, n: 4 }, { d: 7, h: 40, v: 4400, n: 2 }, { d: 8, h: 70, v: 7700, n: 4 }, { d: 9, h: 58, v: 6380, n: 3 }, { d: 10, h: 88, v: 9680, n: 5 }, { d: 11, h: 76, v: 8360, n: 4 }, { d: 12, h: 92, v: 10120, n: 6 }, { d: 13, h: 80, v: 8800, n: 5 }, ]; const [sel, setSel] = __useState(data.length - 1); // último dia selecionado por padrão const [hover, setHover] = __useState(null); const active = hover != null ? hover : sel; const cur = data[active]; return (
Vendas por dia · maio
R$ 78,4k ↑ +24% vs abr
{data.map((b, i) => { const isActive = i === active; const isSel = i === sel; return (
{/* tooltip sobre a barra ativa */} {cur && (
Dia {cur.d} · R$ {cur.v.toLocaleString('pt-BR')}
)}
{['1', '4', '7', '10', '13', '16'].map((d) => ( {d} ))}
{/* detalhe da barra clicada */}
{`${String(cur.d).padStart(2,'0')} · MAIO`}
R$ {cur.v.toLocaleString('pt-BR')}
{cur.n} vendas
+R$ {Math.round(cur.v * 0.07).toLocaleString('pt-BR')} comissão
); } // ── Depoimentos ────────────────────────────────────────────── function Quotes() { const quotes = [ { q: 'Larguei a planilha no primeiro dia. Minha comissão tava sempre uns R$ 500 errada. Agora bate certinho.', n: 'Patrícia Almeida', r: 'Vendedora de imóveis · Habita+', hue: 8, }, { q: 'Os atalhos são obscenamente bons. Lanço uma venda mais rápido do que respondo no WhatsApp.', n: 'João Vitor Souza', r: 'Vendedor de carros · Velocittà', hue: 30, }, { q: 'A gente vende curso, e o Comissa puxa do Hotmart direto. Comissão dos meus afiliados sai no mesmo dia.', n: 'Eliana Ferreira', r: 'Infoprodutora · Mestria', hue: 30, }, ]; return (
{quotes.map((q, i) => (
{[0,1,2,3,4].map((s) => ( ))}
“{q.q}”
x[0]).join('')} hue={q.hue} size={32} />
{q.n}
{q.r}
))}
); } // ── Tabela de preços compacta ──────────────────────────────── function PricingMini() { // Os planos aqui espelham os do app (i18n.js → plan_*). Mudou lá, muda aqui. const plans = [ { n: 'Comissa Base', p: 'Grátis', s: 'por 14 dias', d: 'Pra começar sem custo', f: ['Até 3 links de pagamento rastreáveis', 'Até 50 cadastros de compradores', 'Até 4 produtos cadastrados', 'Dashboard de comissões (mês atual)'], cta: 'Começar grátis', tone: 'ghost', onClick: () => window.openModal('signup'), }, { n: 'Comissa Pro', p: 'R$ 47,90', s: '/mês', d: 'Pra quem vende todo dia', f: ['Até 25 links de pagamento rastreáveis', 'Cadastro de compradores ilimitado', 'Até 15 produtos cadastrados', 'Previsão de recebimento por plataforma', 'Histórico completo de comissões'], cta: 'Testar 14 dias grátis', tone: 'primary', highlight: true, onClick: () => window.openModal('signup'), }, { n: 'Comissa Max', p: 'R$ 97,90', s: '/mês', d: 'Pra times e operações maiores', f: ['Links e produtos ilimitados', 'Alerta de produto parado', 'Exportação CSV/PDF', 'Suporte via WhatsApp', 'Tudo do Comissa Pro'], cta: 'Falar com a gente', tone: 'ghost', onClick: () => window.openModal('contact'), }, ]; return (
{plans.map((p) => (
{p.highlight && ( Mais escolhido )}
{p.n}
{p.p} {p.s && {p.s}}
{p.d}
    {p.f.map((x) => (
  • {/* #A5C9FF é o azul claro pra fundo escuro — sobre o card claro o check sumia. O acento serve os dois casos. */} {x}
  • ))}
))}
); } // ── Depois do fechamento: contrato + parcelas ──────────────── // Duas colunas. À esquerda o contrato caminhando pelos estados reais do // produto (modelo → campos → pendência → enviado); à direita as parcelas de // boleto baixando uma a uma. // // A animação é um passo a cada 2.2s, com `prefers-reduced-motion` respeitado — // quem pediu menos movimento vê o estado final parado, não uma tela quebrada. function PosVendaShowcase() { // Os passos agora carregam o VALOR que entrou em cada um, não só o rótulo da // etapa. "Campos detectados: nome · CPF · CRM" não mostra nada; ver o CRM // sendo preenchido e o contrato travando porque faltou é o que explica o // produto. Os placeholders saem do próprio modelo da empresa. const PASSOS = [ { l: 'Seu modelo, do seu jeito', d: 'contrato-clinica.docx · fonte e cláusulas intactas' }, { l: 'Placeholders encontrados', d: '{{nome}} {{cpf}} {{crm}} {{valor}} {{data}}' }, { l: 'Faltou o CRM da cliente', d: 'o envio trava até você completar', alerta: true }, { l: 'Preenchido e assinado', d: 'Ana Beatriz · CRM 118.442 · há 2 min', ok: true }, ]; // A lista sozinha (paga/atrasada/aberta) não dizia o que o Comissa FAZ, que // era a reclamação. Cada linha passa a carregar a consequência, e a comissão // aparece por parcela: é ela que o vendedor quer saber, não o status do // boleto. `proxima` marca o cursor de próxima em aberto, que é o que a aba // Parcelas usa de verdade no app. // As SEIS parcelas, não quatro. O card dizia "6x" e listava 4: quem lê fica // com a impressão de que o resto não existe ou que o Comissa perde o fio no // meio do plano — que é justo o medo de quem vende parcelado. const PARCELAS = [ { n: 1, st: 'paga' }, { n: 2, st: 'paga' }, { n: 3, st: 'atrasada' }, { n: 4, st: 'aberta' }, { n: 5, st: 'aberta' }, { n: 6, st: 'aberta' }, ]; // Três frases, não quatro parágrafos. O card das parcelas já mostra o // mecanismo; aqui ficam só as respostas que ele não consegue dar sozinho. const COMO_FUNCIONA = [ { t: 'A comissão segue o dinheiro', d: 'Boleto pago, R$ 75 liberados. Nada de comissão cheia na assinatura pra ser estornada se o cliente parar no meio.', }, { t: 'A baixa é automática', d: 'Quem avisa o pagamento é a plataforma de cobrança. Reenvio de webhook não paga duas vezes nem pula parcela.', }, { t: 'Atrasou, fica à vista', d: 'A parcela vencida acende em vermelho, a cobrança é reenviada e a comissão dela volta no dia em que o boleto é pago.', }, ]; const [passo, setPasso] = __useState(0); __useEffect(() => { if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { setPasso(PASSOS.length - 1); return; } const t = setInterval(() => setPasso((v) => (v + 1) % PASSOS.length), 2200); return () => clearInterval(t); }, []); const card = { background: 'var(--surface)', border: '0.5px solid var(--line)', borderRadius: 18, padding: 24, minHeight: 300, }; const eyebrow = { fontSize: 10.5, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--muted-2)', marginBottom: 18, }; return (
{/* ── Contrato ── */}
Contrato
{PASSOS.map((p, i) => { const feito = i <= passo; const atual = i === passo; const cor = p.alerta ? '#D97706' : p.ok ? '#059669' : 'var(--indigo)'; return (
{/* trilho + marcador */}
{feito && } {i < PASSOS.length - 1 && ( )}
{p.l}
{p.d}
); })}
O Comissa não escreve contrato. Ele lê os {'{{campos}}'} do seu modelo, puxa os dados do cliente que já estão na venda e devolve o seu documento pronto pra assinar. Campo obrigatório em branco segura o envio, em vez de mandar contrato furado pro cliente.
{/* ── Parcelas ── Era uma lista de seis linhas com data, status, nota e valor — quatro informações por parcela, 24 no total, pra contar uma história de uma frase. Agora são seis quadradinhos: verde pago, vermelho atrasado, vazio a vencer. Dá pra entender o plano inteiro num relance, e o que o vendedor quer saber (quanto já é dele) está logo abaixo em três números. */}
Parcelas do boleto
Venda de R$ 4.500 em 6x, comissão de 10%. Cada boleto pago libera R$ 75 pra você.
{PARCELAS.map((p) => { const paga = p.st === 'paga'; const atrasada = p.st === 'atrasada'; return (
{p.n}/6
{paga ? '+R$ 75' : atrasada ? 'retida' : 'R$ 75'}
{paga ? 'paga' : atrasada ? 'atrasada' : 'a vencer'}
); })}
{[ { l: 'já é seu', v: 'R$ 150', c: 'var(--green-text)' }, { l: 'retido no atraso', v: 'R$ 75', c: 'var(--rose-text)' }, { l: 'a receber', v: 'R$ 225', c: 'var(--indigo-text)' }, ].map((x) => (
{x.v}
{x.l}
))}
Vale pra boleto e Pix parcelado. Cartão parcelado cai inteiro pela plataforma, então ali a comissão sai de uma vez.
{/* A explicação saiu de dentro do card e virou faixa própria: era lá que cabiam três bullets curtos, e três bullets curtos não explicam um mecanismo de pagamento. Aqui cabe a resposta pra pergunta que o vendedor realmente faz — "quando esse dinheiro fica meu?". */}
Como a parcela vira comissão

Venda parcelada é comissão parcelada — e você vê cada pedaço chegando.

{COMO_FUNCIONA.map((x, i) => (
{String(i + 1).padStart(2, '0')}
{x.t}

{x.d}

))}
); } // ── CTA final ──────────────────────────────────────────────── function CyclingWord({ words, interval = 2400 }) { const [i, setI] = __useState(0); const [phase, setPhase] = __useState('in'); // 'in' | 'out' __useEffect(() => { const t1 = setTimeout(() => setPhase('out'), interval - 450); const t2 = setTimeout(() => { setI((v) => (v + 1) % words.length); setPhase('in'); }, interval); return () => { clearTimeout(t1); clearTimeout(t2); }; }, [i, interval, words.length]); return ( {words[i]} ); } // Vídeo decorativo que só baixa/toca quando chega perto da viewport — mantém // os ~10 MB fora do carregamento inicial (não deixa o site mais lento) e mostra // um poster nítido enquanto o vídeo ainda não começou (paint instantâneo). function AmbientVideo({ src, poster, style, opacity = 0.32 }) { const ref = __useRef(null); const [load, setLoad] = __useState(false); __useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { setLoad(true); io.disconnect(); } }); }, { rootMargin: '300px 0px' }); io.observe(el); return () => io.disconnect(); }, []); // Toca assim que o primeiro frame estiver pronto — evita o play() falhar por // ainda não ter dados (autoplay mudo é sempre permitido). const handleReady = (e) => { const p = e.currentTarget.play(); if (p && p.catch) p.catch(() => {}); }; return (