# 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 ```