---
name: unslop
description: Constrói telas com o unslop/ui, design system grátis da leal labs em HTML, CSS e JS puro, sem cara de IA. Use ao criar ou revisar dashboard, painel, tela de admin, card de métrica, KPI, tabela, gráfico (funil, área, linha, barras, radar, sankey, gauge, heatmap, dispersão), sidebar, formulário de painel, componente de agente de IA (chat, raciocínio, ferramenta, busca, contexto), ou quando pedirem para tirar o "AI slop", o visual genérico ou "cara de template" de uma interface.
---

# unslop/ui

Design system de painel em HTML, CSS e JS puro. Sem framework, sem build, sem dependência.
Site e demos: https://unslop.leallabs.dev

Detalhe longo fica em dois arquivos ao lado desta skill. Leia quando for usar:
- `reference/componentes.md`: marcação de cada componente e dos componentes de IA.
- `reference/graficos.md`: os 15 gráficos, com o `data-u-props` mínimo de cada um.

Se o projeto já tem design system próprio ou é site de marketing com ilustração pesada, não use.

## Instalar

Na raiz do projeto:

```sh
curl -fsSL https://unslop.leallabs.dev/i | sh
```

Cria `./unslop/` (`unslop.css`, `unslop.js`, `fonts/`) e esta skill em `.claude/skills/unslop/`.

```sh
curl -fsSL https://unslop.leallabs.dev/i | sh -s list                # o que existe
curl -fsSL https://unslop.leallabs.dev/i | sh -s add funnel line     # só estes gráficos (core, tokens e dependências vão junto)
curl -fsSL https://unslop.leallabs.dev/i | sh -s add chart-funnel    # só o HTML do bloco pronto, para colar
curl -fsSL https://unslop.leallabs.dev/i | sh -s -- --dir assets/unslop --no-skill
```

`add <gráfico>` baixa `tokens.css`, `core.js`, as fontes e o gráfico. Os gráficos radar, radar-compare,
sankey, radial, gauge, heatmap e rings puxam também `b-shell.js/css`; line, barlist e scatter puxam
`a-chartc.js/css`. O instalador imprime os `<link>` e `<script>` na ordem certa. `add` não traz
`components.css/js` nem `ai.css/js`: para esses, use a instalação completa.

Na página (instalação completa):

```html
<link rel="stylesheet" href="unslop/unslop.css">
<script src="unslop/unslop.js" defer></script>
```

Por CDN, sem copiar arquivos: as mesmas duas URLs em `https://unslop.leallabs.dev/unslop.css` e
`https://unslop.leallabs.dev/unslop.js`. As fontes (Jakarta, JB Mono, Sora) são servidas por `/fonts/`; ao copiar
só o CSS para outro lugar, aponte `url("/fonts/...")` para onde as fontes estão.

## Modelo mental

Três camadas de superfície, de fora para dentro. A profundidade vem da camada, não de sombra.

| Camada | Token | Raio | Papel |
|---|---|---|---|
| Casca | `--card` + borda 1px `--line` | `--r-card` 28px | o card |
| Panel | `--panel` | `--r-panel` 16px | miolo, recuo de 12px da casca; aqui mora o gráfico |
| Tile | `--tile` | `--r-tile` 14px | azulejo de legenda, estatística, linha de dado |

Raio concêntrico: externo = interno + recuo (28 = 16 + 12). Controles usam `--r-ctl` 12px; pílula `--r-pill`.
Título (h3) e descrição de 1 ou 2 linhas ficam fora do panel, na casca.

Tokens (em `tokens.css`; use sempre `var(--*)`):
- Superfície: `--bg`, `--card`, `--panel`, `--tile`, `--sunken` (trilho, célula vazia), `--line`, `--line-2` (borda de controle).
- Texto: `--fg`, `--fg-2` (rótulo, descrição), `--fg-3` (eixo, meta). Os três passam de 4,5:1 sobre `--panel`.
- Acento: `--accent`, `--on-accent`, `--accent-soft`, `--accent-text`. Ação primária: `--primary`, `--on-primary` (tinta, não depende do acento).
- Estado: `--ok`, `--warn`, `--err`, `--info` e as versões `-soft`.
- Séries de gráfico: `--c1` a `--c6` (em JS, `Unslop.color(i)`).
- Tipo: `--font` (Jakarta), `--mono`, escala `--t-xs` a `--t-5xl`. Espaço em passo de 4px: `--s1` a `--s24`.
- Movimento: `--ease`, `--ease-io`, `--spring`, `--d1` a `--d4` (120, 220, 420, 800ms).
- Sombra: `--sh-ctl` (controle) e `--sh-pop` (só popover).

Acento e tema (no `<html>`):
- `data-accent="yellow|blue|green|gold|slate"` (padrão yellow). O acento só pinta ação e seleção; gráficos não mudam de significado com ele.
- `data-theme="dark"` liga o tema escuro. Tudo precisa funcionar nos dois temas e nos 5 acentos.

## Marcação e JS

Classes com prefixo `u-`, BEM leve: `.u-btn`, `.u-btn--primary`, `.u-card__title`. Estado por ARIA
(`aria-pressed`, `aria-checked`, `aria-selected`, `aria-expanded`, `aria-current`), nunca por classe `.active`.

Componente com JS: `data-u` com o nome e `data-u-props` com JSON (aspas simples no atributo):

```html
<div data-u="funnel" data-u-props='{"label":"Cadastro","stages":[{"label":"Visitou"},{"label":"Assinou"}],"periods":[{"label":"7 dias","delta":6.1,"values":[2340,310]}]}'></div>
```

- Monta quando chega perto da tela (lazy). `data-u-eager` monta na hora.
- Injetou HTML depois do carregamento: `Unslop.init(container)`.
- JSON inválido não quebra a página: vai para o console como `[unslop] props inválidas`. Nome de `data-u` que não existe é ignorado em silêncio, confira a grafia.
- Registrar o seu: `Unslop.register("nome", (el, props, U) => ({ update(p){}, destroy(){} }))`.
- Utilitários: `U.count`, `U.anim`, `U.svg`, `U.color`, `U.resize`, `U.fmt` (Intl pt-BR), `U.emit`, `U.reduced`.

## Componentes (resumo; marcação completa em `reference/componentes.md`)

| Quero | Use |
|---|---|
| card em 3 camadas | `.u-card` > `.u-panel` > `.u-tile`; cabeçalho `.u-head` com `.u-label` e `.u-big` |
| botão | `.u-btn` + `--primary`, `--secondary`, `--ghost`, `--accent`; `--sm`, `--lg`, `--icon` |
| campo, busca | `.u-input`, `.u-field`, `.u-search`, `.u-kbd` |
| selo, avatar | `.u-badge` + `--ok/--warn/--err/--info/--delta`; `.u-avatar` |
| checkbox, switch, radio | `.u-check`, `.u-switch`, `.u-radio` (input nativo dentro do label) |
| lista suspensa | `data-u="select"` com `{"options":[...],"value":...,"label":...}` |
| seletor de período | `data-u="period"` (`{"mode":"month"}` para meses) |
| alternador de opções | `data-u="segmented"` com `<button>` filhos e `aria-checked` |
| slider em passos | `data-u="slider"` com `{"min":0,"max":4,"value":2,"texts":[...]}` |
| tarefas / etapas | `data-u="tasks"` com `.u-task[data-state]` |
| sidebar | `<nav class="u-side" data-u="nav">` com `.u-side__item` |
| barras simples | `data-u="bars"` com `{"labels":[...],"series":[[...]],"max":n}` |
| anel segmentado | `data-u="ring"` com `{"segments":[{"v":38,"color":"var(--c1)"}]}` |
| calendário | `data-u="calendar"` com `{"year":2026,"month":6}` |
| tabela com filtro e paginação | `data-u="datatable"` (demo com dados fictícios) |
| alerta | `.u-alert` |

## Gráficos (resumo; `data-u-props` mínimo de cada um em `reference/graficos.md`)

| `data-u` | Para | Núcleo das props |
|---|---|---|
| `funnel` | etapas com conversão | `stages`, `periods[].values` |
| `stages` | funil como barras com ícone | `stages[].icon`, `periods[].values` |
| `earnings` | barras sobre trilho (receita) | `ticks`, `periods[].{total,labels,values}` |
| `area` | séries empilhadas no tempo | `series`, `periods[].{labels,data}` |
| `combo` | volume (barras) e taxa (linha) | `bars`, `line`, `periods[].{labels,bars,line}` |
| `line` | séries no tempo, com `unit` | `series`, `periods[].{labels,data}` |
| `barlist` | ranking com azulejos | `periods[].{total,items,stats}` |
| `scatter` | dispersão por categoria | `categories`, `periods[].data` |
| `radar`, `radar-compare` | uma ou duas séries em eixos | `axes`, `periods[].values` ou `.series` |
| `radial`, `gauge` | partes de um total | `periods[].items[{name,value}]` |
| `sankey` | fluxo fonte para destino | `sources`, `dests`, `periods[].matrix` |
| `heatmap` | intensidade por dia | `periods[].{total,seed,stats}` |
| `rings` | metas (`variant:"activity"`) ou calendário (`"calendar"`) | `rings`/`months` |

Todo gráfico tem `label` e quase todos `periods[].{label,delta}`. Escolha pela pergunta que o dado responde, não pelo visual.
A casca muda por família (`u-chart-*`, `u-chartb-*`, `u-chartc-*`): copie o bloco pronto de `ds/blocks/chart-<nome>.html`
(`sh -s add chart-<nome>`) em vez de montar à mão.

## Regras anti-slop (checáveis)

1. **Ordem do card de gráfico fixa:** casca > panel > rótulo > número > delta > seletor à direita > gráfico > azulejos; título e descrição fora do panel. Repetição exata faz 15 gráficos parecerem um sistema.
2. **Raio concêntrico.** Casca, panel e tile nunca têm o mesmo raio; externo = interno + recuo.
3. **Profundidade por camada, não por sombra.** Nenhuma sombra maior que `0 1px 2px`, exceto popover (`--sh-pop`). Contorno de hover do card é `box-shadow inset`, só em `(hover: hover)`, sem mudar borda nem tamanho.
4. **Hierarquia por tamanho e cor.** Peso máximo 600; título de card 500. Negrito em tudo deixa a tela pesada.
5. **Número de KPI:** `font-variant-numeric: tabular-nums` e formatação pt-BR via `Intl` (`U.fmt`): `1.424`, `R$ 7.462`, `12,5%`.
6. **Número conta até o valor** (`U.count`) na primeira vez que aparece; ao trocar de período rola do valor atual ao novo (250 a 450ms), nunca volta do zero.
7. **Hover isola.** O item sob o cursor destaca e o resto esmaece para 30 a 40%, no gráfico e no azulejo. O rótulo e o número do card acompanham o item e voltam ao total ao sair.
8. **Valor nunca só no tooltip.** Legenda em azulejo com valor visível; toque e leitor de tela não têm hover.
9. **Uma cor de acento por tela.** O resto é neutro. Séries usam `--c1..--c6` na ordem, não arco-íris. Não passe cor nas props do gráfico.
10. **Zero hex, zero `rgb()` solto** em componente ou página: só `var(--*)`. Precisa de cor nova? Crie token em `:root`.
11. **Movimento:** `--ease`, 220 a 800ms, só `transform`, `opacity` e `clip-path`; entra quando visível; nada anima fora da tela; `prefers-reduced-motion` mostra o estado final parado (o core já cuida: `U.reduced`).
12. **Acessível:** foco visível, alvo mínimo de 40px, texto secundário e terciário a 4,5:1 ou mais, `role`/`aria-label` nos controles, SVG de gráfico com `role="img"` e rótulo.
13. **Período:** dropdown com ícone de calendário ou segmented com indicador deslizante; altura 36px, área clicável de 40px. Dois padrões, nada além.
14. **Dados com cara de dado:** números plausíveis, com unidade e período, e que fecham (o total do card bate com a soma dos azulejos). Nada de "Lorem", nem 100% redondo em tudo, nem 1.000, 2.000, 3.000.
15. **Copy pt-BR escrita por gente:** frase direta, meses e moeda certos, nenhum inglês esquecido, nada de "revolucione", "potencialize", "desbloqueie", "seamless".
16. **Nada fixo cobre conteúdo.** Seletor flutuante de tema ou acento fica no cabeçalho ou recolhe ao rolar.
17. **CTA primário** com contraste de 4,5:1 ou mais em qualquer acento: `.u-btn--primary` (tinta) ou `.u-btn--accent`.

## Não faça

- Glow, `box-shadow` colorido, neon, blur decorativo.
- Glass (`backdrop-filter`, card translúcido sobre gradiente).
- Gradiente roxo ou azul-roxo de fundo, texto em gradiente, orbes borradas.
- Hex ou `rgb()` direto, cor de série inventada, mais de um acento.
- Emoji como ícone ou enfeite. Ícone é SVG de traço (classe `u-i`).
- Travessão (o caractere longo) em copy, título, comentário ou dado. Use vírgula, ponto ou parênteses.
- Número sem fonte, período ou unidade; gráfico sem rótulo; dado inventado apresentado como real (se for demo, diga).
- Classe `.active` para estado; `div` clicável em vez de `button`.
- Card dentro de card dentro de card sem as três camadas definidas; mesma sombra e mesmo raio em tudo.
- Ícone dentro de círculo colorido sobre cada título, grade de três cards iguais com ícone, texto "Powered by IA".
- Reimplementar o que o unslop já tem (select, período, tabela, gráfico) com outra lib.

## Fluxo para montar uma tela

1. Instale (completo, ou `add` dos gráficos que a tela usa) e inclua `unslop.css` e `unslop.js`.
2. Estruture a página em cards de 3 camadas; defina `data-accent` e, se pedido, `data-theme` no `<html>`.
3. Para cada gráfico, copie o bloco de `ds/blocks/chart-<nome>.html` ou a marcação de `reference/graficos.md` e troque pelos dados reais (ou por dados plausíveis marcados como exemplo).
4. Controles e navegação: componentes de `reference/componentes.md`.
5. Rode o checklist abaixo.

## Checklist antes de entregar

- [ ] Nenhum hex, `rgb()`, `box-shadow` colorido, `backdrop-filter` ou gradiente de fundo no que você escreveu.
- [ ] Casca, panel e tile com raios diferentes e concêntricos; título fora do panel.
- [ ] Um acento só; séries em `--c1..--c6`; abre bem nos dois temas e em dois acentos pelo menos.
- [ ] Todo `data-u` tem nome registrado (confira a grafia) e `data-u-props` é JSON válido (aspas simples por fora); console sem `[unslop]`.
- [ ] Números em pt-BR, `tabular-nums`, com unidade e período; totais batem.
- [ ] Legenda com valor visível; hover isola; nada depende só de tooltip.
- [ ] Foco visível, alvos de 40px, `aria-label` em botão de ícone, estado por ARIA.
- [ ] `grep` por travessão e por emoji na tela inteira dá zero.
- [ ] Com `prefers-reduced-motion` a tela aparece completa e parada.
- [ ] Largura de celular (360px): nada estoura, azulejos reorganizam, nada fixo cobre conteúdo.
