diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md
index 176fdf45..0a1a7698 100644
--- a/docs/CHANGELOG-AGENTS.md
+++ b/docs/CHANGELOG-AGENTS.md
@@ -4,6 +4,10 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
---
+**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).
+
**PUB5** — slogan da campanha "Mais saúde mais futuro" no hero e no rodapé da home (jorgesolla1313.com.br), no lugar de "Um mandato do tamanho da Bahia" (discurso do mandato vigente; o novo é da campanha 2026 — saúde é o tema-âncora do candidato). Troca de copy cirúrgica, sem redesenho nem migration: h1 do hero em duas linhas uppercase "MAIS SAÚDE / MAIS FUTURO" com `aria-label` acompanhando ("MAIS SAÚDE MAIS FUTURO"), segunda linha já em `font-black` (anatomia existente), e o rodapé em sentence case com ponto ("Mais saúde mais futuro."). Testes que pinavam a string antiga atualizados (`campaignHero.unit`, `campaignHome.unit`, `frontend.e2e`). Fora de escopo decidido no gate: cabeçalho de artigos ("O mandato do tamanho da Bahia") e docs históricos. Verificação: unit 2269 + int 791 verdes, e2e frontend 8/8 em prod mode (`E2E_PROD=1`, `.next-e2e`), lint/format/typecheck/cycles/knip/build verdes; render conferido por screenshot + DOM (hero duas linhas, rodapé com ponto). `/simplify` 3 reviewers: sem P0/P1; P2 corrigidos no impl plan (redação + line refs); débitos descartados por serem convenções pré-existentes ou decisões do gate.
**Recently resolved (2026-08-17):** **OPS51** — o banco de produção saiu do Neon para o Postgres do homeserver (`teqo_1313`, container `teqo-1313`), sem mudança de config: o `~/stack/teqo-1313.env` já apontava para o banco preparado (criado 16/08 com role própria + cadeia completa de 56 migrations + zero conteúdo — verificado), então restaurar = a produção "apareceu" no container. **Divergência deliberada da intenção (questão A/B validada como B):** rejeitado criar `teqo_prod` novo — o argumento "o banco do 1313 tem vida própria" não se sustenta (o site 1313 É a produção deste repo) e o OPS53 já trata `teqo_1313` como produção. Execução 100% no homeserver (PII nunca tocou a workstation): URL do Neon puxada do Vercel (`vercel env pull`, projeto `jorgesolla`, `DATABASE_URL_UNPOOLED` → `NEON_DATABASE_URL` em `~/stack/.env` chmod 600), `pg_dump -Fc` (Neon PG 17.10) → `/srv/hdd/backups/teqo-neon-pre-migracao/teqo-neon-full-20260817-204800.dump` (5,8 MB, sha256 `f12196f2…`), `pg_restore --no-owner --no-privileges` como a role `teqo_1313` (drop schema + grant, padrão do `db-pull`), verificado: contagens por tabela idênticas ao Neon (1919 contatos, 39 posts, 1487 assinaturas, 1484 subscrições, 35 campaign_users, 392 lideranças, 2 pledges, 435 municípios, 6 atividades; supporter segue 0 — blocker LGPD ainda de pé), sequences no topo (`last_value == max(id)`), objetos com dono `teqo_1313`, `payload migrate` no-op (56 = 56, serviço de maintenance `teqo-1313-migrate`), 3 credenciais WebAuthn + 34 sessions + 5 chaves Consent restauradas (RP ID não mudou — biometria segue valendo). Smoke sem deploy: `/`, artigo com conteúdo restaurado, `/campanha/login`, `/admin`, `POST /api/revalidate` (`posts` + `global_privacy-policy`), WebAuthn `login-options`, barreira 307 sem sessão — verdes; smoke de browser (login, biometria, convites) validado manualmente. **Rollback documentado:** Neon nunca foi escrito (só lido); voltar = re-apontar `DATABASE_URL` do `teqo-1313.env` para o Neon + `compose up -d`. **Débito registrado (→ OPS52):** as 40 rows de `media` têm URLs relativas (`/api/media/file/…`) sem arquivos em lugar nenhum — capas dão 500 no homeserver e **404 no próprio Vercel/produção atual** (estado pré-existente; migração manteve paridade). Repo: plano da intenção concluído, `AGENTS.md` e help de `db:pull`/`check-push-chain` sem a referência ao Neon; infra-solla `STATE.md` atualizado à parte.
diff --git a/docs/changelog/2026-08-18-s1.md b/docs/changelog/2026-08-18-s1.md
new file mode 100644
index 00000000..a1374510
--- /dev/null
+++ b/docs/changelog/2026-08-18-s1.md
@@ -0,0 +1 @@
+**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).
diff --git a/docs/plans/secao-conteudos-home-artigos-impl.md b/docs/plans/secao-conteudos-home-artigos-impl.md
new file mode 100644
index 00000000..8aeb6fa4
--- /dev/null
+++ b/docs/plans/secao-conteudos-home-artigos-impl.md
@@ -0,0 +1,106 @@
+# Impl: S1 — Seção de conteúdos na home de campanha — Artigos
+
+Status: aprovado
+Atualizado em: 2026-08-17
+Issue: #17
+Intenção: docs/plans/secao-conteudos-home-artigos.md
+Appetite restante: ~1 dia eng (herdado)
+
+## Leitura da intenção
+
+- **Outcome:** a home pública de campanha ganha, na 3ª posição (entre a prova social e "Por que essa eleição importa"), uma seção "Acompanhe de perto" com bento de 5 artigos (1 grande + 4 pequenos) no desktop e carrossel de 1 por tela no mobile (~390px), lendo o `post` collection com a visibilidade pública existente; sem artigos visíveis, a seção inteira é ocultada (Prova → Problema intacto).
+- **O que NÃO negociar:** zero mudança de schema/migration/Consent; `isPostVisible` fail-closed (nada de bypass do `hidden` eleitoral); cache público = tag `posts` existente; o card inteiro é link para a rota canônica (`getPostCanonicalPath`), mesma aba; bento fixo sem paginação ("Ver artigos →" → `/artigos`); sem zona morta no vazio.
+- **O que reavaliar:** a hipótese "estilos no bloco `[data-theme='campaign-site']` de styles.css" está certa, mas as seções atuais (`campaign-problem`/`campaign-flags`) usam altura fixa + posicionamento absoluto com geometria clampada do Penpot — o grid de conteúdo é alto e variável, então a seção nova usa flow normal com os mesmos tokens tipográficos/inset; e o carrossel mobile é navegação manual 1/tela com dots, job diferente do `CampaignCarousel` (auto-advance de destaques) — ver Decisão 2.
+
+## Abordagem recomendada
+
+```mermaid
+flowchart LR
+ H[page.tsx home] --> S[CampaignContentSection server]
+ S --> V[getVisiblePosts tag posts]
+ V --> SL[slice 5 first post = destaque]
+ SL --> B[Bento desktop md+ grid 4 col]
+ SL --> C[CampaignContentCarousel client · CARD[Card Artigo badge tipo · capa · título · meta]
+ C --> CARD
+ S -.0 visíveis.-> HIDE[(não renderiza Prova → Problema)]
+```
+
+**Opções consideradas:** A | B | C
+**Recomendação:** A — uma seção server (`CampaignContentSection`) que lê `getVisiblePosts()`, fatia 5 e renderiza o bento desktop, com um client component pequeno (`CampaignContentCarousel`) só para o modo mobile (estado de scroll/índice/dots); helpers puros novos em `src/utilities/posts.ts` (dono de formatação de post).
+**Rejeitadas:** B) estender `CampaignCarousel` com uma 3ª variant `content` — o contrato dele (variants `problem`/`flags`, auto-advance 4s, chips, geometria clampada pinada por e2e de swipe/geometria) não tem dots nem "1 de 5"; enfiar o modo manual 1/tela no mesmo módulo torna-o raso e arrisca os dois use-cases pinados; C) tudo client — o bento desktop não precisa de JS e ganharia re-render client.
+
+### Componentes / mudanças
+
+- **`CampaignContentSection`** (`src/components/CampaignContentSection.tsx`, server): dono da seção — eyebrow "Acompanhe de perto", título "A caminhada, em tempo real", subtítulo, link "Ver artigos →" (`/artigos`), bento desktop `hidden md:grid`, carrossel mobile `md:hidden`. `data-home-section="contents"` (convenção `proof`/`problem`/`flags`). Renderiza `null` quando `posts.length === 0`.
+- **`CampaignContentCarousel`** (`src/components/CampaignContentCarousel.tsx`, client): snap-x mandatário 1/tela + dots-botões (`aria-current`) + contador "N de 5 · deslize para ver os próximos", `aria-roledescription="carrossel"`, `data-carousel="contents"`, respeita `prefers-reduced-motion`; padrões copiados do `CampaignCarousel` (scrollToItem/syncActiveItem, `scrollbar-hide`).
+- **Card** (`CampaignArticleCard`, server-friendly, dentro do section file): badge de tipo (Decisão 3), capa `coverImage` (next/image fill, fallback sem imagem), título, meta "há X · categoria" (Decisão 4); `Link` para `getPostCanonicalPath(post)`, mesma aba. Variação `featured` (grande) só no bento.
+- **`formatRelativePostDate`** (`src/utilities/posts.ts`): "hoje", "há 1 dia", "há 5 dias" via `Intl.RelativeTimeFormat('pt-BR')`; novo record `POST_TYPE_BADGE_LABELS` singular (`Notícia|Campanha|Artigo|Evento`).
+- **`src/app/(frontend)/(home)/page.tsx`**: insere `` entre `campaign-proof` e `campaign-problem`; a página passa a depender da tag `posts` (ISR — comportamento já desenhado para consumidores de post; `afterChange` do Post/Tag já revalida).
+- **`src/app/(frontend)/styles.css`**: classes da seção no bloco `[data-theme='campaign-site']` (tokens `--pt-red`, `--campaign-surface`, `--campaign-line`, `--campaign-content-inset`; tipografia `campaign-section-*`); flow normal, sem altura fixa.
+- **Migration:** sem migration. **Access/Consent:** nenhum — leitura pública via helpers existentes.
+- **UI:** Impeccable B — seção nova na home pública com wireframe aprovado; shape → craft → critique → polish no navegador.
+
+### Dados → forma (se aplicável)
+
+- Forma: meta = "há X · categoria" (data relativa + categoria do post) — dados já exibidos no site, nenhum contador inventado (restrição da intenção). Rejeitadas: data longa absoluta (`formatPostDate`) no card — o rascunho aprovado mostra recência, e "prova de atividade" pede relativa; contadores/visualizações — proibido pela intenção.
+
+## Decisões de engenharia
+
+1. **Carrossel mobile novo vs estender `CampaignCarousel`.**
+ Opções: A) `CampaignContentCarousel` próprio | B) variant `content` no `CampaignCarousel`.
+ Recomendação: A — jobs diferentes (auto-advance de destaques vs navegação manual 1/tela com indicador); o contrato atual é pinado por Penpot + e2e. Não é twin: é o mesmo padrão visual-mechanics, encapsulado por job.
+ Alternativas rejeitadas: B porque muda contrato pinado e adiciona dots/counter/aria-current a um módulo que não os expõe.
+
+2. **Badge do card.**
+ Opções: A) label do tipo do post (Notícia/Campanha/Artigo/Evento) | B) "Artigo" fixo (literal do aceite).
+ Recomendação: A — a intenção decidiu "os N mais recentes visíveis, qualquer tipo"; com o feed misto, um badge "Artigo" sobre uma notícia seria desonesto. O aceite foi escrito assumindo artigos; o badge de tipo cumpre o mesmo papel do badge de fonte do wireframe.
+ Alternativas rejeitadas: B porque engana no feed misto (decisão A da intenção).
+ → **Confirmar no gate** (divergência de leitura do aceite).
+
+3. **Ocultação com 1–4 posts.**
+ Opções: A) ocultar só com zero visíveis; bento/carrossel se adaptam | B) exigir os 5 para renderizar.
+ Recomendação: A — zero é o fail-closed do aceite; com 1–4, mostrar o que existe (grid auto preenche; "1 de N" no carrossel).
+ Alternativas rejeitadas: B porque esconderia conteúdo real da campanha no meio de funil.
+
+4. **Meta relativa vs absoluta.**
+ Opções: A) relativa ("há 1 dia · categoria") | B) `formatPostDate` longa.
+ Recomendação: A — rascunho aprovado mostra relativa; gatilho de revisitação: trocar para absoluta é uma linha se a assessoria pedir.
+ Alternativas rejeitadas: B porque foge do rascunho e da intenção de "atividade".
+
+## Fases verificáveis
+
+1. **Tracer (server + helpers):** `formatRelativePostDate` + `POST_TYPE_BADGE_LABELS` em `posts.ts` com unit tests; `CampaignContentSection` com bento desktop + `page.tsx` + CSS mínimo; e2e empty-state (seção ausente no estado default da suite) e full-state (cria posts via `getPayload` → revalida → asserts desktop). Gates parciais: tsc + unit.
+2. **UI mobile:** `CampaignContentCarousel` (dots, contador, swipe) + CSS responsivo; e2e mobile (1/tela, swipe, "1 de 5", click-through para a página do artigo, post com tag `hidden` ausente — fail-closed); cleanup + revalidação no afterAll.
+3. **Gates finais:** `pnpm gate:fast` (tsc, lint, format:check, knip, check:cycles), `pnpm test`, `pnpm build` local, e2e completo.
+
+## Rabbit holes / Não escopo (engenharia)
+
+- Não mexer na API de `getVisiblePosts` (limite/param) para otimizar 5 de ~39 docs — custo irrelevante, outros consumidores dependem do contrato.
+- Não criar hooks de scroll-snap compartilhados ("DRY < 3 call sites").
+- Não animar entrada da seção / parallax / prefetch de imagens (não escopo do wireframe).
+- Sem "Ver todos" além do link `/artigos`; sem paginação; sem curadoria manual.
+- YouTube/Instagram (S2/S3), share WhatsApp (S4): fora.
+- Não tocar `CampaignCarousel`, `PostCard` (editorial), nem as geometrias clampadas das seções existentes.
+
+## Ajustes pós-review (simplify, 2026-08-18)
+
+- **A11y do carrossel:** foco + setas no `` (ArrowLeft/Right navegam), `inert` + `aria-hidden` nos slides inativos (saem do tab order/árvore a11y), `aria-live="polite"` no contador, dots com contraste ≥3:1 (`--campaign-muted`), `tabIndex` só com >1 item, `motion-reduce` corta o zoom da capa.
+- **Label relativa vs ISR (testado e revertido):** a primeira resposta ao congelamento da label ("há 5 minutos" gravado no HTML estático) foi `export const revalidate = 300` na home — **medido em prod (Next 15.4): com `revalidate` temporal, `revalidateTag('posts')` deixa de regenerar a home (probe manual: `/artigos` convergia, a home ficava stale para sempre)** — o bust por tag é o contrato documentado do site, então o `revalidate` foi REMOVIDO e a solução virou threshold na formatação: relativa < 48h (drift limitado), data absoluta além (nunca mente congelada).
+- **Fidelidade ao rascunho:** `gap-2` nos cards pequenos (gap-3 no featured), `mt-1` no header.
+- **Spec e2e:** secret espelhado da env; polling POSITIVO do HTML da home até convergir pós-revalidate (ISR serve stale uma vez sob carga; DOM carregado não refresca); `about:blank` antes do cleanup (prefetch prod dos links apagados geraria 404 no guard); classe morta `.campaign-contents` removida; lock programático 700 ms (paridade com o irmão).
+- **Decisões registradas (refusais):** (a) `formatRelativePostDate` não reusa `formatRelativeAge` (contratos divergentes: `numeric: 'auto'` → "anteontem" vs o "há 2 dias" do rascunho; `round` vs `floor`; fallback absoluto; futuro → "agora"; entrada string de post) — a função é pequena e os consumidores existentes do `formatRelativeAge` (bell/sinal/município) não podem mudar de output; (b) gutter da seção usa `px-5/8/10` + `max-w-[1160px]` em vez do `--campaign-content-inset` dos irmãos (layout flow do rascunho manda — o bento wide não cabe em inset de 180px); (c) "Ver artigos →" → `/artigos` lista só notícias com o feed misto — corte sancionado na intenção (rabbit hole "feed infinito"), link por tipo fica como opção futura.
+
+## Riscos e mitigação
+
+- **Home vira ISR (tag `posts`) em produção:** o build bota o estado atual; `afterChange` do Post/Tag já revalida; escrita direta (seed) segue o runbook de `POST /api/revalidate`. No e2e prod-mode, o teste full-state faz `POST /api/revalidate?tag=posts` antes do primeiro `goto` (REVALIDATE_SECRET novo no webServer env do playwright) e o empty-state roda antes — ordem no arquivo garante estado.
+- **ISR serve stale uma vez após revalidate:** asserts usam `expect` com timeout/poll em vez de assert imediato.
+- **Dois carrosséis na home (problem + contents):** seletores próprios (`data-carousel="contents"`), swipe e2e aponta ao track certo.
+- **Seção em flow normal entre duas de altura fixa:** nenhum e2e existente mede posição absoluta de `problem` (verificado: os testes de geometria medem hero+proof e gaps relativos) — a inserção não quebra pinagem.
+- **Falha do `getPayload` no spec (DB errado):** mesmo caminho do `campaignE2EFixtures` (config importado + `assertTestDatabase` já guarda o processo playwright).
+
+## Aceite de engenharia
+
+- [x] Aceite de produto da intenção ainda coberto (bento 1+4, carrossel 1/tela, fail-closed vazio, links canônicos)
+- [x] Invariantes AGENTS/engineering-standards (sem migration/Consent/access; leitura via helpers com tag `posts`; identificadores em inglês, copy pt-BR)
+- [x] Testes de domínio previstos: unit (`formatRelativePostDate`, labels) + e2e (empty-state, full-state desktop+mobile, fail-closed `hidden`, click-through, cleanup)
diff --git a/playwright.config.ts b/playwright.config.ts
index 92bfa52c..37a02954 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -159,6 +159,9 @@ export default defineConfig({
NEXT_DIST_DIR: process.env.NEXT_DIST_DIR ?? '.next-e2e',
PAYLOAD_SECRET: process.env.PAYLOAD_SECRET ?? 'test-only-secret-not-used-in-production',
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL ?? baseURL,
+ // Lets e2e specs bust the `posts` tag after direct REST deletes (cleanup),
+ // mirroring the documented post-seed runbook against the deployed site.
+ REVALIDATE_SECRET: process.env.REVALIDATE_SECRET ?? 'e2e-revalidate-secret',
},
},
})
diff --git a/src/app/(frontend)/(home)/page.tsx b/src/app/(frontend)/(home)/page.tsx
index f8ec2bcc..a76693ac 100644
--- a/src/app/(frontend)/(home)/page.tsx
+++ b/src/app/(frontend)/(home)/page.tsx
@@ -1,4 +1,5 @@
import { CampaignCarousel, type CampaignCarouselItem } from '@/components/CampaignCarousel'
+import { CampaignContentSection } from '@/components/CampaignContentSection'
import { CampaignFooter } from '@/components/CampaignFooter'
import { CampaignHero } from '@/components/CampaignHero'
@@ -101,6 +102,8 @@ export default async function HomePage() {
+
+
(
+
+