Sistema visual do tAIlly
O mapa é o produto. Esta página é o espécime vivo dos tokens,
componentes, estados e regras de movimento — a mesma folha
(application.css) que serve o app e os artefatos 3D.
A especificação escrita fica em docs/04-DESIGN.md.
Todo número desta página é fictício. Nenhum dado do TSE foi ingerido aqui: as alturas, percentuais e nomes existem só para exercitar o componente. Os únicos valores reais são os hex, as razões de contraste e os ΔE — medidos pelo validador do método, não escolhidos no olho.
Cinco princípios que governam cada pixel
Ausência de dado é buraco
Nunca altura zero, nunca cor de escala. Hachura a 135° + rótulo “sem registro no TSE”.
Todo número tem procedência
Ao lado do valor, de onde veio e quando o arquivo foi gerado.
TSE · votacao_secao_2022_SPdt_geracao 2026-02-13
O tempo é o eixo
Nenhuma tela é fotografia solta: tudo responde “e antes, como era?”.
Cor nunca sozinha
Identidade sempre tem um segundo canal: legenda, rótulo direto, textura ou tabela.
atingido parcial divergente
O dado não faz overshoot
Marca de dado só anima em curva monotônica (--ease-dado). Mola é para cromo.
Ver §08.
Política de cor partidária
a decisão mais carregada do projetoO TSE não publica cor de partido. Logo, toda cor partidária numa tela do tAILly seria invenção nossa — e cor de partido é propaganda, não dado. A política abaixo existe para que o mapa mostre quem ganhou sem parecer que está torcendo.
1Cor de partido não é dado, é convenção. Onde ela aparecer, aparece declarada como convenção do tAILly, com a fonte no rodapé da tela.
2O padrão é a paleta neutra. Partido recebe um dos 8 slots categóricos, não a cor da sua marca. Um modo “cores partidárias” só pode ser ligado quando existir tabela explícita de cores com procedência — enquanto não existir, o botão fica desabilitado com o motivo na tela.
3A cor segue a identidade, nunca o ranking. O slot é preso ao partido_canonico_id e congelado num arquivo versionado: filtrar não repinta ninguém, e PFL→DEM→União Brasil mantém a mesma cor em 1998 e em 2022.
4Os slots 1–4 são politicamente neutros no Brasil (turquesa, laranja, violeta, âmbar). Verde, azul e vermelho ficam no fim da fila de propósito: assim o partido mais votado não ganha, por acaso, a cor da própria propaganda.
5Nada de 9ª cor. Do 9º em diante é “Outros”, cinza. Cor gerada em tempo de execução é proibida — quebra a separação sob daltonismo.
6Sem slot atribuído = cinza + legenda “sem cor atribuída”. Nunca uma cor plausível.
7Swing nunca é vermelho↔azul nem vermelho↔verde. O primeiro seria lido como placar partidário brasileiro; o segundo é o par que a dicromacia mais destrói. O tAILly usa violeta↔âmbar.
Como isso fica em tela
Vencedor por município, dado fictício. A legenda carrega o nome — a cor sozinha nunca identifica.
Teto de séries, medido
- 8séries em barra, pilha e linha (só vizinhos se tocam)
- 3séries no mapa, no scatter e em small multiples — lá qualquer par pode ficar lado a lado. Do 4º em diante: “Outros”, facetas ou small multiples.
Medido com --pairs all: o prefixo de 3 slots passa com ΔE 14,0 (claro) e
14,2 (escuro) sob deuteranopia. O melhor subconjunto de 4 cai para ΔE 7,3 / 6,9 —
banda de piso, que exigiria rótulo direto em cada mancha. Num mapa de 5.570 municípios
isso não existe, então o teto do mapa é 3.
Paleta
todo hex passou pelo validador; nenhum foi escolhido no olhoCategórica — identidade de série · ordem fixa, nunca ciclada
Medido, pares adjacentes: pior ΔE sob protanopia/deuteranopia 11,5 no claro e 12,1 no escuro (alvo ≥ 8); pior ΔE de visão normal 23,0 e 23,4 (piso ≥ 15); os 8 slots ficam ≥ 3:1 contra a superfície nos dois modos.
Por que esta ordem: foram enumeradas as 5.040 ordens possíveis com a turquesa da marca no slot 1; ficaram só as que passam em todos os portões nos dois modos; entre elas, escolhemos a de maior separação mínima com verde, azul e vermelho fora dos quatro primeiros slots.
Sequencial — intensidade (comparecimento, votos, densidade) · uma matiz, claro→escuro
No escuro a âncora inverte: perto de zero é o degrau mais próximo da superfície.
Para escala ordinal (faixas discretas) o começo seguro é o degrau
400 no claro (2,24:1) e 300 no escuro (2,16:1) —
antes disso o degrau some na superfície. Há uma segunda rampa (laranja,
--seq2-*) para quando dois contextos sequenciais dividem a tela.
Divergente — swing (mudança entre eleições) · violeta ↔ âmbar, meio cinza
Dois polos que leem como opostos (frio ↔ quente) sobre o eixo azul-amarelo, o único que protanopia e deuteranopia preservam. Medido: polos a ΔE 23,4 (claro) e 26,5 (escuro) sob deuteranopia; e — o que mais importa — o par mais difícil da escala, o primeiro degrau de cada lado, fica a ΔE 26,1 e 25,0. Uma virada mínima para cada lado nunca se confunde. O meio é cinza, porque “não mudou” tem de parecer nada; “não tem dado” continua sendo hachura, não cinza.
Status — reservado · nunca vira “série 4”, sempre com ícone e rótulo
Quatro papéis, três de severidade e um de ausência. Contraste medido no modo escuro: 8,44 · 9,48 · 6,24 · 5,22 contra a superfície de gráfico; no claro, 6,02 · 5,00 · 6,87 · 4,66. O papel “grave” do método foi fundido em “crítico”: com quatro níveis quentes não dava para manter ΔE ≥ 15 entre eles e distância dos slots categóricos, e dois vermelhos indistinguíveis são pior do que um. Como status sempre vem com ícone e rótulo, a cor nunca carrega o significado sozinha.
Tipografia
serifada só em prosa; todo número em sans ou mono
Figuras: número grande e isolado usa figuras proporcionais —
tabular-nums deixa 121 frouxo em corpo de display.
tabular-nums fica reservado para coluna que alinha na vertical: linha de
tabela e marca de eixo.
Espaço e forma
base 4pxForma das marcas
- ≤24espessura máxima de barra/coluna (
--barra-max); o resto da faixa é ar - 4pxarredondamento só na ponta do dado; quadrado na linha de base
- 2pxespessura de linha, vão de superfície entre marcas que se tocam e anel de superfície nos pontos
- 1pxgrade e eixo, sempre sólidos — tracejado vira ruído e sugere projeção
- ≥8diâmetro do marcador; alvo de ponteiro de ≥24px, sempre maior que a marca
Quem separa é o vão na cor da superfície, nunca uma borda desenhada.
Componentes
mapa · scrubber · painel · legenda · dica · comparadorAlvo de toque: piso de 44 px, com uma isenção só
Todo controle — botão, pílula de filtro, item de sumário, play,
trilho do scrubber, chamada da home — mede pelo menos 44 × 44 px
em qualquer rota (/, /mapa, /estilo,
/status) e em qualquer largura. Não é recomendação: a régua
research/perf/medir_alvos.mjs reprova quem estiver abaixo.
Isenção única, e é a da WCAG 2.5.8: link dentro de uma frase
(/status,
ver no mapa)
herda a altura da linha e fica em ~14 px de caixa. Aumentá-lo empurraria o texto ao
redor — o remédio seria pior. Onde isso acontece, o elemento carrega
data-alvo-excecao com o motivo, e a régua só aceita a exceção que está
declarada assim no DOM.
Consequência prática: um link que não está dentro de frase (a chamada
Abrir o mapa 3D da home, as pílulas do sumário desta página) não
é isento — vira caixa de 44 px por min-height, sem mexer no corpo do texto.
demonstração · dado fictício
Comparecimento · 2022
campo abstrato — não é a malha do IBGE
projeção isométrica
—
valor também está na tabela — a dica nunca é o único caminho
Scrubber de tempo
Cada marco é um pleito real — o vazio entre eles também é dado.
Marco vazado = ano sem granularidade de seção publicada pelo TSE (1994 e 1996).
Arraste, use ← → ou aperte play: as colunas interpolam em
--dur-cena com --ease-dado.
Comparador
Duas eleições no mesmo enquadramento, cortina arrastável. Nunca dois eixos no mesmo gráfico: quando as medidas têm escalas diferentes, são duas telas, não dois eixos.
Tabela gêmea
Todo gráfico tem uma. É por aqui que o valor continua alcançável sem cor e sem hover.
| Unidade | Valor | Variação | Situação |
|---|
Estados
o que a tela faz quando não tem o que mostrarPrimeira carga — esqueleto
Só quando não existe render anterior nenhum. Mantém a geometria final para não haver pulo de layout.
Recarga — segura o quadro
Trocar filtro não mostra esqueleto: o render anterior fica a 45% até o novo chegar.
Drill-down assíncrono
Consulta remota em Parquet leva segundos e a UI diz isso — não trava e não mente.
buscando seção em SP · 2022
62% · 18.761.482 linhas no Parquet remoto
Ausência declarada
Nunca zero. O buraco tem legenda e diz de quem é o limite.
granularidade máxima disponível em 1996: município × zona — o TSE não publica seção antes de 1998.
Movimento
clique em qualquer caixa para repetir✕ mola numa marca de dado
0,0%
A barra ultrapassa e volta. Por alguns frames a tela exibe um número que não existe no banco. Veja o pico.
✔ --ease-dado, monotônica
0,0%
Cresce sem nunca passar do valor final. Todo frame intermediário é um número plausível e menor — nunca um número falso.
É esta a única regra de motion que é regra de integridade, não de gosto.
Curvas
Durações — a que serve cada uma
| Token | ms | Serve para | Curva |
|---|---|---|---|
--dur-toque | 90 | hover, press, foco — resposta tátil | --ease-padrao |
--dur-rapido | 160 | chip, selo, aba, dica aparecendo | --ease-saida |
--dur-medio | 240 | painel, popover, troca de tema | --ease-saida |
--dur-lento | 420 | barra crescendo, comparador abrindo | --ease-dado |
--dur-cena | 700 | voo de câmera, morph entre dois pleitos | --ease-dado |
--dur-passo-tempo | 1100 | um pleito por passo no play da linha do tempo | linear entre marcos |
prefers-reduced-motion corta toda animação para 1ms — inclusive a
interpolação do mapa, que passa a saltar direto para o estado final. O produto continua
inteiro sem movimento nenhum.