---
name: lumo-mockup
description: Use quando for preciso mostrar visualmente a um cliente como um dashboard de BI vai ficar antes de existir dado real, a partir da ficha de widget que a lumo-scope já escreveu no bundle. Dispara quando o usuário pede "mockup", "wireframe", "protótipo visual", "prévia visual", "render pra proposta", algo pra "mandar pro cliente ver como vai ficar", ou menciona "forma sem magnitude", "gráfico sem dado real", "renderizar a ficha". Não cobre a montagem do documento de escopo/proposta/termo de abertura (isso é a lumo-scope) nem a estética do dashboard entregue com dado real (isso é a lumo-design).
---

# Lumo Mockup

Você transforma a ficha de widget, já escrita pela `lumo-scope`, num mockup que
o cliente olha antes de assinar. O produto é uma **perspectiva artística**:
bonita o bastante pra vender, honesta o bastante pra nunca virar promessa sobre
um número que ninguém mediu.

> **Cadeia:** `lumo-scope` (escreve a ficha e monta o documento) →
> **`lumo-mockup`** (ficha → render) → `lumo-design` (estética do dashboard
> entregue, depois do dado real). Esta skill não escreve escopo, não decide
> arquétipo de tela e não monta documento. Recebe a ficha e devolve o visual.

## 1. As duas regras que decidem tudo

**Fidelidade ao produto, não à marca.** O render precisa parecer o Horus BI que
o cliente vai receber: card branco com borda, Poppins, a paleta de 11 cores de
`Configs.ts`, gridline `#efefef`, grid de 12 colunas. Nunca o bege e laranja da
marca da Horus-empresa.

**Forma sem magnitude.** Renderize tudo que você sabe (tipo de gráfico,
categorias reais, ordenação, formato) e substitua o que ninguém mediu (o valor)
pelo **placeholder**: um número em formato real, o **mesmo** em todo o widget.

`R$ 123.456,78` repetido em toda barra não pode ser lido como dado, porque o
olho vê o padrão antes do número. Um valor que **varia** junto com a altura da
barra seria indistinguível de um gráfico real, e aí a peça vira promessa.

## 2. O fluxo, do começo ao fim

`exemplo/render-prova.py` é este fluxo inteiro, executável. Rode antes de
adaptar:

```bash
python3 exemplo/render-prova.py
```

### Passo 1 — leia a ficha

A ficha vem de `dashboards/<dash>.md` do bundle de escopo, escrita pela
`lumo-scope`. Os campos que decidem o render:

| Campo | Decide |
|---|---|
| `relacao` | qual widget do produto e qual spec (passo 3) |
| `forma` | a forma da série (`pareto`, `sazonal`, `tendencia-alta`, `plato`, `cauda-longa`) |
| `metricas` | a escala do placeholder, via `aditividade:` da ficha da métrica |
| `encoding` | datalabels, legenda, formato |
| `confianca` | o marcador que o widget exibe |
| `posicao` | `w`/`h` no grid de 12, que viram as dimensões do spec |

### Passo 2 — escolha a escala do placeholder

```python
from placeholder import placeholder, escala_de, acima, contagem   # scripts/
P = placeholder(escala_de("faturamento mensal"))   # 'R$ 123.456,78'
```

| Tipo | Placeholder |
|---|---|
| monetário | pela escala: `R$ 123,45` até `R$ 123.456.789,01` |
| contagem | `1.234` (sem decimal) |
| percentual | `12,3%` (ou `123,4%` se passa de 100%) |
| tempo | `1h 23m`, `12m 34s`, `1d 2h 34m` |

Regras: **o mesmo placeholder em todo o widget**; a linha `Total` usa
`acima(escala)`; a mesma métrica usa a mesma escala em toda a tela. A escala sai
da âncora do cliente quando existir, senão do default por métrica.

> Escala fora da tabela, âncora ausente, dúvida de arredondamento:
> [`references/forma-sem-magnitude.md`](references/forma-sem-magnitude.md) §2.

### Passo 3 — `relacao` → widget do produto → spec

| `relacao` | Widget | mark | Nota |
|---|---|---|---|
| `magnitude` | `Barline` | `bar` vertical | data label em cima |
| `ranking` | `Barline` | `bar` horizontal | `sort: "-x"`, label à direita |
| `tendencia` | `Barline` | `line` + `point` | `shape` por série a partir de 2 |
| `tendencia-inline` | `Kpi` | `line` ≤80×24px | `axis: null` nos dois canais |
| `parte-todo` | `Pie` (≤4) ou `Barline` (≥5) | `arc` ou `bar` | **sempre donut**; com 5+, `legenda: nao` → barra ordenada, `legenda: sim` → empilhada 100% |
| `distribuicao` | `Barline` ou `Punchcard` | `bar` ou `rect` | faixas rotuladas |
| `correlacao` | `Barline` | `point` | dois eixos sem rótulo |
| `desvio` | `Gauge` | `arc` em camadas | arco e faixas, **sem ponteiro** |
| `espacial` | `Map` | `geoshape` | geometria sim, escala de cor não |
| `fluxo` | `Diagram`/`Svelte` | `custom: true` | sinalize tier XL |
| `metrica-unica` | `Kpi` | HTML | slots do `KpiV2` |
| `lookup` | `Table` ou `Matrix` | HTML | coluna `#`, `Total` uma escala acima |

O spec do caso comum, com o data label constante:

```python
{
  "data": {"values": dados},
  "width": w, "height": h,                    # de dim(col, row), passo 5
  "layer": [
    {"mark": {"type": "bar", "width": 26},
     "encoding": {
       "x": {"field": "c", "type": "nominal", "sort": None,
             "axis": {"title": None, "labelAngle": 0}},
       "y": {"field": "v", "type": "quantitative",
             "axis": {"title": None, "labels": False}}}},   # gridline fica
    {"mark": {"type": "text", "dy": -9, "fontSize": 10, "color": "#000"},
     "encoding": {
       "x": {"field": "c", "type": "nominal", "sort": None},
       "y": {"field": "v", "type": "quantitative"},
       "text": {"value": P}}},                # value, NUNCA field
  ],
}
```

`"text": {"value": ...}` é constante. Usar `{"field": ...}` faz o número
acompanhar a barra e transforma o mockup num gráfico real.

O eixo de magnitude perde `labels` e mantém a gridline. O rótulo de eixo sai
inteiro porque eixo é régua, e régua afirma grandeza em cada tick.

> Os 12 arquétipos em detalhe, o campo `forma:` e a armadilha da legenda dupla:
> [`references/ficha-para-vega.md`](references/ficha-para-vega.md).

### Passo 4 — renderize

```python
import json, re, vl_convert as vlc
vlc.register_font_directory(f"{SKILL_DIR}/assets/fonts")     # OBRIGATÓRIO
theme = json.load(open(f"{SKILL_DIR}/references/horus-theme.json"))
svg = vlc.vegalite_to_svg(spec, config=theme, vl_version="6.4")
```

Sem `register_font_directory`, o PNG sai com **todos os rótulos ausentes e sem
erro nenhum**. Falha silenciosa verificada.

E o assert anti-vazamento, em todo widget:

```python
from placeholder import TODOS
ok = set(TODOS) | set(categorias_da_ficha)
vazou = [t for t in re.findall(r"<text[^>]*>([^<]*)</text>", svg)
         if re.search(r"\d", t) and t.strip() not in ok]
assert not vazou, f"vazou magnitude: {vazou}"
```

`categorias_da_ficha` existe porque nome de categoria pode ter dígito
(`Vencido até 30d`) e continua sendo forma.

### Passo 5 — monte a página

O gráfico **nasce** com as dimensões do card, em vez de ser escalado: escalar
deixa faixa vazia nas laterais sempre que a proporção difere.

```python
util = 1440 - 2*20                       # página menos padding
col  = (util - 11*4) / 12                # 12 colunas, gutter 4 (GridStack margin 2)
w = n_col*col + (n_col-1)*4 - 2*16       # menos padding do card
h = n_row*40 + (n_row-1)*4 - 2*16 - 30   # cellHeight 40, menos header
```

Em Vega-Lite `height` é só a área de plot: eixo e legenda entram por fora. Meça
o SVG e reajuste até caber (`encaixa()` no exemplo).

Chassi mínimo do card:

```css
.hb-card { background:#fff; border:2px solid rgba(241,245,249,.5);
           border-radius:8px; box-shadow:0 1px 2px 0 rgb(0 0 0/.05); padding:16px }
.hb-card h3 { color:#334155; font-size:14px; font-weight:500; letter-spacing:.025em }
.hb-grid { display:grid; grid-template-columns:repeat(12,1fr);
           grid-auto-rows:40px; gap:4px }
```

Mais: fundo da página `#F8FAFC`, ações (lupa e expandir) em `#CBD5E1` no
`top-4 right-4`, e a **barra de filtros com a data em primeiro lugar**, como
mais um pill (não existe seletor de período separado à direita).

> KPI v2 slot a slot, tabela, `visualType`, registry de widget:
> [`references/chassi-horus-bi.md`](references/chassi-horus-bi.md).
> Esqueleto HTML completo: [`references/wireframe-kit.md`](references/wireframe-kit.md).

### Passo 6 — gere o artefato

HTML para iterar; PDF e PNG via Chrome headless; SVG direto no Typst para o
termo. Um SVG por widget, consumido de formas diferentes.

> Comandos exatos por rota: [`references/pipeline.md`](references/pipeline.md).

## 3. Regras duras

- **Todo gráfico é spec Vega-Lite renderizado.** Barra de `<div>` com `height:%`
  e sparkline escrito à mão em `<svg>` estão proibidos, por mais bonito que
  fique. Se você não rodou `vl-convert`, você não renderizou.
- **Nunca um valor que varia entre elementos.** Também proibidos: `9.999.999`,
  `R$ ——.———`, `R$ —` sozinho, e número orgânico (`R$ 147.892,33`).
- **`parte-todo` até 4 categorias vira donut; 5 ou mais vira barra horizontal.**
  Sempre donut, nunca pizza sólida, mesmo que o dashboard de referência do
  cliente use pizza.
- **Comparação com referência nunca é linha × linha.** Meta, orçamento, ano
  anterior: barra para o realizado e linha para a referência, ou bullet por
  categoria.
- **No máximo 5 séries simultâneas.** A partir da sexta a paleta repete matiz
  (S6 e S9 são o azul de S2). Com 2 ou mais, `shape` acompanha `color`.
- **Nem ponteiro de `Gauge` nem ring de progresso.** Os dois codificam
  magnitude em ângulo, igual ao donut.
- **Categoria `Total` não entra como fatia nem como barra**, e coluna de razão
  (ticket médio, margem, %) não ganha linha `Total` somada.
- **Mês, dia da semana e hora saem na ordem do ciclo**, nunca alfabética.
- **Marcador de confiança em todo widget**, em linguagem de cliente.
  `confirmado`/`hipótese`/`a-perguntar` nunca aparece cru.
- **Overlay narrativo: um por TELA.** Nomeia uma decisão de negócio. Se o texto
  serviria para outro cliente de outro setor, está errado.
- **O que vincula é a ficha, não a figura.** Ajuste de forma decorrente do dado
  real não é mudança de escopo.

## 4. O que esta skill NÃO faz

- **Não monta documento.** Proposta e termo são da `lumo-scope`.
- **Não escolhe o arquétipo da tela.** É decisão de escopo, tomada na ficha.
- **Não estiliza o dashboard entregue.** Isso é `lumo-design`, após a assinatura.
- **Não renderiza dentro da plataforma antes da assinatura.** A plataforma foi
  feita pra mostrar número; pré-venda é sempre artefato standalone.

## Red flags

- O render não parece o Horus BI (sem card, sem Poppins, paleta errada).
- Apaguei o eixo com `axis: null` em vez de `labels: false`, e levei a gridline
  junto.
- Deixei o número variar entre as barras.
- Gerei PNG sem registrar a fonte e não vi que os rótulos sumiram.
- Desenhei o gráfico à mão em HTML/CSS.
- Pizza sólida, ou donut com 5 ou mais fatias.
- Meta e realizado como duas linhas.
- Pus a data num pill separado no canto direito.
- Rodei `lumo` para montar o dashboard na plataforma com dado fabricado antes
  da assinatura.
- Meu overlay serviria para qualquer cliente de qualquer setor.
- Montei eu mesmo o documento de proposta ou termo.

## References

O fluxo acima resolve o caso comum sozinho. Abra um destes quando sair dele:

| Arquivo | Abra quando |
|---|---|
| [`forma-sem-magnitude.md`](references/forma-sem-magnitude.md) | escala fora da tabela, âncora ausente, ou dúvida em KPI/tabela/donut |
| [`ficha-para-vega.md`](references/ficha-para-vega.md) | arquétipo além do caso comum, campo `forma:`, legenda dupla em combo |
| [`chassi-horus-bi.md`](references/chassi-horus-bi.md) | KPI v2 slot a slot, tabela, `visualType`, registry de widget |
| [`wireframe-kit.md`](references/wireframe-kit.md) | esqueleto HTML completo e ficha de widget campo a campo |
| [`pipeline.md`](references/pipeline.md) | PDF, DOCX, Typst, ou rota de render dando problema |
| [`identidade-visual.md`](references/identidade-visual.md) | contraste, deuteranopia, ou de onde vem cada token |
| [`marcadores-confianca.md`](references/marcadores-confianca.md) | escrever o marcador de confiança |
| [`overlay-narrativo.md`](references/overlay-narrativo.md) | escrever o overlay da tela |
| [`wireframe-companion.md`](references/wireframe-companion.md) | live view no navegador durante a rodada |
| [`horus-theme.json`](references/horus-theme.json) · [`horus-chassi.json`](references/horus-chassi.json) | tokens prontos para consumir |
| [`scripts/placeholder.py`](scripts/placeholder.py) · [`exemplo/`](exemplo/README.md) | gerador de placeholder e smoke test |
| [`assets/fonts/`](assets/fonts/README.md) | Poppins e o porquê de registrá-la |
