# Diretrizes de tema: claro e escuro
Regras para manter os dois temas consistentes. Não tratam da identidade visual
(degradê da marca, arredondamentos, hierarquia): essas decisões já estão tomadas
e não mudam.
---
## 1. Só existem os tons declarados
A escala do Tailwind anda de 100 em 100: `50, 100, 200, 300 … 900, 950`.
Os meios-tons (`150, 250, 350, 450, 550, 650, 750, 850`) **não existem por
padrão**. Uma classe como `text-slate-350` não gera CSS nenhum, e o elemento
herda silenciosamente a cor do pai. Era a causa da maior parte das
inconsistências: o mesmo texto ficava certo num tema e lavado no outro.
Os oito meios-tons estão declarados em [`app/globals.css`](app/globals.css),
cada um no ponto médio exato entre os dois degraus oficiais vizinhos:
| tom | hex | entre |
|-----|-----|-------|
| `slate-150` | `#eaeef4` | 100 e 200 |
| `slate-250` | `#d6dee8` | 200 e 300 |
| `slate-350` | `#b0bccc` | 300 e 400 |
| `slate-450` | `#7c8ca2` | 400 e 500 |
| `slate-550` | `#56647a` | 500 e 600 |
| `slate-650` | `#3d4b5f` | 600 e 700 |
| `slate-750` | `#283548` | 700 e 800 |
| `slate-850` | `#162032` | 800 e 900 |
**Precisa de um tom novo?** Declare no `@theme` antes de usar. Nunca escreva uma
classe com um valor que não está na escala.
---
## 2. A regra da simetria
No tema escuro o fundo é escuro, então o texto precisa **clarear**.
- **Texto:** o número do `dark:` é sempre **menor** que o do claro.
`text-slate-800 dark:text-slate-200` ✅ · `text-slate-400 dark:text-slate-500` ❌
- **Fundo e borda:** o número do `dark:` é sempre **maior**.
`bg-white dark:bg-slate-900` ✅ · `border-slate-200 dark:border-slate-800` ✅
Um par invertido não quebra o build e não aparece em revisão de código: só
enfraquece o contraste justamente onde ele deveria aumentar. Havia 71 deles.
---
## 3. Pares canônicos por papel
Use estes. Os números são a razão de contraste no tema claro (sobre branco) e no
escuro (sobre `slate-900`). O mínimo para texto é **4.5:1**; para ícone ou
elemento gráfico, **3:1**.
| Papel | Par | Claro | Escuro |
|-------|-----|-------|--------|
| Título / texto principal | `text-slate-850 dark:text-slate-100` | 16.3 | 16.3 |
| Texto principal alternativo | `text-slate-800 dark:text-slate-200` | 14.6 | 14.5 |
| Texto secundário | `text-slate-700 dark:text-slate-300` | 10.4 | 12.0 |
| Corpo de leitura | `text-slate-650 dark:text-slate-350` | 8.9 | 9.3 |
| Rótulo / apoio | `text-slate-500 dark:text-slate-400` | 4.8 | 7.0 |
| Menor ênfase | `text-slate-500 dark:text-slate-450` | 4.8 | 5.2 |
`text-slate-500 dark:text-slate-400` é o par mais usado do projeto (100
ocorrências): é o rótulo em caixa alta, a legenda, o texto de apoio.
**Não use** `slate-400` como cor de texto no tema claro: sobre branco dá 2.56:1.
Era o erro mais repetido.
---
## 4. Exceção: superfícies escuras nos dois temas
Alguns blocos são escuros independente do tema: painéis de CTA com degradê,
bloco de código, tooltip do diagrama de pinos. Dentro deles:
- **Não use variante `dark:`.** O fundo não muda, então o texto também não deve.
- Escreva um valor só: `text-slate-200`, `text-white`, `text-slate-350`.
Exemplo correto, no bloco de código ([`CodeExample.jsx`](components/CodeExample.jsx)):
```jsx
{/* escuro sempre */}
{idx + 1} {/* sem dark: */}
```
---
## 4.1. Botão nunca inverte preto ↔ branco
O padrão abaixo **não é aceitável**, apesar de tecnicamente "acompanhar" o tema:
```jsx
/* ERRADO: preto no tema claro, branco no escuro */
className="bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-950"
```
Ele deixa a interface sem cor e faz o mesmo botão trocar completamente de
aparência entre os temas. Botão carrega a **própria cor**, igual nos dois temas:
| Papel | Classe | Contraste com texto branco |
|-------|--------|---------------------------|
| Ação principal | `bg-gradient-to-r from-purple-600 to-indigo-600 text-white` | 5.4 a 6.3 |
| Ação secundária | `bg-blue-600 hover:bg-blue-700 text-white` | 5.2 |
| Ação terciária | `bg-white dark:bg-slate-900` + borda + texto neutro | · |
Como a cor é a mesma nos dois temas, o texto branco também é, sem variante
`dark:`. Isso vale inclusive dentro de painel escuro: o botão de gradiente
funciona sobre fundo claro e escuro.
---
## 4.2. Painel de destaque acompanha o tema
Painéis de CTA não podem ficar escuros no tema claro. O padrão é um degradê
suave da marca no claro e o degradê escuro no escuro:
```jsx
className="bg-gradient-to-br from-indigo-50 to-purple-50
dark:from-slate-950 dark:to-purple-950/20
border border-indigo-200 dark:border-slate-800/80"
```
Com o conteúdo pareado: título `text-slate-900 dark:text-white`, corpo
`text-slate-600 dark:text-slate-400`, etiqueta
`bg-indigo-500/10 text-indigo-700 dark:bg-white/10 dark:text-slate-350`.
---
## 4.3. Separação de superfície no tema claro
Entre near-whites o contraste é sempre baixo. Quem separa o card do fundo é a
**borda**, não o fundo. Por isso:
| Camada | Valor | Contraste |
|--------|-------|-----------|
| Fundo da página | `from-slate-100 via-slate-50 to-purple-100/40` | · |
| Card | `bg-white` (sólido, sem `/70` ou `/80`) | 1.10 vs fundo |
| Borda do card | `border-slate-300` | 1.48 vs card |
| Divisória interna | `border-slate-200` | mais sutil |
`border-slate-200/60` sobre card branco dava 1.14, praticamente imperceptível. Não use borda
com opacidade para delimitar card.
---
## 5. Cor de série vem do dado, não da classe
Cada série tem sua cor em [`public/series.json`](public/series.json) (campo
`cor`). Ela entra por `style`, nunca por classe Tailwind:
```jsx
```
Esses valores são iguais nos dois temas. Ao usá-los como **fundo**, mantenha o
alfa baixo (de `18` a `30` em hex) para que funcione sobre superfície clara e escura.
Como **texto**, só sobre superfície neutra, nunca sobre outra cor de série.
### Nunca escreva cor fixa em `style` inline
`style` inline vence qualquer classe, inclusive as variantes `dark:` e
`hover:`, que deixam de existir para aquele elemento. Só cor **vinda do dado**
pode ir no inline.
```jsx
/* ERRADO: anula border-slate-300 e dark:border-slate-800/80 */
/* ERRADO: 'transparent' anula também o hover:border-purple-400/50 */
style={{ borderColor: isSelected ? serie.cor : 'transparent' }}
/* CERTO: sem valor, a classe assume o estado de repouso */
style={{ borderColor: isSelected ? serie.cor : undefined }}
```
Esse bug é traiçoeiro porque **some depois do primeiro hover**: o
`onMouseLeave` faz `style.borderColor = ''`, que apaga o inline e devolve o
controle à classe. A borda passa a aparecer e nunca mais some, dando a
impressão de que só falha "antes de passar o mouse".
---
## 6. Auditar antes de commitar
```bash
npm run auditar-temas
```
Aponta quatro coisas: tom que não existe na escala, par invertido, par abaixo de
AA e cor fixa em `style` inline. Saída esperada: tudo zero.
Sai com código 1 se achar problema, então serve direto em hook de pre-commit ou
em CI:
```bash
npm run auditar-temas && npm run build
```