feat(S5): banda visual própria para a seção 'Acompanhe de perto' na home

This commit is contained in:
Francisco Solla 2026-08-18 12:45:30 -03:00
parent 228d5da9c8
commit a60b3880ca
5 changed files with 88 additions and 1 deletions

View file

@ -4,6 +4,10 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
---
**S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `<section>` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções `toHaveCSS` no e2e full-state da seção (fundo `#ebe9e9` exato — pega token quebrado em vez de só "≠ branco", hairline top `1px` com cor `rgb(0 0 0 / 12%)` exata, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida.
**S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `<section>` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções de estilo computado no e2e full-state da seção (fundo ≠ branco ≠ prova, hairline top presente, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida.
**S1** — seção "Acompanhe de perto" na home de campanha (jorgesolla1313.com.br), na 3ª posição entre a prova social e "Por que essa eleição importa": bento de 5 artigos do `post` collection (1 card grande + 4 pequenos) no desktop e carrossel de 1 conteúdo por tela com dots e contador "N de M" no mobile (~390px), badge de tipo (Notícia/Campanha/Artigo/Evento), capa, título e meta "há X · categoria" (relativa < 48h, data absoluta além — nunca mente congelada na ISR); card inteiro é link para a rota canônica do artigo. Fail-closed: sem posts visíveis (`isPostVisible` — tag `hidden` eleitoral nunca vaza) a seção some inteira e Prova → Problema voltam a ser contíguos. Zero schema/migration/Consent; leitura via `getVisiblePosts` sob a tag `posts` (home estática revalidada pelo mecanismo `posts` existente). Primeira fatia do board aprovado (S2/S3 = YouTube/Instagram). Verificação: unit 2282 + int 791 verdes, e2e frontend 10/10 em dev e prod (`E2E_PROD=1`, `.next-e2e`), suíte completa prod 169-170 passed com flakes ambientais de campanha (máquina compartilhada; testes diferentes a cada run, nenhum no código tocado); criação via REST com hooks reais; polling positivo do estado da home pós-revalidate (ISR serve stale uma vez sob carga) e navegação para about:blank antes do cleanup (prefetch de links apagados); lint/format/typecheck/cycles/knip/build verdes. `/simplify` 3 reviewers: 2 P1 de a11y (foco+setas no track, `inert` em slides inativos) corrigidos; o `revalidate = 300` proposto contra o congelamento da label relativa foi TESTADO e REVERTIDO — no Next 15.4 em prod ele trava o bust por tag (`revalidateTag('posts')` deixava de regenerar a home; medido em probe manual) — a label relativa ganhou threshold de 48h com fallback absoluto; P2 absorvidos (dots ≥3:1, aria-live no contador, motion-reduce na capa, fidelidade de gaps, secret do e2e espelhado da env); débitos deferidos documentados no impl plan (refusal do `formatRelativeAge` por contrato, gutter flow-centered, link `/artigos` sancionado pela intenção).
**S1** — seção "Acompanhe de perto" na home de campanha (jorgesolla1313.com.br), na 3ª posição entre a prova social e "Por que essa eleição importa": bento de 5 artigos do `post` collection (1 card grande + 4 pequenos) no desktop e carrossel de 1 conteúdo por tela com dots e contador "N de M" no mobile (~390px), badge de tipo (Notícia/Campanha/Artigo/Evento), capa, título e meta relativa "há X · categoria"; card inteiro é link para a rota canônica do artigo. Fail-closed: sem posts visíveis (`isPostVisible` — tag `hidden` eleitoral nunca vaza) a seção some inteira e Prova → Problema voltam a ser contíguos. Zero schema/migration/Consent; leitura via `getVisiblePosts` sob a tag `posts`; a home ganhou `revalidate = 300` para a label relativa não congelar na ISR. Primeira fatia do board aprovado (S2/S3 = YouTube/Instagram). Verificação: unit 2282 + int 791 verdes, e2e frontend 10/10 (empty-state + full-state desktop/mobile, fail-closed com tag oculta, click-through, teclado/setas, swipe, dots; criação via REST com hooks reais; cleanup convergindo a cache de dev), lint/format/typecheck/cycles/knip/build verdes. `/simplify` 3 reviewers: 2 P1 de a11y (foco+setas no track, `inert` em slides inativos) e o congelamento da data relativa na ISR corrigidos; P2 absorvidos (dots ≥3:1, aria-live no contador, motion-reduce na capa, fidelidade de gaps, secret do e2e espelhado da env); débitos deferidos documentados no impl plan (refusal do `formatRelativeAge` por contrato, gutter flow-centered, link `/artigos` sancionado pela intenção).

View file

@ -0,0 +1 @@
**S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `<section>` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções `toHaveCSS` no e2e full-state da seção (fundo `#ebe9e9` exato — pega token quebrado em vez de só "≠ branco", hairline top `1px` com cor `rgb(0 0 0 / 12%)` exata, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida.

View file

@ -0,0 +1,68 @@
# Impl: Separar visualmente a seção "Acompanhe de perto" da prova social na home
Status: aprovado
Atualizado em: 2026-08-18
Issue: #33
Intenção: docs/plans/separacao-visual-secao-conteudos-home.md
Appetite restante: herdado (~0,5 dia de eng — mudança de uma linha + teste)
## Leitura da intenção
- **Outcome:** a fronteira entre a faixa de prova social (branca) e a seção "Acompanhe de perto" fica visível de imediato, desktop e mobile; cards brancos continuam destacados; nada além da superfície muda.
- **O que NÃO negociar:** nenhum redesenho da seção (cards/carrossel/links/textos); hero e outras seções intactos; nenhuma cor nova; fail-closed inalterado (sem posts visíveis a seção continua sumindo — o teste e2e de contiguidade Prova→Problema não pode quebrar).
- **O que reavaliar:** a intenção recomenda "banda de fundo própria (A)" como meio de separação, mas o rascunho UI aprovado no gate (`separacao-visual-secao-conteudos-home-ui-draft.html`, Cena 1/3 recomendadas) mostra **banda + hairlines juntas** (`border-y border-zinc-300 bg-zinc-100`) — que é a opção C da própria intenção. O wireframe aprovado (`docs/campanha/wireframe-solla-1313.html`, seção 7) também desenha zona de cor própria com `border-top/bottom: 1px solid var(--line)`. O rascunho é o contrato visual do gate: recomendo **C**, não A.
## Abordagem recomendada
```mermaid
flowchart LR
P[home page.tsx] --> S[CampaignContentSection]
S --> B{bg + bordas no <section>}
B -->|C| BAND["border-y border-(--campaign-line)<br/>bg-(--campaign-band)"]
BAND --> CARD[CampaignArticleCard<br/>branco, inalterado]
BAND --> E2E[teste e2e: estilos computados<br/>seção ≠ branco, borda top > 0, card branco]
```
**Opções consideradas:**
- **A — banda de fundo própria, sem hairlines** (`bg-(--campaign-band)`): separa, mas a borda entre o branco da prova e a banda cinza é só contraste de tom; o desenho aprovado (wireframe seção 7 + draft Cena 1) inclui hairline.
- **B — hairlines mantendo fundo branco** (draft Cena 2, alternativa): mínimo, fiel ao wireframe no borda mas não na zona de cor; na home toda branca lê menos de longe (é o fallback da própria intenção).
- **C — banda + hairlines juntas** (draft Cena 1/3 recomendadas): o hairline em cima cria a fronteira exata Prova→Conteúdos; o hairline embaixo separa da faixa de prova social e a zona de cor dá o ritmo branco→banda→escuro previsto no wireframe.
**Recomendação:** **C** — é literalmente o que o rascunho aprovado mostra e o que o wireframe seção 7 especifica; A e B são subconjuntos que lêem menos na home branca.
**Rejeitadas:** A e B como solução única (draft as marca como alternativa; a recomendada é banda + hairline). Qualquer redesenho de cards/tipografia/paleta — fora de escopo pelo anti-goal da intenção.
### Componentes / mudanças
- **`CampaignContentSection`** (`src/components/CampaignContentSection.tsx`): trocar a classe do `<section>` de `bg-white` para `border-y border-(--campaign-line) bg-(--campaign-band)`. É a única mudança de runtime. Tokens existentes (`--campaign-band: #ebe9e9`, `--campaign-line: rgb(0 0 0 / 12%)` em `styles.css:179-180`); `border-(--campaign-line)` reusa o padrão já usado em `CampaignArticleCard.tsx:24`.
- **Migration:** nenhuma (superfície pura, sem schema).
- **Access / Consent:** nenhum.
- **UI:** Impeccable B — encaixe de token em componente existente, sem shape novo. Contrastes conferidos sobre `#ebe9e9`: eyebrow `--pt-red` ≈ 4,9:1, copy `--campaign-muted` ≈ 4,5:1, título preto — ok. Cards já são `bg-white` + `border-(--campaign-line)` + `shadow-sm` e saltam na banda. O placeholder de capa (`bg-(--campaign-band)`) funde com a banda quando não há capa — estado de fallback de posts sem imagem (fora do escopo; produção usa capas reais).
### Dados → forma (se aplicável)
- Não aplicável — nenhum dado novo; a leitura da intenção ("Vou apresentar dados? Não") se mantém.
## Fases verificáveis
1. **Tracer** — mudança de classe no `<section>`; `pnpm exec tsc --noEmit` + lint.
2. **UI/teste** — asserções de estilo computado no teste e2e existente da seção (`tests/e2e/frontend.e2e.spec.ts`, describe serial "Campaign home content section"): dentro do teste full-state, após os checks do bento em desktop (1280px), conferir via `getComputedStyle` que (a) `contents` ≠ `rgb(255, 255, 255)` e ≠ `proof` (faixa de prova social), (b) `border-top-width` > 0 e `border-top-color` não transparente, (c) card `a[href^="/artigo/"]` dentro da seção é `rgb(255, 255, 255)` — cards saltam na banda. Sem screenshot diff (o repo não usa regressão visual).
3. **Gates** — `pnpm gate:fast` (ou ao menos tsc + lint + unit), depois `pnpm test:e2e` no describe afetado (serial, DB de teste do worktree), `pnpm build`, `pnpm format:check`, `knip`, `check:cycles`.
## Rabbit holes / Não escopo (engenharia)
- "Aproveita e muda o placeholder de capa para `--campaign-surface`/tom mais escuro para saltar na banda": melhora cosmética fora do aceite, e o placeholder é fallback (capa real vem do media). Deixar como está; registrar como débito se notado no /simplify.
- "Banda só no desktop, hairline no mobile" ou vice-versa: o draft trata mobile e desktop iguais; variantes não pedidas.
- Mexer na classe do card, no carrossel ou em `page.tsx`: explícito fora de escopo.
## Riscos e mitigação
- **Contiguidade Prova→Problema com seção oculta:** o teste existente (`frontend.e2e.spec.ts:524-528`) ainda valida gap ≤ 1px quando a seção some; a mudança não afeta o retorno `null` fail-closed (o `<section>` nem renderiza). Sem risco.
- **`--campaign-band` compartilhado com "Nossa caminhada" e placeholder de capa:** seções não adjacentes (flags fica abaixo do problema escuro); a intenção já mapeou e aceitou o ritmo.
- **Borda dupla no mobile carrossel:** dots/leitura usam `--campaign-muted`/`--pt-red`, visíveis sobre a banda; nada a ajustar.
## Aceite de engenharia
- [x] Aceite de produto da intenção ainda coberto (fronteira visível, nada além muda, fail-closed intacto)
- [x] Invariantes AGENTS/engineering-standards (sem schema/access/LGPD; identifiers em inglês; copy pt-BR intacta)
- [x] Testes previstos: asserções de estilo computado no e2e full-state existente (desktop; o trecho mobile do mesmo teste segue cobrindo o carrossel na banda)

View file

@ -49,7 +49,11 @@ export const CampaignContentSection = async () => {
const [featured, ...rest] = cards
return (
<section aria-labelledby="contents-title" data-home-section="contents" className="bg-white">
<section
aria-labelledby="contents-title"
data-home-section="contents"
className="border-y border-(--campaign-line) bg-(--campaign-band)"
>
<div className="mx-auto w-full max-w-[1160px] px-5 py-12 sm:px-8 lg:px-10 lg:py-16">
<div className="flex flex-wrap items-end justify-between gap-3">
<div>

View file

@ -631,6 +631,16 @@ test.describe('Campaign home content section', () => {
await expect(firstCard.locator('h3')).toHaveText('E2e Artigo em destaque')
await expect(firstCard.locator('span').last()).toContainText(categoryName)
// S5 — the section must read as a zone of its own: a banded background
// with a top hairline against the white proof strip, and white cards
// that pop against the band. Exact computed values pin the tokens
// (`--campaign-band` #ebe9e9, `--campaign-line` rgb(0 0 0 / 12%)) so a
// broken or removed token cannot silently pass.
await expect(section).toHaveCSS('background-color', 'rgb(235, 233, 233)')
await expect(section).toHaveCSS('border-top-width', '1px')
await expect(section).toHaveCSS('border-top-color', 'rgba(0, 0, 0, 0.12)')
await expect(firstCard).toHaveCSS('background-color', 'rgb(255, 255, 255)')
await featured.click()
await expect(page).toHaveURL(new RegExp(`${featuredHref}$`))
await page.goBack()