<!-- title: Nabismed Headless — Contrato da API -->

# Nabismed Headless — Contrato da API

Documentação do que a API (`wp-json`) do tema `nabismed-headless` entrega pro
front-end. Serve de referência pro Lovable e pra quem for consumir o
`wp-json` diretamente. Atualizada a cada componente novo adicionado.

## Autenticação

Toda chamada exige o header `X-CMS-Token: Bearer SEU_TOKEN` (o valor de
`REST_GATE_TOKEN` no `.env` do tema) — não vai mais em query string, pra não
vazar em log de acesso do servidor, histórico do navegador ou Referer. Sem o
token certo, a API responde `403`.

## Domínio das URLs

- **Mídia e a própria API** sempre em `https://cms.nabismed.com.br/...`.
- **Links do front-end** (menu, páginas, botões) sempre no domínio da marca
  do site, configurado no campo "URL da Marca" em Opções do Site → Header e
  Footer (ex.: `https://flowermed.com.br`).

## Endpoints principais

- `GET /wp-json/headless/v1/home` — devolve a página definida como
  "página inicial" em Configurações → Leitura, **independente do ID/slug**
  dela. Nessa resposta, `globals.seo.yoast.og_url` (e `canonical`, quando
  existir) são forçados pra raiz do domínio da marca (`https://marca.com/`),
  já que o slug real da página costuma ser descritivo por SEO.
- `GET /wp-json/wp/v2/pages/{id}` ou `GET /wp-json/wp/v2/pages?slug=xxx`
  — item único. Só nesse formato (item único, não lista) a resposta é limpa:
  saem `id, date, date_gmt, slug, status, type, link, title, content,
  excerpt, featured_media, author, parent, menu_order, comment_status,
  ping_status, yoast_head_json` — sobra só `globals`, `acf` (se o post type
  tiver outros grupos ACF além de Componentes de Conteúdo) e `node`.
- `GET /wp-json/wp/v2/pages` (sem slug) — listagem normal, mantém os
  campos padrão do WP (precisa de id/slug/link/title pra montar cards).

(todas exigem o header `X-CMS-Token`, omitido acima só pra encurtar.)

## `globals`

Presente em toda resposta. Dados de site (Opções do Site), não de uma página
específica.

### `globals.menu`

Lista solta (não fica dentro de `header`, porque o mesmo menu pode ser usado
em mais de um lugar do front). Um item por location de menu que tiver um WP
Menu atribuído — hoje: `primary` (Menu Principal), `secondary` (Menu
Secundário), `complementary` (Menu Complementar), atribuídos em Aparência →
Menus.

```json
"menu": [
  {
    "location": "primary",
    "slug": "menu-principal",
    "name": "Menu Principal",
    "items": [
      {
        "id": 12,
        "label": "Produtos",
        "title": "",
        "url": "https://flowermed.com.br/produtos/",
        "target": "",
        "rel": "",
        "class": "",
        "classes": [],
        "parentId": 0,
        "order": 1,
        "active": false,
        "ancestor": false
      }
    ]
  }
]
```

`active` = é o link da página atual; `ancestor` = é pai/ancestral da página
atual (WP marca isso automaticamente pelas classes `current-menu-item` /
`current-menu-parent` / `current-menu-ancestor`).

### `globals.seo`

Vem de Opções do Site → Header e Footer → aba "Imagens Padrões" + do
`yoast_head_json` do Yoast SEO pra página/post sendo servido.

- `favicon` — campo "Padrão ícone do site" (512×512).
- `defaultImage` — campo "OG Image - Compartilhamento" (1280×630). Usado como
  fallback quando o post não tem imagem de OG própria.
- `yoast` — o `yoast_head_json` do post, com dois ajustes:
  - `og_image` é garantido: se o Yoast não tiver uma, usa `defaultImage` (ou
    `/og/default.jpg` em último caso).
  - a chave `schema` (JSON-LD) é removida — é grande e o front normalmente
    não precisa dela vinda por aqui.

### `globals.header`

- `sitename` — nome do site (`og_site_name` do Yoast, cai pro nome do WP se
  vazio).
- `titulo` / `descricao` — título/descrição SEO da página atual (Yoast).
- `brand` — campo "Logo do Cabeçalho".
- `telefone`, `whatsapp`, `email` — campos de mesmo nome.
- `social` — repeater "Perfís Online em Redes Sociais": lista de
  `{ social_network, url, name }`.

### `globals.footer`

Mesmos `telefone`/`whatsapp`/`email`/`social` do header (podem divergir no
futuro se um dia o cliente pedir números diferentes por seção — hoje vêm da
mesma fonte), mais:

- `endereco` — campo "Endereço".
- `mapsUrl` — campo "URL Google Maps"; se vazio, monta automaticamente um
  link de busca do Google Maps a partir do endereço.
- `copyright` — montado como `"Todos os Direitos Reservados © {ano} {sitename}."`.
- `links` — repeater "Páginas de Política e Termos": lista de
  `{ page: { title, url, target } }` (campo ACF tipo Link).
- `designer` — assinatura fixa da agência (Herick Correa), igual em todo
  projeto feito com esse template — não vem de nenhum campo.

### `globals.metatags`

Repeater "Adicionar tags `<meta ...>`" (aba METAS): lista de
`{ meta_name, meta_content }`. O front deve montar `<meta name="{meta_name}"
content="{meta_content}">` pra cada item.

### `globals.scripts` e `globals.styles`

Vêm de Opções do Site → Scripts e Styles.

```json
"scripts": {
  "tags_embed": [
    { "nome": "...", "categoria_da_tag": "essentials|behavior|marketing|store", "id_da_tag": "...", "tag_embed": "<script>...</script>" }
  ],
  "external": ["https://.../script.js"],
  "head": "código solto pra colar dentro de <head>, sem a tag <script>",
  "footer": "idem, antes de </body>"
},
"styles": {
  "external": ["https://.../style.css"],
  "head": "...",
  "footer": "..."
}
```

- **`tags_embed`** — cada linha é uma tag de terceiro (ex.: Google Tag
  Manager) já com o snippet completo em `tag_embed`. `categoria_da_tag`
  (essenciais/comportamento/marketing/loja) é pra um futuro box de
  consentimento de cookies no front — a tag só deve ser injetada se o
  visitante aceitar aquela categoria (categoria "essenciais" carrega sempre).
- **`external`** — apenas URLs (o front monta a tag `<script src="">` /
  `<link rel="stylesheet">`).
- **`head`/`footer`** (scripts e styles) — código solto, sem a tag
  `<script>`/`<style>` em volta (o admin foi instruído a não colar a tag).

### `globals.noscript`

Campo "Adicionar códigos dentro de `<noscript ...>`" — string com o conteúdo
que vai dentro de `<noscript>...</noscript>` (ex.: fallback de pixel do GTM).

## `node` — componentes de conteúdo

Array presente quando a página/post tem pelo menos uma linha em
"Componentes de Conteúdo" (ACF Flexible Content). Cada item é um objeto
plano: `"type"` identifica o componente, o resto são os campos dele — sem
nenhum wrapper extra.

```json
{ "type": "hero", "title": "...", "image_principal": { ... }, "links": [ ... ] }
```

Campos que não têm valor **continuam aparecendo**, só que vazios (`""`,
`null`, `0` ou `[]`) — nunca somem do JSON.

### Convenção de imagem

Todo campo de imagem sai no mesmo formato: `alt` + uma chave por breakpoint
(`desktop`, e `mobile` quando o componente usa dois tamanhos), cada
breakpoint com `url`, `width`, `height` e `webp` (sempre a mesma URL do
`url` + `.webp` no final).

```json
{
  "alt": "texto alternativo",
  "desktop": { "url": "...", "width": 1200, "height": 800, "webp": "....webp" },
  "mobile": { "url": "...", "width": 960, "height": 640, "webp": "....webp" }
}
```

Se o campo ACF de imagem não tiver texto alternativo preenchido, `alt` cai
pra `"imagem ilustrativa representando {título do componente}"`.

### Convenção de texto 3.strong

Vários campos de texto (títulos, textos curtos, depoimentos etc.) aceitam a
convenção `*trecho*` — igual negrito no WhatsApp. A API já converte isso em
`<strong>trecho</strong>` antes de entregar o JSON; o front não precisa (e
não deve) interpretar o asterisco sozinho. Chamamos essa conversão de
**"texto 3.strong"** — quando um campo aceitar, a tabela do componente
menciona esse nome em vez de repetir a explicação toda vez.

## Componentes

### Hero (`type: "hero"`)

| Campo | Descrição |
| --- | --- |
| `variant` | `home`, `interna` ou `post` — ver variações abaixo |
| `position` | `esquerda`, `direita` ou `noimage` — mesma lógica do Texto + Imagem, ver abaixo |
| `subtitle` | texto pequeno acima do título |
| `title` | título — no admin, `*trecho*` (destaque estilo whatsapp) já sai convertido em `<strong>trecho</strong>` no JSON; o front só renderiza o HTML, não precisa interpretar o asterisco |
| `text` | texto de apoio |
| `image_principal` | imagem principal, tamanho conforme a `variant` — vazia (não `null`) quando `position` é `noimage` |
| `image_complementar` | imagem pequena adicional — `null` quando `variant` é `post` **ou** `position` é `noimage` |
| `links` | lista de CTAs — `[]` quando `variant` é `post` (independe de `position`). Cada item: `{ type: "link", title, url, target }` ou `{ type: "whatsapp", title }` (o front monta a ação do whatsapp, a API só entrega o texto do botão) |

**Variações (`variant`):**

| Valor | Label no admin | Diferença |
| --- | --- | --- |
| `home` | Alto - Como na Home | banner alto (imagem 960×1200 desktop / 640×800 mobile), com `image_complementar` e `links` |
| `interna` | Estreito - Como nas páginas internas | banner mais baixo (imagem 1200×800 desktop / 960×640 mobile), com `image_complementar` e `links` |
| `post` | Post - Como nas páginas de publicações | banner de post (imagem 1600×900 desktop / 960×540 mobile), **sem** `image_complementar` (`null`) e **sem** `links` (`[]`) |

**Variações (`position`):**

| Valor | Label no admin | Diferença |
| --- | --- | --- |
| `esquerda` | Imagem na Esquerda | imagem à esquerda, texto à direita |
| `direita` | Imagem na Direita | imagem à direita, texto à esquerda |
| `noimage` | Só texto, sem imagem | sem `image_principal`/`image_complementar` (continuam no JSON, vazios); `links` não é afetado |

### Texto + Imagem (`type: "texto_imagem"`)

| Campo | Descrição |
| --- | --- |
| `position` | `esquerda`, `direita` ou `noimage` — ver variações abaixo |
| `subtitle` | texto pequeno acima do título |
| `title` | título — mesmo comportamento do Hero: `*trecho*` já sai como `<strong>trecho</strong>` |
| `content` | HTML do editor WYSIWYG (parágrafos, listas, links) |
| `image` | imagem do bloco — tamanhos `hero_internas` (desktop) / `hero_internas_mobile` (mobile) |

**Variações (`position`):**

| Valor | Label no admin | Diferença |
| --- | --- | --- |
| `esquerda` | Imagem na Esquerda | imagem à esquerda, texto à direita |
| `direita` | Imagem na Direita | imagem à direita, texto à esquerda |
| `noimage` | Só texto, sem imagem | só o bloco de texto — front deve ignorar `image` (continua vindo no JSON, mas vazia) |

### Cards (`type: "cards"`)

| Campo | Descrição |
| --- | --- |
| `variant` | `padrao` ou `compacto` — ver variações abaixo |
| `columns` | `2`, `3` ou `4` — quantas colunas por linha (mesma opção pras duas variações) |
| `items` | lista de cards. Cada item: `{ icon, title, text, link }` |

**Campos de cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `icon` | `{ icon, color, strokeWidth, size }` — specs completas pro front chamar a renderização do ícone (ver abaixo) |
| `title` | título curto do card |
| `text` | texto curto de apoio |
| `link` | `{ title, url, target }` ou `null` quando o card não tem link/CTA |

**`icon` (objeto):**

| Campo | Descrição |
| --- | --- |
| `icon` | slug do ícone [Lucide](https://lucide.dev/icons/) em kebab-case (ex.: `"heart-pulse"`) — string vazia quando o card não tem ícone |
| `color` | cor do traço, hex (ex.: `"#000000"`) |
| `strokeWidth` | espessura do traço (ex.: `1.5`) |
| `size` | tamanho do ícone em px (ex.: `24`) |

**Variações (`variant`):**

| Valor | Label no admin | Diferença |
| --- | --- | --- |
| `padrao` | Padrão | cards com mais respiro — ideal pra destacar poucos pilares/diferenciais |
| `compacto` | Compacto | cards mais densos — ideal pra grades de benefícios (até 12 itens) |

### Cards Editoriais (`type: "cards_editoriais"`)

Cards com imagem de fundo e texto sobreposto — usado pra destacar seções/categorias (ex.: "Tratamentos", "Prescritores", "Pesquisas recentes"). Sem `variant` — um formato só.

| Campo | Descrição |
| --- | --- |
| `items` | lista de cards. Cada item: `{ color, kicker, title, text, link, image }` |

**Campos de cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `color` | `{ red, green, blue, alpha }` ou `null` quando o card não tem cor definida |
| `kicker` | rótulo curto acima do título (ex.: "Tratamentos") — pode vir vazio |
| `title` | título do card |
| `text` | texto curto de apoio |
| `link` | botão do card — `{ title, url, target }` ou `null` quando vazio |
| `image` | imagem de fundo do card, tamanho `galeria` (640×480) |

> No admin, `color`/`kicker`/`title`/`text` ficam agrupados em "Informações do Card" só por organização — no JSON eles saem soltos, direto no item (`image` é campo irmão, fora do grupo).

### Banner CTA (`type: "banner_cta"`)

Banner de largura total com imagem de fundo, overlay de cor e chamada pra ação — pra destacar um convite específico fora do fluxo normal de conteúdo. Sem `variant` — um formato só.

| Campo | Descrição |
| --- | --- |
| `color` | `{ red, green, blue, alpha }` (overlay) ou `null` quando não definida |
| `kicker` | rótulo curto acima do título — pode vir vazio |
| `title` | título — mesmo comportamento do Hero: `*trecho*` já sai como `<strong>trecho</strong>` |
| `text` | texto de apoio |
| `links` | lista de CTAs — mesmo formato do Hero: `{ type: "link", title, url, target }` ou `{ type: "whatsapp", title }` |
| `image` | imagem de fundo — tamanhos `hero_internas` (desktop) / `hero_internas_mobile` (mobile) |

### Passos Numerados (`type: "passos"`)

Lista numerada de etapas (ex.: "Avaliação → Prescrição → Acompanhamento"). Sem `variant` — um formato só.

| Campo | Descrição |
| --- | --- |
| `kicker` | rótulo curto acima do título — pode vir vazio |
| `title` | título — mesmo comportamento do Hero: `*trecho*` já sai como `<strong>trecho</strong>` |
| `text` | descrição opcional abaixo do título |
| `items` | lista de passos. Cada item: `{ icon, title, text }` |

**Campos de cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `icon` | `{ icon, color, strokeWidth, size }` — mesmo formato do campo de ícone do Cards |
| `title` | título do passo |
| `text` | descrição do passo — mesmo comportamento do título (`*trecho*` já sai como `<strong>trecho</strong>`), e preserva quebras de linha literais (`\n`) — o front decide como renderizar a quebra de linha |

### Lista de Interesses WhatsApp (`type: "lista_de_interesses_whatsapp"`)

Chamada + grade de opções selecionáveis (uma por post, ex.: cada tratamento) pra montar uma mensagem de WhatsApp com o que a pessoa marcou. O botão de WhatsApp em si (número, texto) é global do site (Site Options), não faz parte deste componente — ele só entrega a chamada e as opções disponíveis.

Consome o campo "Lista Content Type" (plugin HC Boost ACF) — o campo só guarda a configuração (quais post types, ordem, separado/mixado, paginação, itens por página); quem roda a busca e monta a lista de posts é este componente.

| Campo | Descrição |
| --- | --- |
| `kicker` | rótulo curto acima do título — pode vir vazio |
| `title` | título — mesmo comportamento do Hero: `*trecho*` já sai como `<strong>trecho</strong>` |
| `text` | texto de apoio abaixo do título |
| `lists` | `{ mode, order, pagination, perPage, groups }` — ver abaixo |

**`lists`:**

| Campo | Descrição |
| --- | --- |
| `mode` | `"separate"` (uma lista por post type, cada um na sua ordem de seleção) ou `"mixed"` (todos os post types numa lista só, ordenados juntos) |
| `order` | `"recent"` (mais recentes primeiro), `"oldest"`, `"az"` ou `"za"` |
| `pagination` | `true`/`false` — se a listagem deve ter paginação no front |
| `perPage` | quantidade de itens por página/por lista — em branco no admin, cai pro padrão de Configurações > Leitura do WordPress |
| `groups` | lista de grupos — em `mode: "separate"`, um grupo por post type selecionado; em `mode: "mixed"`, um único grupo com todos juntos |

**Cada item em `groups`:**

| Campo | Descrição |
| --- | --- |
| `postType` | slug do post type (ex.: `"post_tratamento"`), ou `null` quando `mode` é `"mixed"` |
| `total` | total de posts publicados desse post type (ou do conjunto, se mixado) |
| `totalPages` | total de páginas considerando `perPage` |
| `items` | posts da primeira página, cada um: `{ id, title, description, card_text, link }` |

**Cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `title` | título real do post (`get_the_title()`) |
| `description` | campo "Descrição Chamada" (`descricao_chamada`, até 150 caracteres) |
| `card_text` | campo "Descrição Card" (`descricao_card`, até 60 caracteres) — texto pensado pro espaço curto do card selecionável |
| `link` | permalink do post |

### Perguntas e Respostas (`type: "perguntas_e_respostas"`)

Bloco de FAQ — chamada + lista de perguntas/respostas.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — mesmo comportamento do Hero: `*trecho*` já sai como `<strong>trecho</strong>` |
| `text` | texto de apoio — campo textarea com quebra automática de parágrafo (`wpautop` do ACF): já vem com `<p>`/`<br>` prontos, além do `*trecho*` → `<strong>trecho</strong>` |
| `items` | lista de perguntas. Cada item: `{ question, answer }` |

**Campos de cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `question` | texto simples da pergunta |
| `answer` | resposta — mesmo comportamento de `text` (parágrafos automáticos + `*trecho*` → `<strong>trecho</strong>`) |

### Lista de Publicações (`type: "lista_de_publicacoe"`)

> O `type` está com esse nome mesmo (sem o "s" final) — o nome do campo no ACF nasceu truncado ao gerar automaticamente a partir do rótulo "Lista de Publicações"; documentado aqui como realmente está salvo, não é erro de digitação.

Grade de publicações recentes (imagem, categoria, chamada) + link "ver todas". Consome o campo "Lista Content Type" (mesmo campo da Lista de Interesses WhatsApp) — a diferença é só o formato de cada item, que aqui inclui imagem e categoria.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — mesmo comportamento do Hero: `*trecho*` já sai como `<strong>trecho</strong>` |
| `content` | texto de apoio — campo WYSIWYG do ACF, já vem como HTML pronto do próprio editor |
| `lists` | `{ mode, order, pagination, perPage, groups }` — mesmo formato da Lista de Interesses WhatsApp |
| `link_all` | link "ver todas as publicações" — `{ title, url, target }`, ou `null` se não preenchido |

**Cada item em `lists.groups[i].items`:**

| Campo | Descrição |
| --- | --- |
| `title` | título real do post (`get_the_title()`) |
| `description` | campo "Descrição Chamada" (`descricao_chamada`) |
| `card_text` | campo "Descrição Card" (`descricao_card`) — texto curto pro card |
| `category` | `{ name, slug }` da primeira categoria do post, ou `null` se o post type não tiver a taxonomia "category" |
| `image` | imagem destacada do post, tamanho `cards` — mesmo formato de imagem dos outros componentes |
| `link` | permalink do post |

### Galeria de Imagens (`type: "galeria_de_imagens"`)

Grade de fotos, com título e texto de apoio opcionais acima. `subtitle`/`title`/`content` são os mesmos campos "Apresentação" usados em outros componentes (Lista de Publicações etc.) — não vieram do JSON original do Lovable, foram adicionados aqui pra manter a mesma convenção do resto do template.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — mesmo comportamento do Hero: `*trecho*` já sai como `<strong>trecho</strong>`; pode vir vazio |
| `content` | texto de apoio — campo WYSIWYG do ACF, já vem como HTML pronto do próprio editor |
| `columns` | `2`, `3`, `4` ou `6` — quantas colunas o grid deve ter |
| `items` | lista de fotos. Cada item: `{ alt, url, thumbnail }` |

**Cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `alt` | texto alternativo da imagem |
| `url` | tamanho `galeria_zoom` (até 1500px de largura, sem crop — nunca amplia além do original) — pro lightbox/zoom (ex.: Fancybox) |
| `thumbnail` | `{ url, webp, width, height }` — tamanho `galeria` (640×480, com crop) — pra grade |

### Depoimentos (`type: "depoimentos"`)

Carrossel de depoimentos (paciente, cliente etc.), com título e texto de apoio opcionais acima. O JSON é só a lista de itens, na ordem cadastrada — montar o carrossel (arrastar/setas/autoplay) é responsabilidade do front.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong; pode vir vazio |
| `content` | texto de apoio — campo WYSIWYG do ACF, já vem como HTML pronto do próprio editor |
| `items` | lista de depoimentos. Cada item: `{ text, name, complement }` |

**Cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `text` | o depoimento em si — aceita texto 3.strong, e já vem com `<br>` pronto nas quebras de linha (campo com `new_lines: "br"` no ACF) |
| `name` | nome de quem deu o depoimento |
| `complement` | informação complementar opcional (ex.: cidade/UF) — pode vir vazio |

### CTA Simples (`type: "cta_simples"`)

Chamada final de seção, sem imagem — título, texto de apoio e um link só. Pra chamada com imagem/overlay/múltiplos botões (link ou WhatsApp), ver Banner CTA.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong |
| `content` | texto de apoio — campo WYSIWYG do ACF, já vem como HTML pronto do próprio editor |
| `link` | `{ title, url, target }`, ou `null` se não preenchido |

### Cards Carrossel (`type: "cards_carrossel"`)

Igual ao Cards no formato de cada item, mas em carrossel (arrastar com o mouse, setas ou bullets — comportamento real é do front, o JSON só entrega a lista final de items). Quem edita o conteúdo escolhe a origem dos cards: um repetidor manual, ou uma lista de posts de algum content type — nos dois casos o item final sai no mesmo formato, o front não precisa saber qual foi a origem.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong |
| `columns` | `2`, `3` ou `4` — quantos cards ficam visíveis por vez no carrossel (não é grade) |
| `items` | lista de cards, sempre no mesmo formato do Cards: `{ icon, title, text, link }` |

**Quando a origem é uma lista de posts (campo "Tipo do Card" = "Lista de Conteúdos"):**

| Campo do item | De onde vem |
| --- | --- |
| `icon` | campo "Ícone" (`icone_lucide`, grupo "Apresentação do Conteúdo") — sem ícone escolhido, cai pro padrão `Sparkle` |
| `title` | título real do post (`get_the_title()`) |
| `text` | campo "Descrição Card" (`descricao_card`) |
| `link` | permalink do post, com `title` vazio |

A quantidade de posts é a própria "Quantidade por página" do campo "Lista Content Type" — não tem campo extra pra isso; o campo "Paginação" dele não tem efeito aqui, carrossel não pagina.

### Big Numbers (`type: "nossos_numeros"`)

Cartões de número em destaque (ex.: "5 canabinoides em estudo") — bom pra abrir seções científicas/institucionais com dados objetivos.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong |
| `columns` | `2`, `3` ou `4` — quantas colunas por linha |
| `items` | lista de números. Cada item: `{ value, title, description }` |

**Cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `value` | o número/dado em destaque — texto solto de propósito, nem todo item é numérico (ex.: `"COA"`, `"17025"`) |
| `title` | rótulo curto do dado |
| `description` | texto de apoio opcional — pode vir vazio |

### Conteúdo em Abas (`type: "conteudo_abas"`)

Conteúdo em abas (ex.: comparar canabinoides — "CBD", "THC", "CBG", cada um com seu próprio texto). O JSON entrega a lista de abas na ordem cadastrada; alternar entre elas é responsabilidade do front.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong |
| `content` | texto de apoio — campo WYSIWYG do ACF, já vem como HTML pronto do próprio editor |
| `tabs` | lista de abas. Cada item: `{ label, code, content }` |

**Cada item em `tabs`:**

| Campo | Descrição |
| --- | --- |
| `label` | rótulo da aba (ex.: "Canabidiol") — é o que aparece na própria aba/botão de navegação |
| `code` | sigla curta (ex.: "CBD") — **opcional**, pode vir vazio; quando preenchida, aparece em destaque ao lado do conteúdo (não na aba); vazia, o conteúdo ocupa a largura toda |
| `content` | conteúdo da aba — campo WYSIWYG do ACF, já vem como HTML pronto |

### Cuidados com a Formulação / COA (`type: "cuidados_da_formulacao"`)

Texto + selos de confiança à esquerda, um cartão central com uma sigla em destaque (ex.: "COA" — Certificate of Analysis), e uma lista de análises de segurança à direita.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong |
| `content` | texto de apoio — texto simples (sem `<p>`/`<br>` automático) |
| `badges` | lista de selos de confiança (texto solto, ex.: "Análises independentes") — só os preenchidos entram na lista (o campo tem 1 selo obrigatório + 2 opcionais) |
| `code` | sigla em destaque no cartão central (ex.: "COA") — opcional, pode vir vazio |
| `code_title` | legenda abaixo da sigla no cartão (ex.: "Certificado de Análise") |
| `items` | lista de análises de segurança. Cada item: `{ icon, title, description }` |

**Cada item em `items`:**

| Campo | Descrição |
| --- | --- |
| `icon` | `{ icon, color, strokeWidth, size }` — mesmo formato de ícone dos outros componentes |
| `title` | nome curto da análise (ex.: "Metais pesados") |
| `description` | descrição curta (ex.: "Contaminantes inorgânicos.") |

### Timeline (`type: "timeline"`)

Linha do tempo (ex.: rastreabilidade "Origem → Lote → COA") — lista de etapas em ordem, cada uma com título curto e descrição.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong |
| `content` | texto de apoio — campo WYSIWYG do ACF, já vem como HTML pronto do próprio editor |
| `items` | lista de etapas, na ordem cadastrada. Cada item: `{ title, description }` |

---

### Manifesto (`type: "manifesto"`)

Bloco de frases grandes em fundo de destaque (cor customizável por instância) — usado pra declarações fortes tipo "segurança também é reconhecer limites". `title` é a primeira frase grande; cada item de `items` é uma frase seguinte, separada por um filete. Todas aceitam texto 3.strong.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | primeira frase grande — aceita texto 3.strong |
| `content` | texto de apoio — campo WYSIWYG do ACF, já vem como HTML pronto do próprio editor |
| `link` | link opcional — `{ title, url, target }` ou `null` |
| `background_color` | cor de fundo do bloco (hex), definida pelo editor |
| `text_color` | cor do texto principal (hex), definida pelo editor |
| `highlight_color` | cor do trecho em destaque (texto 3.strong) dentro das frases grandes |
| `items` | frases seguintes, na ordem cadastrada. Cada item: `{ title }`, aceita texto 3.strong |

---

### Banner App (`type: "banner_app"`)

Divulgação do app Nabismed — duas fotos do app em uso (paisagem + retrato, sobrepostas) em fundo com gradiente diagonal (135º, canto superior esquerdo → canto inferior direito), cores customizáveis por instância.

| Campo | Descrição |
| --- | --- |
| `subtitle` | rótulo curto acima do título — pode vir vazio |
| `title` | título — aceita texto 3.strong |
| `content` | texto de apoio — campo de área de texto simples do ACF |
| `link` | link opcional — `{ title, url, target }` ou `null` |
| `background_color_start` | cor inicial do gradiente de fundo (hex), definida pelo editor |
| `background_color_end` | cor final do gradiente de fundo (hex), definida pelo editor |
| `text_color` | cor do texto principal (hex), definida pelo editor |
| `highlight_color` | cor do trecho em destaque (texto 3.strong) dentro do título |
| `image_landscape` | foto do app em orientação paisagem — `{ alt, desktop: { url, width, height, webp } }` |
| `image_portrait` | foto do app em orientação retrato, sobreposta à imagem paisagem — mesma forma de `image_landscape` |

---

_Componentes novos entram aqui assim que forem adicionados ao template._
