# Design Tokens — Horus BI

## Conteúdo

- 1. Paleta categórica acessível (Okabe-Ito)
- 2. Sequencial e divergente
- 3. Semântica fixa
- 4. `visualType` — quando usar cada valor
- 5. Tipografia e números
- 6. Máximo de cores por gráfico
- 7. Accent por app (`color` + `icon` no corpo do app)
- 8. Light como tema padrão do Horus

Single source of truth for palette, semantic color, `visualType`, typography,
`color`/`icon` do app, and the light/dark context used in Lumo widget YAML.

---

## 1. Paleta categórica acessível (Okabe-Ito)

Use esta paleta — e somente ela — para séries discretas (categorias sem ordem,
legendas de linha, fatias de pizza, barras multi-série). É perceptivamente
distinguível mesmo em daltonismo protanopia/deuteranopia.

| # | Nome | Hex | Uso típico |
|---|------|-----|------------|
| 1 | Laranja | `#e69f00` | Primeira série de destaque |
| 2 | Azul céu | `#56b4e9` | Segunda série |
| 3 | Verde | `#009e73` | Terceira série |
| 4 | Amarelo | `#f0e442` | Quarta série (evitar fundo branco — baixo contraste) |
| 5 | Azul | `#0072b2` | Quinta série |
| 6 | Vermelhão | `#d55e00` | Sexta série |
| 7 | Roxo-rosa | `#cc79a7` | Sétima série |
| 8 | Preto | `#000000` | Reserve para enfatizar ou legendas (não como série padrão) |

**Regra de uso:** aplique Okabe-Ito exclusivamente para dados categóricos. Nunca
use esta paleta para representar magnitude, gradiente ou desvio em torno de um
centro — esses casos têm paletas próprias nas seções 2 e 3.

Máximo prático por gráfico: 6–7 cores. Acima disso, agrupe o restante em "Outros"
com cinza `#9ca3af` — o olho humano não distingue mais que isso.

---

## 2. Sequencial e divergente

### 2.0 Primeiro decida a família, só depois escolha a paleta

Errar a **família** de escala é um erro de leitura, e nenhuma paleta bonita
conserta. Decida nesta ordem, antes de abrir a lista de hex:

```
o campo em `color` é categórico (loja, canal, status, produto)?
├─ sim → CATEGÓRICA (teto de cores na §6)
└─ não, é quantitativo, data ou rank
   ├─ é data ou rank? ───────────────→ SEQUENCIAL (ver 2.0b)
   └─ é medida
      ├─ existe um ponto médio com significado (zero, meta, média)
      │  E o dado cai dos dois lados dele? ──→ DIVERGENTE (declare o midpoint)
      └─ caso contrário ─────────────────────→ SEQUENCIAL
```

**A cláusula "e o dado cai dos dois lados" não é detalhe.** Uma paleta
divergente aplicada a dado que só tem um lado gasta metade da rampa numa cor que
sugere um sinal inexistente: um mapa de receita (sempre positiva) em RdBu faz os
estados de receita baixa parecerem negativos. Se todos os valores estão do mesmo
lado do ponto médio, use sequencial mesmo que a métrica "possa" ser negativa em
teoria.

Três classes de medida, por como se comportam:

| Comportamento | Métricas | Escala |
|---|---|---|
| **Sempre divergente** (o centro é intrínseco) | correlação, NPS, satisfação em escala centrada, variação percentual | divergente, sempre, mesmo que o dado do período só tenha um lado |
| **Divergente conforme o dado** | lucro, resultado, desvio de meta, saldo | divergente **se** cruzar o midpoint; sequencial se não cruzar |
| **Nunca divergente** | receita, quantidade, contagem, ticket, prazo | sequencial, porque não existe "lado negativo" com significado |

A diferença entre as duas primeiras linhas importa na virada de período: uma
métrica "divergente conforme o dado" muda de família quando o filtro muda, e a
legenda precisa acompanhar. Uma métrica "sempre divergente" mantém a mesma
escala em todo período, o que preserva a comparabilidade entre telas.

### 2.0b Data e rank em cor são rampa, nunca categoria

Dois casos que a plataforma aceita e que quase sempre saem errados:

- **Data ou período no canal de cor.** Doze meses em paleta categórica viram
  doze cores sem relação entre si, e o olho perde a ordem que é justamente o
  ponto de mostrar tempo. Use rampa sequencial, do claro (mais antigo) ao escuro
  (mais recente).
- **Rank ou posição no canal de cor.** Mesma coisa: 1º, 2º e 3º têm ordem. Use
  rampa, com o extremo **escuro no 1º lugar** (rampa invertida em relação ao
  valor numérico, porque rank 1 é o "maior" em significado e o menor em número).

Se o número de períodos ou posições for pequeno e a comparação for entre eles
(não a ordem), aí sim categórica se justifica. O teste: se alguém pode perguntar
"qual veio antes?", é rampa.

### Sequencial — magnitude pura

Use single-hue (Blues, Greens) ou Viridis para representar intensidade contínua
onde o zero é um extremo natural. Aplicações típicas: heatmap, choropleth, calendar.

- **Blues** (claro→escuro): `#dbeafe` → `#1e40af` — mapas de receita por estado,
  calendários de volume.
- **Greens** (claro→escuro): `#dcfce7` → `#15803d` — conversão, saúde operacional.
- **Viridis** (amarelo→verde→roxo): recomendado quando o mapa precisa funcionar
  em impressão P&B ou com acessibilidade máxima.

No widget Map (choropleth), configure `colorScale.min` e `colorScale.max` com os
extremos hex da rampa escolhida:

```yaml
colorScale:
  min: "#dbeafe"   # azul claro
  max: "#1e40af"   # azul escuro
```

### Divergente — desvio de um ponto médio

Use quando o zero (ou meta, ou média) tem significado próprio e os dois lados
divergem em sentido oposto. Paleta canônica: **RdBu** (vermelho→branco→azul).

- Abaixo da meta / negativo: tons de vermelho (`#ef4444` → `#fca5a5`)
- Ponto médio / neutro: branco ou cinza claro `#f1f5f9`
- Acima da meta / positivo: tons de azul (`#93c5fd` → `#1d4ed8`)

Applique divergente no Gauge via `colorScale` com três bandas: zona vermelha
(`#dc2626`), zona âmbar (`#f59e0b`) e zona verde (`#22c55e`) — veja seção 3.

---

## 3. Semântica fixa

Estes tokens têm significado de negócio fixo em toda a plataforma Horus. Aplique-os
de forma consistente em todos os dashboards do mesmo app. Jamais reutilize uma cor
semântica para um conceito diferente no mesmo projeto.

| Significado | Hex | Onde usar |
|---|---|---|
| Receita / positivo | `#22c55e` | KPI de receita, banda verde no Gauge, seta de variação positiva |
| Custo / negativo | `#ef4444` | KPI de custo, banda vermelha no Gauge, seta de variação negativa |
| Alerta / atenção | `#f59e0b` | Banda âmbar do Gauge, indicadores de atenção |
| Volume / neutro | `#3b82f6` | Contagens, volumes sem conotação positiva/negativa |
| % / margem | `#8b5cf6` | KPIs de margem, participação percentual |
| Contexto (base) | `#6b7280` | Período anterior, baseline, série de referência |
| Contexto (suave) | `#9ca3af` | Grid lines, eixos, séries "Outros", dados secundários |
| Título (escuro) | `#37474f` | Texto principal de título em fundo claro |
| Título (alternativo) | `#334155` | Variação slate para títulos em cards com accent |

### Aplicação nos KPIs

```yaml
visual:
  numberColor:
    value: "#22c55e"           # receita, fallback se expression não resolver
    expression:
      js: "return this.primaryValue >= 0 ? '#22c55e' : '#ef4444'"  # dinâmico por sinal
  titleColor:
    value: "#37474f"
```

`expression` só é lida quando é um objeto com `.js` (string não vazia). Uma string
crua com sintaxe de planilha, como `"if(value>=0,'#22c55e','#ef4444')"`, não casa
nenhuma forma aceita, e o renderer cai em silêncio no `.value`. O corpo do `.js`
precisa de `return` e lê `this.primaryValue` / `this.secondaryValue`, nunca `value`.

Duas armadilhas próximas no mesmo bloco: `numberFormat.expression` nunca é lida
pelo renderer em nenhum caminho, use `numberFormat.format`; e `kpiVersion` é
comparado com `=== 2` estrito, então `kpiVersion: "2"` (string entre aspas) cai
silenciosamente no KPI clássico.

No Gauge, mapeie as três semânticas nas bandas:

```yaml
colorScale:
  - { from: 0,  to: 50,  bgColor: "#ef4444", name: "Abaixo da meta" }
  - { from: 50, to: 80,  bgColor: "#f59e0b", name: "Próximo da meta" }
  - { from: 80, to: 100, bgColor: "#22c55e", name: "Na meta" }
```

---

## 4. `visualType` — quando usar cada valor

`visualType` está disponível nos widgets: Barline, Pie, Gauge, Map, Punchcard e
Forecast. Escolha uma estratégia por dashboard e seja consistente — misturar os
três valores no mesmo dashboard produz um resultado visualmente ruidoso.

### `transparent` — o default recomendado

Remove o card (sem fundo, sem borda, sem sombra). Use `transparent` para a maioria
dos gráficos num dashboard moderno: produz um visual borderless, limpo, onde os
dados fluem sem moldura.

```yaml
visualType: "transparent"
```

Restrição: com `transparent`, `backgroundColor` não tem efeito — não o defina.

### `default` — card com moldura

Apresenta o widget em um card com fundo e borda. Use com parcimônia: máximo
1–2 widgets por dashboard recebem `default` para criar ênfase. Um dashboard
inteiro em `default` parece uma grade de caixas repetidas — o look mais vanilla
do Horus.

```yaml
visualType: "default"
backgroundColor: "#ffffff"    # opcional; vazio = fundo do tema
```

### `highlight` — card elevado / accent

Card com elevação e/ou cor de accent. Reserve para o gráfico-assinatura do
dashboard — o que conta a história principal. Um único widget `highlight` por
tela cria hierarquia visual; dois ou mais diluem o efeito.

```yaml
visualType: "highlight"
```

### Regra de consistência

| Estratégia | `transparent` | `default` | `highlight` |
|---|---|---|---|
| Dashboard moderno borderless | todos | — | 0–1 assinatura |
| Dashboard clássico com cards | — | todos | 1 assinatura |
| Misto disciplinado | maioria | 1–2 ênfases | 1 assinatura |

Nunca misture `transparent` e `default` no mesmo dashboard sem uma razão
deliberada — o contraste entre cards com moldura e sem moldura parece erro, não
design.

---

## 5. Tipografia e números

### Hierarquia máxima de 4 níveis

| Nível | Elemento | Tamanho referência |
|---|---|---|
| 1 | Número KPI (primário) | `numberSize: 30` |
| 2 | Título do card / KPI | `titleSize: 16` |
| 3 | Corpo (labels de eixo, legenda) | padrão do tema |
| 4 | Caption (anotações, drilldown) | menor que o corpo |

Acima de 4 níveis o olho começa a perder a hierarquia. Se precisar de mais,
consolide — não invente um quinto tamanho.

### KPI: tamanhos e formato

```yaml
visual:
  numberSize: 30    # número grande e legível
  titleSize: 16     # título abaixo do número
  numberFormat:
    format: "reduced"   # 1.2M, 34K, 2.8B — nunca escreva o número inteiro num KPI
```

Use `numberFormat.format: "reduced"` (K/M/B) por padrão para KPIs de volume e
receita. Para percentuais use `"default"` com `mask: PCT2` na expressão de origem.
Para valores monetários com casas decimais use `"currency"`.

### Cores de texto — nunca preto puro

Preto puro (`#000000`) em texto cria contraste excessivo no tema claro do Horus e
parece "pesado". Use os tokens de título da seção 3:

- Títulos: `#37474f` ou `#334155`
- Contexto/secundário: `#6b7280`
- Mais suave ainda: `#9ca3af`

---

## 6. Máximo de cores por gráfico

**Regra: máximo 6–7 cores, 1 accent racionado, cinza de contexto.**

### Como aplicar

- Escolha 1 cor de accent (a da marca / seção 7) e use-a na série principal ou
  no elemento mais importante do gráfico.
- As demais séries recebem cores da paleta Okabe-Ito (seção 1) ou tons de cinza
  dessaturado (`#6b7280`, `#9ca3af`) para dados secundários.
- "Outros" / "Restante" sempre em cinza `#9ca3af`.

### Onde configura no YAML

**Por série (Barline `valueConfig`):**

```yaml
valueConfig:
  0:
    coloringType: "byValue"
    color: "#0072b2"    # accent — série principal
  1:
    coloringType: "byValue"
    color: "#9ca3af"    # cinza — série de referência
```

**Por categoria (Barline `dimensionConfig`, Pie `xAxisColumnsConfig`):**

```yaml
# Barline 2D
dimensionConfig:
  - { value: "Norte",   color: "#0072b2" }
  - { value: "Sudeste", color: "#e69f00" }
  - { value: "Sul",     color: "#009e73" }

# Pie
xAxisColumnsConfig:
  - { value: "Norte",   color: "#0072b2" }
  - { value: "Outros",  color: "#9ca3af" }
```

A lógica `byExpression` permite colorir dinamicamente via JS inline —
útil para regras semânticas (positivo/negativo):

```yaml
axisColoringType: "byExpression"
axisColoringExpression: "if(value > 0) return '#22c55e'; return '#ef4444';"
```

---

## 7. Accent por app (`color` + `icon` no corpo do app)

> **Não existe wrapper `app_data`.** `nome`, `icon` e `color` ficam soltos no
> corpo do arquivo do app (`apps/<slug>--<id>.yaml`). Confira com
> `lumo scaffold app`.

Cada app Horus tem uma cor de accent e um ícone que aparecem na barra de navegação
e diferenciam visualmente o app de outros no mesmo tenant. Defina sempre — os
defaults são genéricos demais.

```yaml
# corpo do app (sem wrapper)
nome: "Hotelaria — Ocupação"
  color: "#0072b2"          # hex lowercase; aparece na nav e como accent do app
  icon: "mdi:bed"           # Iconify — escolha ícone do domínio do negócio
```

### Escolha do `color`

O `color` deve ser a cor de accent dominante do dashboard — a mesma que você usa
no KPI principal ou na série de destaque. Não use uma cor que não aparece nos dados:
o accent da nav e o accent do conteúdo devem ser o mesmo token.

### Ícones Iconify por domínio

| Domínio | Ícone sugerido |
|---|---|
| Hotelaria | `mdi:bed` |
| Posto de combustível | `mdi:gas-station` |
| Varejo / loja | `mdi:store` |
| Logística | `mdi:truck` |
| Financeiro | `mdi:chart-line` |
| RH | `mdi:account-group` |
| Saúde | `mdi:hospital-box` |
| Construção civil | `mdi:crane` |

Qualquer ícone do catálogo Iconify funciona — use `mdi:`, `tabler:`, `ph:` ou
outros prefixos disponíveis. Escolha o que for mais reconhecível para o público
do app.

---

## 8. Light como tema padrão do Horus

**O tema padrão do Horus é light.** Cards têm fundo `#ffffff`, o fundo do
dashboard é cinza claro (`#f8fafc` / `#f1f5f9`). Todas as escolhas de cor desta
referência assumem fundo claro.

Consequências práticas:

- `backgroundColor` em cards `default` dispensa configuração explícita — vazio
  resulta em `#ffffff` automaticamente.
- `valueColor` vazio herda o cinza de texto do tema (`#374151`) — adequado para
  a maioria dos eixos.
- O amarelo Okabe-Ito `#f0e442` tem baixo contraste sobre branco: evite-o como
  cor de texto ou borda; funciona bem como preenchimento de barra larga.

### Dark

O tema escuro é suportado pela plataforma e é o default dos **Svelte widgets**.
Para configuração detalhada de dark — variáveis CSS, paleta adaptada, Tailwind
`dark:` classes — consulte `references/svelte-widgets.md`. Esta referência não
detalha dark; tudo aqui é light-first.
