9.7 KiB
Impl: Seção de vídeo da história na home de campanha (S8)
Status: em execução Atualizado em: 2026-08-19 Issue: #91 Intenção: docs/plans/secao-video-historia-home.md Appetite restante: herdado (~1 dia eng)
Divergência de produto aprovada pelo humano (2026-08-19)
Embed do YouTube no lugar de self-hosted. O plano de intenção previa asset próprio
(WebM/AV1 + MP4/H.264 em public/videos/, lazy total, poster extraído/fornecido). Durante a
execução o humano constatou que o vídeo já está publicado no canal do deputado
(https://www.youtube.com/shorts/i_fbclWWC5o — "18 de agosto de 2026", confirmado pelo dono do
produto como o vídeo da história; a assessoria usa títulos datados) e decidiu embutir o
YouTube, divergindo do aceite "sem dependência de terceiros". Motivação do produto: cada play
embutido conta como view no canal (alimenta o algoritmo; o canal já é vitrine na seção
"Acompanhe de perto"). Aceite atualizado:
- Seção com o vídeo da história via iframe
youtube-nocookie(LGPD),playsinline,loading="lazy"nativo (iframe só é buscado perto do viewport), frame 9:16,allowFullScreen, título a11y. - Deixa de valer: lazy-until-click total, poster próprio, chip de duração, orçamento de
~15 MB de assets no repo (nada é commitado; os encodes one-off ficam em
data/encode/, gitignored, como artefato local caso se volte a self-hosted). - Mantém-se: posição após "Bandeiras", cenas desktop (2 colunas) e mobile (empilhado) do rascunho, CTA "Assistir à história" (agora rola até o player), zero schema/Consent, seções existentes intactas.
Leitura da intenção
Outcome e aceite da intenção (docs/plans/secao-video-historia-home.md), com a divergência
aprovada abaixo: seção com o vídeo da história na home pública, posição após "Bandeiras",
cenas desktop/mobile do rascunho aprovado, zero schema/migration/Consent, seções existentes
intactas, original de 236 MB fora do repo. O que permanece NÃO negociável: copy/pt-BR e
identificadores em inglês; lazy (nada pesado baixa sem o usuário pedir — o loading="lazy" do
iframe cobre isso); sem autoplay forçado; seções existentes intocadas.
Abordagem recomendada
flowchart LR
YT["YouTube Short i_fbclWWC5o (canal do Solla)"] --> EMB["iframe youtube-nocookie (9:16, lazy, playsinline)"]
EMB --> SEC["CampaignStorySection (client island)"]
SEC --> HOME["src/app/(frontend)/(home)/page.tsx — após seção bandeiras"]
Opções consideradas: A) iframe do YouTube (decisão do humano) | B) <video> nativo
self-hosted com WebM/AV1 + MP4/H.264 (plano original)
Recomendação (humano): A — o vídeo já vive no canal; embed soma views ao canal e evita
15 MB de asset no repo; troca de vídeo sem deploy.
Rejeitadas (pelo humano, sobre o plano aprovado): B — dependência própria do aceite; os
artefatos de encode já produzidos (14.9 MB total, validados por design-vision) ficam em
data/encode/ como fallback local.
Decisões de engenharia
- Embed (decisão do humano, supersede o formato/encode do plano original). iframe
youtube-nocookie.com/embed/i_fbclWWC5o(privacy LGPD) complaysinline=1&rel=0, 9:16,loading="lazy"(busca só perto do viewport),allow(autoplay, fullscreen, gyroscope…),allowFullScreen,titlea11y. Rejeitadas:youtube.com/embed(versão com cookies), o padrão de overlay próprio + click (não dá para disparar play cross-origin sem o IFrame API — duplo clique no iOS, fragilidade sem ganho). - Formato/encode (histórico — não entra). A conversão one-off foi executada e validada
(MP4 H.264 720×1280 CRF 27 + WebM AV1 810×1440 CRF 39, 14.9 MB total, poster AVIF da foto
JOA00327 escolhida pelo humano) e fica como artefato local em
data/encode/(gitignored) — fallback pronto se um dia se voltar a self-hosted. Nada disso é commitado. - Posição na home. Opções: A) após conteúdos | B) após "Bandeiras" (fim da página, antes do footer) | C) página própria. Recomendação: B (da intenção) — zona de conversão acima da dobra intacta, fechamento emocional no fim, semente da seção 6 "Quem é Jorge Solla" do plano do site. Rejeitadas: A porque compete com o bloco do problema no funil; C porque estoura o appetite de "uma seção na home".
- CTA "Assistir à história". Botão que rola (smooth) até o player — play cross-origin não é disparável de fora do iframe; o visitante chega no frame com o player do YouTube à mostra.
- Estilos. Seguir o padrão atual
CampaignContentSection(containermax-w-[1160px],px-5 sm:px-8 lg:px-10 py-12 lg:py-16, tokens--campaign-band/--campaign-line/--campaign-muted/--pt-red) — seção em fluxo, não o padrão absoluto deproblem/flags. Fundo branco (contraste entre--campaign-bandde bandeiras e o footer#180a09).
Componentes / mudanças
CampaignStorySection(src/components/CampaignStorySection.tsx): client island ('use client', precedenteCampaignCarousel) — copy (eyebrow/título/copy do rascunho), frame 9:16 com iframe do YouTube (nocookie, lazy, playsinline); CTA rola até o player.data-home-section="story".page.tsx(src/app/(frontend)/(home)/page.tsx): inserir<CampaignStorySection />após a seçãobandeiras, antes do footer. Nenhuma seção existente é tocada.- Assets: nenhum commitado (o vídeo vive no YouTube; encodes em
data/encode/gitignored). - Migration: sem migration (zero schema).
- Access / Consent: N/A — conteúdo estático público.
- UI: Impeccable B — seguir o rascunho aprovado (desktop 2 colunas / mobile empilhado); shape → craft → critique → polish contra o rascunho; verificação mobile 320–430px (o frame 9:16 full-width fica alto — checar folga vertical e sem overflow horizontal).
Dados → forma (se aplicável)
- N/A: sem métrica/KPI. Nenhum dado estático (o player do YouTube traz o próprio thumbnail e duração).
Fases verificáveis
Encode (tracer)— executado como fallback antes da divergência: encodes H.264 + AV1 (14.9 MB) + poster AVIF da foto JOA00327 (validados por design-vision), retidos emdata/encode/(gitignored), fora do commit.- UI —
CampaignStorySection(embed) + wiring no page; shape/craft contra o rascunho; crítica visual (design-vision) + polish; e2e novo emfrontend.e2e.spec.ts(seção presente após bandeiras, iframe com src nocookie +loading="lazy"+allowfullscreen, CTA rola até o player, mobile sem overflow; requests do YouTube abortados no teste para o CI ficar hermético). - Gates —
pnpm gate:fast,pnpm format:check,pnpm exec knip,pnpm check:cycles,pnpm test,pnpm build; e2e no CI; push viapnpm push.
Débitos (capture-review-debts — triage autônoma)
- Absorvidos nesta entrega: JSDoc enxuto; e2e sem
toHaveURLredundante; asserts de desktop (2 colunas, centro vertical) +allow/playsinline; plano sem conteúdo morto da era self-hosted;compute-pressurenoallowdo iframe (silencia o console.error do player do YouTube — guard do e2e falha em console.error) + abort global doyoutube-nocookieno fixturee2eTest.ts(hermeticidade CI, mesma filosofia dos stubs de feed). - Diferidos (registrados aqui, não como Issue): (1) rótulo do CTA "Assistir à história"
promete ação que só rola — copy aprovada no gate, alternativa mais honesta ("Ver a história
em vídeo") se a assessoria reabrir o copy; (2) classe do CTA duplica
sectionHeaderLinkClassNamedeCampaignContentSection— consolidar quando S5 (#33) terminar a separação da seção de conteúdos (evitar conflito com trabalho in-progress). - Descartados: Safari <16.4/Firefox <121 ignoram
loading="lazy"em iframe (carrega eager — degradação graciosa, sem ação); artefatos de encode self-hosted emdata/encode/(gitignored, fallback documentado).
Rabbit holes / Não escopo (engenharia)
- Player JS / playlist / tracking de views próprias (o embed já conta views no YouTube) / analytics de playback.
- Overlay próprio + click para "autoplay" do embed (cross-origin; IFrame API é fragilidade sem ganho — o player do YouTube já tem o próprio play).
- Pipeline de transcodificação versionado no repo (conversão one-off local, não commitada).
- Legendas/transcrição (item futuro separado, fora do escopo da intenção).
- Trocar o vídeo por outro Short: mudança de ID no
srcdo iframe (um lugar).
Riscos e mitigação
- Embed depender do YouTube (risco aceito pelo humano): se o Short cair/for bloqueado, a
seção degrada para o quadro cinza com o player vazio. Mitigação: o fallback self-hosted
(artefatos em
data/encode/) fica pronto para reverter em horas. - Short trocado/ID errado → troca de ID no
srcdo iframe (um lugar). - CI/e2e tocando o YouTube →
page.routeabortayoutube-nocookie.comno teste; os asserts são estruturais (src, lazy, allowfullscreen, posição), não de conteúdo do player. - Iframe pesado no mobile →
loading="lazy"nativo (busca só perto do viewport) + seção no fim da página.
Aceite de engenharia
- Aceite de produto (atualizado pelo humano em 2026-08-19): seção com o vídeo da história via embed do YouTube (nocookie, playsinline, lazy, 9:16), posição após Bandeiras, zero schema, seções existentes intactas
- Invariantes AGENTS/engineering-standards (sem schema/Consent/access; i18n pt-BR na UI, identificadores em inglês; sem tocar seções existentes)
- Testes previstos: e2e da seção (estrutura + scroll + mobile) — sem unit (JSX estático; nenhuma lógica de domínio pura a testar)