plan(S1-S4): planos de intenção — seção de conteúdos na home de campanha #21

Merged
fsolla merged 1 commit from plans/plan-issue-2 into main 2026-08-18 00:47:53 +00:00
9 changed files with 649 additions and 0 deletions

View file

@ -0,0 +1,85 @@
# Compartilhar conteúdos da home no WhatsApp (S4)
Status: registrado
Atualizado em: 2026-08-17
Issue: #19
Priority: P2
Model: composer-2.5
Impeccable: B — botão discreto de compartilhar por card de conteúdo (não compete com o CTA)
Rascunho UI: docs/plans/secao-conteudos-home-ui-draft.html + PNG embutido abaixo
Appetite: ~0,5–1 dia eng; botão por card + wa.me com mensagem pré-configurada + copiar link
Responsável: —
## Intenção
Depois que a seção de conteúdos da home estiver funcional (S1+S2+S3), cada card de conteúdo vira compartilhável no WhatsApp — o canal nº 1 de compartilhamento no Brasil. O visitante manda o artigo/vídeo/post para alguém com uma mensagem pronta (título + link), sem sair da home. É a fatia de viralidade do board: conteúdo circulando de volta para a campanha na reta final.
## Persona e fluxo
- **Persona / contexto:** visitante (eleitor, liderança, simpatizante) no meio de funil que achou um conteúdo bom e quer mandar para o grupo/família.
- **Job principal:** repassar um conteúdo específico da home para alguém no WhatsApp com um clique, com texto pronto.
- **Fluxo desejado:** vê o card → toca no botão de compartilhar (ícone discreto no canto do card) → opção "Compartilhar no WhatsApp" (abre `wa.me` com mensagem pré-configurada contendo título + link do conteúdo) e/ou "Copiar link" → cola onde quiser.
- **Anti-goals de produto:** o botão de compartilhar NUNCA compete com o CTA primário da página (botão discreto, não é o foco do card); não é compartilhamento por seção inteira (v1 é por card); sem WhatsApp Business API (é `wa.me` do próprio remetente).
### Esboço de fluxo (B)
```text
[card de conteúdo] → [botão compartilhar (ícone)] → [menu: WhatsApp / Copiar link]
→ [wa.me com mensagem "Olha isso do Solla: <título> — <link>"] → [remetente envia pelo próprio WhatsApp]
```
### Rascunho UI (B)
![Rascunho UI — compartilhar](secao-conteudos-home-ui-draft-compartilhar.png)
Fonte iterável: [`secao-conteudos-home-ui-draft.html`](secao-conteudos-home-ui-draft.html) (cena `compartilhar`).
## Objetivo e aceite
- Todo card de conteúdo da seção (Artigo/YouTube/Instagram) tem um botão discreto de compartilhar (canto do card, sem competir com o CTA primário da página).
- Ação principal: abre `wa.me` com mensagem pré-configurada **por fonte** (decisão do cliente): artigo → "Olha isso do Solla: {título} — {link}" · vídeo → "Olha esse vídeo do Solla: {título} — {link}" · post IG → "Olha esse post do Solla: {título} — {link}". Link: artigo → página canônica do artigo; YT/IG → URL da plataforma. Nova aba com `noopener`.
- Ação secundária: "Copiar link" (navegador/clipboard) com feedback de "copiado".
- O botão é acessível (aria-label, alvo de toque ≥44px no mobile) e não muda o layout do card. No mobile, o botão vive no card único do carrossel (1 conteúdo por tela — decisão do cliente); no desktop, no canto de cada card do bento.
- Sem migration/schema/Consent — o fluxo é montagem de URL no cliente, sem escrita no servidor.
- Mensagem padrão honesta (ex.: "Olha isso do Solla: {título} — {link}") — sem texto enganoso.
## Dados (intenção)
- **Vou apresentar dados?** Não — só a URL compartilhada. (Se a assessoria quiser medir circulação, UTM por card é um item futuro separado.)
- **Decisões desbloqueadas:** o visitante decide compartilhar; nenhum dado é coletado por este fluxo.
## Direção no codebase (hipótese)
- **Áreas prováveis:** componente client pequeno no card de conteúdo da seção S1/S2/S3 (`src/components/`), reusando `buildWhatsAppUrl` (`src/utilities/phone.ts`) e o padrão de copiar link já existente (ex.: `ShareNucleusDialog`/`campaignListShare` — ver `docs/plans/compartilhar-pagina.md` como precedente de wa.me + copiar).
- **Precedente a olhar:** `compartilhar-pagina.md` (padrão `wa.me` + copiar link + `noopener`), `src/utilities/phone.ts` (`buildWhatsAppUrl`), plano-site §7.1 ("Compartilhamento por seção" — ideia do cliente).
- **Risco de acoplamento:** o botão vive no card generalizado da seção — implementar no ponto único de renderização do card (não em três lugares por plataforma).
## Dependências
- **S3** (seção completa: artigos + YouTube + Instagram) — dura (conteúdos funcionais antes de compartilhar).
## Fora de escopo
- Compartilhar por outros canais (e-mail, X, threads) — só WhatsApp + copiar link no v1.
- UTM por seção/card e medição de circulação (item futuro — ideia §7.1 do plano-site).
- Imagem OG por seção/card (geração de card de preview) — item futuro (§7.1).
- WhatsApp Business API / disparo em massa (vedado pela Res. TSE 23.610; é `wa.me` do próprio remetente).
## Rabbit holes de produto
- **Botão de compartilhar competindo com o CTA.** Se alguém "só completar": dois pesos visuais por card. **Corte:** ícone discreto no canto, sem destaque de cor de marca.
- **Compartilhar a seção inteira.** Se alguém "só completar": mensagem gigante com N links. **Corte:** v1 é por card, um link por mensagem.
- **Telemetria disfarçada de compartilhar.** Se alguém "só completar": vira rastreador de clique. **Corte:** nada é coletado neste item; UTM fica para item futuro explícito.
## Questões em aberto (produto)
- **Mensagem padrão por fonte?** **Opções:** A) template único "Olha isso do Solla: {título} — {link}" | B) específica por fonte (artigo/vídeo/post). **Recomendação:** B — decisão do cliente nesta sessão. _(decidido — B)_
## Referências
- GitHub Issue #19
- Rascunho UI (gate): `docs/plans/secao-conteudos-home-ui-draft.html` (cena `compartilhar`) + PNG acima
- `docs/campanha/plano-site-campanha-2026.md` §7.1 — "Compartilhamento por seção" (ideia do cliente registrada)
- `docs/plans/compartilhar-pagina.md` — precedente de wa.me + copiar link no repo
- `src/utilities/phone.ts` — `buildWhatsAppUrl`
- `docs/plans/secao-conteudos-home-artigos.md` (S1) — estrutura do card onde o botão entra

View file

@ -0,0 +1,93 @@
# Seção de conteúdos na home de campanha — Artigos (S1)
Status: registrado
Atualizado em: 2026-08-17
Issue: #17
Priority: P1
Model: composer-2.5
Impeccable: B — nova seção na home pública (3ª posição), padrão visual do wireframe já aprovado
Rascunho UI: docs/plans/secao-conteudos-home-ui-draft.html + PNGs embutidos abaixo
Appetite: ~1 dia eng; seção com cards de artigos reusando o sistema `post` existente
Responsável: —
## Intenção
A home pública de campanha (jorgesolla1313.com.br) não tem nenhuma zona de conteúdo atualizado: ela é estática entre o hero e o CTA. Na reta final (eleição 04/10), as coisas acontecem rápido e o site precisa mostrar atividade. O wireframe já tem a seção "Acompanhe de perto" (board de conteúdo) aprovada — ela só não foi implementada. Este item é a primeira fatia da seção, com **artigos** do site (a fonte que já existe no Teqo); YouTube e Instagram entram nas fatias seguintes (S2/S3), em série, para a estrutura não divergir.
## Persona e fluxo
- **Persona / contexto:** visitante da home de campanha (eleitor, liderança, imprensa) — celular na maioria dos casos, chegou pelo WhatsApp/redes, no meio de funil.
- **Job principal:** ver que a campanha está viva agora (prova de atividade) e ler um conteúdo recente sem sair do clima da página.
- **Fluxo desejado:** rola a home → encontra a seção (3ª, logo antes de "Por que essa eleição importa") → vê cards de artigos recentes com badge "Artigo" → clica e vai para a página do artigo (rota canônica do post) → pode voltar e continuar na home.
- **Anti-goals de produto:** a seção NÃO vira um feed infinito; NÃO compete com o CTA primário; NÃO mostra conteúdo invisível/oculto (mecanismo `hidden`/`isPostVisible` segue valendo — fail-closed).
### Esboço de fluxo (B)
```text
[home: hero → prova social] → [SEÇÃO: eyebrow + título + cards artigos] → [clique no card]
→ [página do artigo: /[type]/[category]/[slug]] → [voltar à home]
```
### Rascunho UI (B)
![Rascunho UI — desktop](secao-conteudos-home-ui-draft-desktop.png)
![Rascunho UI — mobile](secao-conteudos-home-ui-draft-mobile.png)
Fonte iterável: [`secao-conteudos-home-ui-draft.html`](secao-conteudos-home-ui-draft.html). Nesta fatia S1, o bento mostra só cards "Artigo" (a cena `desktop` já ilustra a posição final com as 3 fontes para referência).
## Objetivo e aceite
- A home pública exibe uma nova seção na 3ª posição (entre a prova social e "Por que essa eleição importa"), com eyebrow/título/subtitulo e links de acompanhar conforme o wireframe.
- A seção lista artigos recentes visíveis do sistema `post` existente (mesma regra de visibilidade do site: `isPostVisible` fail-closed — conteúdo oculto por tag `hidden` não aparece).
- Cada card mostra: badge "Artigo", imagem de capa, título e meta (data/categoria); o card inteiro é um link para a página canônica do artigo (`getPostCanonicalPath`), abrindo na mesma aba.
- Layout: bento 1 card grande + 4 pequenos no desktop; **no mobile (~390px) a seção vira um carrossel de 1 conteúdo por tela** (desliza entre os cards, com indicador de página), sem quebrar o fluxo da página.
- Se não houver artigos visíveis: a seção inteira é ocultada (nada de zona morta), mantendo Prova → "Por que essa eleição importa" (cena `vazio` do rascunho).
- Zero mudança em schema/migration/Consent: artigos leem o `post` collection com os helpers de cache existentes (`getVisiblePosts` / tag `posts`).
## Dados (intenção)
- **Vou apresentar dados?** Sim, superfície neste item — mas são **conteúdos** (artigos), não métricas.
- **Decisões desbloqueadas:** o visitante decide qual artigo ler (clique); nenhum número novo de campanha é inventado aqui.
- **Forma:** *adiada ao plano de implementação* — restrição de produto: meta = data/categoria do post (já exibidas no site), sem contadores inventados.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `src/app/(frontend)/(home)/page.tsx` (inserir a seção entre prova e problema), novo componente de seção/card em `src/components/`, estilos no bloco `[data-theme='campaign-site']` de `src/app/(frontend)/styles.css` (padrão das seções atuais), leitura via `src/utilities/posts.ts` (`getVisiblePosts`, `getPostCanonicalPath`, `formatPostDate`).
- **Precedente a olhar:** wireframe `docs/campanha/wireframe-solla-1313.html` (seção 7, board IG+YT — o design aprovado a adaptar para artigos); seções atuais da home (`campaign-problem`, `campaign-flags`) para o ritmo visual; `src/components/PostCard.tsx` e a rota `/artigos` para o formato de card de post existente.
- **Risco de acoplamento:** a seção respeita o mecanismo eleitoral `hidden`/`isPostVisible` (nada de bypass); o cache público é o tag `posts` existente (revalidação já cobre); não duplicar o conceito de "pessoa" nem criar collection nova.
## Dependências
- Nenhuma (primeira fatia). S2 (YouTube) e S3 (Instagram) dependem desta.
- Soft: `docs/campanha/plano-site-campanha-2026.md` — decisões de produto do site (board §4.2, posição discutida na §3).
## Fora de escopo
- YouTube e Instagram na seção → S2 e S3 (deste lote).
- Compartilhamento WhatsApp → S4 (issue separada).
- Player de vídeo inline, lightbox, qualquer embed de rede no load (decisão do plano-site: clique abre na plataforma; sem iframe no carregamento).
- Curadoria manual do feed (é automático pelo mais recente), configuração no admin, "pausar feed" (kill switch) → S2/S3.
- Redesign da home, alteração de CTA primário, outras seções do wireframe.
## Rabbit holes de produto
- **Feed infinito / paginação.** Se alguém "só completar": a seção vira uma lista sem fim competindo com o CTA. **Corte neste item:** bento fixo (1 + 4), sem paginação; link "Ver artigos →" aponta para a rota `/artigos` existente.
- **Segunda fonte de "artigos" paralela ao `post`.** Se alguém "só completar": duplica o sistema de notícias. **Corte:** só o `post` collection com a visibilidade pública existente.
- **Vazio honesto.** A seção oculta quando sem conteúdo — não exibe placeholder de "em breve" queimando o meio de funil.
## Questões em aberto (produto)
- **Posição exata da seção?** **Opções:** A) 3ª seção, logo antes de "Por que essa eleição importa" (recomendação — traz conteúdo atualizado pra cima; decisão do cliente nesta sessão) | B) posição 7 do wireframe (após "Quem é Jorge Solla"). **Recomendação:** A. _(decidido — A)_
- **Quais artigos entram no bento?** **Opções:** A) os N mais recentes visíveis, qualquer tipo de post (noticia/campanha/artigo/evento) | B) só `noticia` (igual à rota `/artigos`). **Recomendação:** A — a home de campanha agrega tudo que estiver visível; o filtro por tipo fica barato depois se a assessoria pedir. _(decidido — A)_
- **Mobile: 1 conteúdo por tela?** **Opções:** A) carrossel de 1 card por vez (decisão do cliente nesta sessão) | B) empilhado. **Recomendação:** A. _(decidido — A)_
## Referências
- GitHub Issue #17
- Rascunho UI (gate): `docs/plans/secao-conteudos-home-ui-draft.html` + PNGs acima
- Wireframe aprovado: `docs/campanha/wireframe-solla-1313.html` (seção 7 "Acompanhe de perto")
- Plano geral do site: `docs/campanha/plano-site-campanha-2026.md` (§4.2 engenharia do board, §3 posições)
- `src/utilities/posts.ts` — visibilidade/cache/rotas canônicas de artigos
- `src/app/(frontend)/(home)/page.tsx` — onde a seção entra
- `AGENTS.md` — Posts & Tags (visibilidade `hidden` fail-closed, cache `posts`)

View file

@ -0,0 +1,93 @@
# Seção de conteúdos na home de campanha — Instagram (S3)
Status: registrado
Atualizado em: 2026-08-17
Issue: #20
Priority: P1
Model: cursor-grok-4.5-medium
Impeccable: B — estende a seção S1/S2 com cards de Instagram (mesma estrutura, badge "Instagram")
Rascunho UI: docs/plans/secao-conteudos-home-ui-draft.html + PNGs embutidos abaixo
Appetite: ~1–2 dias eng; integração de leitura com a Graph API + cards na seção
Responsável: —
## Intenção
A seção "Acompanhe de perto" (S1 artigos + S2 YouTube) ganha os posts do Instagram oficial (`@depjorgesolla`). Instagram é onde a campanha mais posta no dia a dia (bastidores, caravanas, memes, avisos) — é a fonte que dá o ritmo de "atividade agora". A fatia segue o desenho aprovado do plano-site: automático via Instagram Graph API (conta Business/Creator + app Meta), servidor-side, sem script de embed no load; clique abre o post **na plataforma**.
## Persona e fluxo
- **Persona / contexto:** visitante da home de campanha, majoritariamente mobile, que quer ver a campanha pulsando hoje.
- **Job principal:** ver os posts recentes do Instagram sem sair do fluxo da página e abrir o que chamar atenção direto no app/rede.
- **Fluxo desejado:** rola até a seção → vê cards 1:1 com badge "Instagram", legenda e data → clica → abre o post no Instagram (nova aba, `noopener`), mantendo a home aberta.
- **Anti-goals de produto:** nenhum script/embed pesado do Instagram no load (LCP do mobile — decisão explícita); sem lightbox; o board nunca quebra se o token expirar; IG só liga depois do setup da Meta (review leva dias — sequenciar go-live).
### Esboço de fluxo (B)
```text
[seção S1+S2 + cards IG] → [clique no card IG] → [instagram.com/... nova aba] → [volta à home intacta]
Token expirado/API fora → [mantém último snapshot / oculta só os cards IG — nunca quebra a página]
```
### Rascunho UI (B)
![Rascunho UI — desktop](secao-conteudos-home-ui-draft-desktop.png)
![Rascunho UI — mobile](secao-conteudos-home-ui-draft-mobile.png)
Fonte iterável: [`secao-conteudos-home-ui-draft.html`](secao-conteudos-home-ui-draft.html). A cena `desktop` mostra o bento final; nesta fatia S3 entram os cards Instagram (1:1).
## Objetivo e aceite
- A seção passa a incluir os posts mais recentes da conta oficial: cards 1:1 com badge "Instagram", legenda (título) e data; clique abre o post na plataforma (nova aba, `noopener`). **Mobile: carrossel de 1 conteúdo por tela (decisão do cliente).**
- Fonte automática: leitura servidor-side via Instagram Graph API (token long-lived com refresh, perfil Business/Creator) com cache — nenhuma chave no cliente, nenhum embed no load.
- **Exclusão por item (obrigatório — posts de grade):** o perfil do Instagram tem imagens publicadas SÓ para montar o grid do feed (ex.: grade 3×3 em mosaico), que não são conteúdo real de campanha. O admin precisa marcar posts específicos como "não exibir" (por ID da mídia), e eles somem do board — o carrossel/bento pula para o próximo elegível. Sem esse mecanismo, a seção pode mostrar mosaicos sem sentido; é o requisito nº 1 desta fatia.
- Fail-closed: token expirado/API fora/quota → mantém último snapshot em cache; sem snapshot, a seção segue com as outras fontes — a página nunca quebra nem mostra erro.
- **Configuração no admin** conforme plano-site §4.2 (sem trava de schema nesta intenção); **sem credenciais IG configuradas, os cards IG simplesmente não aparecem** (a seção vive com artigos + YouTube).
- Kill switch "pausar feed" do plano-site vale para o board como um todo.
- Zero mudança em schema de conteúdo público; migrações não destrutivas no máximo (config no admin).
## Dados (intenção)
- **Vou apresentar dados?** Sim, superfície neste item — metadados do post (imagem, legenda, data) vindos da plataforma.
- **Decisões desbloqueadas:** o visitante decide qual post abrir; nada de métricas inventadas.
- **Forma:** *adiada ao plano de implementação* — restrição de produto: legenda/data como a plataforma mostra; sem contadores derivados.
## Direção no codebase (hipótese)
- **Áreas prováveis:** mesmo componente de seção/card de S1/S2 (`src/components/`, `src/app/(frontend)/(home)/page.tsx`, bloco `campaign-site` em `styles.css`); loader servidor-side novo com cache (`src/utilities/` ou `src/lib/`); config no admin (global do Payload, grupo `Configurações`).
- **Precedente a olhar:** S1/S2 (estrutura); `docs/campanha/plano-site-campanha-2026.md` §4.2 (Graph API, token long-lived com refresh ~60 dias, kill switch, "IG só liga após setup da Meta"); `src/utilities/posts.ts` (padrão de cache).
- **Risco de acoplamento:** mesma generalização da seção — card de conteúdo único (Artigo/YouTube/Instagram) sem código duplicado por plataforma; o setup da Meta é sequenciamento de go-live (externo), não bloqueia a implementação.
## Dependências
- **S2** (estrutura da seção completa: artigos + YouTube) — dura.
- Soft: `docs/campanha/plano-site-campanha-2026.md` §4.2 + pendência §5 ("mix mandato/campanha no board").
## Fora de escopo
- Compartilhamento WhatsApp → S4 (issue separada).
- Script de embed do Instagram no load (proibido pelo plano-site).
- Curadoria manual além da exclusão por item (etiquetas, ordenação manual, destaques); stories; DMs; estatísticas além do básico do post; outras contas.
- Setup da Meta (app/credenciais) é tarefa da assessoria/produção — a implementação só consome as credenciais quando existirem.
## Rabbit holes de produto
- **Embed no load.** Se alguém "só completar": o script do Instagram derruba o LCP (decisão explícita do plano-site). **Corte:** thumbnail + link para a plataforma.
- **Refresh de token como feature de produto.** Se alguém "só completar": vira um painel de OAuth. **Corte:** token long-lived com refresh automático conforme o plano-site; falha → fail-closed (snapshot/oculta).
- **IG sem credencial = erro.** Se alguém "só completar": a seção mostra erro de configuração. **Corte:** sem credencial, cards IG não existem — nada de aviso.
- **Exclusão vira curadoria pesada.** Se alguém "só completar": lista enorme de IDs, razões obrigatórias, aprovação. **Corte:** marcar/desmarcar item (thumbnail + ID) no admin; sem workflow.
## Questões em aberto (produto)
- **Mix mandato/campanha no board** — mesma questão da S2: **Opções:** A) automático (recomendação — pendência já registrada no plano-site §5, decidir com a assessoria) | B) filtrar por hashtag/tag. **Recomendação:** A. _(decidido — A + exclusão por item)_
- **Exclusão por item: o que aparece para marcar?** **Opções:** A) o admin vê a lista dos últimos posts (thumbnail + ID) e marca os que não devem aparecer (recomendação — sem isso, marcar por ID puro é inviável para a assessoria) | B) só campo de IDs com documentação. **Recomendação:** A — a interface de exclusão é parte desta fatia. _(assumido — validar com produto)_
- **Grade do feed (grid) entra?** **Opções:** A) posts de grade entram no automático e são excluídos um a um pela assessoria quando aparecem (recomendação — simples, transparente) | B) tentar detectar grade por heurística e já ocultar. **Recomendação:** A — heurística de "post de grade" é frágil (o IG não sinaliza); a exclusão manual resolve o caso real. _(assumido — validar com produto)_
## Referências
- GitHub Issue #20
- Rascunho UI (gate): `docs/plans/secao-conteudos-home-ui-draft.html` + PNGs acima
- `docs/campanha/plano-site-campanha-2026.md` §4.2 — decisões do board IG (Graph API, token, kill switch, sem embed) e §5 pendência "mix mandato/campanha"
- Wireframe: `docs/campanha/wireframe-solla-1313.html` (seção 7)
- `docs/plans/secao-conteudos-home-artigos.md` (S1) e `secao-conteudos-home-youtube.md` (S2)
- `AGENTS.md` — padrões de cache, admin em pt, convenções de nome

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -0,0 +1,285 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<title>UI draft — Seção de conteúdos na home de campanha</title>
</head>
<body class="bg-zinc-950 text-zinc-100 antialiased">
<main class="mx-auto max-w-5xl p-8">
<p class="text-sm text-zinc-400">
Visitante da home pública de campanha (jorgesolla1313). Seção 3 da página: logo após a
prova social, imediatamente antes de "Por que essa eleição importa". Card de conteúdo
generalizado (Artigo / YouTube / Instagram) — clique leva ao artigo ou à plataforma.
</p>
<!-- ============ CENA: desktop (estado final, 3 fontes) ============ -->
<section data-shot="desktop" class="mt-8 rounded-2xl bg-white p-6 text-zinc-900 shadow-xl">
<!-- contexto: prova social (barra) -->
<div class="flex items-center justify-center gap-10 rounded-xl bg-zinc-100 py-3">
<span class="text-sm font-bold text-zinc-400">3.333 proposições</span>
<span class="text-sm font-bold text-zinc-400">1.031 discursos</span>
<span class="text-sm font-bold text-zinc-400">3º mandato</span>
</div>
<!-- ===== SEÇÃO DE CONTEÚDOS (posição 3) ===== -->
<div class="mt-6">
<div class="flex flex-wrap items-end justify-between gap-3">
<div>
<p class="text-xs font-extrabold tracking-[0.14em] text-zinc-500 uppercase">
Acompanhe de perto
</p>
<h2 class="mt-1 text-2xl font-extrabold text-zinc-900">
A caminhada, em tempo real
</h2>
<p class="mt-1 text-sm text-zinc-500">
Bastidores, caravanas e as lutas do mandato: conteúdo atualizado, direto das redes.
</p>
</div>
<div class="flex gap-4 text-sm font-bold">
<a href="#" class="text-red-700 underline-offset-4 hover:underline">Ver artigos →</a>
<a href="#" class="text-red-700 underline-offset-4 hover:underline">Seguir no Instagram →</a>
<a href="#" class="text-red-700 underline-offset-4 hover:underline">YouTube →</a>
</div>
</div>
<!-- bento: 1 grande + 4 pequenos -->
<div class="mt-5 grid gap-4 md:grid-cols-4">
<!-- card grande: artigo destaque -->
<a href="#" class="group col-span-2 row-span-2 flex flex-col gap-3 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-[16/9] overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
Artigo
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
capa 16:9 (placeholder)
</span>
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="text-base leading-snug font-extrabold text-zinc-900 group-hover:underline">
Título do artigo em destaque, duas ou três linhas para caber
</h3>
<p class="text-xs leading-relaxed text-zinc-500">
Resumo curto do artigo quando houver, uma linha de contexto.
</p>
<span class="mt-auto text-xs text-zinc-400">há 1 dia · categoria</span>
</div>
</a>
<!-- pequeno: vídeo YouTube -->
<a href="#" class="group flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-video overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
YouTube
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
thumb 16:9
</span>
</div>
<h3 class="text-sm leading-snug font-extrabold text-zinc-900 group-hover:underline">
Título do vídeo recente, até duas linhas
</h3>
<span class="text-xs text-zinc-400">há 2 dias · 12,4 mil visualizações</span>
</a>
<!-- pequeno: vídeo YouTube -->
<a href="#" class="group flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-video overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
YouTube
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
thumb 16:9
</span>
</div>
<h3 class="text-sm leading-snug font-extrabold text-zinc-900 group-hover:underline">
Título do segundo vídeo, até duas linhas
</h3>
<span class="text-xs text-zinc-400">há 5 dias · 8,1 mil visualizações</span>
</a>
<!-- pequeno: Instagram -->
<a href="#" class="group flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-square overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
Instagram
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
post 1:1
</span>
</div>
<h3 class="text-sm leading-snug font-extrabold text-zinc-900 group-hover:underline">
Legenda do post recente, até duas linhas
</h3>
<span class="text-xs text-zinc-400">há 3 dias</span>
</a>
<!-- pequeno: Instagram -->
<a href="#" class="group flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-square overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
Instagram
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
post 1:1
</span>
</div>
<h3 class="text-sm leading-snug font-extrabold text-zinc-900 group-hover:underline">
Legenda do segundo post, até duas linhas
</h3>
<span class="text-xs text-zinc-400">há 6 dias</span>
</a>
</div>
</div>
<!-- ===== FIM DA SEÇÃO DE CONTEÚDOS ===== -->
<!-- contexto: "Por que essa eleição importa" (barra) -->
<div class="mt-6 rounded-xl bg-zinc-900 px-6 py-5 text-white">
<p class="text-xs font-extrabold tracking-[0.14em] text-zinc-400 uppercase">
Por que essa eleição importa
</p>
<p class="mt-1 text-lg font-extrabold">
Eleger deputado é coisa séria. O Congresso tem nas mãos vidas reais e o SUS.
</p>
</div>
</section>
<!-- ============ CENA: mobile (carrossel — 1 conteúdo por tela) ============ -->
<section data-shot="mobile" class="mt-8 w-[390px] rounded-2xl bg-white p-4 text-zinc-900 shadow-xl">
<!-- contexto: prova social -->
<div class="flex items-center justify-center gap-6 rounded-xl bg-zinc-100 py-2.5">
<span class="text-xs font-bold text-zinc-400">3.333 proposições</span>
<span class="text-xs font-bold text-zinc-400">1.031 discursos</span>
<span class="text-xs font-bold text-zinc-400">3º mandato</span>
</div>
<!-- seção -->
<div class="mt-5">
<p class="text-xs font-extrabold tracking-[0.14em] text-zinc-500 uppercase">
Acompanhe de perto
</p>
<h2 class="mt-1 text-xl font-extrabold text-zinc-900">A caminhada, em tempo real</h2>
<p class="mt-1 text-sm text-zinc-500">
Bastidores, caravanas e as lutas do mandato: conteúdo atualizado, direto das redes.
</p>
<!-- carrossel: 1 card por tela + dots -->
<div class="mt-4 overflow-hidden rounded-xl">
<!-- card ativo -->
<a href="#" class="group flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-video overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
YouTube
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
thumb 16:9
</span>
</div>
<h3 class="text-sm leading-snug font-extrabold text-zinc-900 group-hover:underline">
Título do vídeo recente, até duas linhas
</h3>
<span class="text-xs text-zinc-400">há 2 dias · 12,4 mil visualizações</span>
</a>
</div>
<!-- dots de navegação -->
<div class="mt-3 flex items-center justify-center gap-2" aria-label="Página do carrossel">
<span class="h-2 w-2 rounded-full bg-zinc-300"></span>
<span class="h-2 w-2 rounded-full bg-zinc-900"></span>
<span class="h-2 w-2 rounded-full bg-zinc-300"></span>
<span class="h-2 w-2 rounded-full bg-zinc-300"></span>
<span class="h-2 w-2 rounded-full bg-zinc-300"></span>
</div>
<p class="mt-2 text-center text-xs font-semibold text-zinc-500">1 de 5 · deslize para ver os próximos</p>
</div>
</section>
<!-- ============ CENA: card com compartilhar (S4 — WhatsApp) ============ -->
<section data-shot="compartilhar" class="mt-8 rounded-2xl bg-white p-6 text-zinc-900 shadow-xl">
<p class="text-sm font-bold text-zinc-700">
Card de conteúdo com botão discreto de compartilhamento (canto do card, sem competir com
o CTA primário): abre WhatsApp com mensagem pré-configurada + opção de copiar link.
</p>
<div class="mt-4 grid max-w-3xl grid-cols-3 gap-4">
<div class="group relative flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-video overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
YouTube
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
thumb 16:9
</span>
</div>
<h3 class="text-sm leading-snug font-extrabold text-zinc-900">
Título do vídeo recente, até duas linhas
</h3>
<span class="text-xs text-zinc-400">há 2 dias · 12,4 mil visualizações</span>
<button
type="button"
class="absolute top-4 right-4 flex h-7 w-7 items-center justify-center rounded-full bg-white text-xs font-bold text-zinc-700 shadow"
aria-label="Compartilhar"
>
↗
</button>
</div>
<div class="group relative flex flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-3">
<div class="relative aspect-video overflow-hidden rounded-lg bg-zinc-200">
<span class="absolute top-2 left-2 rounded-full bg-zinc-800 px-2 py-0.5 text-[10px] font-extrabold tracking-wider text-white uppercase">
Artigo
</span>
<span class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-zinc-400">
capa 16:9
</span>
</div>
<h3 class="text-sm leading-snug font-extrabold text-zinc-900">
Título do artigo, até duas linhas
</h3>
<span class="text-xs text-zinc-400">há 1 dia · categoria</span>
<button
type="button"
class="absolute top-4 right-4 flex h-7 w-7 items-center justify-center rounded-full bg-white text-xs font-bold text-zinc-700 shadow"
aria-label="Compartilhar"
>
↗
</button>
</div>
<div class="flex flex-col justify-center gap-2 rounded-xl border border-dashed border-zinc-300 bg-zinc-50 p-3">
<p class="text-xs font-semibold text-zinc-500">
Menu aberto (v1): "Compartilhar no WhatsApp" (mensagem pré-configurada com título +
link) e "Copiar link".
</p>
<div class="rounded-lg border border-zinc-200 bg-white p-2 text-xs font-semibold text-zinc-700">
WhatsApp ↗
</div>
<div class="rounded-lg border border-zinc-200 bg-white p-2 text-xs font-semibold text-zinc-700">
Copiar link
</div>
</div>
</div>
</section>
<!-- ============ CENA: vazio (fail-closed) ============ -->
<section data-shot="vazio" class="mt-8 rounded-2xl bg-white p-6 text-zinc-900 shadow-xl">
<p class="text-sm font-bold text-zinc-700">
Vazio / fail-closed — nenhuma fonte com conteúdo disponível (artigos invisíveis ou APIs
sem snapshot). Recomendação: a seção inteira é ocultada; a página mantém o fluxo
Prova → Por que essa eleição importa, sem zona morta.
</p>
<div class="mt-4 flex items-center justify-center gap-10 rounded-xl bg-zinc-100 py-3">
<span class="text-sm font-bold text-zinc-400">3.333 proposições</span>
<span class="text-sm font-bold text-zinc-400">1.031 discursos</span>
<span class="text-sm font-bold text-zinc-400">3º mandato</span>
</div>
<div class="mt-4 rounded-xl bg-zinc-900 px-6 py-5 text-white">
<p class="text-xs font-extrabold tracking-[0.14em] text-zinc-400 uppercase">
Por que essa eleição importa
</p>
<p class="mt-1 text-lg font-extrabold">
Eleger deputado é coisa séria. O Congresso tem nas mãos vidas reais e o SUS.
</p>
</div>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,93 @@
# Seção de conteúdos na home de campanha — YouTube (S2)
Status: registrado
Atualizado em: 2026-08-17
Issue: #18
Priority: P1
Model: cursor-grok-4.5-medium
Impeccable: B — estende a seção S1 com cards de vídeo (mesma estrutura de card, badge "YouTube")
Rascunho UI: docs/plans/secao-conteudos-home-ui-draft.html + PNGs embutidos abaixo
Appetite: ~1–2 dias eng; integração de leitura com a API do YouTube + cards na seção S1
Responsável: —
## Intenção
A seção "Acompanhe de perto" da home de campanha (criada em S1 com artigos) ganha os vídeos do canal oficial. O canal já existe (`@JorgeSollaDep`) e posta com frequência — vídeos são o conteúdo de maior engajamento na reta final. A fatia segue o desenho aprovado no plano do site: automático via API do YouTube, servidor-side, sem iframe no carregamento; clique abre o vídeo **na plataforma** (decisão do wireframe).
## Persona e fluxo
- **Persona / contexto:** visitante da home de campanha que quer ver a caminhada em movimento (bastidores, caravanas, entrevistas).
- **Job principal:** ver que o canal está ativo e assistir o vídeo mais recente sem caçar o canal.
- **Fluxo desejado:** rola até a seção → vê o card grande do último vídeo (thumbnail 16:9 + badge "YouTube" + título + views/data) e cards menores dos seguintes → clica no card → abre o vídeo no YouTube (nova aba, `noopener`), mantendo a home aberta.
- **Anti-goals de produto:** nenhum embed/player inline no v1 (decisão do plano-site §4.2: "sem iframe no load", LCP do mobile); o board não é curadoria manual; a seção nunca quebra se a API falhar.
### Esboço de fluxo (B)
```text
[seção S1 + cards YT] → [clique no card YT] → [youtube.com/... nova aba] → [volta à home intacta]
API indisponível → [mantém último snapshot / oculta só os cards YT — nunca quebra a página]
```
### Rascunho UI (B)
![Rascunho UI — desktop](secao-conteudos-home-ui-draft-desktop.png)
![Rascunho UI — mobile](secao-conteudos-home-ui-draft-mobile.png)
Fonte iterável: [`secao-conteudos-home-ui-draft.html`](secao-conteudos-home-ui-draft.html). A cena `desktop` mostra o bento final (Artigo + YouTube + Instagram); nesta fatia S2 entram só os cards YouTube.
## Objetivo e aceite
- A seção S1 passa a incluir os vídeos mais recentes do canal oficial: card grande = último vídeo (16:9), pequenos = próximos, com badge "YouTube", título, data e visualizações. **Mobile: carrossel de 1 conteúdo por tela (decisão do cliente).**
- Fonte automática: leitura servidor-side da API pública do YouTube (dados dos últimos vídeos do canal), com cache (ISR/revalidação) — nenhuma chave no cliente, nenhum iframe no load.
- Clique no card abre o vídeo **na plataforma** (URL canônica do vídeo), em nova aba com `noopener` — sem lightbox, sem player inline (decisão do wireframe; player inline fica fora de escopo).
- **Exclusão por item (curadoria mínima):** o admin pode marcar vídeos específicos para NÃO aparecer (ex.: vídeo errado, conteúdo que não é de campanha, transmissão incompleta). Itens excluídos não são exibidos nem contam para "o último vídeo" — o board pula para o próximo elegível. Mesmo mecanismo de exclusão das outras plataformas (S3), configurado no mesmo lugar no admin.
- Fail-closed: API sem resposta/token expirado/quota estourada → mantém o último snapshot em cache se existir; sem snapshot, a seção continua com as outras fontes (ou oculta por completo se nada houver) — a página nunca quebra nem mostra erro.
- A configuração (chave/IDs + exclusões) vive no admin, seguindo o desenho do plano-site §4.2 (sem trava de schema nesta intenção — o executor define a forma); sem chave configurada, os cards YT simplesmente não aparecem.
- Zero mudança em schema de conteúdo público / migrations destrutivas; a visibilidade eleitoral `hidden` dos posts não se aplica aqui (fonte externa), mas o kill switch do feed (pausar) segue o desenho do plano-site.
## Dados (intenção)
- **Vou apresentar dados?** Sim, superfície neste item — metadados do vídeo (título, thumbnail, data, visualizações) vindos da plataforma.
- **Decisões desbloqueadas:** o visitante decide qual vídeo assistir (clique); visualizações são fato da plataforma (não inventar).
- **Forma:** *adiada ao plano de implementação* — restrição de produto: mostrar views como a plataforma mostra (ex. "12,4 mil"), nunca métrica derivada da campanha.
## Direção no codebase (hipótese)
- **Áreas prováveis:** mesmo componente de seção/card de S1 (`src/components/`, `src/app/(frontend)/(home)/page.tsx`, bloco `campaign-site` em `styles.css`); loader/fetch servidor-side novo em `src/utilities/` ou `src/lib/` com cache (padrão `unstable_cache` do repo); config no admin (global do Payload, grupo `Configurações` — sem nome de arquivo travado).
- **Precedente a olhar:** S1 (estrutura da seção); `docs/campanha/plano-site-campanha-2026.md` §4.2 (decisões: Data API v3, server-side, ISR, thumbnails CDN, sem iframe no load); padrões de cache do repo (`src/utilities/posts.ts`, `unstable_cache` + tags); `seed-posts.mjs` se precisar de referência de chamada externa com cache de download.
- **Risco de acoplamento:** seção compartilhada com S3 (Instagram) — a estrutura de card generalizada criada em S1 não pode virar código duplicado por plataforma; o kill switch de "pausar feed" do plano-site é um toggle único do board (não por plataforma na intenção — validar no plano de implementação).
## Dependências
- **S1** (estrutura da seção + card generalizado) — dura.
- Soft: `docs/campanha/plano-site-campanha-2026.md` §4.2 (decisões de engenharia do board).
## Fora de escopo
- Instagram → S3 (mesma seção, mesmo lote).
- Compartilhamento WhatsApp → S4 (issue separada).
- Player inline / lightbox / iframe no load (decisão do wireframe: clique abre na plataforma).
- Curadoria manual além da exclusão por item (playlists, etiquetas, ordenação manual, seleção de destaques); download/armazenamento dos vídeos; estatísticas além do que a API entrega (comentários, inscritos).
- Outros canais/contas (a configuração aponta um canal oficial).
## Rabbit holes de produto
- **Embed no load.** Se alguém "só completar": iframes de YouTube derrubam o LCP no mobile (decisão explícita do plano-site §4.2). **Corte:** thumbnail + link para a plataforma.
- **Vídeo "em destaque" com escolha manual.** Se alguém "só completar": vira curadoria semanal. **Corte:** automático (último vídeo elegível = card grande).
- **Exclusão vira curadoria pesada.** Se alguém "só completar": lista enorme de IDs, razões obrigatórias, aprovação. **Corte:** marcar/desmarcar item no admin; sem workflow.
- **Fail-closed vira erro visível.** Se alguém "só completar": mensagem de "erro ao carregar" queima a home. **Corte:** seção nunca mostra erro — snapshot ou ocultação silenciosa.
## Questões em aberto (produto)
- **Misturar mandato e campanha no board?** **Opções:** A) automático — expõe o que o canal postar (recomendação: pendência já registrada no plano-site §5, decidir com a assessoria antes do go-live) | B) filtrar por termo/playlist. **Recomendação:** A, com o kill switch do plano-site + exclusão por item como rede de segurança. _(decidido — A + exclusão por item)_
- **Exclusão por item: qual granularidade?** **Opções:** A) lista de IDs excluídos por plataforma, gerenciada no admin (recomendação — simples, auditável, suficiente para vídeos errados/indesejados) | B) razão/etiqueta obrigatória por exclusão. **Recomendação:** A — o motivo pode ser um campo opcional de texto livre, sem virar workflow. _(assumido — validar com produto)_
## Referências
- GitHub Issue #18
- Rascunho UI (gate): `docs/plans/secao-conteudos-home-ui-draft.html` + PNGs acima
- `docs/campanha/plano-site-campanha-2026.md` §4.2 — decisões do board (API, cache, sem iframe, fail-closed) e §5 pendência "mix mandato/campanha"
- Wireframe: `docs/campanha/wireframe-solla-1313.html` (seção 7)
- `docs/plans/secao-conteudos-home-artigos.md` (S1 — estrutura que esta fatia estende)
- `AGENTS.md` — padrões de cache (`unstable_cache`), admin em pt, convenções de nome (identificadores em inglês)