/* Componentes da fundação de interface (etapa 1, prompt #36).
 *
 * CSS escrito à mão, servido pelo WhiteNoise. Sem framework, sem build.
 * Depende só de tokens.css. O styleguide em /empresa/<id>/ui/ exercita
 * tudo daqui — componente que não aparece lá não existe.
 */

/* ---------- Botões ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-6);
  padding: 7px var(--esp-14);
  border: 1px solid var(--line);
  border-radius: var(--raio);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-weight: var(--peso-medio);
  cursor: pointer;
  text-decoration: none;
}
.btn:hover { background: var(--line-2); }
.btn:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }
/* #168: o sistema inteiro tinha ZERO estado `:active` (medido: 0 ocorrências no
   arquivo). O botão respondia ao ponteiro parado (`:hover`) e não respondia ao
   ponteiro APERTADO -- entre o clique e a tela trocar não havia nada dizendo
   "pegou", e em formulário lento isso vira o segundo clique.
   O gesto é afundar 1px, não pintar: `.btn` tem três variantes com fundos
   diferentes (`--btn-primario-hover`, `--btn-perigo-hover`, `--line-2`) e um
   pressionado por COR precisaria de três tokens novos -- o afundar serve às
   três com uma linha e sem decidir cor nenhuma. `transform` não entra na
   transição global de app.css (só `background-color` e `color`), então o
   retorno é instantâneo, que é o ponto, e o bloco `prefers-reduced-motion` não
   tem o que zerar aqui. Botão `disabled` não recebe evento de ponteiro: não
   dispara `:active`, sem precisar de `:not()`. */
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.5; cursor: default; }

.btn-primario { background: var(--ink); border-color: var(--ink); color: var(--branco); }
.btn-primario:hover { background: var(--btn-primario-hover); }

/* #288: a ação destrutiva de LINHA -- vermelha no texto, sem preenchimento.
   O `.btn-perigo` sólido fica para o botão que confirma a exclusão. */
.btn-perigo-sutil { color: var(--vermelho); }
.btn-perigo-sutil:hover { background: var(--vermelho-fraco); border-color: var(--vermelho); }
.btn-perigo { background: var(--vermelho); border-color: var(--vermelho); color: var(--branco); }
.btn-perigo:hover { background: var(--btn-perigo-hover); }

.btn-mini { padding: 3px 9px; font-size: var(--corpo-miudo); border-radius: calc(var(--raio) - 3px); }

/* ---------- Cartão ---------- */

.cartao {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--raio);
}
.cartao-cab {
  /* #310/B — MEDIDO pelo mesmo método nas duas telas, com o banco de produção
   * em 1366px: o vão entre o cabeçalho e o primeiro conteúdo é de 10 px na
   * Início e de 35 px na área de Segurança. A causa não é o componente: é o
   * ELEMENTO. O sistema inteiro escreve `<div class="cartao-cab">` (margem de
   * fábrica zero) e só a área do servidor escreve `<h2 class="cartao-cab">`,
   * que o navegador entrega com `margin: 18.675px 0` -- 18,7 px empurrando o
   * título para baixo DENTRO do cartão e outros 18,7 separando-o do conteúdo.
   * Um componente que declara o próprio padding não pode herdar a margem de
   * um elemento que ele não escolheu. Zerar aqui NÃO mexe em quem já era
   * `<div>`: div nasce com margem 0 (conferido depois da mudança -- a Início
   * continua em 10 px). Guarda:
   * `test_o_cabecalho_do_cartao_nao_carrega_margem_de_fabrica`. */
  margin: 0;
  /* #310/5 — E O CORPO PELA MESMA RAZÃO DA MARGEM. Zerar só a margem deixou o
   * mesmo componente em DOIS corpos na mesma tela: ~24 px no
   * `<h2 class="cartao-cab">` e ~15 px no `<summary class="cartao-cab">`, e
   * foi esta rodada que criou a diferença ao usar `<summary>`. O corpo que
   * fica é o do SISTEMA: `<div class="cartao-cab">` (Início, A pagar) sempre
   * herdou `--corpo`, então nenhuma tela antiga se move -- só a área do
   * servidor, que era a única fora do padrão. Declarar `font-size` junto de
   * `margin` ao estilizar `<h2>` é o que o app.css já faz (`:1607`, `:1763`).
   * Guarda: `test_o_cabecalho_do_cartao_tem_UM_corpo_so`. */
  font-size: var(--corpo);
  padding: var(--esp-12) var(--esp-16);
  border-bottom: 1px solid var(--line-2);
  font-weight: var(--peso-forte);
}
.cartao-corpo { padding: var(--esp-16); }

/* ---------- Campo ---------- */

.campo {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 7px var(--esp-10);
  border: 1px solid var(--linha-campo); /* 3:1 (#42); estrutura fica em --line */
  border-radius: calc(var(--raio) - 3px);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
}
/* #168: era `:focus` puro. `:focus` casa também com o clique de MOUSE, e como
   esta regra tem especificidade (0,2,0) ela vencia o `:focus-visible` global de
   app.css (0,1,0) -- o sistema tinha, na prática, DOIS dialetos de foco, e o
   dialeto que ganhava era o que pinta anel para quem nunca tocou o teclado.
   Promovida. Em campo de TEXTO editável a promoção não tira nada de ninguém: o
   navegador trata todo campo editável como sempre merecedor de foco visível, e
   `:focus-visible` dispara nele mesmo no clique. Quem realmente para de pintar
   é o campo de escolha suspenso, mais abaixo neste arquivo -- lá o anel aceso
   pelo clique de mouse era ruído permanente.
   ⚠️ A palavra que nomeia aquele elemento NÃO aparece aqui de propósito, e não
   é preciosismo: a guarda de desenho do #71 (em `tests/`) acha o bloco dele com
   um `re.search` cujo `[^{]*` atravessa comentário, chave e quebra de linha --
   ela lê o PRIMEIRO lugar do arquivo onde a palavra aparece e assume que a
   próxima `{` abre o bloco certo. Escrevê-la aqui em cima faz a guarda ler o
   corpo desta regra e reprovar por "não declara appearance". Custou uma suíte
   vermelha no #168. Não reintroduzir a palavra acima do bloco de desenho.
   Geometria intocada de propósito -- offset 0 é o dialeto de CAMPO deste
   arquivo (o anel encosta na borda que a cor já reforça); o offset 1px do
   `.btn` é o dialeto de BOTÃO. Mudar a pseudo-classe não é hora de mudar forma.
   `.campo-monetario input` herda daqui -- não tem, nem precisa de, regra
   própria. */
.campo:focus-visible { outline: 2px solid var(--azul); outline-offset: 0; border-color: var(--azul); }
.campo::placeholder { color: var(--ink-3); }

/* ---------- Etiqueta (pílula de estado) ---------- */

.etiqueta {
  display: inline-flex;
  align-items: center;
  padding: 1px 9px;
  border-radius: 999px;
  font-size: var(--corpo-rotulo);
  font-weight: var(--peso-forte);
  white-space: nowrap;
}
.etiqueta.ok      { background: var(--verde-fraco);    color: var(--verde); }
.etiqueta.aberto  { background: var(--azul-fraco);     color: var(--azul); }
.etiqueta.vencido { background: var(--vermelho-fraco); color: var(--vermelho); }
.etiqueta.falta   { background: var(--amarelo-fraco);  color: var(--amarelo); }

/* ---------- Chip (filtro aplicado) e sugestão (filtro proposto) ---------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-6);
  padding: var(--esp-2) var(--esp-6) var(--esp-2) var(--esp-10);
  border-radius: 999px;
  background: var(--azul-fraco);
  color: var(--azul);
  font-size: var(--corpo-miudo);
  font-weight: var(--peso-medio);
}
.chip button {
  border: 0;
  padding: 0 var(--esp-4);
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  border-radius: 999px;
}
.chip button:hover { background: var(--chip-remover-hover); }
.chip button:focus-visible { outline: 2px solid var(--azul); }

.sugestao {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-6);
  padding: var(--esp-2) var(--esp-10);
  border: 1px dashed var(--ink-3);
  border-radius: 999px;
  background: none;
  color: var(--ink-2);
  font-size: var(--corpo-miudo);
  cursor: pointer;
}
.sugestao:hover { border-color: var(--azul); color: var(--azul); }
.sugestao:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }

/* ---------- Pastilha (iniciais da conta) ---------- */

.pastilha {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--azul-fraco);
  color: var(--azul);
  font-size: var(--corpo-mini);
  font-weight: var(--peso-maximo);
  letter-spacing: 0.02em;
}

/* ---------- Abas (navegação entre fatias de uma mesma tela) ---------- */
/* Nasceu como `.aba-cadastro` no #57, quando os cinco Cadastros ganharam
   abas. O nome amarrava o componente a UMA tela; a mecânica não tem nada de
   cadastro. Movida para cá e renomeada na Tarefa 6 (#70) -- os valores são
   copiados das regras reais de static/css/app.css (não do bloco do brief da
   tarefa, que tinha `gap`/`margin-bottom`/`font-weight` levemente diferentes
   por ter sido escrito antes da última mudança no arquivo). Quem marca a aba
   corrente para o leitor de tela é o `aria-current="page"` do HTML -- a cor
   é reforço, não a informação. */
.abas {
  display: flex;
  gap: var(--esp-4);
  flex-wrap: wrap;
  margin-bottom: var(--esp-12);
  border-bottom: 1px solid var(--line);
}
.aba {
  padding: var(--esp-6) var(--esp-12);
  font-size: 14px;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.aba:hover { color: var(--ink); }
.aba.ativa { color: var(--azul); border-bottom-color: var(--azul); font-weight: 550; }
/* #168, medido antes de escrever: a aba NÃO está sem foco visível e NÃO leva
   regra própria. É um `<a href>`, recebe foco do DOM, e nada aqui declara
   outline -- então vale o `:focus-visible` global de app.css
   (`outline: 2px solid var(--azul)`, e `--acao: var(--azul)`, a MESMA cor dos
   anéis deste arquivo). Escrever `.aba:focus-visible` local seria reintroduzir
   o anel por componente que o #163 mandou não reintroduzir: o global acompanha
   sozinho se a cor de ação mudar, o local não. Sem `:active` também -- aba é
   navegação, o pressionar já é respondido pela tela trocando, e afundar 1px em
   texto sem moldura lê como desalinho, não como botão. */

/* ---------- Tabela ---------- */

/* O wrapper .rolo é quem rola no eixo X. As sombras de borda entram por
 * classe (.sombra-esq/.sombra-dir) postas pelo JS SÓ quando há conteúdo
 * escondido daquele lado — recalculadas em scroll/resize/troca de tela,
 * porque um container display:none mede zero e mente. */
.rolo { overflow-x: auto; }

.tabela {
  width: 100%;
  border-collapse: separate; /* sticky + fundo por célula exigem separate */
  border-spacing: 0;
}

.tabela th,
.tabela td {
  height: var(--altura-linha);
  box-sizing: border-box;
  padding: var(--pad-linha) var(--esp-10);
  border-bottom: 1px solid var(--line-2); /* divisória; zebra é proibida */
  background: var(--surface);
  text-align: left;
  vertical-align: middle;
  white-space: nowrap;
}
.tabela th {
  font-size: var(--corpo-rotulo);
  font-weight: var(--peso-forte);
  color: var(--ink-3);
  border-bottom: 1px solid var(--line);
}
/* ⭐ #100: O CABEÇALHO DA TABELA GRUDA, E O DE DIA GRUDA ABAIXO DELE — dois
 * níveis coordenados, nunca sobrepostos (referência v3).
 *
 * `top: 0` e não a altura da barra do topo: `.topo` NÃO é sticky neste sistema
 * (medido — `app.css` lhe dá `min-height: 56px` e nenhuma `position`), então
 * ela rola para fora e a banda de cima é a própria viewport. O 102px que a v3
 * usa vem da fita que a referência tem e o sistema não — copiá-lo grudaria o
 * cabeçalho 102px abaixo do topo, com uma faixa vazia acima.
 *
 * O sticky vai nos `th` e não no `thead`: é o que funciona igual em todo
 * navegador, e o bloco continente de um sticky dentro de tabela é a TABELA
 * INTEIRA (medido no #99) — ou seja, ele gruda enquanto a tabela estiver na
 * tela, que é exatamente o desejado.
 *
 * `z-index: 3` porque o cabeçalho de dia é 2: quando os dois se encontram,
 * quem fica por cima é o da tabela. O fundo já é opaco (`var(--surface)`, na
 * regra acima); sem isso as linhas apareceriam por baixo ao rolar. */
.tabela thead th {
  position: sticky;
  top: 0;
  z-index: 3;
}
/* ⭐ DINHEIRO ALINHA À DIREITA EM TODA TABELA DO SISTEMA — E ISTO É REGRA DE
 * COMPONENTE, NÃO DE TELA (#98).
 *
 * O dono reportou o defeito em UMA tela (Cadastros/Contas: 190.295,38 / 0,00 /
 * -453.497,05 à esquerda, milhares em colunas diferentes) e apontou A pagar
 * como o certo. A medição no navegador (1920×1080, getComputedStyle) mostrou
 * que o problema é MUITO maior e tem uma causa só, de cascata:
 *
 *   `.direita` mora em app.css e vale (0,1,0). `.tabela th, .tabela td`, logo
 *   acima, vale (0,1,1) e declara `text-align: left`. Especificidade maior
 *   vence — então `class="direita"` NUNCA funcionou DENTRO de uma tabela, em
 *   NENHUMA tela, desde que componentes.css passou a carregar por último
 *   (#70). O `<th class="direita">` do DRE, os `<td class="direita valor">`
 *   da Análise e a coluna VALOR de Lançamentos estavam todos à esquerda,
 *   escritos como se estivessem à direita. `.num` funcionava porque é
 *   `.tabela td.num` (0,2,1) — e é por isso que só A pagar parecia certo.
 *
 * Medido ANTES (elementos de tabela cujo texto é dinheiro, computados como
 * NÃO-direita): Lançamentos 114, Análise 36, DRE 15, Cadastros/Contas 4,
 * Cadastros/Cartões 2, Início 1 — contra 0 em A pagar e A receber.
 *
 * Duas classes entram junto de `.num`, e cada uma por um motivo:
 *   - `.direita` — para RESSUSCITAR o que os templates já pediam;
 *   - `.valor` — porque `.valor` é O papel tipográfico do dinheiro
 *     (app.css: "Cifra", tabular-nums) e obrigar quem escreve template a
 *     lembrar de DUAS classes para uma coisa só é o defeito se repetindo.
 *     Com esta regra, `class="valor"` numa célula basta.
 * Fora de tabela nada muda: lá o alinhamento é de LAYOUT (flex/margin-left:
 * auto), e `text-align` numa caixa que mede o próprio conteúdo não faria nada.
 */
.tabela td.num, .tabela th.num,
.tabela td.direita, .tabela th.direita,
.tabela td.valor, .tabela th.valor { text-align: right; }

/* ⭐ UM VERMELHO SÓ PARA VALOR NEGATIVO (#98).
 *
 * O dono viu o MESMO -453.497,05 vermelho no Início e preto em Cadastros/
 * Contas. A varredura achou uma TERCEIRA cor, em A pagar/A receber: o sistema
 * tinha DOIS tokens de vermelho vivos ao mesmo tempo —
 *   `--erro`     #b3261e (app.css)          via `.negativo`
 *   `--vermelho` #c0392f (tokens.css)       via `.ap-sobra .num.negativo`,
 *                                           `.tabela-conciliacao .negativo`,
 *                                           `.heroi-saida`
 * — e Cadastros não punha classe nenhuma, então o negativo saía em `--ink`.
 *
 * A REGRA: valor monetário negativo é vermelho em toda tela, com UM token só,
 * `--vermelho`. Positivo não ganha cor (tinta normal); o verde continua
 * reservado ao par entrada/saída dos relatórios. O motivo, em uma linha: numa
 * coluna lida em diagonal oito horas por dia o "−" some antes do número, e a
 * cor é a redundância que faz o negativo ser VISTO — mas só funciona se for a
 * MESMA cor em toda tela, e eram três.
 *
 * Por que `--vermelho` e não `--erro`: (a) o #70 decidiu que cor literal só
 * vive em tokens.css, e `--erro` é literal do app.css; (b) o código mais novo
 * (#71 em diante) já tinha convergido para `--vermelho` em três lugares;
 * (c) contraste sobre --surface #fff: 5,43:1, AA para texto normal (o --erro
 * media 6,54:1 — os dois passam, então o desempate é (a) e (b)).
 * `--erro` FICA, com o papel que sobrou e que é o nome dele: estado de ERRO
 * (`.errorlist`, `.alerta`, `.menu-perigo`), que não é valor negativo.
 */
.negativo { color: var(--vermelho); }

/* A marca de conferência com o banco (#48) nasceu para a LISTA de contas do
 * Início, que é FLEX: daí `flex-basis:100%` (empurra a marca para a linha de
 * baixo) e `padding-left:36px` (alinha sob o nome, depois da marca redonda).
 * No #98 ela saiu da célula de dinheiro de Cadastros/Contas e virou COLUNA
 * própria — e dentro de um `<td>` as duas declarações não só não ajudam como
 * eram parte do "aviso espremido ao lado do saldo" que o dono reportou: não há
 * linha de baixo para quebrar, e 36px de recuo dentro de uma célula é recuo
 * puro. Escopo mínimo (`.tabela td >`) para não tocar no Início. */
.tabela td > .conta-nao-conferida,
.tabela td > .conta-conferida,
.tabela td > .conta-sem-ancora { flex-basis: auto; padding-left: 0; margin-top: 0; }
.conta-sem-ancora { font-size: var(--corpo-mini); color: var(--ink-3); }

/* PARA ONDE VAI A SOBRA DE LARGURA NAS SEIS TELAS DE CADASTRO (#98).
 *
 * Tirar a coluna ATIVO devolveu de 114px (Contas) a 1058px (Tags) à tabela — e
 * o layout automático não devolve esse espaço para quem precisa dele: ele o
 * REPARTE. Medido no navegador, com a ATIVO fora e sem esta regra:
 *   Tags     NOME 631 · AÇÕES 617   ← a coluna de um BOTÃO ficou com 617px, e o
 *                                     "Editar" pulou de x=1516 para x=977,
 *                                     saindo da posição que ele tem nas outras
 *                                     quatro abas (diretriz 6 do dono: mesma
 *                                     função, mesma posição)
 *   Centros  NOME  85 · CÓDIGO 1084
 *   Cartões  NOME  75 · DIA DE VENCIMENTO 740
 *
 * `width:100%` na PRIMEIRA coluna (o NOME, nas seis fatias) torna ela a coluna
 * gulosa: ela fica com toda a sobra e as demais encolhem para o próprio
 * conteúdo. Depois, medido:
 *   Tags     NOME 1170 · AÇÕES 79     Contas  NOME 999 · BANCO 74 · SALDO 97
 *   Centros  NOME 1089 · CÓDIGO  81   Cartões NOME 903 · LIMITE 86 · DIA 180
 * e o "Editar" volta a x=1516 nas CINCO.
 *
 * Por que o NOME e não outra: é a coluna que IDENTIFICA o registro e a única
 * que trunca com dado real — a semente tem nome curto de propósito e não diz
 * nada sobre isso (regra do #78/#79); os nomes de conta da Link ce é que
 * precisam do espaço. Mesma escolha que `.gruda-desc` já faz em A pagar. */
.tabela-cadastro th:first-child,
.tabela-cadastro td:first-child { width: 100%; }

/* #209: a coluna de ACOES de Cadastros tem largura PROPRIA, e nao a do
 * conteudo da aba da vez.
 *
 * O comentario acima (#98) termina em "e o Editar volta a x=1516 nas CINCO".
 * Isso parou de valer no #195, que deu o Excluir so as cinco fatias com motor
 * de fusao (`{% if pode_excluir_fatia %}` em templates/cadastros/lista.html):
 * Cartoes nao tem fundir/retratar. Com `celula-acoes { width: 1% }` (app.css)
 * a coluna encolhe ate o conteudo, entao uma acao a menos vira uma COLUNA mais
 * estreita -- e o botao Editar, MESMA funcao, muda de lugar conforme a aba.
 * Ninguem decidiu isso; foi efeito colateral do #195.
 *
 * Medido em 27/08/2026, 1440x900, ANTES desta regra:
 *   Contas/Centros/Contatos/Tags  ACOES 146,92px  Editar em x=1207
 *   Cartoes                       ACOES  78,50px  Editar em x=1276  (+69px)
 * O conteudo mais largo que a tela produz e "Editar Excluir": 58,50 + 63,64 +
 * 4,78 de intervalo + 20 de padding da celula = 146,92px. 148px e esse numero
 * ARREDONDADO de 146,92 para cima -- os 1,08px que sobram nao tem justificativa
 * funcional nenhuma, sao arredondamento para numero redondo, e chama-los de
 * "folga" seria inventar razao para numero magico. O que importa e o valor ser
 * MAIOR que o natural das cinco abas: com isso a coluna passa a medir 148px em
 * TODAS -- alinhamento por declaracao, nao por coincidencia de dois rotulos
 * terem a mesma largura. Quem quiser trocar 148 por 147 nao precisa de
 * permissao: a guarda (prova_interface_98:394) exige IGUALDADE EXATA do x nas
 * cinco abas, sem tolerancia, entao ela reprova qualquer valor que nao alinhe.
 *
 * O escopo e `.tabela-cadastro` DE PROPOSITO: `.celula-acoes` tambem veste
 * Lancamentos, a tela de Cartao e Usuarios, e nessas tres a celula guarda o
 * menu `⋯` e mede 47px -- numero de que o `#cartao-tela .gruda-valor
 * { right: 47px }` (abaixo) depende, com guarda de colisao de `<th>` na
 * prova_interface_121. Alargar `.celula-acoes` sem escopo mexeria nas quatro
 * telas de uma vez. Medido depois desta regra: Lancamentos 47px e Cartao 47px,
 * inalterados nas duas larguras.
 *
 * Se o rotulo dos botoes crescer, as quatro abas passam de 148px e Cartoes
 * fica para tras: a prova_interface_98 (item 7.4, "a coluna ACOES cai na MESMA
 * posicao nas cinco abas") reprova. O numero nao apodrece em silencio. */
.tabela-cadastro th.celula-acoes,
.tabela-cadastro td.celula-acoes { min-width: 156px; } /* #291: era 148; com "Excluir…" (#290) a coluna media 5px a mais que a de Cartões, que só tem Editar (prova 98) */

.tabela tbody tr:hover th,
.tabela tbody tr:hover td { background: var(--linha-hover); }
.tabela tbody tr.selecionada th,
.tabela tbody tr.selecionada td { background: var(--azul-fraco); }

.sub-linha {
  display: block;
  font-size: var(--corpo-rotulo);
  color: var(--ink-3);
}
:root[data-densidade="compacto"] .sub-linha { display: none; }

/* Colunas congeladas. As larguras são pares amarrados:
 * seleção mede 34px  -> a descrição gruda em left:34px;
 * ações  mede 172px  -> o valor gruda em right:172px.
 * Mudou um, muda o outro.
 *
 * #79: ações media 246px desde o #36 (`ea8af23`) e o comentário daquela
 * época só documentava o ACOPLAMENTO, nunca justificava o VALOR -- medido
 * agora (`.acoes-linha` no hover real, dois botões "Dar baixa"/"Adiar"):
 * conteúdo 140,8px (82,95 + 53,84 + 4px de gap) + 20px de padding da célula
 * (10px de cada lado) = 160,8px preciso. 246px era ~89px de folga pura.
 * 172px dá ~11px de sobra sobre o preciso -- o suficiente para um foco
 * visível não cortar borda, sem devolver a folga antiga. */
.tabela .gruda-sel   { position: sticky; left: 0;      width: 34px; min-width: 34px; max-width: 34px; z-index: 2; }
.tabela .gruda-desc  { position: sticky; left: 34px;   z-index: 2; }
.tabela .gruda-valor { position: sticky; right: 172px; z-index: 2; }
.tabela .gruda-acoes { position: sticky; right: 0;     width: 172px; min-width: 172px; max-width: 172px; z-index: 2; }

/* #205: na tela de cartão a coluna que fica à direita do valor é só a de
   ações; o `right: 172px` acima foi medido para a lista de Lançamentos, que
   tem Situação E Ações depois dele. Medido em 1366px ANTES desta linha (com
   a coluna de ações já na tabela, sem este ajuste): o `<th>` Valor ocupava
   1011-1134 e o Centro de custo ia até 1136 -- os dois se sobrepunham, e a
   coluna Ações (celula-acoes) media 1259-1306, 47px de largura. A prova de
   navegador mede que nenhum `<th>` se sobrepõe a outro, então este número
   não pode apodrecer em silêncio. */
#cartao-tela .tabela .gruda-valor { right: 47px; }

/* Teto por coluna, por CLASSE -- nunca por `nth-child`: a mesma coluna
 * (categoria, conta...) aparece em posições DIFERENTES em cada tela, então
 * uma regra posicional acertaria a coluna errada numa tela vizinha.
 *
 * A técnica é CSS puro: `max-width` limita o MÍNIMO que a coluna EXIGE, não
 * o máximo que ela pode ocupar -- numa tabela `width:100%` sem excedente, a
 * folga que sobra é redistribuída de volta pelo algoritmo de layout (medido:
 * os mesmos tetos abaixo rendem larguras finais MAIORES que o valor nominal
 * quando o container tem espaço de sobra). `overflow: hidden` +
 * `text-overflow: ellipsis` seguram o `white-space: nowrap` global
 * (`componentes.css` acima) para dentro do teto -- ele continua protegendo
 * as colunas curtas (VALOR, CONTA, datas) de quebrar linha; aqui ele é o que
 * garante uma única linha para a reticência aparecer.
 *
 * `.gruda-desc` (Descrição, A pagar) já é o teto da própria coluna -- não
 * precisa de classe extra. `.celula-descricao`/`.celula-categoria`
 * (Lançamentos) e as três abaixo (A pagar) são a mesma técnica, uma por
 * estrutura de tela: onde a coluna já tinha nome próprio, o teto entra
 * nele; onde não tinha (Categoria/Conta/Contato do A pagar, Conta de
 * Lançamentos), a classe nasceu aqui. `.col-conta` é comum às duas telas de
 * propósito -- é a MESMA coluna (nome de conta bancária) nas duas. */
.tabela .gruda-desc     { max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.tabela .celula-descricao { max-width: 280px; overflow: hidden; text-overflow: ellipsis; }
.tabela .celula-categoria { max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
.tabela .col-categoria  { max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
.tabela .col-contato    { max-width: 160px; overflow: hidden; text-overflow: ellipsis; }
.tabela .col-conta      { max-width: 140px; overflow: hidden; text-overflow: ellipsis; }

/* ⭐ #187: OS TETOS ENCOLHEM ENTRE 901px E 1200px, e este intervalo NÃO é
 * arbitrário -- ele é exatamente a faixa que ficava sem saída.
 *
 * MEDIDO (Chromium, HTML de produção da Link ce, 19/08/2026): a tabela da
 * lista soma 1153px de colunas (seleção 30 + tipo 28 + Descrição 280 +
 * Sinais 73 + Categoria 180 + Conta 140 + Responsável 120 + Valor 153 +
 * Situação 102 + Ações 47). Em 1093px de largura útil -- que é um 1366 com
 * zoom de 125%, o caso real de quem usa notebook -- a PÁGINA inteira passava
 * a rolar para o lado (scrollWidth 1169 contra clientWidth 1093).
 *
 * 🔴 **A saída óbvia está proibida, e por medição, não por gosto:** ligar
 * `overflow-x: auto` no `.rolagem-horizontal` acima de 900px faz o
 * `overflow-y` computar `auto` junto (um eixo non-visible arrasta o outro),
 * o quadro vira o ancestral de rolagem do `position: sticky`, e o CABEÇALHO
 * PARA DE GRUDAR -- medido no #70/Tarefa 10, topo indo a -341px. Por isso a
 * media query de rolagem para em 900px, e por isso a correção aqui é por
 * TETO DE COLUNA (R40), que não toca no contexto de rolagem.
 *
 * A ordem do que encolhe segue o que a coluna perde ao ser truncada: nome de
 * categoria e de conta têm prefixo repetido e o `title` mostra o inteiro;
 * a descrição é a identidade da linha e encolhe por último e menos. */
@media (min-width: 901px) and (max-width: 1200px) {
  .tabela .celula-descricao { max-width: 220px; }
  .tabela .celula-categoria { max-width: 130px; }
  .tabela .col-conta        { max-width: 110px; }
  .tabela .col-responsavel  { max-width: 90px; overflow: hidden; text-overflow: ellipsis; }
}
/* Abaixo de 1000px sobravam ainda 49px (medido: scrollWidth 1029 contra 980).
 * Aqui a descrição também cede -- acima deste ponto ela é a última a encolher,
 * mas numa tela deste tamanho a alternativa é a página rolar para o lado, que
 * é pior para TODAS as colunas. Abaixo de 900px a media query de rolagem
 * assume e o quadro passa a rolar sozinho. */
@media (min-width: 901px) and (max-width: 1000px) {
  .tabela .celula-descricao { max-width: 180px; }
  .tabela .celula-categoria { max-width: 110px; }
  .tabela .col-conta        { max-width: 95px; }
}

/* Sombra na fronteira congelada, só quando há conteúdo escondido do lado. */
.rolo.sombra-esq .gruda-desc  { box-shadow: var(--sombra-coluna-esq); }
/* #287: era `-var(--sombra-coluna-esq)` -- CSS inválido, descartado: a
   fronteira direita nunca ganhava sombra. O token certo existia sem uso. */
.rolo.sombra-dir .gruda-valor { box-shadow: var(--sombra-coluna-dir); }

/* ⭐ O CHECKBOX DE SELEÇÃO É SEMPRE VISÍVEL; SÓ AS AÇÕES DEPENDEM DA LINHA
 * ATIVA (#98, decisão do dono).
 *
 * O que estava errado, e é diretriz violada e não gosto: a MESMA função —
 * marcar N linhas e agir em lote — era desenhada de dois jeitos. Em
 * `Lançamentos` o checkbox é `<td><input type="checkbox">` sem classe, SEMPRE
 * visível (`templates/lancamentos/linha.html`); em `A pagar` o mesmo controle
 * só existia no `:hover`. Diretriz 6 do #77: "mesma função, mesmo componente,
 * mesma posição — se duas telas fazem a mesma coisa de jeitos diferentes, uma
 * das duas está errada". Quem não tinha caminho era o MOUSE: a tela não dava
 * pista nenhuma de que havia o que selecionar até o ponteiro passar por cima.
 *
 * O custo em densidade é ZERO PIXEL, medido no navegador sobre o HTML de
 * produção (449 linhas, `horizonte=30`), antes e depois: largura da tabela
 * 1246→1246, altura do documento 28450→28450, linhas na 1ª dobra 11→11
 * (1920×1080) e 6→6 (1366×768). A razão é estrutural: `opacity` não tira do
 * fluxo, e a coluna de seleção já ocupava 34px fixos, pintada ou não.
 *
 * As AÇÕES continuam no hover/foco de propósito: com tudo visível a tela
 * pintaria 449 caixas MAIS 898 botões, e 898 botões permanentes numa tela
 * olhada oito horas por dia é a poluição que a diretriz 4 proíbe.
 *
 * `:focus-within` NÃO É ENFEITE — sem ele a tabela fica inacessível por
 * teclado, e ele JÁ FUNCIONAVA (medido: 12 `Tab` do topo chegam ao checkbox
 * da 1ª linha, e ao entrar o foco acende as ações). Não remover. */
.tabela .acoes-linha { opacity: 0; display: inline-flex; gap: var(--esp-4); }
.tabela tbody tr:hover .acoes-linha,
.tabela tbody tr:focus-within .acoes-linha { opacity: 1; }

/* #306: 24x24 e o minimo de alvo de clique (WCAG 2.5.8); o checkbox nu do
   navegador mede 13x13. A celula tem 34px, cabe sem mexer na altura da linha. */
.tabela .gruda-sel input[type="checkbox"] { margin: 0; width: 24px; height: 24px; }

/* Cabeçalho de agrupamento e rodapé de saldo do dia. O conteúdo vive num
 * <span class="cola"> sticky em left:0 — senão o texto some no scroll lateral. */
.tabela .dia-cab td,
.tabela .dia-saldo td {
  background: var(--page);
  font-size: var(--corpo-rotulo);
  color: var(--ink-2);
  height: auto;
  padding-top: 5px;
  padding-bottom: 5px;
}
.tabela .dia-cab td { font-weight: var(--peso-forte); }
/* #105: a MOLDURA da linha de agrupamento, igual nas três listas. A v3
 * (`referencia-visual-v3.html`, `.dia-cab td`) fecha a faixa em cima E
 * embaixo com `--line`; A pagar e A receber vinham sem borda superior e com
 * a inferior em `--line-2` — a divisória fraca, que é a de SEPARAR LINHA DE
 * DADO, não a de fechar bloco. Medido antes: topo 0px, base #f1f0ec.
 * Fica em regra própria porque `.dia-saldo` não é faixa de bloco e continua
 * só com o fundo. */
.tabela .dia-cab td {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
/* #105: A FAIXA DE AGRUPAMENTO NÃO REAGE AO HOVER — nas três listas.
 * `.tabela tbody tr:hover th/td` (linha 319) pinta #fafaf8 em QUALQUER linha,
 * e numa faixa de estrutura isso faz duas coisas ruins: promete interação que
 * não existe (medido — a faixa não tem listener de clique; o `a-pagar.js` só
 * lhe reescreve a contagem e a soma) e CLAREIA a faixa, desfazendo no ponteiro
 * o contraste que ela existe para ter. O hover continua inteiro nas linhas de
 * dado, que é onde ele rastreia a leitura e revela `.acoes-linha`.
 * Precisa ser regra própria por causa da conta de especificidade: a do hover é
 * (0,2,3) e passaria por cima do fundo declarado acima, que é (0,2,1). Esta é
 * (0,3,3). Em Lançamentos a regra da faixa já é (0,3,0) e venceria sozinha —
 * está escrito aqui assim mesmo, porque comportamento igual nas três telas não
 * deve depender de um acidente de contagem de seletor. */
.tabela tbody tr.dia-cab:hover > td,
.tabela tbody tr.linha-dia:hover > th { background: var(--page); }
.cola {
  position: sticky;
  left: 0;
  display: inline-block;
  padding-left: var(--esp-10);
  padding-right: var(--esp-10);
}

/* #87: cabeçalho de dia e rodapé de saldo de Lançamentos FUNDIDOS numa linha.
 * Medido antes de desenhar (regra #39; protótipo com este CSS + os piores
 * valores reais da Link ce em produção — saldo -10.081.451,11, total de dia
 * 19.419.080,40, "50 lançamentos"): as seis informações exigem 895,8px de
 * 1238px úteis da célula (main 1280 − 2×16 − 2×10) — cabem numa linha só,
 * com 342px de sobra. `height: auto` porque a linha de estrutura não precisa
 * dos 48px de uma linha de dado: 6px de respiro bastam (≈34px no total).
 * A linha herda o sticky de `.linha-dia > th` (app.css) de propósito — ver
 * o comentário no template. */
.tabela .linha-dia > .dia-fundido {
  height: auto;
  padding-top: var(--esp-6);
  padding-bottom: var(--esp-6);
}
/* ⭐ #105: A FAIXA DE DIA DE LANÇAMENTOS PINTA `--page`, COMO A DAS OUTRAS
 * DUAS LISTAS — E A DECLARAÇÃO PRECISA MORAR AQUI, NÃO EM `app.css`.
 *
 * O defeito que o dono viu: a faixa de dia de Lançamentos saía BRANCA, do
 * mesmo tom das linhas de lançamento, enquanto A pagar e A receber a
 * destacavam. `app.css` declarava `background: #f2f5fa` em `.linha-dia > th`
 * e essa declaração NUNCA CHEGOU À TELA: `.tabela th { background:
 * var(--surface) }` (acima, neste arquivo) empata em especificidade — as duas
 * são (0,1,1) — e vence por vir depois na cascata do #70. Medido no navegador
 * antes da mudança: faixa #ffffff, linha de dado #ffffff.
 *
 * Por isso a correção não é "consertar o valor lá": qualquer cor escrita em
 * `app.css` para este seletor continua morta. O seletor daqui é (0,2,1) e
 * ganha de `.tabela th` por especificidade, não por ordem.
 *
 * `--page` e não um tom novo: é o que `.dia-cab` das outras duas listas já
 * usa e o que a v3 manda para as três. */
.tabela .linha-dia > .dia-fundido {
  background: var(--page);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.dia-fundido-conteudo { display: flex; align-items: baseline; gap: var(--esp-12); }
.dia-fundido-saldos { margin-left: auto; display: inline-flex; align-items: baseline; gap: 18px; }
/* #187: a faixa do dia virou TRÊS células, e o total mora na coluna Valor.
 * As duas células novas herdam o fundo e as bordas de `.dia-fundido` (a regra
 * do #105, acima) — sem isto a faixa sairia listrada, com um pedaço `--page` e
 * dois brancos, que é o defeito de cobertura por outro caminho. */
.tabela .linha-dia > .dia-total,
.tabela .linha-dia > .dia-resto {
  background: var(--page);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  height: auto;
  padding-top: var(--esp-6);
  padding-bottom: var(--esp-6);
}
/* O total é o número que se procura na faixa: mesmo corpo e peso que tinha
 * quando morava na fita (a regra de hierarquia do #87 continua valendo), só
 * que agora na coluna certa. O rótulo fica antes, curto, no mesmo padrão dos
 * outros pares. */
.tabela .linha-dia > .dia-total { white-space: nowrap; }
.dia-total .dia-rotulo { margin-right: var(--esp-4); }
.dia-total .valor { font-size: var(--corpo-miudo); font-weight: 650; }
/* Hierarquia (o próprio prompt do #87 reprova seis textos com o mesmo peso):
 * a data âncora fica no 600/12px de `.dia-data`; os saldos são contexto —
 * valor em peso 400 (o 600 de `.valor` é para linha de dado); o total do dia
 * é o número que se procura — maior, mais pesado e com cor de sinal. A regra
 * do total vem DEPOIS para vencer a dos saldos na mesma especificidade. */
.dia-fundido-saldos .saldo-par .valor { font-weight: var(--peso-normal); }
/* #187: `.dia-liquido` MORREU aqui -- o total saiu da fita e virou célula
 * própria (`.dia-total`, abaixo). A regra ficou órfã no mesmo commit em que o
 * elemento saiu, e regra órfã é o que faz a próxima pessoa procurar um
 * elemento que não existe. */
/* #87 (sequência): o realizado ESMAECE quando repete e ganha ÊNFASE quando
 * muda — a variação é que informa (regra do dono, #87). Quem decide "mudou"
 * é `agrupar_em_dias` (core/relatorios/extrato.py): desde o #88, comparação
 * com o VIZINHO DE BAIXO na ordem exibida (= dia anterior no calendário) —
 * "cor = dinheiro se moveu NESTE dia"; última linha da página sempre forte.
 * Esmaecido = --ink-3, token existente, com o contraste MEDIDO contra o fundo
 * REAL da linha fundida NO NAVEGADOR (prova_interface_87, seção 7).
 * ⚠ ATUALIZADO NO #105: o fundo real deixou de ser var(--surface) #fff e
 * passou a ser var(--page) #f7f7f5 (a regra logo acima) — o número velho,
 * 4,875:1, foi medido contra o branco e EXPIROU quando o sistema mudou (regra
 * do #87 sobre número medido). O de hoje está em prova_interface_105, que
 * mede os seis textos da faixa contra o fundo que o navegador pinta.
 * Contexto histórico: o #f2f5fa que `app.css` declarava para `.linha-dia > th`
 * nunca chegou à tela — `.tabela th` o vencia por ordem na cascata do #70 —,
 * e é justamente isso que o #105 corrigiu. É também a cor
 * que o valor do Previsto já herda de `.tabela th`: a linha inteira fica
 * quieta e o ÚNICO forte é o que mudou.
 * A diferença esmaecido/ênfase vem de MAIS ênfase no que mudou (o azul que o
 * valor já tinha + peso 600), nunca de mais apagamento — condição do dono.
 * --azul (tokens.css) == --acao (app.css) == #276fc6, 5,029:1 no mesmo fundo.
 * As regras vêm DEPOIS da de peso 400 acima para vencer na mesma
 * especificidade. O valor nunca é omitido: quem decide é a classe, o span
 * existe sempre (sticky — o contexto grudado precisa do número). */
.dia-fundido-saldos .saldo-realizado.realizado-igual { color: var(--ink-3); }
.dia-fundido-saldos .saldo-realizado.realizado-mudou { color: var(--azul); font-weight: var(--peso-forte); }
/* A Compacta encolhe TAMBÉM a linha fundida, não só as linhas de dado — é a
 * linha de ESTRUTURA, e estrutura é o primeiro lugar de onde a densidade
 * tira espaço. Medido em 1366×768 com o HTML de produção: com a fundida em
 * 27,75px (padding 3px) a 5ª linha de dado terminava em 776px — 8px abaixo
 * da viewport, por causa das fundidas intermediárias; padding 2px +
 * line-height 1.25 derrubam a fundida para ~20px e a 5ª linha entra. */
/* #209: a regra alcanca as TRES celulas da faixa, e nao so a primeira.
 * Entre o #105 e o #187 a faixa era UMA celula, e mexer no padding dela
 * bastava. O #187 partiu a faixa em `.dia-fundido` + `.dia-total` +
 * `.dia-resto` e deu `var(--esp-6)` de padding as duas novas (regra acima),
 * sem estende-las a esta -- e a altura de uma linha de tabela e a da celula
 * MAIS ALTA. Medido em 27/08/2026, antes desta correcao, em 1366x768 e
 * 1920x1080: a faixa media 33,75px na Normal e 33,75px na Compacta, com
 * `.dia-fundido` ja em padding 2px e as duas irmas segurando a linha em 6px.
 * A Compacta encolhia a linha de DADO (48 -> 41,5px) e nao encolhia nada da
 * ESTRUTURA -- exatamente o oposto do que o paragrafo acima decidiu.
 * Guardado por prova_interface_87 ("Compacta encolhe a linha fundida"), que
 * exige a Compacta pelo menos 4px menor que a Normal. */
:root[data-densidade="compacto"] .tabela .linha-dia > .dia-fundido,
:root[data-densidade="compacto"] .tabela .linha-dia > .dia-total,
:root[data-densidade="compacto"] .tabela .linha-dia > .dia-resto {
  padding-top: var(--esp-2);
  padding-bottom: var(--esp-2);
}
:root[data-densidade="compacto"] .dia-fundido-conteudo { line-height: 1.25; }

/* Relatório de muitas colunas (DRE tem até 13 meses; a tabela cruzada, mais).
 * A densidade é DECLARADA aqui, não herdada de uma segunda tipografia global
 * -- era assim que o sistema tinha dois corpos de texto sem ninguém ter
 * decidido isso (#70). */
.tabela-densa { font-size: var(--corpo-denso); }
.tabela-densa th,
.tabela-densa td { height: var(--altura-linha-densa); padding: var(--esp-4) var(--esp-8); }

/* ---------- Toast de desfazer ---------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--esp-12);
  padding: var(--esp-10) var(--esp-16);
  border-radius: var(--raio);
  background: var(--ink);
  color: var(--branco);
  box-shadow: var(--sombra-flutuante);
  z-index: 60;
}
.toast .btn-mini { background: none; border-color: var(--sobre-escuro-borda); color: var(--branco); }
.toast .btn-mini:hover { background: var(--sobre-escuro-hover); }
.toast-contador { color: var(--sobre-escuro-fraco); font-size: var(--corpo-miudo); min-width: 2ch; text-align: right; }

/* ---------- Véu (fundo de modal, drawer, diálogo e paleta) ---------- */

.veu {
  position: fixed;
  inset: 0;
  background: var(--veu);
  z-index: 50;
}

/* Fundo travado (#77): com QUALQUER camada aberta (modal, drawer, diálogo,
 * paleta), a página atrás não pode rolar por baixo do véu -- o véu cobre a
 * TELA, mas sem isto a roda do mouse ainda movia o conteúdo por trás dele. */
body.fundo-travado { overflow: hidden; }

/* ---------- Modal ---------- */

.modal {
  position: fixed;
  left: 50%;
  top: 9vh;
  transform: translateX(-50%);
  width: min(560px, calc(100vw - 32px));
  border-radius: var(--raio);
  background: var(--surface);
  box-shadow: var(--sombra-painel);
  overflow: hidden;
  z-index: 51;
}
/* Barra de 4px colorida por tipo: vermelho = saída, verde = entrada. */
.modal-barra { height: 4px; }
.modal.saida   .modal-barra { background: var(--vermelho); }
.modal.entrada .modal-barra { background: var(--verde); }
.modal-cab { display: flex; align-items: center; justify-content: space-between; padding: var(--esp-14) 18px 0; font-weight: var(--peso-forte); }
.modal-corpo { padding: var(--esp-14) 18px 18px; }
.modal-pe { display: flex; justify-content: flex-end; gap: var(--esp-8); padding: 0 18px var(--esp-16); }

/* #203: com DUAS ações ("excluir só este R$ 4.200,00" × "excluir este e os 4
 * próximos") os rótulos carregam valor e número, e os três botões não cabem
 * lado a lado num modal de 560px -- MEDIDO em navegador
 * (`ferramentas/foto_local_203_dialogo.py`), quebravam em duas linhas tortas.
 * Em coluna cada escolha se lê inteira, e a ordem visual passa a ser a da
 * decisão: o caminho comum primeiro, o de alcance maior depois, o Cancelar por
 * último (`order`, sem mexer na ordem do DOM, que é a da tabulação e começa
 * no Cancelar de propósito).
 *
 * A ação de alcance maior fica em CONTORNO, não em vermelho cheio: duas caixas
 * vermelhas idênticas empurram para o clique errado -- quem lê rápido escolhe
 * pela forma, não pelo texto. Mesmo token de cor, peso diferente. */
.modal-pe.empilhado { flex-direction: column; align-items: stretch; }
.modal-pe.empilhado [data-papel="cancelar"] { order: 2; }
.modal-pe.empilhado .btn-perigo.mais-largo {
  background: transparent;
  border-color: var(--vermelho);
  color: var(--vermelho);
}
.modal-pe.empilhado .btn-perigo.mais-largo:hover {
  background: var(--vermelho);
  color: var(--branco);
}

/* Diálogo de confirmação reusa .modal; a lista de perdas é o que o desfazer
 * NÃO restaura — é ela que justifica o diálogo em vez do toast. */
.modal-perdas { margin: var(--esp-10) 0 0; padding-left: 20px; color: var(--ink-2); }
.modal-perdas li { margin: var(--esp-2) 0; }

/* Modal grande (#77: edição de lançamento em modal). Doze campos mais a
 * seção de anexos não cabem em 1366×768 com o corpo em fluxo normal -- o
 * corpo rola sozinho e o Salvar (dentro de `.acoes-do-formulario`) fica
 * FORA da área de rolagem, sempre visível. Escopado a `.modal-editar` de
 * propósito: a confirmação e o "trocar em lote" são curtos, cabem sem
 * rolar, e não devem ganhar altura máxima nem corpo em flex por tabela.
 * `max-height`, não `height`: um lançamento sem anexo fica do tamanho do
 * conteúdo; só o que passa do teto rola. */
.modal-editar { max-height: min(86vh, 720px); display: flex; flex-direction: column; }
.modal-editar .modal-corpo {
  flex: 1 1 auto;
  min-height: 0;
  /* #268: era `hidden`, e `hidden` num corpo com teto de altura NÃO é
   * "não rola": é CORTA. Medido em 03/09/2026 no modal de editar pessoa,
   * 1366x768: 955px de conteúdo em 612px de caixa, 343px inalcançáveis --
   * inclusive o `Salvar` -- sem barra de rolagem, sem sinal nenhum de que
   * havia mais coisa. `auto` é a REDE: no caminho certo (filho em flex, com
   * `min-height: 0`) o corpo nunca transborda e nada muda; no dia em que um
   * conteúdo novo entrar sem essa estrutura, ele ROLA em vez de sumir.
   * Conteúdo escondido em silêncio é a pior falha possível numa tela. */
  overflow-y: auto;
  padding: 0;
  display: flex;
  flex-direction: column;
}
/* #268: o contrato do modal grande -- corpo que rola, ações fixas -- estava
 * escrito nos NOMES do formulário de lançamento (`.editar-lancamento`), então
 * só valia para ele. O `usuarios.js` pede `modal-editar` desde o #177-B e
 * herdava METADE dele: o teto de altura sim, a área rolável não. A segunda
 * linha é ESTRUTURAL (o formulário que for filho direto do corpo, qualquer
 * um), e é o que faz formulário novo nascer com o contrato inteiro em vez de
 * repetir este defeito. Qualificado em `> form` de propósito: `> *` alcançaria
 * o `<script>` do `json_script` e um `display: flex` o tornaria VISÍVEL. */
.modal-editar .editar-lancamento,
.modal-editar .modal-corpo > form { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* #268: `.formulario` é o CARTÃO da tela cheia (`app.css`: teto de 480px,
 * padding e margem próprios, fundo). Dentro do modal o cartão sobrava: 448px
 * de campos num corpo de 524, encostados à esquerda com 96px vazios à
 * direita -- MEDIDO na foto de produção do dono --, e era essa largura que
 * derrubava as permissões para UMA coluna (o grid delas pede 440px para duas,
 * e tinha 386). Aqui o espaçamento já é do `.campos-da-edicao` e do rodapé;
 * o cartão zera, e o formulário ocupa o corpo inteiro. */
.modal-editar .modal-corpo > form.formulario { max-width: none; margin: 0; padding: 0; background: transparent; }
.modal-editar .campos-da-edicao { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--esp-14) 18px; }
.modal-editar .acoes-do-formulario {
  flex: 0 0 auto;
  margin: 0;
  padding: var(--esp-12) 18px var(--esp-16);
  border-top: 1px solid var(--line-2);
  background: var(--surface);
}

/* ---------- Drawer ---------- */

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(440px, 100vw);
  background: var(--surface);
  border-left: 1px solid var(--line);
  /* #100/M3b: a sombra pesada (-12px 0 32px rgba(0,0,0,.15)) saiu. A v3 usa só
   * uma linha, e o motivo é de leitura: o painel fica AO LADO da lista, não
   * flutuando sobre ela — a lista continua sendo o assunto. A cor literal do
   * rgba também morre aqui: sobra `--line`, que é token. */
  box-shadow: -1px 0 0 var(--line);
  display: flex;
  flex-direction: column;
  z-index: 51;
}
.drawer-cab { display: flex; align-items: center; justify-content: space-between; padding: var(--esp-14) 18px; border-bottom: 1px solid var(--line-2); font-weight: var(--peso-forte); }
.drawer-corpo { padding: var(--esp-16) 18px 40px; overflow-y: auto; }

/* ⭐ #100/M3b — ACABAMENTO DO DRAWER DE DETALHE contra a referência v3.
 *
 * O componente já existia (`UI.abrirDrawer`, #41) e já era o painel de detalhe
 * que a v3 desenha — nada foi construído nem substituído aqui. O que mudou é
 * hierarquia e peso:
 *
 *   - a descrição e o VALOR entram grandes no corpo (a v3 usa 17px e 26px);
 *     antes o valor vinha pequeno, empurrado por `float:right`, com menos peso
 *     que a etiqueta ao lado;
 *   - os campos ganharam divisória e rótulo de largura fixa, que é o que
 *     alinha a coluna de valores quando um rótulo é bem mais longo que outro;
 *   - a sombra pesada (`-12px 0 32px`) virou a linha fina da v3: o painel não
 *     precisa parecer que flutua sobre a lista, precisa ficar ao lado dela. */
.painel-descricao { margin: 0 0 var(--esp-2); font-size: 17px; font-weight: 620; }
.valorao { font-size: 26px; font-weight: 640; letter-spacing: -0.02em; }
.painel-auditoria { font-size: var(--corpo-nota); color: var(--ink-3); margin: var(--esp-6) 0 var(--esp-14); }
.acoes-drawer { display: flex; gap: var(--esp-8); margin-bottom: 18px; flex-wrap: wrap; }

.linha-campo {
  display: flex; align-items: center; gap: var(--esp-10);
  padding: var(--esp-8) 0; border-bottom: 1px solid var(--line-2);
  font-size: 13.5px;
}
.linha-campo .rot { color: var(--ink-3); width: 132px; flex: none; font-size: var(--corpo-rotulo); }

/* #263 — a OBSERVAÇÃO. Mesmo corpo dos campos (13,5px), tinta cheia porque é
   conteúdo e não etiqueta, e `overflow-wrap: anywhere` porque o texto vem de
   fora. MEDIDO em produção em 02/09/2026: **1.545 observações contêm URL**
   (182 na RB, 1.363 na Link ce), 646 têm alguma sequência de mais de 40
   caracteres sem espaço, e a maior chega a **397** — o painel tem 132px só de
   rótulo, e uma dessas cria rolagem horizontal onde nenhuma tela do sistema
   tem (R40). A quebra de linha vem do `linebreaksbr`, esta regra cuida da
   palavra que não cabe. */
.painel-observacao {
  margin: 0; font-size: var(--corpo-miudo); line-height: 1.45;
  color: var(--ink); overflow-wrap: anywhere;
}
.linha-campo .sub-linha { display: block; color: var(--ink-3); font-size: var(--corpo-nota); }
/* O cadeado acompanha o texto do campo, sem puxar a linha de base. */

/* Campo vazio que convida a preencher (v3). É link de verdade — abre o MESMO
 * modal do botão "Editar", nunca um segundo caminho para o mesmo lugar. */
/* #103: 24px de altura mínima — a WCAG 2.5.8 pede alvo de 24x24, e o portão
 * mediu este link em 43x20 (ele é MEU, do M3b, e ficou 4px abaixo). O padding
 * vertical resolve sem mexer na altura da `.linha-campo`, porque `inline-flex`
 * com `min-height` não empurra a linha: o campo já tem 8px de padding próprio. */
.definir {
  display: inline-flex; align-items: center; min-height: 24px;
  color: var(--azul); cursor: pointer; text-decoration: none;
}
.definir:hover { text-decoration: underline; }
.definir:focus-visible { outline: 2px solid var(--azul); outline-offset: 2px; }

.fechar-x {
  border: 0;
  background: none;
  padding: var(--esp-2) var(--esp-8);
  font-size: 18px;
  line-height: 1;
  color: var(--ink-3);
  cursor: pointer;
  border-radius: 6px;
}
.fechar-x:hover { background: var(--line-2); color: var(--ink); }
.fechar-x:focus-visible { outline: 2px solid var(--azul); }

/* ---------- Paleta de comandos ---------- */

.cmd {
  position: fixed;
  left: 50%;
  top: 12vh;
  transform: translateX(-50%);
  width: min(520px, calc(100vw - 32px));
  border-radius: var(--raio);
  background: var(--surface);
  box-shadow: var(--sombra-painel);
  overflow: hidden;
  z-index: 55;
}
.cmd-campo {
  width: 100%;
  box-sizing: border-box;
  border: 0;
  border-bottom: 1px solid var(--linha-campo); /* campo interativo (#42) */
  padding: 13px var(--esp-16);
  font: inherit;
  /* #169: `outline: none` INTENCIONAL, e por evidência do JS, não por estética.
     Enquanto a paleta existe o foco NUNCA sai daqui: `abrirCamada` foca o
     primeiro `input` da caixa ao abrir (ui.js:74) e a armadilha de Tab devolve
     o foco para dentro dela (ui.js:115-131) — dentro de `.cmd` o único elemento
     que casa com o seletor de focáveis é este campo, então `primeiro` e
     `ultimo` são ele mesmo e Tab/Shift+Tab voltam para cá. Os `.cmd-item` nunca
     recebem foco de DOM: o item corrente é apontado por `aria-activedescendant`
     (ui.js:696), o padrão ARIA de combobox, comentado no `.cmd-item` abaixo.
     Um anel em volta de um campo que está SEMPRE focado não distingue estado
     nenhum — é ruído permanente. Não é o caso do `.segmentado`, onde o
     `outline-offset: -2px` existe porque ali o foco de fato circula.
     Se um dia entrar um segundo focável dentro de `.cmd` (um botão de fechar,
     um filtro), esta linha vira defeito: o foco passa a circular e o anel volta
     a informar. */
  outline: none;
}
.cmd-lista { max-height: 46vh; overflow-y: auto; padding: var(--esp-6) 0; margin: 0; list-style: none; }
.cmd-grupo {
  padding: var(--esp-8) var(--esp-16) 3px;
  font-size: 11.5px;
  font-weight: var(--peso-forte);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.cmd-item { display: flex; justify-content: space-between; gap: var(--esp-12); padding: 7px var(--esp-16); cursor: pointer; }
.cmd-item .atalho { color: var(--ink-3); font-size: var(--corpo-rotulo); }
.cmd-item.ativo { background: var(--azul-fraco); }
/* #168: o `.ativo` acima NÃO é foco faltando -- é o foco. ui.js mantém o foco
   real no `.cmd-campo` e aponta o item corrente por `aria-activedescendant`
   (`campo.setAttribute("aria-activedescendant", marcado.id)`), o padrão ARIA de
   combobox. O `<li>` nunca recebe foco do DOM, então `:focus-visible` aqui
   seria regra que jamais dispara. Sem `:active` também: dar fundo de pressionado
   exigiria escolher entre `--line-2` (hover) e `--azul-fraco` (ativo) e a
   escolha colidiria com o significado de um dos dois -- é decisão de cor, e
   decisão de cor não se toma sozinho. */
.cmd-vazio { padding: var(--esp-14) var(--esp-16); color: var(--ink-3); }


/* ---------- Tooltip acessível (.explica) — #42 ----------
 * O antídoto do erro dos R$ 7 mi tem de abrir por TECLADO: o alvo é focável
 * (tabindex=0/role=button) e a explicação aparece em :hover E :focus-visible.
 */
.explica-alvo { position: relative; cursor: help; }
.explica {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 45;
  min-width: 240px;
  max-width: 340px;
  padding: var(--esp-8) var(--esp-12);
  border-radius: calc(var(--raio) - 3px);
  background: var(--ink);
  color: var(--branco);
  font-size: var(--corpo-rotulo);
  font-weight: var(--peso-normal);
  line-height: 1.45;
  box-shadow: var(--sombra-flutuante);
}
.explica-alvo:hover .explica,
.explica-alvo:focus-visible .explica,
.explica-alvo:focus .explica { display: block; }
.explica-alvo:focus-visible { outline: 2px solid var(--azul); outline-offset: 2px; }

/* #71, Tarefa 3: `select` no desenho do sistema --------------------------
 * Medido antes de escrever (commit 64dee9a): `.campo` (linha 52) nao tem
 * regra para o ELEMENTO `select` -- quem nao recebe `class="campo"` sai com
 * a moldura e a seta do sistema operacional, ao lado de campos desenhados.
 * `--altura-campo` NAO existe em tokens.css (medido, grep exaustivo) e a
 * decisao foi nao cria-lo: `.campo` hoje nao fixa altura nenhuma, so
 * `padding` + fonte, entao o `select` espelha o MESMO padding em vez de
 * herdar uma altura decretada -- assim os dois continuam casando se a fonte
 * mudar amanha. `appearance: none` tira a moldura nativa; a seta e um SVG
 * embutido (nao um arquivo -- o WhiteNoise so serve hash novo depois de
 * `collectstatic`, que esta fora desta rodada). Mexe SO no elemento
 * `select`, nao em `.campo` -- outro agente acrescenta regra de formulario
 * neste mesmo arquivo, em arvore separada.
 */
select {
  appearance: none;
  background-color: var(--surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23555' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-position: right 10px center;
  background-repeat: no-repeat;
  background-size: 12px 8px;
  border: 1px solid var(--linha-campo); /* 3:1 (#42); mesma borda de .campo */
  border-radius: calc(var(--raio) - 3px);
  box-sizing: border-box;
  color: var(--ink);
  font: inherit;
  /* #76: `font` e TAQUIGRAFIA -- ela arrasta o `line-height: 1.5` do corpo
     para dentro do controle, e o `<select>` nao cresce para acomodar: medido
     em Lancamentos, caixa de 32px com 14px de padding deixava 16px uteis para
     uma linha de 22,5px, e o texto "Nao repete" saia cortado na base. Controle
     de formulario usa `normal`, que e o que o navegador ja fazia antes de a
     regra do #71 existir. */
  line-height: normal;
  padding: 7px 28px 7px var(--esp-10); /* espelha .campo (7px 10px), com espaco para a seta */
}

/* #168: mesma promoção de `.campo` acima, e aqui ela MUDA o que se vê. `select`
   não é campo editável: com `:focus` puro, clicar no seletor com o mouse
   deixava o anel de 2px aceso na tela até o foco sair -- num formulário de
   lançamento com quatro selects, isso é ruído permanente para quem opera no
   mouse. Com `:focus-visible` o anel fica reservado a quem chegou pelo teclado.
   Cobre as três formas do seletor sem repetir a regra: `select` (0,0,1) alcança
   `select.campo` e `select.cat-select-grupo`, e nenhuma das duas declara
   outline próprio -- quem vence em `select.campo` é o `.campo:focus-visible`
   acima, com o MESMO anel. Um só dialeto nos três. */
select:focus-visible { outline: 2px solid var(--azul); outline-offset: 0; border-color: var(--azul); }

/* Achado medindo no navegador (nao no plano): `.campo` (acima) e a
 * `.cat-select-grupo` legada (`static/css/app.css`) pintam o fundo com a
 * taquigrafia `background: ...`, que reseta `background-image` mesmo sem
 * redeclarar -- e as duas sao seletor de CLASSE, especificidade maior que o
 * `select` do elemento acima, entao venciam independente da ordem de carga.
 * Resultado sem este bloco: nem seta nativa (o `appearance: none` acima
 * aplicava, sem concorrente) nem a nossa -- select sem indicador NENHUM.
 * Aqui a seta e reafirmada com a especificidade `select.<classe>`, sem
 * tocar a borda/cor/altura que ja pertencem a essas classes. */
select.campo {
  /* #76: `.campo` (0,1,0) vence o seletor de ELEMENTO `select` (0,0,1) e
     reimpoe o `font: inherit`, que arrasta o line-height do corpo. Medido: o
     unico select do sistema com `class="campo"` (o Horizonte do A pagar)
     ficava em 19,5px enquanto Cartoes, Categorias e Lancamentos iam para
     `normal`. Vale so para `select` -- em `input` o `.campo` funciona. */
  line-height: normal;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23555' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-position: right 10px center;
  background-repeat: no-repeat;
  background-size: 12px 8px;
  padding-right: 28px;
}
/* `.cat-select-grupo` (app.css legado) tem `height: 26px` fixo -- o padding
 * vertical de 7px da regra geral nao cabe (7+7+borda estoura 26px e corta o
 * texto, medido no navegador). Aqui o padding volta ao `0 8px` que o app.css
 * ja usava para select antes desta tarefa; so a seta e o espaco pra ela sao
 * novos.
 *
 * Ponta solta (revisao pos-#71): o `background-size` nascia `10px 7px` aqui
 * contra `12px 8px` em Cadastros/A pagar -- MESMO svg, tamanho menor "por
 * causa da altura de 26px". Medido: nao procede. A altura de 26px so exige o
 * PADDING menor (acima, `0 24px 0 8px` em vez de `7px 28px 7px 10px`) --
 * `background-image` nao ocupa espaco no box model, entao o tamanho da seta
 * e independente da altura do elemento. Com `right 8px center` e
 * `padding-right: 24px`, uma seta de 12px cabe inteira (8+12=20px de 24px
 * reservados, 4px de folga) sem cortar nem empurrar o texto -- e a altura de
 * `.cat-linha` (32px sem filhos, 36,78px com toggle) fica INTACTA, porque
 * nao depende da imagem de fundo do select. Unificado para as tres telas
 * lerem a MESMA seta, tamanho incluso; medicao antes/depois em
 * docs/medicoes/interface-71-f2.txt. */
select.cat-select-grupo {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23555' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-position: right 8px center;
  background-repeat: no-repeat;
  background-size: 12px 8px;
  padding: 0 var(--esp-24) 0 var(--esp-8);
}
/* #71, Tarefa 2: o formulario de cadastro era `as_p` cru solto abaixo da
 * tabela -- rotulo com dois-pontos (label_suffix padrao do Django), select
 * com a moldura do sistema, checkbox nu e centralizado sem dizer do que
 * era, e um `<button>` sem classe (o azul que nao e azul de nada no
 * sistema). Aqui o formulario ganha a mesma moldura dos outros blocos
 * (`.cartao`) e os campos entram na MESMA linha do rotulo quando fazem
 * sentido lado a lado -- `campo-grupo-caixa` e so para o checkbox, que
 * antes ficava sozinho, centralizado, sem dizer do que era.
 */
/* A base de `.campo-grupo` faltava, e sem ela o `-caixa` abaixo nao teria
   sobre o que aplicar `flex-direction: row`. Ela herda o que `.formulario p`
   (app.css:448) fazia enquanto o formulario era `as_p`: rotulo em cima, campo
   embaixo. Trocado o wrapper, a regra antiga deixou de alcancar -- achado no
   merge da rodada, nao pelo teste, porque nenhum teste olhava layout. */
.campo-grupo { display: flex; flex-direction: column; gap: var(--esp-4); }
/* Seletor COMPOSTO, sem classe nova: o elemento ja carrega `cartao` e
   `formulario`. A rodada tinha criado `.cartao-formulario`, e esse nome fora
   APOSENTADO pelo #70 (`test_interface_unificada_70.py::NOMES_APOSENTADOS`) na
   leva que desambiguou o cartao de KPI -- reciclar um nome aposentado para
   outro conceito e exatamente o que aquela rodada passou dez tarefas
   desfazendo, e a suite pegou.
   De quebra resolve a disputa de largura: `.formulario` (app.css:447) pede
   480px e este pede 520px; composto tem especificidade maior (0,2,0) e vence
   por REGRA, nao por ordem de carga. */
.formulario.cartao { margin-top: var(--esp-16); max-width: 520px; }
/* #304: o mesmo respiro entre grupos vale no MODAL, onde o formulario de
   cadastro nao leva `.cartao` (o modal ja e a moldura) -- medido na foto da
   prova 304: sem isto o rotulo do campo seguinte encostava no campo anterior. */
.formulario.cartao .campo-grupo + .campo-grupo,
form[data-cadastro] .campo-grupo + .campo-grupo,
form[data-cadastro] .campo-grupo + .campos-lado-a-lado,
form[data-cadastro] .campos-lado-a-lado + .campo-grupo { margin-top: var(--esp-12); }
/* #304: a regra que compensava o padding do `.cartao-cab` dentro do
   `.formulario.cartao` (ponta solta pos-#71) saiu junto com o <h2> que ela
   compensava -- o titulo da pagina inteira e o <h1> da tela, e no modal e a
   barra do proprio modal. */
.campo-grupo-caixa { align-items: center; display: flex; flex-direction: row; gap: var(--esp-8); }
.campo-grupo-caixa label { margin: 0; order: 2; }
/* #304: subtitulo de um par de campos ("Identificacao no extrato OFX" acima
   de codigo do banco + numero da conta). Mesma tinta e corpo do <legend> das
   condicoes do lancamento (`.condicao legend`): e o mesmo papel. */
.titulo-do-grupo { margin: var(--esp-16) 0 var(--esp-4); font-size: var(--corpo-miudo); font-weight: var(--peso-forte); color: var(--ink-3); }
/* #304: campos em par (`.campos-lado-a-lado`, app.css) dentro do formulario
   de cadastro -- o `.campo-grupo` e filho flex e cresce (`.cresce`); o
   `margin-top` entre grupos passa a ser o gap da linha, nao da coluna. */
.formulario .campos-lado-a-lado { margin-top: var(--esp-12); }
.formulario .campos-lado-a-lado .campo-grupo + .campo-grupo { margin-top: 0; }
/* #71, Tarefa 5 -> #98, B8: OS TRES CONTROLES SOLTOS DE LANCAMENTOS, E O
 * QUE SOBROU DELES.
 *
 * O #71 juntou num cartao unico a `Transferencia` (que vivia colada no <h1>
 * sem moldura), o `Filtrar` (isolado numa linha so dele) e a entrada rapida
 * inline. O #98 desmontou esse cartao, e cada peca foi para onde ela pertence:
 *   - a entrada rapida MORREU (uso medido ZERO em producao -- ver
 *     `core/urls.py`); com ela morreram `.entrada-rapida`, `.campo-rapido` e
 *     `.confirmacao`;
 *   - o `Filtrar` desceu para a `.barra-da-lista`, junto do Localizar, do
 *     Exportar e da ordem (diretriz 6);
 *   - a `Transferencia` subiu para o cabecalho da tela (`block acoes`) e
 *     passou a abrir o modal com o tipo ja escolhido.
 * Nao sobrou nenhuma regra de `.cartao-lancar-rapido`: o cartao nao existe
 * mais em template nenhum. */

/* #71, ponta solta (revisao pos-#71) -- `templates/a_pagar/tela.html` era o
 * UNICO template do sistema com um `<style>` dentro do `<body>`, contra a
 * decisao travada do #70 ("nenhum template carrega CSS dentro do <body>";
 * `tests/test_interface_unificada_70.py::test_nenhum_template_carrega_css_dentro_do_corpo`).
 * A rodada #71 (Tarefa 4) tirou UM `style` inline do controle de horizonte
 * (`.ap-periodo`/`.ap-periodo .campo`, comentario original preservado
 * abaixo) e o pos DENTRO desse `<style>` -- que ja existia e so engordou.
 * Bloco movido para ca, verbatim, exceto o `style="margin-left:auto"` do
 * `<span class="ap-sobra">` de "Disponivel hoje": virou `.ap-controles >
 * .ap-sobra` abaixo, escopado ao container flex que o continha -- as OUTRAS
 * duas ocorrencias de `.ap-sobra` na mesma tela (no rotulo do horizonte, e
 * dentro de `.ap-cartao .cartao-corpo`, que e grid, nao flex) nao usam
 * `margin-left:auto`, e um seletor geral `.ap-sobra { margin-left: auto }`
 * teria empurrado as duas tambem.
 * Nenhuma regra abaixo precisou vencer outra do resto deste arquivo por
 * ORDEM de carga: todas tem pelo menos um seletor composto (duas classes
 * ou mais, ou combinador descendente) contra as regras gerais de elemento/
 * classe unica que ja existem aqui (`.btn`, `.btn:hover`, `.campo`, `.cartao-
 * corpo`, `.negativo`) -- a especificidade maior ja resolve, esteja este
 * bloco no INICIO ou no FIM do arquivo. Medido antes/depois no navegador
 * (mesmo layout, mesmos valores computados):
 * docs/medicoes/interface-71-f2.txt. */
.ap-topo { display: flex; gap: var(--esp-12); flex-wrap: wrap; align-items: stretch; margin-bottom: var(--esp-14); }
.ap-cartao { flex: 1 1 200px; }

/* ⭐ #100/M3a — O SENTINELA DO CARREGAMENTO PROGRESSIVO.
 *
 * É uma linha de tabela como as outras, e por isso herda a altura e a
 * divisória de `.tabela td`. O que ela acrescenta é o centro: o botão fica no
 * meio, onde o olho já está ao terminar de rolar.
 *
 * O "carregando…" só aparece durante a requisição — o htmx põe e tira a classe
 * `htmx-request` no elemento com `hx-indicator`. Sem isso a linha ficaria muda
 * enquanto o bloco vem, e silêncio depois de uma ação é defeito (diretriz 8). */
.bloco-pe { text-align: center; }
.bloco-pe .bloco-conta { margin-left: var(--esp-12); font-size: var(--corpo-rotulo); color: var(--ink-3); }
.bloco-carregando { display: none; margin-left: var(--esp-12); font-size: var(--corpo-rotulo); color: var(--ink-2); }
.htmx-request .bloco-carregando,
.htmx-request.bloco-carregando { display: inline; }

/* ⭐ #100 — OS TRÊS CARTÕES DE URGÊNCIA EM LANÇAMENTOS, como ATALHOS DE FILTRO.
 *
 * Mesma geometria de `.ap-topo`/`.ap-cartao` de propósito (diretriz 6: mesma
 * função, mesmo componente, mesma posição) -- o que muda é o PAPEL: em
 * `A pagar` eles resumem os blocos da própria lista; aqui recortam a lista de
 * baixo. Por isso são `<a>` e não `<div>`: quem clica espera navegar, e um
 * link dá teclado, foco e menu de contexto de graça.
 *
 * Nenhum token novo: as cores saem de `--surface`, `--line`, `--ink`. */
.cartoes-urgencia {
  display: flex; gap: var(--esp-12); flex-wrap: wrap; align-items: stretch;
  margin-bottom: var(--esp-14);
}
/* #275 (decisão do dono, opção B): o cartão vira UMA linha -- rótulo · contagem
 * à esquerda, valor à direita -- e o pé saiu (a frase vive no `title`). Medido
 * em 1366×768: 103px → ~56px por cartão, e a primeira linha de dado sobe 40px.
 * O que NÃO mudou: o tom do número (#183, :has() abaixo), a moldura do ativo
 * (v3) e o fundo branco sobre o qual o contraste foi medido. */
.cartao-urgencia {
  flex: 1 1 200px;
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--esp-12);
  padding: var(--esp-10) var(--esp-14);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--raio);
  color: inherit; text-decoration: none;
}
.cartao-urgencia:hover { background: var(--line-2); }
.cartao-urgencia:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }
/* #314: o "Resto do mês" nos últimos 8 dias do mês -- sem faixa, sem `href`,
 * nada a clicar. Sem isto ele acende ao passar o mouse e promete um recorte
 * que não existe. Mesmo par do `.cartoes-da-fatura` (#283) e do
 * `.cartoes-saude` (#309), mas pela CLASSE do cartão, nunca por
 * `:not([href])`: os cartões da fatura e da Saúde também são
 * `.cartao-urgencia` sem `href` (são `<div>`) e têm regras próprias. */
.cartao-urgencia.cartao-sem-faixa { cursor: default; }
.cartao-urgencia.cartao-sem-faixa:hover { background: var(--surface); }

/* O ativo com MOLDURA ESCURA (v3). Borda de 2px sem mudar o padding
 * compensaria por fora e moveria o conteúdo 1px ao ativar -- o `inset` da
 * sombra desenha a segunda linha para dentro, sem mexer na caixa. */
.cartao-ativo {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.cartao-urgencia-cab {
  display: flex; align-items: center; gap: var(--esp-8);
  font-size: var(--corpo-rotulo); font-weight: var(--peso-forte); color: var(--ink-3);
}
.cartao-urgencia-total { font-size: 18px; font-weight: 650; font-variant-numeric: tabular-nums; }

/* ⭐ #183 — O NUMERO CARREGA A COR DO PROPRIO TOM.
 *
 * Relato do dono: "em 'lançamentos' 'a pagar' 'a recerber', os cards desses
 * menus, estão sem cor, apenas preto". Medido nas tres telas antes de mexer:
 * os NOVE cartoes traziam o total em `rgb(15,23,42)` -- a tinta padrao --
 * enquanto a etiqueta de quantidade AO LADO ja mostrava a cor do tom.
 *
 * O dado do tom sempre esteve no HTML (`.etiqueta.vencido/.falta/.aberto`,
 * vindo de `core/relatorios/urgencia.py` e de `core/views/a_pagar.py`); so o
 * numero nao o usava. Por isso a cor sai do `:has()` do proprio cartao, e nao
 * de classe nova no template: nenhuma view muda, nenhum token nasce, e o
 * cartao continua com UMA fonte de verdade para o tom. `:has()` ja e vocabulario
 * deste projeto (`.fila-pendencias > li:has(.etiqueta.vencido)`, app.css).
 *
 * SO O NUMERO. O rotulo e o pe seguem na tinta de contexto -- colorir tudo
 * apagaria a hierarquia do cartao, e o herói do Inicio (#149) ja usa
 * exatamente este desenho: valor colorido, texto neutro.
 *
 * Contraste MEDIDO sobre o fundo real do cartao (branco), pela
 * `prova_interface_183_cor_dos_cards`: vermelho 4,98:1 · ambar 4,76:1 ·
 * azul 5,17:1 -- os tres acima dos 4,5 da diretriz §7. Trocar o fundo do
 * cartao obriga a remedir os tres (o alerta do CLAUDE.md: tinta e fundo
 * andam juntos). */
.cartao-urgencia:has(.etiqueta.vencido) .cartao-urgencia-total,
.ap-cartao:has(.etiqueta.vencido) .ap-total { color: var(--vermelho); }
.cartao-urgencia:has(.etiqueta.falta) .cartao-urgencia-total,
.ap-cartao:has(.etiqueta.falta) .ap-total { color: var(--amarelo); }
.cartao-urgencia:has(.etiqueta.aberto) .cartao-urgencia-total,
.ap-cartao:has(.etiqueta.aberto) .ap-total { color: var(--azul); }
.cartao-urgencia:has(.etiqueta.ok) .cartao-urgencia-total,
.ap-cartao:has(.etiqueta.ok) .ap-total { color: var(--verde); }
/* O pé explica o que o clique faz. Fica em `--ink-2` e não em `--ink-3`
 * porque é instrução, não rodapé: a pessoa precisa lê-lo ANTES de clicar. */
.cartao-urgencia-pe { font-size: var(--corpo-miudo); color: var(--ink-2); }

/* #283: a linha de cartões da FATURA -- o mesmo componente, sem ser link
 * (nada a clicar: sem hover, cursor normal). A nota do meio ("vence 03/10 ·
 * limite · de 25/08 a 24/09") toma a folga e o número fica à direita, como no
 * #275. O número da fatura leva a cor do estado pelo `:has()` do #183, só nos
 * dois estados que pedem atenção (fechada = âmbar, é a que se paga; vencida =
 * vermelho); recebendo e paga ficam na tinta. `small` é o rótulo "disponível"
 * dentro do número do cartão: número sem nome não diz o que é. */
.cartoes-da-fatura .cartao-urgencia { cursor: default; }
.cartoes-da-fatura .cartao-urgencia:hover { background: var(--surface); }
/* Dois cartões (fatura, cartão), metade cada. MEDIDO na base semeada: com
 * TRÊS (o terceiro só para a contagem de compras) os três quebravam em quatro
 * linhas (126px); a contagem foi para a nota da fatura. Rótulo e número nunca
 * quebram (`nowrap`); a nota é quem cede. */
/* #288: `360px` de base estourava uma janela de 360 (328 úteis) em 21px --
   medido pela prova 121. `min()` mantém os 360 onde cabem. */
.cartoes-da-fatura .cartao-urgencia { flex: 1 1 min(360px, 100%); min-width: 0; }
@media (max-width: 480px) {
  /* num celular o cartão vira duas linhas: rótulo + número em cima, a nota
     embaixo inteira -- espremida entre os dois ela saía uma palavra por linha
     (foto em 360px). */
  .cartoes-da-fatura .cartao-urgencia { flex-wrap: wrap; }
  .cartoes-da-fatura .cartao-urgencia-nota { flex: 1 1 100%; order: 3; }
}
.cartoes-da-fatura .cartao-urgencia-cab,
.cartoes-da-fatura .cartao-urgencia-total { white-space: nowrap; }
.cartao-urgencia-nota { flex: 1 1 auto; min-width: 0; font-size: var(--corpo-miudo); color: var(--ink-2); }
/* #283: "Pago em [data] [Registrar pagamento]" numa linha só -- antes o rótulo
 * ficava sobre o campo e o botão embaixo (três linhas, ~100px). */
.ap-controles form.cola { display: flex; align-items: center; gap: var(--esp-8); flex-wrap: wrap; }
.ap-controles form.cola > label { display: inline-flex; align-items: center; gap: var(--esp-6); margin: 0; }
.cartao-urgencia-cab .marca { margin-left: var(--esp-2); }
.cartoes-da-fatura .cartao-urgencia:has(.marca-vencido) .cartao-urgencia-total { color: var(--vermelho); }
.cartoes-da-fatura .cartao-urgencia:has(.marca-aberto) .cartao-urgencia-total { color: var(--amarelo); }
.cartao-urgencia-total small { font-size: var(--corpo-nota); font-weight: var(--peso-medio); color: var(--ink-2); margin-right: var(--esp-4); }
/* #283: o salto por ano no menu do navegador de fatura. */
.outros-anos { display: flex; justify-content: center; gap: var(--esp-12); margin-top: var(--esp-6); padding: var(--esp-8) var(--esp-16) 0; border-top: 1px solid var(--line); }
.menu-de-periodo-itens .outros-anos a { display: inline; padding: 0; color: var(--azul); }
.ap-cartao .cartao-corpo { display: grid; gap: var(--esp-2); }
/* #184: o nome do cartao vira o controle de troca de fatura quando a empresa
   tem mais de um cartao ativo. Herda o peso do `<strong>` que ele substitui,
   para a tela de um cartao so e a de varios terem a MESMA aparencia em repouso
   -- o que muda e so o estado corrente. A cor selecionada e a mesma da aba
   corrente (`--azul`), pelo mesmo motivo da diretriz 6. Foco visivel vem do
   `:focus-visible` global do app.css, como nas abas (#168). */
/* O `min-height` NAO e decoracao: medido na foto local do #184, o link nascia
   com 23px de altura -- o MESMO defeito de alvo de clique que o #182 pagou no
   seletor de periodo (WCAG 2.5.8 exige 24). `display: flex` + `align-items`
   porque `min-height` sozinho nao centra o texto num elemento inline. */
.ap-cartao-nome {
  font-weight: var(--peso-maximo);
  color: var(--ink);
  text-decoration: none;
  display: flex;
  align-items: center;
  min-height: 24px;
}
.ap-cartao-nome:hover { text-decoration: underline; }
.ap-cartao-nome[aria-current="page"] { color: var(--azul); }
.ap-cartao:has(.ap-cartao-nome[aria-current="page"]) { border-color: var(--azul); }
.ap-total { font-size: 22px; font-weight: 650; }
.ap-sobra { font-size: var(--corpo-miudo); color: var(--ink-2); }
/* `.ap-sobra .num.negativo { color: var(--vermelho) }` MORREU aqui no #98:
 * virou o caso geral (`.negativo`, no bloco da tabela, acima). Era um dos três
 * lugares que já apontavam para `--vermelho` enquanto `.negativo` apontava
 * para `--erro` — a duplicata era a própria origem das duas cores. */
/* #71, item 4: a largura do <select> de horizonte era um `style` inline
   (`display:inline-block;width:auto`) -- agora resolvida aqui, junto de
   `.ap-sobra`, que é a classe do rótulo que o envolve. */
.ap-periodo { display: flex; align-items: center; }
.ap-periodo .campo { display: inline-block; width: auto; }
.ap-controles { display: flex; gap: var(--esp-10); align-items: center; margin-bottom: var(--esp-10); flex-wrap: wrap; }
/* Ponta solta: era `style="margin-left:auto"` no `<span class="ap-sobra">`
   de "Disponível hoje" -- ver o comentário do bloco, acima. */
.ap-controles > .ap-sobra { margin-left: auto; }
.ap-lote {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  display: none; align-items: center; gap: var(--esp-14);
  background: var(--ink); color: var(--branco); border-radius: var(--raio);
  padding: var(--esp-10) var(--esp-16); box-shadow: var(--sombra-flutuante); z-index: 40;
}
.ap-lote.visivel { display: flex; }
.ap-lote .btn-mini { background: none; border-color: var(--sobre-escuro-borda); color: var(--branco); }
.ap-lote .btn-mini:hover { background: var(--sobre-escuro-hover); }
.ap-fora { color: var(--ink-3); font-weight: var(--peso-normal); }

/* #71: a fila Aplicar/Cancelar da previa de conciliacao. Nasceu como `style`
   inline no mesmo commit em que a rodada tirava o `<style>` do corpo do
   `a_pagar` -- contradicao apontada na revisao final. Decorativo puro, cabe
   aqui. */
.acoes-da-previa { align-items: center; display: flex; flex-wrap: wrap; gap: var(--esp-10); }

/* ---------- formulário de lançamento (#84) ----------
   O modal unificado (criar/editar, despesa/receita/transferência). As seções
   condicionais são CSS puro com `:has()`, como a repetição da entrada rápida:
   os campos dos três tipos coexistem no DOM (trocar de tipo preserva o que
   já foi digitado) e o SERVIDOR decide o que vale — campo escondido ainda
   submete, e é a view quem ignora `dt_pagamento` sem `pago`, recusa série
   paga e só lê origem/destino no tipo TRANSFERENCIA. */

/* Controle segmentado: radios visualmente ocultos, rótulos como botões.
   A célula é o span FILHO DIRETO do label (`label > span`), um por item —
   o combinador de filho é deliberado (#86, item 1): com descendente, os
   spans internos (.ponto-tipo, .icone-tipo) também viravam célula, com
   padding e borda próprios, e o label (que herda flex-direction: column de
   .formulario-de-edicao) os empilhava em duas linhas. */
.segmentado { display: inline-flex; border: 1px solid var(--linha-campo); border-radius: var(--raio); overflow: hidden; }
.segmentado label { display: inline-flex; }
.segmentado input { position: absolute; opacity: 0; pointer-events: none; }
.segmentado label > span { display: inline-flex; align-items: center; gap: var(--esp-6); padding: 7px var(--esp-14); font-size: var(--corpo-miudo); color: var(--ink-2); cursor: pointer; border-left: 1px solid var(--line); }
.segmentado label:first-child > span { border-left: none; }
/* #86, item 2 (decisão do dono): dois vocabulários DELIBERADOS — a bolinha
   carrega natureza (vermelho saída/verde entrada, a mesma da lista); a
   transferência não é nem uma nem outra e leva o ⇄, alinhado ao texto na
   horizontal, mesma altura e peso dos irmãos. */
.segmentado .icone-tipo { line-height: 1; }
.segmentado input:checked + span { background: var(--azul-fraco); color: var(--azul); font-weight: var(--peso-forte); }
.segmentado input:focus-visible + span { outline: 2px solid var(--azul); outline-offset: -2px; }
.segmentado input:disabled + span { color: var(--ink-3); cursor: not-allowed; background: var(--line-2); }

/* Toggle "Pago": checkbox estilizado como chave. O rótulo fica ACIMA da
   chave (#86, item 4), no mesmo padrão dos outros campos — quem dá a coluna,
   o corpo 12px e a cor fraca do rótulo é `.formulario-de-edicao label`
   (app.css); aqui só o cursor e o respiro da chave. */
.chave-pago { cursor: pointer; }
.chave-pago .chave { margin-top: 3px; }
.chave-pago input { position: absolute; opacity: 0; pointer-events: none; }
.chave-pago .chave { width: 34px; height: 18px; border-radius: 9px; background: var(--linha-campo); position: relative; transition: background .12s; flex: none; }
.chave-pago .chave::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--branco); transition: left .12s; }
.chave-pago input:checked + .chave { background: var(--verde); }
.chave-pago input:checked + .chave::after { left: 18px; }
.chave-pago input:focus-visible + .chave { outline: 2px solid var(--azul); outline-offset: 2px; }

/* Tipo: transferência esconde conta/categoria e o Pago (#322: a condição
   À vista/Fixo/Parcelado ficou, a transferência também se repete); os dois
   lados e a nota aparecem. Fora da transferência, o rótulo do campo de data diz
   "Vencimento"; nela, "Data". */
.form-lancamento:has(.seg-tipo input[value="TRANSFERENCIA"]:checked) .so-movimento { display: none; }
.form-lancamento:not(:has(.seg-tipo input[value="TRANSFERENCIA"]:checked)) .so-transferencia { display: none; }
.form-lancamento .rotulo-data-transferencia { display: none; }
.form-lancamento:has(.seg-tipo input[value="TRANSFERENCIA"]:checked) .rotulo-vencimento { display: none; }
.form-lancamento:has(.seg-tipo input[value="TRANSFERENCIA"]:checked) .rotulo-data-transferencia { display: inline; }

/* Condição: nº de vezes só com Fixo/Parcelado; as notas explicando cada um
   idem; o Pago some quando há repetição (nenhuma parcela de série nasce
   paga — core/recorrencia.py) e o bloco do pagamento só abre com o toggle. */
/* #203: "Nº de vezes" é do PARCELADO. O fixo não tem número de vezes — ele
   existe todo mês. A regra inverteu: some por padrão, aparece no parcelado. */
.form-lancamento .campo-repeticoes { display: none; }
.form-lancamento:has(.seg-repete input[value="parcelado"]:checked) .campo-repeticoes { display: block; }
.form-lancamento .so-fixo, .form-lancamento .so-parcelado { display: none; }
.form-lancamento:has(.seg-repete input[value="fixo"]:checked) .so-fixo { display: block; }
.form-lancamento:has(.seg-repete input[value="parcelado"]:checked) .so-parcelado { display: block; }
.form-lancamento:not(:has(.seg-repete input[value="nao"]:checked)) .condicao-pago { display: none; }
/* #322: o que só vale quando há repetição (a nota da transferência em série,
   que nasce em aberto). Some no À vista. */
.form-lancamento:has(.seg-repete input[value="nao"]:checked) .so-repetida { display: none; }
/* #241: o bloco esconde a DATA de pagamento enquanto não há baixa -- mas a
   CONTA que paga manda no filtro e no saldo mesmo com o lançamento em aberto
   (medido: 1.738 assim em produção). Escondê-la deixava a pessoa sem ver nem
   trocar o campo que decide em que conta o lançamento aparece. O bloco fica
   quando a conta está preenchida (`.tem-conta-de-pagamento`). */
.form-lancamento:not(:has(.chave-pago input:checked)) .bloco-pago:not(.tem-conta-de-pagamento) { display: none; }
.form-lancamento:not(:has(.chave-pago input:checked)) .bloco-pago.tem-conta-de-pagamento [name="dt_pagamento"] { display: none; }

/* #86, item 5: sem moldura — o <fieldset>/<legend> continuam pela semântica,
   mas a seção é só um rótulo de grupo discreto (--ink-3, --corpo-miudo,
   600 — NÃO azul: azul sinaliza clicável e isto não clica). */
.condicao { border: 0; border-radius: 0; padding: 0; margin: var(--esp-4) 0; min-inline-size: auto; }
.condicao legend { font-size: var(--corpo-miudo); color: var(--ink-3); font-weight: var(--peso-forte); padding: 0; margin-bottom: var(--esp-6); }
.condicao .campos-lado-a-lado { align-items: center; }
/* #86, item 4: sem `margin-left: auto` no toggle — Pago fica ao lado do
   segmentado À vista·Fixo·Parcelado (as duas decisões sobre quando o
   dinheiro sai), com o gap normal da fila. */
.condicao .condicao-pago { flex: 0 0 auto; }
.condicao-edicao { display: flex; flex-direction: column; gap: var(--esp-8); margin: var(--esp-4) 0; }

/* Os dois lados da transferência: um movimento só, com a seta no meio. */
.par-de-contas { display: flex; gap: var(--esp-10); align-items: end; }
/* #261: era um `<span>` decorativo, virou o botão de inverter os lados. O
   desenho não muda -- o que muda é que agora ele responde ao clique. */
.par-de-contas .seta-transferencia { font-size: 20px; color: var(--ink-3); padding-bottom: var(--esp-8); flex: none;
  background: none; border: 0; cursor: pointer; line-height: 1; padding-inline: var(--esp-6); border-radius: var(--raio); }
.par-de-contas .seta-transferencia:hover { color: var(--ink); background: var(--linha-hover); }
.par-de-contas .seta-transferencia:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }

/* Erro junto do campo (#84): a mensagem mora embaixo do campo que falhou.
   #287: a regra vivia aqui E mais abaixo (`.erro-campo`, 11px); a de baixo
   vencia por cascata e esta era código morto com cara de vivo. Ficou uma. */

.tags-do-lancamento { display: flex; flex-wrap: wrap; gap: var(--esp-6); align-items: center; margin-top: var(--esp-8); }
.tags-do-lancamento .chip { cursor: pointer; }
/* #287: `.formulario-de-edicao label` (app.css) põe TODO label do formulário
   em coluna e todo input com 34px de altura -- o chip de tag virava um círculo
   com a caixa em cima e o nome embaixo (foto da auditoria). Aqui o chip volta
   a ser linha, e a caixa volta ao tamanho de caixa. */
.formulario-de-edicao .tags-do-lancamento .chip { flex-direction: row; align-items: center; gap: var(--esp-6); color: var(--azul); }
.formulario-de-edicao .tags-do-lancamento .chip input { height: auto; width: auto; margin: 0; }
/* #86, item 3: o `display: block` sem guarda VENCIA o atributo `hidden` (a
   regra do UA é [hidden]{display:none} em especificidade mínima) — era por
   isso que a sugestão aparecia sempre. O :not([hidden]) devolve o mando ao
   atributo. */
.sugestao-categoria:not([hidden]) { display: block; margin-top: 3px; }
.sugestao-categoria .desfazer-sugestao { font-size: inherit; }

/* #86, item 7: o R$ mora dentro do campo de valor. O span posicionado fica
   FORA do input (input não tem filho), sobre a área que o padding-left
   reserva — por isso ele sobrevive intacto ao estado disabled do pré-corte.
   Peso 600 e 16px no número; SEM cor: destaque é forma, não semáforo. */
.campo-monetario { position: relative; display: block; }
.campo-monetario .prefixo-moeda {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  font-size: var(--corpo-miudo); color: var(--ink-3); pointer-events: none;
}
.campo-monetario input { width: 100%; padding-left: 34px; text-align: right; font-weight: var(--peso-forte); font-size: 16px; }

/* ---------- vocabulário por tipo (#88) ----------
   Receita fala Cliente/Recebido/recebimento; despesa fala Fornecedor/Pago/
   pagamento. Pares de <span> trocados pelo tipo marcado no segmentado — o
   MESMO padrão de .rotulo-vencimento/.rotulo-data-transferencia acima. O
   :checked funciona também com o radio desabilitado (editar pré-corte); na
   perna de transferência editada não há segmentado e vale o padrão
   (despesa), exceto onde o template põe texto neutro. */
.form-lancamento .rotulo-receita { display: none; }
.form-lancamento:has(.seg-tipo input[value="RECEITA"]:checked) .rotulo-receita { display: inline; }
.form-lancamento:has(.seg-tipo input[value="RECEITA"]:checked) .rotulo-despesa { display: none; }

/* Observação (#88): o app.css dá a todo campo altura de UMA linha
   (height: var(--linha)) — o textarea precisa desfazer isso. */
.campo-observacao textarea { height: auto; min-height: 52px; padding: var(--esp-6) var(--esp-8); resize: vertical; }

/* Área de anexo desabilitada no criar (#88): a cara do que ela vira depois
   de salvar, sem fingir que já funciona. */
/* #198: o campo de anexo do modal de LANCAR. A area desabilitada acima
   continua existindo para telas que ainda a usem; aqui o arquivo entra de
   verdade, no mesmo <form> do lancamento. */
.campo-anexos { display: block; margin-top: var(--esp-8); }
.campo-anexos input[type="file"] { display: block; width: 100%; margin-top: var(--esp-4); font-size: var(--corpo-miudo); }
.campo-anexos .ajuda { display: block; margin-top: var(--esp-4); color: var(--ink-3); font-size: var(--corpo-miudo); }


/* ---- combobox de busca (#111) --------------------------------------------
   O campo em si é `.campo` (mesma altura, mesma borda, MESMO foco visível de
   todo campo do sistema — diretriz 6). Só a lista é nova.

   `.combo-item` tem altura mínima de 24px de propósito: é o alvo de clique
   mínimo do §12 do AGENTS.md, o mesmo critério em que o checkbox de 13×13
   reprova hoje. Componente novo não entra devendo. */
.combo { position: relative; }
.combo-lista { position: absolute; z-index: 40; left: 0; right: 0; top: 100%; margin: var(--esp-2) 0 0; padding: var(--esp-4) 0;
  list-style: none; max-height: 280px; overflow-y: auto; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--raio); box-shadow: var(--sombra-flutuante); }
/* #256: `pre-wrap`, e não `pre`. Os dois preservam o recuo da hierarquia (o
   filtro `recuo` insere NBSP por nível, e é o que mostra que "CABO DROP" é
   filha de "INSUMO DE MATERIAIS"); a diferença é que `pre` NÃO QUEBRA, e nome
   de categoria de quarto nível não cabe em largura nenhuma que caiba na tela.
   Medido: com `pre`, a lista rolava na horizontal mesmo depois de alargada
   para 380 px. Agora o item cresce em ALTURA quando precisa -- e só quando.
   `align-items: flex-start` porque um item de duas linhas centralizado
   desalinha o texto em relação aos vizinhos de uma linha. */
.combo-item { display: flex; align-items: flex-start; min-height: 24px;
  padding: var(--esp-4) var(--esp-10);
  font-size: var(--corpo-miudo); color: var(--ink); cursor: pointer;
  white-space: pre-wrap; overflow-wrap: anywhere; }

/* #112: o cabeçalho de grupo — a categoria mãe — como o `<optgroup>` nativo
   mostrava. O dono cobrou a hierarquia de volta depois do #111: "antes ela
   categoria subcategorias na lista, agora listou item a item".
   `role="presentation"`: é rótulo, não opção; não recebe foco nem clique. */
.combo-grupo { padding: var(--esp-6) var(--esp-10) var(--esp-2); font-size: var(--corpo-miudo);
  font-weight: var(--peso-forte); color: var(--ink-2); cursor: default; }

/* A seta que dizia "isto é um seletor". O `<input>` não tem a do navegador,
   e sem ela o campo perde a cara de campo de escolha. */
.combo::after { content: "▾"; position: absolute; right: 10px; top: 50%;
  transform: translateY(-50%); pointer-events: none; color: var(--ink-3);
  font-size: var(--corpo-mini); }
.combo-campo { padding-right: 26px; }
/* #168: mesmo caso do `.cmd-item` -- combobox.js mantém o foco no `.combo-campo`
   e marca o item por `aria-activedescendant`; o `[aria-selected="true"]` abaixo
   JÁ É o indicador de posição do teclado. Sem `:focus-visible` (nunca
   dispararia) e sem `:active` (mesma colisão de cor com o hover). */
.combo-item:hover,
.combo-item[aria-selected="true"] { background: var(--line-2); }
.combo-vazio { padding: var(--esp-8) var(--esp-10); font-size: var(--corpo-miudo); color: var(--ink-3); }


/* #121/#306: o alvo de 24x24 do checkbox da linha (WCAG 2.2 SC 2.5.8) era
 * escopado em `#cartao-tela` (o #121 nasceu sabendo do defeito e consertou so
 * a tela dele, "fora do escopo" para as outras). O #306 levou a regra para
 * `.tabela .gruda-sel input` (acima, ao lado do `margin: 0`), que cobre
 * Lancamentos, A pagar e Cartao de uma vez -- era a causa unica dos 489
 * alvos pequenos do A pagar medidos no #109. */

/* ⭐ #124 — O ATRIBUTO `hidden` NÃO ESCONDE QUANDO HÁ REGRA DE `display`.
 *
 * A regra do UA é `[hidden] { display: none }` com especificidade MÍNIMA:
 * qualquer regra de `display` do projeto a vence, e o elemento fica visível
 * com o atributo `hidden` ali, mentindo. Medido em 13/08/2026 no campo da
 * fatura: `atributo_hidden: true`, `display: flex`, **visível de fato**.
 * O dono viu antes de mim -- "o campo está aparecendo selecionando qualquer
 * conta".
 *
 * Já tinha acontecido com `.sugestao-categoria` (veja o comentário dela, acima)
 * e a lição não estava num lugar onde o próximo agente tropeçasse. Agora está.
 *
 * Dois seletores de atributo (0,2,0) vencem a regra de classe que dá o flex. */
[data-campo-da-fatura][hidden] { display: none; }

/* ⭐ #125 — O MODAL DE LANÇAR É MAIOR QUE OS OUTROS, e os números explicam.
 *
 * Medido em 13/08/2026, em 1366x768, com o modal ABERTO:
 *   largura 560px  -> 41% da tela, com **806px sobrando** ao lado
 *   altura  588px  -> o conteúdo cabe e NÃO rola
 * e, com a lista de categorias ABERTA (o que o dono pediu para melhorar):
 *   lista 203px de altura -> **~6 itens de 221 categorias**
 *   a lista termina 3px antes do fim do modal (o `.modal-corpo` tem
 *   `overflow:hidden` e a corta), com **115px de tela sobrando**.
 *
 * Por isso a largura sobe para 760px (ainda deixa 606px de folga em 1366 e
 * não vira linha longa demais em 1920) e o modal ganha ALTURA MÍNIMA: sem ela
 * o modal tem a altura do conteúdo, e a lista continuaria cortada no mesmo
 * lugar por mais largo que ele fique.
 *
 * ESCOPADO em `.modal-lancar`: os modais de confirmação continuam pequenos --
 * caixa de "tem certeza?" com 760px seria pior, não melhor. */
/* #182 — O TETO TEM DE CONTAR O `top`, e nao contava.
 *
 * `.modal` abre em `top: 9vh` (linha ~629). Com `max-height: 92vh` a conta
 * fechava em 101vh: medido em 1366x768 pela `prova_interface_77` (#181), o
 * modal terminava em 776px numa viewport de 768 -- 8px abaixo da dobra, com a
 * borda de baixo cortada. Em 1920x1080 nao aparecia, porque ali o teto de
 * 860px morde antes do percentual.
 *
 * 82vh deixa a mesma folga de 9vh embaixo que o `top` deixa em cima:
 * 9 + 82 + 9 = 100. Em telas altas NADA muda -- o `min(...)` continua caindo
 * nos 860px/800px de sempre; o efeito e so onde o defeito estava.
 *
 * `min-height` desce junto porque `min-height` VENCE `max-height` em CSS:
 * deixa-lo em 88vh anularia o conserto exatamente nos modais cheios, que sao
 * os que estouravam.
 *
 * (A regra `.modal-editar { max-height: min(86vh, 720px) }`, escrita no #77
 * para isto, continua morta por especificidade -- o modal carrega
 * `modal modal-editar modal-lancar` e `.modal.modal-lancar` (0,2,0) vence
 * `.modal-editar` (0,1,0). Fica onde esta, sem efeito: hoje o teto daqui ja e
 * MENOR que o dela, entao ressuscita-la nao mudaria pixel nenhum.) */
.modal.modal-lancar {
  width: min(760px, calc(100vw - 32px));
  max-height: min(82vh, 860px);
  min-height: min(78vh, 800px);
}

/* #126 — o teto da lista de categorias é MEDIDO, não fixo.
 *
 * Aqui houve `max-height: 280px` e depois `290px`, e o dono viu o resultado:
 * "ela não vai até o fim do modal". Valor fixo não podia acertar -- o espaço
 * real muda com a altura da tela, com a posição do campo e com o modal.
 * Quem calcula agora é `esticarAteOFim()` em `combobox.js`, na abertura.
 *
 * O `max-height: 280px` de `.combo-lista` (acima) fica como PISO SEM
 * JavaScript: sem JS não há combobox nenhum, é o `<select>` nativo. */

/* #128 — a lista de categorias, seguindo `docs/referencia/lan-modal-lancar.png`.
 *
 * DUAS tentativas minhas foram descartadas pelo dono, e as duas por eu ter
 * inventado em vez de ler: "cinza + negrito" (opinião pura) e depois um
 * marcador `::before` que virou **bolinha duplicada** -- o filtro `recuo` do
 * template JÁ insere "· " em cada nível, e eu somei um círculo em cima.
 *
 * O que a referência usa para separar galho de folha, e só isto:
 *   MÃE   -> TEXTO EM MAIÚSCULAS, cor apagada, não clicável
 *   FOLHA -> Texto normal, cor de leitura, clicável
 *   os dois com o RECUO por nível, que já vem do template.
 *
 * Nenhum marcador novo: a hierarquia já é dita pelo recuo e pelo caixa-alta.
 * Menos tinta, mesma informação. */
.combo-item-so-leitura {
  color: var(--ink-2);
  cursor: default;
  text-transform: uppercase;
  font-size: var(--corpo-nota);
  letter-spacing: 0.02em;
}

/* #262 — o item desabilitado que carrega um MOTIVO (hoje só a conta já usada
   no outro lado da transferência). Única diferença para o de cima: o motivo é
   uma frase, e o caixa-alta que separa galho de folha na árvore de categorias
   a transformaria em grito. Nenhum elemento novo na lista -- o motivo é o
   próprio texto do item. */
.combo-item-bloqueado { text-transform: none; letter-spacing: normal; }

/* ---------- Calendario (seletor de data) -- #135, tarefas 10 e 11 --------
 *
 * O icone dentro do campo segue o MESMO padrao de .campo-monetario (acima):
 * wrapper com position:relative, elemento absoluto na borda, padding do
 * input abrindo espaco. So que aqui o wrapper e criado pelo calendario.js
 * na hora de montar -- nao existe no template, porque nenhum dos cinco
 * templates com input[data-data] foi tocado nesta rodada (o brief so lista
 * base.html entre os arquivos a modificar).
 *
 * O popover (.calendario) e position:fixed e filho de document.body, NUNCA
 * do modal -- ver o comentario grande no topo de calendario.js sobre por
 * que `.modal` (overflow:hidden + transform) cortaria um popover fixed que
 * fosse descendente dele. left/top vem do JS, calculados a cada abertura.
 *
 * z-index 61: acima do veu (50), do modal e do drawer (51), da paleta (55)
 * e do toast (60) -- o calendario tem que aparecer por cima de qualquer
 * camada que esteja aberta quando o campo de data estiver nela.
 *
 * Alvo de clique dos dias: 24x24 CSS px (WCAG 2.2 SC 2.5.8). O AGENTS.md
 * global do agente cita 13x13 como piso -- 13x13 e o piso MINIMO da propria
 * norma quando ela permite excecao por espacamento equivalente; 24x24 e o
 * alvo direto, sem depender de excecao, e foi o que o brief pediu. */

/* #288: `display: block` fazia o invólucro ter a largura do LABEL, e o ícone
   ia parar longe do campo (no Lançar: input de 255px, ícone a 520px). Com
   `fit-content` o invólucro abraça o input e o ícone fica na borda dele. */
.campo-data-com-icone { position: relative; display: block; width: fit-content; max-width: 100%; }
.campo-data-com-icone input { padding-right: 34px; }

.gatilho-calendario {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  border-radius: calc(var(--raio) - 3px);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-2);
}
.gatilho-calendario:hover { background: var(--line-2); }
.gatilho-calendario:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }

.calendario {
  position: fixed;
  z-index: 61;
  width: 244px;
  box-sizing: border-box;
  padding: var(--esp-8);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--raio);
  /* Mesma sombra de elevacao ja usada em .combo-lista, .cmd e .toast --
     valor reaproveitado, nao inventado (grep confirma as quatro ocorrencias
     identicas no arquivo antes desta secao). */
  box-shadow: var(--sombra-flutuante);
}
.calendario[hidden] { display: none; }

.calendario-topo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--esp-2) var(--esp-2) var(--esp-6);
}
.calendario-topo button {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  border-radius: calc(var(--raio) - 3px);
  color: var(--ink-2);
  font-size: var(--corpo);
  line-height: 1;
  cursor: pointer;
}
.calendario-topo button:hover { background: var(--line-2); }
.calendario-titulo {
  font-weight: var(--peso-forte);
  font-size: var(--corpo-miudo);
  text-transform: capitalize;
}

.calendario-grade { width: 100%; border-collapse: collapse; }
.calendario-grade th {
  font-size: var(--corpo-mini);
  font-weight: var(--peso-medio);
  color: var(--ink-3);
  padding: var(--esp-2) 0;
}
.calendario-grade td { padding: 1px; text-align: center; }

.calendario-dia {
  width: 26px;
  height: 26px;
  min-width: 24px;
  min-height: 24px;
  border: none;
  border-radius: calc(var(--raio) - 3px);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: var(--corpo-miudo);
  cursor: pointer;
}
.calendario-dia:hover { background: var(--line-2); }
.calendario-dia-fora { color: var(--ink-3); }
/* Hoje: so o contorno -- a cor de preenchimento fica reservada para o dia
   em foco/selecionado (aria-selected), senao os dois se confundem quando
   coincidem (hoje == dia em foco, o caso mais comum de abertura). */
.calendario-dia-hoje { box-shadow: inset 0 0 0 1px var(--azul); }
.calendario-dia[aria-selected="true"] { background: var(--azul); color: var(--branco); }

.calendario-rodape {
  display: flex;
  justify-content: center;
  padding-top: var(--esp-6);
  margin-top: var(--esp-4);
  border-top: 1px solid var(--line-2);
}
.calendario-rodape button {
  border: none;
  background: none;
  color: var(--azul);
  font: inherit;
  font-size: var(--corpo-miudo);
  padding: var(--esp-4) var(--esp-10);
  border-radius: calc(var(--raio) - 3px);
  cursor: pointer;
}

/* #153 — o menu de período. Sem JS: o <details> abre no clique do <summary>. */
.menu-de-periodo { position: relative; display: inline-block; }
.menu-de-periodo > summary { cursor: pointer; list-style: none; }
.menu-de-periodo > summary::-webkit-details-marker { display: none; }
.menu-de-periodo-itens {
  position: absolute; z-index: 20; top: 100%; left: 50%; transform: translateX(-50%);
  min-width: 208px; padding: var(--esp-8) 0;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--raio);
  box-shadow: var(--sombra-painel);
}
.menu-de-periodo-itens a { display: block; padding: var(--esp-8) var(--esp-16); text-align: center; }
.menu-de-periodo-itens a[aria-current="true"] { font-weight: var(--peso-maximo); }
/* #168: como a `.aba` -- `<a href>` real, foco do DOM real, nenhum outline
   declarado aqui, logo o `:focus-visible` global de app.css já pinta o anel.
   Vale para o `> summary` acima pelo mesmo motivo. Sem regra local, sem
   `:active`: item de menu que fecha o menu ao ser solto já se anuncia. */
.periodo-personalizado {
  display: flex; flex-direction: column; gap: var(--esp-6);
  margin-top: var(--esp-8); padding: var(--esp-12) var(--esp-16) var(--esp-4); border-top: 1px solid var(--line);
}
.calendario-rodape button:hover { background: var(--line-2); }

/* #168: `:active` no calendário, e aqui ele não é conforto -- é o ÚNICO retorno
   que estes botões conseguem dar. O cabeçalho de calendario.js decide que "FOCO
   REAL NUNCA SAI DO CAMPO": dia, mês anterior/próximo e Hoje nascem com
   `tabIndex = -1` e são escolhidos por `mousedown` com `preventDefault`, e o
   dia corrente é marcado por `aria-activedescendant`/`aria-selected`. Logo
   nenhum deles recebe foco do DOM e `:focus-visible` neles seria regra morta --
   NÃO adicionar (a decisão é de portal, está justificada em duas razões lá, e
   não se reabre daqui). Sobra o ponteiro: `:hover` diz "está sob o cursor" e
   nada dizia "apertei". O mesmo afundar de 1px do `.btn`, um só gesto no
   sistema, sem cor. */
.calendario-topo button:active,
.calendario-rodape button:active,
.calendario-dia:active { transform: translateY(1px); }

/* ---------------------------------------------- usuários e permissões (#177-B)
   Nada de paleta nova: os tokens são os mesmos das outras telas, porque esta
   tela não é especial -- é o CRUD de quem entra. O único desenho próprio é o
   bloco de permissões, e ele existe por densidade: nove caixas em coluna
   viravam uma tela de rolagem para responder "o que este funcionário faz?". */

/* O login abaixo do nome, no corpo miúdo: quem administra procura pelo nome
   da pessoa, e precisa do login só na hora de conferir. */
.usuario-login {
  display: block;
  font-size: var(--corpo-mini);
  color: var(--ink-3);
}

.usuario-tudo {
  font-weight: var(--peso-medio);
  color: var(--ink-2);
}

.tabela.tabela-usuarios td,
.tabela.tabela-usuarios th {
  padding: var(--pad-linha);
}

/* Papel, permissoes e ultimo acesso sao campos CURTOS. Sem teto, o layout
   automatico da tabela reparte a sobra entre elas e "3 de 9" ganha 340px de
   coluna -- o defeito do #98/item 7, do outro lado da tabela. A coluna
   PESSOA e a unica que cresce. */
.tabela.tabela-usuarios th:nth-child(2),
.tabela.tabela-usuarios td:nth-child(2),
.tabela.tabela-usuarios th:nth-child(3),
.tabela.tabela-usuarios td:nth-child(3),
.tabela.tabela-usuarios th:nth-child(4),
.tabela.tabela-usuarios td:nth-child(4) {
  width: 1%;
  white-space: nowrap;
}

.usuario-nome { font-weight: var(--peso-medio); }

.usuario-sem-acesso { color: var(--ink-3); }

/* Três colunas em tela larga, uma em telas estreitas -- largura é o que é
   responsivo (R40); altura de controle e corpo de texto não. */
.permissoes {
  border: 1px solid var(--line);
  border-radius: var(--raio);
  padding: var(--esp-12);
  margin: var(--esp-16) 0;
}

.permissoes legend {
  font-size: var(--corpo-mini);
  font-weight: var(--peso-medio);
  color: var(--ink-2);
  padding: 0 var(--esp-6);
}

.permissoes-grupo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--esp-4) var(--esp-16);
  margin-bottom: var(--esp-12);
}

.permissoes-grupo:last-child { margin-bottom: 0; }

.permissoes-grupo h3 {
  grid-column: 1 / -1;
  margin: 0 0 var(--esp-2);
  font-size: var(--corpo-mini);
  font-weight: var(--peso-forte);
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Caixa desabilitada é o caso do ADMIN: ela continua legível, só deixa de
   ser acionável. Apagar o texto esconderia o que ele pode fazer. */
.permissoes input[disabled] + span { color: var(--ink-3); }

/* 🔴 `[hidden]` do navegador e especificidade ZERO: qualquer regra de CLASSE
   que declare display vence, e o aviso aparece para todo mundo -- a tela
   dizendo "Administrador tem acesso total" para um Operador. Pego na foto
   local; nenhum teste de HTML veria, porque o atributo ESTA no markup. */
.nota-admin[hidden] { display: none; }

.nota-admin {
  border-left: 3px solid var(--atencao);
  background: var(--atencao-fraco);
  padding: var(--esp-8) var(--esp-12);
  border-radius: var(--raio);
}

/* As duas ações que tiram acesso ficam separadas do formulário, depois do
   Salvar: são POSTs próprios e consequências diferentes. Sem cor de perigo --
   nenhuma das duas apaga nada, e vermelho aqui prometeria uma destruição que
   não acontece. */
.usuario-acoes-perigo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-8);
  margin-top: var(--esp-16);
  padding-top: var(--esp-12);
  border-top: 1px solid var(--line);
}

.usuario-acoes-perigo form { margin: 0; }
.usuario-acoes-perigo .rotulo { flex-basis: 100%; margin: 0; }

.erro-campo {
  margin: var(--esp-2) 0 0;
  font-size: var(--corpo-mini);
  color: var(--erro);
}

/* Responsável no formulário (#177-D): virou `<select>` editável, então o CSS
   do #177-C1 (feito para um texto só-leitura, em linha) saiu junto. Largura
   cheia como Categoria e Conta -- campo estreito sozinho na linha destoa dos
   vizinhos, e a assimetria não significa nada. */
.campo-responsavel select { width: 100%; }

/* Teto de coluna, como as vizinhas (R40): nome de pessoa não pode empurrar
   a tabela para a rolagem horizontal. */
.tabela .col-responsavel {
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* #192: as duas acoes do cabecalho de relatorio (Excel, Imprimir) lado a
   lado, na ordem da lista de lancamentos. Sem o `gap` os dois `.btn` ficam
   colados pelo espaco em branco do HTML, que nao e espacamento -- e o que
   sobra da fonte. */
.acoes-do-relatorio { display: flex; gap: var(--esp-8); align-items: center; }
/* 🔴 Medido na foto local do #192: o `<a class="btn">` saia com 39px e o
   `<button class="btn">` com 32px -- o `app.css` da altura de `--linha` a
   todo `button`, e o link so tinha o padding do `.btn`. Duas acoes irmas,
   lado a lado, com sete pixels de diferenca. A altura vem do registro de
   controle do sistema (`--linha`), nao do padding de cada um. */
.acoes-do-relatorio .btn { height: var(--linha); padding: 0 var(--esp-14); }

/* ───────────────────────── #309: a aba Saúde do servidor ─────────────────────
 * Nenhuma folha nova, e isso é a decisão #70 com régua atrás
 * (`test_classe_sem_dono_168` lê exatamente três folhas). O que dava para
 * reusar foi reusado: os números são `.cartoes-urgencia` (o componente do
 * #275), as tabelas são `.tabela`, o estado é `.etiqueta` e a linha do gráfico
 * é a `.grafico-linha` dos relatórios. Sobram três coisas que a fundação não
 * tem -- e que, sem regra, o navegador desenharia do jeito dele. */

/* Estes cartões são LEITURA: não levam a lugar nenhum. Mesma família do
 * `.cartoes-da-fatura` (#283) -- componente igual, afeto de clique fora. Sem
 * isto o cartão acende ao passar o mouse e promete navegação que não existe. */
.cartoes-saude .cartao-urgencia { cursor: default; }
.cartoes-saude .cartao-urgencia:hover { background: var(--surface); }

/* `<figure>` nasce com `margin: 1em 40px` do navegador: cinco gráficos
 * recuados 40px de cada lado dentro de um cartão que já tem padding. */
.grafico-do-servidor { margin: 0 0 var(--esp-12); }
.grafico-do-servidor figcaption { font-size: var(--corpo-rotulo); color: var(--ink-3); }
/* A altura vem do ATRIBUTO `height` que o `{% linha_svg %}` escreve -- quem
 * chama escolhe o número, e uma altura aqui o sobreporia em silêncio. Só a
 * largura é da folha, porque ela é do layout e não do gráfico. */
.grafico-do-servidor svg { display: block; width: 100%; }

/* Um `<meter>` por núcleo. Em fila os 40 desta máquina estouram a largura; a
 * grade que se ajusta sozinha cabe em 1366 e continua cabendo em 360. */
/* #309/revisão: CINCO gráficos empilhados custavam cinco fileiras de 60px
 * cada, com 1.280px de largura por gráfico -- largura que nenhuma série de 168
 * pontos usa. Em duas colunas a seção inteira cabe em três fileiras. O ÚLTIMO,
 * quando fica sozinho na fileira, ocupa as duas colunas: meia fileira vazia lê
 * como "faltou um gráfico". */
/* DUAS colunas, fixas, e não `auto-fit`: medido na foto de 1366px, o
 * `repeat(auto-fit, minmax(280px, 1fr))` cabia QUATRO gráficos por fileira e
 * espremia 168 pontos em 300px -- a linha virava um borrão. Abaixo de 900px
 * (o breakpoint do sistema) volta a uma coluna. */
.graficos-do-servidor { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--esp-16); }
@media (max-width: 900px) { .graficos-do-servidor { grid-template-columns: minmax(0, 1fr); } }
.graficos-do-servidor > .grafico-do-servidor:last-child:nth-child(odd) { grid-column: 1 / -1; }

.nucleos { display: grid; grid-template-columns: repeat(auto-fit, minmax(40px, 1fr)); gap: var(--esp-4) var(--esp-8); }
.nucleos meter { width: 100%; height: var(--esp-10); }

/* 🔴 #309 — A ÁREA DO SERVIDOR EMPILHA CARTÕES COM TABELA DENTRO, E `.blocos`
 * É GRADE. Item de grade nasce com `min-width: auto`, que vale MIN-CONTENT: a
 * `min-width: 520px` que o `.rolagem-horizontal > .tabela` dá à tabela vira a
 * largura mínima do CARTÃO, o cartão estoura a coluna e é a PÁGINA que passa a
 * rolar de lado -- medido em 390px nas três abas: 714 / 762 / 570 de
 * `scrollWidth` contra 390 de `clientWidth` (prova 309). Com `min-width: 0` o
 * corte volta para o `.rolagem-horizontal`, que já sabe rolar sozinho abaixo
 * de 900px (app.css). Só classes existentes no seletor: `.abas ~ .blocos` é a
 * assinatura desta área (`base_seguranca.html`). */
.abas ~ .blocos > .cartao { min-width: 0; }

/* 🔴 #309/FOTO DE PRODUÇÃO — O MESMO DEFEITO ACIMA DOS 900px, E COM OUTRA
 * CAUSA: a coluna DETALHE da tabela de Eventos.
 *
 * O `min-width: 0` acima curou o 390px. Em 1366px a página continuava rolando
 * de lado, e isso NÃO apareceu na prova de navegador: a semente dela grava
 * `{"jail": "sshd"}` e `{"status": 502}` -- detalhe de 10 caracteres. Com o
 * banco de PRODUÇÃO a mesma coluna recebe a mensagem de erro do Postgres
 * ("o valor nulo na coluna ... viola a restrição de não-nulo DETAIL: ...") e o
 * user-agent inteiro que o Caddy registra, e o filtro `detalhe_legivel` já
 * corta em 120 CARACTERES -- que, com o `white-space: nowrap` global, são
 * 886px de célula. É a regra R36: fixture não representa a distribuição do
 * dado real.
 *
 * MEDIDO (Chromium, HTML da view com o banco de produção, 20/09/2026, 1366px):
 *   ANTES  tabela 1489px num quadro de 1214  ->  scrollWidth 1565 x clientWidth 1366
 *          colunas: Momento 119 · Origem 77 · Tipo 201 · IP 124 · Usuário 82 ·
 *          DETALHE 886
 *   DEPOIS tabela 1214 (= o quadro)          ->  scrollWidth 1366 x clientWidth 1366
 *          colunas: 119 · 77 · 201 · 124 · 82 · DETALHE 611
 *
 * Por que `max-width` E `width: 100%` juntos, e não só o teto (R40):
 *   - `max-width: 200px` é o que limita o MÍNIMO que a coluna EXIGE -- é ele
 *     que tira os 886px da conta e impede a tabela de estourar o quadro;
 *   - `width: 100%` é a coluna GULOSA do #98 (`.tabela-cadastro th:first-child`):
 *     sem ele a sobra é REPARTIDA, e medido em 1366px o Tipo ficava com 322px
 *     para um conteúdo de 201 enquanto o Detalhe -- a única coluna que a pessoa
 *     lê para saber o que aconteceu -- ficava com 320. Com ele, as cinco
 *     colunas ficam do tamanho do conteúdo e TODA a sobra vai para o Detalhe:
 *     611px em 1366, 531 em 1200, 331 em 1000, e nada é truncado em 1366.
 *
 * O 200 não é número redondo: 901px é a largura mais estreita em que o
 * `.rolagem-horizontal` AINDA NÃO rola sozinho (a media query do app.css pega
 * de 900 para baixo), e ali o quadro mede 835px contra 603 que as outras cinco
 * colunas pedem -- teto de 232px. 200 é esse teto com ~30px de folga para o
 * Tipo crescer (a chave mais longa hoje, `seguranca_fail2ban_exec`, mede 201).
 * Medido `scrollWidth == clientWidth` em 1366, 1200, 1000, 901, 390 e 360.
 *
 * A prova de navegador (`prova_interface_309.py`) só pegava isto depois de a
 * semente dela ganhar um evento com Detalhe LONGO de verdade -- foi o que ela
 * passou a ter junto com esta regra. */
.tabela .col-detalhe {
  max-width: 200px;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ───────────── #310: a aba Segurança responde "está tudo bem agora?" ─────────
 * Medido na tela real com o banco de PRODUÇÃO (1366px, 20/09/2026): 4.228 px
 * de altura, 2.947 deles (70%) na tabela de Eventos, primeira linha de dado a
 * 566 px, e quatro blocos de vigilância VAZIOS custando 148 px cada com o
 * mesmo peso visual de um cheio. Um alerta crítico lia igual a "nenhum IP
 * banido agora".
 *
 * Nenhuma folha nova e nenhuma cor nova (decisões #70 e #142/#149): a
 * distinção desta tela vem do COMPORTAMENTO -- o que abre, o que recolhe e o
 * que navega --, não de tinta inventada. */

/* O VEREDITO. Não é `.cartao` de propósito: cartão é caixa de conteúdo, e isto
 * é a RESPOSTA da tela -- a primeira coisa que se lê. A barra de 3px à
 * esquerda é a mesma gramática do `.aviso` das mensagens do sistema (app.css),
 * e 3px é o literal dela; aqui ele não é espaçamento, é a espessura do
 * indicador de estado. */
.veredito {
  padding: var(--esp-14) var(--esp-16);
  border-radius: var(--raio);
  border-left: 3px solid var(--line);
  background: var(--surface);
}
/* `--corpo-heroi` (24px) é o tamanho do nome da empresa na Início: o mesmo
 * papel -- a frase que fecha a tela -- com o mesmo corpo. */
.veredito-frase {
  margin: 0;
  font-size: var(--corpo-heroi);
  font-weight: var(--peso-forte);
  line-height: 1.2;
}
/* Texto e ação na mesma linha de base. Serve ao pior alerta e a cada um dos
 * outros -- mesma função, mesmo componente.
 * 🔴 SEM `justify-content: space-between`, e isso é o conserto de um defeito
 * visto na foto: com ele, os botões dos dois alertas saíam empilhados na borda
 * direita, a ~800 px do texto da própria linha, e qual era de qual só se sabia
 * pelo alinhamento vertical. O botão encosta no fim do SEU texto. O nome do
 * alerta viaja no botão em `.oculto-visual` para quem lê por leitor de tela.
 * Guarda: `test_o_botao_de_ciencia_fica_colado_no_texto_da_linha`. */
.veredito-linha {
  display: flex;
  align-items: baseline;
  gap: var(--esp-12);
  margin-top: var(--esp-8);
}
/* 🔴 `--ink` e `--ink-2`, nunca `.nota` (`--ink-3`): MEDIDO, `--ink-3` sobre
 * `--vermelho-fraco` dá 4,35:1 -- abaixo dos 4,5 da diretriz 7. Sobre os
 * outros dois fundos fracos ele passa raspando (4,55 e 4,59), e uma regra que
 * só é legível em dois dos três estados é uma armadilha esperando o crítico.
 * Medido: `--ink` 16,32-17,22:1 e `--ink-2` 6,93-7,31:1 sobre os três.
 * Guarda: `test_o_par_do_veredito_alcanca_o_minimo_de_texto`. */
.veredito-texto { margin: 0; color: var(--ink); }
/* #310/6 — A PÍLULA DO PIOR ALERTA SUMIA: `.etiqueta.vencido` e
 * `.veredito-critico` têm o MESMO fundo (`--vermelho-fraco`), então "Crítico"
 * saía como texto solto enquanto "Aviso" saía como pílula, no mesmo bloco.
 * Decisão: as duas continuam pílulas -- é o componente de gravidade do
 * sistema (#142), e tirá-lo daqui quebraria "mesma função, mesmo componente".
 * O contorno é da própria tinta, então cada estado se resolve sozinho.
 * MEDIDO (tinta da etiqueta contra o fundo do veredito, mínimo 3:1 de
 * elemento de interface): vencido/crítico 4,55:1 · falta/crítico 4,35:1 ·
 * falta/aviso 4,59:1 · ok/crítico 4,37:1 · ok/aviso 4,61:1. */
.veredito .etiqueta { border: 1px solid currentColor; }
.veredito-detalhe { color: var(--ink-2); }
.veredito-outros { margin: var(--esp-8) 0 0; padding: 0; list-style: none; }

/* Os três estados, com o par tinta/fundo da fundação (#142/#169), medido:
 * verde 4,57:1 · âmbar 4,59:1 · vermelho 4,55:1 sobre o fraco de cada um.
 * A barra da esquerda é elemento de interface (1.4.11, 3:1) contra a página:
 * verde 4,57:1 · âmbar 4,55:1 · vermelho 4,76:1. */
.veredito-ok { background: var(--verde-fraco); border-left-color: var(--verde); }
.veredito-ok .veredito-frase { color: var(--verde); }
.veredito-aviso { background: var(--amarelo-fraco); border-left-color: var(--amarelo); }
.veredito-aviso .veredito-frase { color: var(--amarelo); }
.veredito-critico { background: var(--vermelho-fraco); border-left-color: var(--vermelho); }
.veredito-critico .veredito-frase { color: var(--vermelho); }

/* A LINHA DE NÚMEROS que substituiu os quatro blocos de vigilância. Cada um é
 * um link para os eventos daquela origem: número que não navega é enfeite.
 * Bloco vazio custava 148 px; aqui um zero custa a largura de "0". */
.numeros-de-vigilancia {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--esp-4) var(--esp-12);
  margin: 0;
  padding: 0 var(--esp-2);
  color: var(--ink-2);
}
.numero-de-vigilancia {
  display: inline-flex;
  align-items: baseline;
  gap: var(--esp-6);
  color: inherit;
  text-decoration: none;
}
.numero-de-vigilancia strong { color: var(--ink); font-variant-numeric: var(--num-tabular); }
/* O afeto de clique é só de quem CLICA: o número sem recorte que o contenha
 * ("banidos agora") é `<span>`, e sublinhar no hover prometeria navegação que
 * não existe -- o mesmo motivo do `.cartoes-saude` no #309. */
a.numero-de-vigilancia:hover { color: var(--ink); text-decoration: underline; }
/* O separador é do CSS e não do HTML: um `<span>·</span>` por número seria
 * markup que a leitura de tela anuncia sem ter o que dizer. */
.numero-de-vigilancia + .numero-de-vigilancia::before { content: "·"; color: var(--ink-3); }

/* EXPANSOR EM FLUXO -- e por isso ele NÃO entra no `MENUS_FLUTUANTES` do
 * `ui.js` (decisão travada do #279): fechar o histórico porque a pessoa clicou
 * na tabela seria perder contexto (diretriz 2). Nenhum `position: absolute`
 * aqui é o que o `test_todo_details_que_flutua_esta_no_seletor` lê. */
.expansor-de-eventos > summary { cursor: pointer; }
/* O `<h2>` dentro do `<summary>` devolve a seção ao sumário de cabeçalhos sem
 * mexer no visual: `inline` para o triângulo do `<summary>` não ficar numa
 * linha só dele, e `font: inherit` para o corpo vir do `.cartao-cab`, que é
 * quem manda na aparência do cabeçalho de cartão. */
.expansor-de-eventos > summary > h2 { display: inline; font: inherit; margin: 0; }
/* Recolhido, a divisória do `.cartao-cab` seria um risco no pé de uma caixa
 * de uma linha só. */
.expansor-de-eventos:not([open]) > summary { border-bottom: 0; }

/* ======================================================================
 * #311 — A ABA SEGURANÇA POR SUPERFÍCIE ATACADA (mockup B, aprovado pelo
 * dono, com o enxerto de sparkline do mockup A).
 *
 * O que organiza a tela deixou de ser "o que aconteceu" e passou a ser *por
 * onde se entra*: SSH (porta 22), Site (porta 443) e Sistema (a máquina).
 * Três ladrilhos de postura na primeira linha, o painel da superfície mais
 * grave com o DOBRO da largura — com a exposição declarada, a pressão por
 * hora, a tabela de origens e o formulário que a fecha no pé dela —, e as
 * duas superfícies calmas na coluna lateral.
 *
 * 🔴 NENHUMA COR NOVA E NENHUMA FAMÍLIA NOVA (#142/#149). O mockup declarava
 * um `--m-mono` para os endereços IP; ele NÃO entrou — família nova é token
 * travado. O IP se lê caractere a caractere por `--num-tabular` + peso forte
 * + `white-space: nowrap`, que é o que o sistema já tem.
 *
 * 🔴 SEVERIDADE NUNCA SÓ POR COR. Três reforços, sempre: a PALAVRA (a
 * etiqueta: exposto / sob varredura / normal / atenção), a POSIÇÃO (o pior
 * ladrilho vem primeiro — `postura.superficies` ordena) e a BARRA de 4 px à
 * esquerda (a do `.veredito` mede 3 px; o ladrilho a engrossa em
 * `.veredito-ladrilho`, e há régua que lê a largura computada).
 *
 * O LADRILHO É UM `.veredito` (diretriz 6: mesma função, mesmo componente).
 * Um ladrilho de postura É o veredito de uma superfície — barra à esquerda,
 * fundo fraco, frase colorida —, e os pares tinta/fundo dele já estão
 * medidos no bloco acima. O modificador só muda o TAMANHO da frase e o
 * arranjo do topo. ====================================================== */

/* A frescura da tela, no canto direito do cabeçalho: quando o coletor leu a
 * máquina pela última vez. `--ink-2` sobre `--page` mede 7,24:1. */
.carimbo { margin: 0; font-size: var(--corpo-nota); color: var(--ink-2); text-align: right; }
.carimbo strong { color: var(--ink); font-weight: var(--peso-medio); }

/* A primeira linha da tela. O primeiro ladrilho é mais largo porque a frase
 * da superfície grave é a mais longa (ela diz o que está aberto E quantos
 * estão tentando); abaixo de 1000 px os três empilham. */
.postura { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: var(--esp-12); margin-bottom: var(--esp-12); }
/* O ladrilho é um link para a âncora do painel da própria superfície: clicar
 * no que preocupa leva ao lugar onde se resolve. */
.veredito-ladrilho { display: block; text-decoration: none; color: inherit; border-left-width: 4px; }
.veredito-ladrilho:hover { text-decoration: none; }
.veredito-ladrilho:hover .ladrilho-nome { text-decoration: underline; }
/* 17px: entre o corpo (15) e o herói (24). A frase do ladrilho é manchete de
 * um bloco de 1/3 de tela -- com 24px ela quebra em três linhas em 1366, e
 * com 15px deixa de ser a primeira coisa que se lê. */
.veredito-ladrilho .veredito-frase { font-size: 17px; margin-top: var(--esp-6); line-height: 1.25; }
.ladrilho-topo { display: flex; align-items: baseline; justify-content: space-between; gap: var(--esp-8); }
.ladrilho-nome { font-weight: var(--peso-forte); font-size: var(--corpo); color: var(--ink); }
/* "porta 22" / "porta 443" / "máquina": o endereço da superfície, no mesmo
 * tom das notas. Vale no ladrilho e no cabeçalho do painel. */
.porta-da-superficie { font-size: var(--corpo-mini); color: var(--ink-2); font-weight: var(--peso-normal); }
/* Os números do ladrilho são a `.numeros-de-vigilancia` do #310 -- mesma
 * função (uma fila de números com separador), mesmo componente. */
.veredito-ladrilho .numeros-de-vigilancia { margin-top: var(--esp-6); font-size: var(--corpo-nota); }

/* O corpo: a superfície grave ocupa o dobro da largura da coluna calma. */
.grade-de-superficies { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: var(--esp-12); align-items: start; }
.pilha-de-superficies { display: flex; flex-direction: column; gap: var(--esp-12); min-width: 0; }
/* O cabeçalho do painel leva o resumo da superfície à direita. `flex-wrap`
 * porque em 390 px o resumo desce para a linha de baixo em vez de espremer o
 * nome da superfície. */
.cartao-cab-nota { font-size: var(--corpo-nota); font-weight: var(--peso-normal); color: var(--ink-2); font-variant-numeric: var(--num-tabular); }
/* 🔴 O COMENTÁRIO ACIMA PROMETIA "à direita" E A REGRA SÓ TINHA TIPOGRAFIA:
 * `.cartao-cab` é bloco, então a nota saía COLADA no título, divergindo do
 * mockup. O `:has()` limita o `flex` aos cabeçalhos que CARREGAM a nota --
 * pôr `display: flex` no `.cartao-cab` mexeria em todo cartão do sistema. */
.cartao-cab:has(.cartao-cab-nota) {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--esp-8) var(--esp-12);
}

/* A EXPOSIÇÃO DECLARADA: o que está aberto, e por quê. Uma linha por fato,
 * com a etiqueta de gravidade, a frase e — quando o fato é um alerta aberto —
 * o botão que dá ciência, colado no texto dele (a regra do `.veredito-linha`,
 * pela mesma razão: botão a 800 px do próprio texto não se sabe de quem é). */
.exposicao { margin: 0 0 var(--esp-12); padding: 0; list-style: none; }
.exposicao li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--esp-8); padding: var(--esp-6) 0; font-size: var(--corpo-nota); color: var(--ink); }
.exposicao li + li { border-top: 1px solid var(--line-2); }
/* `--ink-2` (7,58:1 sobre branco) e não `--ink-3` (4,76): o "porquê" é
 * explicação, não rodapé — quem lê a tela de segurança lê esta frase. */
.exposicao-porque { color: var(--ink-2); }

/* A PRESSÃO POR HORA. A faixa de barras é o gráfico mais simples que responde
 * "está piorando?" sem eixo, sem legenda e sem biblioteca. */
.pressao { margin: 0 0 var(--esp-12); }
/* 🔴 #312b — O GRÁFICO RESERVA A FAIXA DELE. Os 4 px de folga que havia aqui
 * não separavam nada: com as barras altas (e elas passam de 40 px sempre que
 * a hora de pico aparece) o rótulo ficava ENCOSTADO no bloco de cor, e os
 * descendentes de "Pressão por hora" se confundiam com o topo das barras --
 * visto em 3x de DPI no HTML renderizado com o banco de produção, e relatado
 * pelo dono na tela publicada. A caixa do rótulo nunca chegou a cruzar a das
 * barras (MEDIDO: 4 px de folga em 1366, 1100, 390 e 360 px, com todas as
 * barras em 100%): o defeito é de FOLGA, não de sobreposição, e a régua da
 * prova mede a folga em vez de medir o cruzamento, que nunca aconteceria.
 * 8 px é o passo do sistema (`--esp-8`), o mesmo `gap` que o rótulo já usa
 * entre os dois textos dele. */
.pressao-rotulo { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: var(--esp-8); font-size: var(--corpo-nota); color: var(--ink-2); margin-bottom: var(--esp-8); }
.pressao-rotulo b { color: var(--ink); }
/* 44px é altura de DESENHO, não de controle: a R40 proíbe `vh`/`vw` em campo
 * e tipografia, e esta caixa é o gráfico. */
.barras { display: flex; align-items: flex-end; gap: var(--esp-2); height: 44px; border-bottom: 1px solid var(--line); padding-bottom: 1px; }
/* `--amarelo` sobre branco mede 4,76:1 e sobre `--page` 4,55:1 — acima dos
 * 3:1 de elemento gráfico (1.4.11). O pico é `--vermelho` (4,98:1 / 4,76:1),
 * e a cor NÃO é o único sinal dele: o pico também vai escrito por extenso na
 * linha do rótulo ("pico 6 às 20/09 18 h"). */
.barra { flex: 1 1 0; min-width: 0; background: var(--amarelo); border-radius: 2px 2px 0 0; }
.barra-pico { background: var(--vermelho); }
.eixo-das-horas { display: flex; justify-content: space-between; font-size: var(--corpo-mini); color: var(--ink-2); margin-top: var(--esp-2); font-variant-numeric: var(--num-tabular); }
/* A SPARKLINE DA LINHA DA TABELA — o enxerto do mockup A que o dono pediu.
 * Sem ela, dois IPs com o mesmo total e a mesma janela ficam indistinguíveis,
 * e a leitura "um parou às 02:01, o outro não parou" morre. A escala é
 * compartilhada pelas linhas (senão um IP de 1 tentativa/hora desenha a mesma
 * linha cheia de um de 30); quem calcula é `seguranca/postura.py`.
 *
 * 🔴 #312c — ELA ERA FEITA DE BARRAS, E NESSE ESTADO NÃO ENTREGAVA A LEITURA.
 * MEDIDO em produção, 1366 px: a janela real do ataque tem 24 horas, e 24
 * barras com 1 px de vão numa caixa de 32 px davam **0,38 px de barra** cada
 * uma -- os vãos sozinhos consomem 23 dos 32 px. Coluna que não informa é
 * pior que coluna nenhuma, porque ocupa espaço e finge.
 *
 * O traço contínuo não tem vão entre pontos: a forma deixa de depender do
 * número de horas. É a `linha_svg` da aba Saúde (diretriz 6: mesma função,
 * mesmo componente), e o `.grafico-linha` dela vem de `app.css`.
 *
 * A TINTA CONTINUA NEUTRA (`--ink-3`, 4,76:1 sobre branco -- acima dos 3:1 de
 * elemento gráfico), e não o `--azul` dos gráficos de relatório: numa tabela
 * de segurança a cor é reservada para gravidade, e uma linha azul por origem
 * competiria com as etiquetas. O traço é 1,5 (e não os 2,5 do gráfico de
 * 480 px): numa caixa de 22 px, 2,5 px de traço apagam a própria forma. */
.grafico-do-ritmo { display: block; overflow: visible; }
.grafico-do-ritmo .grafico-linha { stroke: var(--ink-3); stroke-width: 1.5; }

/* A TABELA DE ORIGENS É APERTADA, e isso é MEDIDO, não estética: com o padrão
 * do `.tabela` ela pedia 742px num painel de 687 e o botão "Banir" ficava do
 * lado de fora do `.rolo` -- um controle que existe e não se alcança sem rolar
 * a tabela para o lado. Seis colunas num painel de 1,4fr não cabem com o
 * respiro de uma tabela de lançamentos.
 *   · o respiro lateral cai para `--esp-6` (6 colunas × 2 lados = 48px);
 *   · a sparkline ganha largura FIXA de 32px (a forma se lê igual, e a coluna
 *     deixa de crescer com o número de horas da janela);
 *   · "quem tentou ser" tem TETO (R40: tabela larga se resolve por teto de
 *     coluna), com o texto inteiro no `title` -- uma varredura que tenta 20
 *     nomes de usuário estouraria a coluna e empurraria tudo de novo.
 *   · as bordas da tabela encostam no quadro (a primeira e a última célula
 *     perdem o respiro externo) -- ele é margem duplicada do `.cartao-corpo`.
 * Medido depois, com o dado de produção: 676px em 687 de útil, com o "Banir"
 * inteiro dentro do quadro. */
.tabela-de-origens th, .tabela-de-origens td { padding-left: var(--esp-6); padding-right: var(--esp-6); }
.tabela-de-origens th:first-child, .tabela-de-origens td:first-child { padding-left: 0; }
.tabela-de-origens th:last-child, .tabela-de-origens td:last-child { padding-right: 0; }
/* #312c: a caixa do ritmo. LARGURA MEDIDA, não escolhida: a coluna "Ritmo"
 * mede 63 px em produção porque o CABEÇALHO ("RITMO", em caixa alta com
 * `letter-spacing`) é mais largo que a caixa do gráfico -- havia folga
 * DENTRO da coluna, e crescer a caixa até ela não custa um pixel de tabela.
 * Com 48 px e a janela de 24 h de produção, cada ponto ganha 2,09 px (eram
 * 1,39 com 32 px, e 0,38 px de barra DESENHADA antes do traço). A largura
 * vive no `width` do `<svg>` (é argumento de quem chama a `linha_svg`); aqui
 * ela vira TETO DA COLUNA, para o "Ritmo" nunca voltar a crescer. */
.tabela-de-origens .celula-do-ritmo { width: 48px; }
/* #312 — O ESTADO DE BANIMENTO DO IP, na linha de baixo do ENDEREÇO. Ele
 * nasceu na coluna Ações e voltou para cá com a foto na mesa: lá o texto
 * empurrava a tabela para fora dos 687 px do painel e a coluna "Janela"
 * perdia a hora final (a tabela media 676 em 687 desde o #311, sem folga).
 * Debaixo do endereço ele cabe de graça -- "203.0.113.10" é mais largo que
 * "banido 2×". Nenhuma classe nova: é a `.exposicao-porque`, o mesmo tom de
 * explicação da exposição do SSH (par `--ink-2`/`--surface` já medido). */
.tabela-de-origens td .exposicao-porque { display: block; font-size: var(--corpo-mini); white-space: nowrap; }
.col-alvos { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 🔴 #312b — O TETO DA COLUNA "JANELA". R40: tabela larga se resolve por teto
 * de COLUNA, nunca por rolagem dentro do quadro -- e era rolagem o que havia,
 * escondendo o botão "Banir" na borda direita.
 *
 * MEDIDO em 21/09/2026 no HTML renderizado com o banco de PRODUÇÃO, 1366 px,
 * com o pior texto que esta tela produz (três usuários distintos na linha e o
 * estado `pedido às HH:MM` no lugar do botão): a tabela pedia 749 px num
 * quadro de 687. Coluna a coluna: Origem 122 · Tentativas 97 · Quem tentou
 * ser 160 · Ritmo 57 · Janela 194 · Ações 119. A Janela era a mais larga, e a
 * única cujo conteúdo são DUAS datas completas; as outras estavam no mínimo
 * do próprio cabeçalho ou já tinham teto.
 *
 * O teto NÃO corta com reticências: ele deixa o par quebrar em duas linhas.
 * Reticência aqui esconderia a hora do fim, que é metade do fato. Cada
 * carimbo é `nowrap` para a quebra cair SÓ na seta -- sem isso o navegador
 * quebra em qualquer espaço ("20/09" / "14:16 → 21/09" / "11:47").
 *
 * Depois, mesmo caso: 687 px de tabela em 687 de quadro, com a Janela em
 * 132 px -- e o `pedido às 14:36` inteiro dentro do quadro. */
.tabela-de-origens .col-janela { white-space: normal; max-width: 140px; }
.tabela-de-origens .col-janela span { white-space: nowrap; }

/* O formulário da defesa cabe numa linha só, e os dois campos livres têm
 * largura DECLARADA: sem isso o `<input>` nasce com a largura padrão do
 * navegador (~230px cada), a linha quebra em três e o "Adicionar" desce para
 * baixo do "Validade" -- medido na foto com o dado de produção. */
.defesa .formulario-em-linha input[name="rede"] { width: 185px; }
.defesa .formulario-em-linha input[name="apelido"] { width: 110px; }

/* O endereço é identificador que se compara caractere a caractere. O sistema
 * não tem família monoespaçada e o #142 trava a tipografia: o que dá a
 * leitura é o número tabular, o peso e o não-quebrar. */
.endereco { font-weight: var(--peso-forte); color: var(--ink); white-space: nowrap; font-variant-numeric: var(--num-tabular); }

/* A DEFESA, no pé do painel da superfície que ela fecha — e não numa seção
 * genérica lá embaixo. É a tese do mockup B: a ação mora dentro do problema. */
.defesa { margin-top: var(--esp-12); padding-top: var(--esp-12); border-top: 1px solid var(--line); }
.defesa-titulo { margin: 0 0 var(--esp-6); font-size: var(--corpo-rotulo); text-transform: uppercase; letter-spacing: .07em; color: var(--ink-2); font-weight: var(--peso-forte); }

/* Pares rótulo/valor das superfícies calmas: um fato por linha, valor à
 * direita, fio entre eles. */
.pares { display: flex; flex-direction: column; margin: 0; }
.pares > div { display: flex; align-items: baseline; justify-content: space-between; gap: var(--esp-12); padding: var(--esp-6) 0; font-size: var(--corpo-nota); }
.pares > div + div { border-top: 1px solid var(--line-2); }
.pares dt { color: var(--ink-2); margin: 0; }
.pares dd { margin: 0; color: var(--ink); text-align: right; font-variant-numeric: var(--num-tabular); }

/* Os caminhos que a varredura pediu. Quebram no meio de propósito
 * (`overflow-wrap`): um `/wp-content/plugins/.../readme.txt` esticaria a
 * coluna lateral e faria a PÁGINA rolar de lado em 390 px. */
.caminhos { margin: var(--esp-6) 0 var(--esp-12); padding: 0; list-style: none; font-size: var(--corpo-nota); color: var(--ink); }
.caminhos li { padding: 1px 0; display: flex; justify-content: space-between; gap: var(--esp-8); }
.caminhos code { overflow-wrap: anywhere; min-width: 0; }
.caminhos span { color: var(--ink-2); font-variant-numeric: var(--num-tabular); flex: none; }

/* A composição dos eventos do período, no expansor. Quatro segmentos, e cada
 * um vai NOMEADO com o número na legenda logo abaixo — a barra é o resumo
 * visual, a legenda é o dado. Os quatro tons medidos contra branco:
 * `--ink-3` 4,76:1 · `--vermelho` 4,98:1 · `--amarelo` 4,76:1 ·
 * `--verde` 4,78:1. */
.composicao { display: flex; height: 8px; border-radius: 999px; overflow: hidden; margin: var(--esp-8) 0 var(--esp-6); background: var(--line-2); }
.composicao span { display: block; }
.legenda-da-composicao { display: flex; flex-wrap: wrap; gap: var(--esp-4) var(--esp-12); font-size: var(--corpo-nota); color: var(--ink-2); font-variant-numeric: var(--num-tabular); margin: 0; }
.legenda-da-composicao b { color: var(--ink); }

/* 1000px é onde a coluna lateral fica estreita demais para os pares
 * rótulo/valor caberem numa linha. 900 é o breakpoint do sistema (#289) e
 * continua sendo o do resto da tela; este é de GRADE de três colunas, que o
 * sistema não tinha. */
@media (max-width: 1000px) {
  .postura { grid-template-columns: minmax(0, 1fr); }
  .grade-de-superficies { grid-template-columns: minmax(0, 1fr); }
  .carimbo { text-align: left; }
}

/* ===================== #324: A LINHA QUE PARECE TRANSFERÊNCIA ============== * Pílula na coluna Estado, com o corpo em fluxo (não flutua: #279). A pílula é
 * o MESMO par da sugestão de categoria (`.etiqueta-sugerida`: `--azul` sobre
 * `--azul-fraco`, tracejada) -- é uma sugestão da máquina, e o azul é a cor da
 * transferência na tela (#249). Exceção na tela: não pesa nas linhas comuns.
 * A coluna Estado tem 132 px; por isso o corpo empilha e o `<select>` ocupa a
 * largura dela. */
.aviso-transferencia { margin-top: var(--esp-4); }
.aviso-transferencia > summary {
  display: inline-block; cursor: pointer; list-style: none;
  font-size: var(--corpo-mini); padding: var(--esp-2) var(--esp-8);
  border-radius: 999px; background: var(--azul-fraco); color: var(--azul);
  border: 1px dashed var(--etiqueta-sugerida-borda);
}
.aviso-transferencia > summary::-webkit-details-marker { display: none; }
.aviso-transferencia-corpo { display: flex; flex-direction: column; gap: var(--esp-6); margin-top: var(--esp-6); }
.opcao-transferencia { display: flex; flex-direction: column; gap: var(--esp-4); align-items: flex-start; }
/* Na coluna de 132 px o `<select>` no corpo padrão cortava "Escolha a c…"
   (foto da prova 324): corpo miúdo e a largura da coluna. */
.opcao-transferencia select { width: 100%; font-size: var(--corpo-mini); }
/* A recusa do servidor (422) cai aqui: dita em vermelho, junto da ação. */
.aviso-transferencia-erro { font-size: var(--corpo-mini); color: var(--vermelho); }
.aviso-transferencia-erro:empty { display: none; }
/* ============ #324: a coluna "No sistema" da conciliação ============
 * O que o sistema diz (a descrição, ou contato · conta quando a descrição só
 * repetiria a do banco) e o "Editar" na MESMA linha: o texto toma a sobra e
 * corta com reticências (a regra de `.col-sistema .linha-memo` no app.css), o
 * "Editar" nunca quebra. Lado a lado, e não empilhado, porque empilhar
 * subiria a altura de TODAS as linhas por um controle de uma palavra
 * (diretriz 4) -- o mesmo raciocínio do "Desfazer" ao lado da etiqueta. */
.sistema-da-linha { display: flex; align-items: baseline; gap: var(--esp-8); min-width: 0; }
.sistema-da-linha .linha-memo { flex: 1 1 auto; min-width: 0; }
.sistema-da-linha .editar-da-linha { flex: 0 0 auto; }
/* Contato · conta no lugar da descrição repetida: é metadado do lançamento,
   não o nome dele -- tinta secundária (--ink-2 sobre --surface: 7,6:1). */
.tabela-conciliacao .linha-onde { color: var(--ink-2); }
/* ============ #325: a diferença CONHECIDA na lista de Extratos por conta ====
 * A conta com `diferenca_conhecida` continua mostrando o número, mas SEM o
 * alerta âmbar (`.prova-diverge`): o motivo vem embaixo, em tinta secundária
 * (--ink-2 sobre --surface: 7,6:1, o mesmo par da `.linha-onde` do #324).
 * O número fica em --ink (o neutro da tabela), sem o ⚠. */
.prova-conhecida { color: var(--ink); }
.prova-conhecida .prova-motivo {
  display: block; font-size: var(--corpo-mini); color: var(--ink-2);
  white-space: normal; text-align: right;
}
/* #325, achado da foto em 390 px: com lote em alguma conta a tabela de
 * Extratos por conta media 377 px num quadro que não cabe isso e a página
 * rolava de lado (413 × 390) -- com ou sem o motivo (medido escondendo-o: a
 * mesma largura), então o defeito é anterior a esta rodada. No celular o
 * rótulo da coluna quebra em duas linhas e o respiro lateral das células
 * encolhe; acima de 760 px nada muda. */
@media (max-width: 760px) {
  .tabela-extratos th { white-space: normal; }
  .tabela-extratos th, .tabela-extratos td { padding-left: var(--esp-4); padding-right: var(--esp-4); }
}

/* ============ #325: "Só no sistema" e o rodapé que explica a diferença ============
 * A marca do lançamento só no sistema usa o par NEUTRO já medido da
 * `.marca-ja_importada` (#109: --ink-2 sobre --line-2) -- ele não é criar,
 * conciliar nem ignorar, e cor nova pediria medição nova para dizer o mesmo.
 * A divergência e a abertura não explicada reusam `.divergencia-grave`
 * (âmbar, 4,51:1): mesma função, mesmo componente (diretriz 6). */
.marca-so-no-sistema { background: var(--line-2); color: var(--ink-2); }
.sistema-da-linha .editar-so-no-sistema { flex: 0 0 auto; }
.combinar > summary { display: inline; cursor: pointer; list-style: none; font-size: var(--corpo-mini); }
.combinar > summary::-webkit-details-marker { display: none; }
/* A abertura ocupa a linha inteira da tabela: é UMA frase, não uma linha do
   extrato. */
.linha-abertura td { font-size: var(--corpo-miudo); color: var(--ink-2); padding: var(--esp-8) var(--esp-12); }
/* As parcelas da diferença, à direita dela, numa linha que QUEBRA: em 390 px o
   rodapé não cabe numa fila só (foto da prova 325). */
.regua-de-conciliacao:has(.regua-explicacao) { flex-wrap: wrap; row-gap: var(--esp-8); }
.regua-explicacao {
  flex: 1 1 320px; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--esp-4) var(--esp-12);
  margin: 0; font-size: var(--corpo-nota); color: var(--ink-2);
}
.regua-explicacao .ligacao { font-size: inherit; }
.regua-explicacao .valor { font-weight: var(--peso-forte); }
.regua-abertura.nao-explicada .valor { color: var(--amarelo); }
/* #325: a linha do filtro QUEBRA quando as oito abas não deixam 180 px a cada
   campo -- e os dois campos descem juntos, com a largura inteira. Medido na
   prova 258 com contadores de 4 dígitos: abas 1029 px, campos 102 px cada se
   não quebrasse. Com os contadores de um lote comum (2 dígitos) cabe em uma. */
.filtro-linhas { flex-wrap: wrap; }
.filtro-campos { flex: 1 1 368px; display: flex; gap: var(--esp-8); min-width: 0; }
