/*
 * Beachrock — Design System (Zero-Build, tokens próprios)
 * Temas: Refined Light (Padrão) & Sleek Dark (Adaptativo)
 * 100% Sólido, alta legibilidade, contraste calibrado e tipografia de precisão.
 */

/* Plano 23 — as fontes saem de `vendor/assets/fonts/` (versões e origem em
   `vendor/assets/SOURCES.md`), não do Google Fonts. Cada família é UM arquivo
   variável, subset latino: o mesmo arquivo serve todos os pesos, por isso o
   `font-weight` é um intervalo. O Propshaft reescreve o `url()` para o caminho
   com digest. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/inter-latin-496a588b.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/jetbrains-mono-latin-68dbce1c.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  /* ==========================================================================
     Refined Light (Padrão)
     ========================================================================== */
  --bg-canvas: #f8fafc;
  --bg-surface: #ffffff;
  --bg-subtle: #f1f5f9;
  --bg-stripe: #f8fafc;
  --bg-hover: #f1f5f9;
  
  --border-color: #e2e8f0;
  --border-subtle: #edf2f7;
  --border-focus: #3b82f6;
  
  --text-primary: #0f172a;
  --text-secondary: #475569;
  --text-muted: #94a3b8;
  
  --accent: #2563eb;
  --accent-hover: #1d4ed8;
  /* O fundo cheio com texto `--accent-text` (botão primário, segmento ativo) e o seu hover. Não são o `--accent` e o
     `--accent-hover`: no escuro aqueles são cor de texto sobre fundo escuro e precisam ser claros, e o branco sobre
     eles dá 3,68:1 e 2,54:1. Estes são iguais nos dois temas: branco a 5,17:1, e o hover escurece para 6,70:1. */
  --accent-fill: #2563eb;
  --accent-fill-hover: #1d4ed8;
  --accent-light: #eff6ff;
  /* Plano 57 (R1 da validação): texto em acento SOBRE `--accent-light`. No claro
     é o próprio `--accent` (4,75:1). No escuro o `--accent` dá 4,0:1 sobre o
     #172554, abaixo dos 4,5:1 do WCAG AA para texto de 12–13 px, e é o par do item
     ATIVO da navegação, do selo "Recife" e do `.alert-info`. Lá ele vira #60a5fa
     (5,78:1). Calculado com a fórmula de luminância relativa do WCAG 2.x.
     O `--accent-light` do escuro é azul-marinho, não cinza: o cinza (#1e293b) tinha a luminância do
     `--bg-subtle` do hover, e a aba ativa parecia uma aba com o mouse em cima. */
  --accent-on-light: #2563eb;
  --accent-text: #ffffff;
  
  --success: #16a34a;
  --success-bg: #f0fdf4;
  --success-border: #bbf7d0;

  --warning: #b45309;
  --warning-bg: #fffbeb;
  --warning-border: #fde68a;

  /* Perigo (as ações irreversíveis do admin): `--danger` sobre `--bg-surface` dá 6,47:1 no claro e 6,41:1 no
     escuro (#f87171 sobre #111827), acima dos 4,5:1 do AA. Calculado com a luminância relativa do WCAG 2.x;
     `admin_guardas_test.rb` refaz a conta nos três blocos. */
  --danger: #b91c1c;
  --danger-bg: #fef2f2;
  --danger-border: #fecaca;

  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);

  /* ==========================================================================
     Plano 49, Etapa 1 — tokens de FORMA (espaço, tipo, raio, medida, entrelinha).

     Vivem aqui, fora dos blocos de tema, porque não mudam entre claro e escuro.
     🚨 Uma escala FECHADA: 6 degraus de tipo, 8 de espaço, 3 raios. Antes destes
     tokens havia 17 tamanhos de fonte entre 0,7 e 2,1 rem — sete deles espremidos
     entre 0,7 e 0,9, sem regra nenhuma dizendo qual usar. Era a causa mecânica da
     falta de hierarquia: quando tudo tem quase o mesmo tamanho, nada é o principal.
     Se um bloco precisar de um tamanho novo, a resposta é subir a BASE, nunca
     abrir exceção — abrir exceção é como se chegou aos 17.
     ========================================================================== */

  /* Escala tipográfica. --fs-xs: rótulo de coluna, meta, legenda;
     --fs-sm: texto de tabela, ressalva; --fs-base: corpo;
     --fs-md: frase de destaque da linha; --fs-lg: título de seção/card;
     --fs-xl: título de página. */
  --fs-xs:   0.75rem;
  --fs-sm:   0.8125rem;
  --fs-base: 0.9375rem;
  --fs-md:   1.0625rem;
  --fs-lg:   1.375rem;
  --fs-xl:   1.75rem;

  /* Escala de espaço — múltiplos de 4 px. */
  --space-1: 0.25rem; --space-2: 0.5rem;  --space-3: 0.75rem; --space-4: 1rem;
  --space-5: 1.5rem;  --space-6: 2rem;    --space-7: 3rem;    --space-8: 4rem;

  --radius-sm: 4px; --radius: 6px; --radius-lg: 10px;

  --measure: 68ch;            /* limite de leitura de bloco de texto */
  --lh-tight: 1.3; --lh: 1.5;

  /* Plano 57 — tokens de MOVIMENTO. As curvas nativas (`ease`, `ease-out`) são
     fracas: a resposta ao clique tem de começar rápido, e é isso que esta curva
     faz. O que se MOVE (`transform` do press, da seta) usa os dois tokens. Troca
     de cor no hover usa `--dur-press` com `ease` (cor não tem "partida" a dar
     impulso). E três efeitos pontuais têm duração própria, explícita e comentada
     no lugar: o crossfade do "Link copiado!" (120 ms), a entrada do disclosure
     (180 ms, que o formulário da denúncia reaproveita) e a volta do spinner
     (0,7 s). O "Carregando o formulário…" da denúncia espera 300 ms antes de aparecer. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --dur-press: 160ms;
}

/* ⚠️ CSS não aceita variável em `@media` — os breakpoints ficam aqui como
   documentação e são escritos como literal, sempre os MESMOS três:

     --bp-sm  480px — telefone grande
     --bp-md  720px — onde a tabela vira lista e o cromo volta
     --bp-lg  960px — duas colunas

   (Plano 49, Etapa 1. A regra base é a do CELULAR; `min-width` acrescenta —
   mobile-first de verdade, não CSS de desktop encolhido.) */

/* ==========================================================================
   Sleek Dark (quando preferência do SO for dark e não houver data-theme="light")
   ========================================================================== */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg-canvas: #090d16;
    --bg-surface: #111827;
    --bg-subtle: #1f2937;
    --bg-stripe: #172033;
    --bg-hover: #1f2937;
    
    --border-color: #374151;
    --border-subtle: #1f2937;
    --border-focus: #60a5fa;
    
    --text-primary: #f8fafc;
    --text-secondary: #94a3b8;
    --text-muted: #64748b;
    
    --accent: #3b82f6;
    --accent-hover: #60a5fa;
    --accent-light: #172554;
    --accent-on-light: #60a5fa;
    --accent-text: #ffffff;
    
    --success: #4ade80;
    --success-bg: rgba(34, 197, 94, 0.15);
    --success-border: rgba(34, 197, 94, 0.3);

    --warning: #fbbf24;
    --warning-bg: rgba(245, 158, 11, 0.15);
    --warning-border: rgba(245, 158, 11, 0.3);

    --danger: #f87171;
    --danger-bg: rgba(239, 68, 68, 0.15);
    --danger-border: rgba(239, 68, 68, 0.3);

    --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.5);
    --shadow-md: 0 4px 8px -1px rgba(0, 0, 0, 0.7);
  }
}

/* ==========================================================================
   Forçado via atributo data-theme="dark"
   ========================================================================== */
[data-theme="dark"] {
  --bg-canvas: #090d16;
  --bg-surface: #111827;
  --bg-subtle: #1f2937;
  --bg-stripe: #172033;
  --bg-hover: #1f2937;
  
  --border-color: #374151;
  --border-subtle: #1f2937;
  --border-focus: #60a5fa;
  
  --text-primary: #f8fafc;
  --text-secondary: #94a3b8;
  --text-muted: #64748b;
  
  --accent: #3b82f6;
  --accent-hover: #60a5fa;
  --accent-light: #172554;
  --accent-on-light: #60a5fa;
  --accent-text: #ffffff;
  
  --success: #4ade80;
  --success-bg: rgba(34, 197, 94, 0.15);
  --success-border: rgba(34, 197, 94, 0.3);

  --warning: #fbbf24;
  --warning-bg: rgba(245, 158, 11, 0.15);
  --warning-border: rgba(245, 158, 11, 0.3);

  --danger: #f87171;
  --danger-bg: rgba(239, 68, 68, 0.15);
  --danger-border: rgba(239, 68, 68, 0.3);
}

/* Plano 57 — o bloco `[data-theme="light"]` que vivia aqui saiu: repetia, valor
   por valor, o `:root` lá de cima. O claro forçado num SO escuro funciona sem ele
   porque o bloco dark do `@media` exclui `:root[data-theme="light"]` — então o
   `:root` vale. Era uma segunda cópia que podia divergir em silêncio. */

/* ==========================================================================
   Reset & Base
   ========================================================================== */

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-sans);
  background-color: var(--bg-canvas);
  color: var(--text-primary);
  font-size: var(--fs-base);
  line-height: var(--lh);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  color: var(--accent);
  text-decoration: none;
  transition: color 0.15s ease;
}

/* Plano 57 — TODO `:hover` deste arquivo mora dentro deste media query (há
   guarda em `test/integration/polimento_interface_test.rb`). No toque, o navegador aplica o hover no
   tap e o deixa PRESO até o próximo toque em outro lugar: linha de tabela acesa,
   chip azul, link sublinhado — estado que o usuário não pediu. `:focus-visible`
   fica FORA: teclado não é hover. */
@media (hover: hover) and (pointer: fine) {
  a:hover {
    color: var(--accent-hover);
    text-decoration: underline;
  }
}

/* ==========================================================================
   Containers & Layout
   ========================================================================== */

.container {
  width: 100%;
  max-width: 1140px;
  margin-left: auto;
  margin-right: auto;
  /* Plano 49, Etapa 3 — o padding cresce com a tela, nunca o conteúdo. */
  padding-inline: var(--space-4);
}

@media (min-width: 720px) {
  .container { padding-inline: var(--space-5); }
}

.main-content {
  flex: 1;
  padding-top: 1.75rem;
  padding-bottom: 3.5rem;
}

/* ==========================================================================
   Header & Footer
   ========================================================================== */

.site-header {
  background-color: var(--bg-surface);
  border-bottom: 1px solid var(--border-color);
  padding: 0.75rem 0;
  box-shadow: var(--shadow-sm);
}

/* Fixo no topo só a partir de 720 px (decisão do autor, 27/10/2026). No celular ele tem duas linhas (117 px a
   375 px, medido) e fixo comia um sétimo da tela o tempo todo; ali ele rola com a página. */
@media (min-width: 720px) {
  .site-header {
    position: sticky;
    top: 0;
    z-index: 100;
  }
}

.header-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
}

/* --- Plano 57 — a navegação entre as três telas ---------------------------
   Abaixo de 720 px ela desce para uma 2ª linha que ocupa a largura toda
   (`order: 3` + `flex-basis: 100%`), com os três itens dividindo o espaço: no
   1º andar cabem só a marca e o botão de tema — é o que o Plano 49 mediu em
   375 px. A partir de 720 px ela volta para o meio da linha única. */
.site-nav {
  order: 3;
  flex-basis: 100%;
  display: flex;
  gap: var(--space-1);
}

.nav-link {
  flex: 1;
  text-align: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-secondary);
  transition: background-color var(--dur-press) ease, color var(--dur-press) ease,
              transform var(--dur-press) var(--ease-out);
}

.nav-link-ativo {
  background-color: var(--accent-light);
  color: var(--accent-on-light);
}

@media (hover: hover) and (pointer: fine) {
  .nav-link:hover {
    background-color: var(--bg-subtle);
    color: var(--text-primary);
    text-decoration: none;
  }
  .nav-link-ativo:hover {
    background-color: var(--accent-light);
    color: var(--accent-on-light);
  }
}

/* Com "Salvos" no menu, o respiro lateral dos itens cai para 8 px até 959 px. Abaixo de 720, os 4 itens somavam
   359 px para 343 a 375 px; na linha única, a 720 px a folga era 0,1 px com o número de um dígito, e com "Salvos 12"
   o cabeçalho quebrava em duas linhas (medido). */
@media (max-width: 959px) {
  .nav-link { padding-inline: var(--space-2); }
}

@media (min-width: 720px) {
  .site-nav {
    order: 0;
    flex-basis: auto;
    margin-right: auto;
    /* Plano 61 — era `--space-5` (24 px). Os 12 px que saem daqui são parte do que
       faltava para o "Sair" caber na linha em 720 e 960 px.
       A navegação só existe com sessão: o cabeçalho do login não muda. */
    margin-left: var(--space-3);
  }
  .nav-link { flex: none; }
}

.brand-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--text-primary);
}

@media (hover: hover) and (pointer: fine) {
  .brand-link:hover { text-decoration: none; }
}

.brand-icon {
  font-size: var(--fs-lg);
}

.brand-name {
  font-weight: 700;
  font-size: var(--fs-lg);
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

.brand-tag {
  font-size: var(--fs-xs);
  font-weight: 700;
  background-color: var(--accent-light);
  color: var(--accent-on-light);
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Os selos de cobertura (IPTU e ITBI com o período) saíram do cabeçalho (decisão do autor, 27/10/2026). A mesma
   informação continua nos números da home e em `/dados`. */

.site-footer {
  background-color: var(--bg-surface);
  border-top: 1px solid var(--border-color);
  padding: 1.25rem 0;
  margin-top: auto;
}

.footer-content {
  text-align: center;
}

.footer-text {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* O aviso de licença é discreto como o resto do rodapé: a cor do texto em volta, com o sublinhado dizendo que é
   link. O hover vai para a cor do texto principal, não para o azul do `a:hover` global. */
.footer-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--border-color);
  text-underline-offset: 3px;
  transition: color var(--dur-press) ease, text-decoration-color var(--dur-press) ease;
}

@media (hover: hover) and (pointer: fine) {
  .footer-link:hover {
    color: var(--text-primary);
    text-decoration-color: currentColor;
  }
}

/* `/dados`: o texto corrido dentro dos cards. */
.dados-corpo {
  padding: var(--space-4) 1.25rem;
}

.dados-corpo > p + p,
.dados-passos > li + li {
  margin-top: var(--space-2);
}

.dados-passos {
  padding-left: 1.25rem;
}

/* O traço de "2015–2026" é ponto de quebra: no celular o período partia em duas linhas. */
.dados-periodo {
  white-space: nowrap;
}

/* ==========================================================================
   Hero & Search Section
   ========================================================================== */

.hero-card {
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: 2.5rem 2rem;
  box-shadow: var(--shadow-sm);
  margin-top: 0.5rem;
  text-align: center;
}

.hero-title {
  font-size: var(--fs-xl);
  font-weight: 800;
  letter-spacing: -0.03em;
  margin-bottom: 0.5rem;
  color: var(--text-primary);
}

.hero-subtitle {
  font-size: var(--fs-md);
  color: var(--text-secondary);
  max-width: 680px;
  margin: 0 auto 2rem auto;
}

.search-form-wrap {
  max-width: 780px;
  margin: 0 auto;
}

.search-bar-inline {
  display: flex;
  gap: 0.5rem;
}

/* ==========================================================================
   Campos de filtro (lista de candidatos, Plano 37)

   ⚠️ R5 da validação de 15/09/2026: `.filter-bar`, `.filter-group` e
   `.filter-label` foram REMOVIDAS aqui. A 49.1 trocou a barra por
   `.filtros-grade`/`.campo` e as três ficaram sem nenhum uso — a única
   ocorrência de cada uma era a própria definição. Classe que existe e não faz
   nada é o mesmo convite ao defeito que a `.flex-wrap` nunca definida (Plano 49,
   D3): funciona onde ninguém mediu, e a próxima sessão a usa achando que ela
   quebra linha.
   ========================================================================== */

.filter-input {
  width: 4rem;
  padding: 0.3rem 0.5rem;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
}

.filter-select {
  padding: 0.3rem 0.5rem;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background-color: var(--bg-surface);
  color: var(--text-primary);
  max-width: 16rem;
}

.filter-select-sm {
  max-width: 7rem;
}

.filter-input-wide {
  width: 7rem;
}

/* Plano 57 — o "Filtrar" era o `<input type=submit>` cru do navegador, o único
   controle da aplicação sem estilo. Agora é o botão primário, mais compacto que o
   `.search-btn` da consulta (aqui ele divide a linha com "Limpar"). */
.filter-submit {
  padding: 0.45rem 1.1rem;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--accent-text);
  background-color: var(--accent-fill);
  border: 1px solid transparent;
  border-radius: var(--radius);
  transition: background-color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}

/* O botão também é link ("Convidar", "Ir para Salvos"). Sem a cor e o sublinhado aqui, vale o `a:hover` global, que
   pinta o texto de `--accent-hover`: no claro, a mesma cor do fundo, e rótulo e ícone somem. */
@media (hover: hover) and (pointer: fine) {
  .filter-submit:hover { color: var(--accent-text); text-decoration: none; background-color: var(--accent-fill-hover); }
}

@media (max-width: 640px) {
  .search-bar-inline {
    flex-direction: column;
  }
}

.search-input {
  flex: 1;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  padding: 0.75rem 1rem;
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  color: var(--text-primary);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.search-input:focus {
  border-color: var(--border-focus);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}

.search-input::placeholder {
  color: var(--text-muted);
}

.search-btn {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: 600;
  padding: 0.75rem 1.5rem;
  background-color: var(--accent-fill);
  color: var(--accent-text);
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  transition: background-color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
  white-space: nowrap;
}

@media (hover: hover) and (pointer: fine) {
  .search-btn:hover { color: var(--accent-text); background-color: var(--accent-fill-hover); }
}

.search-btn:disabled {
  opacity: 0.7;
  cursor: not-allowed;
}

/* Spinner do botão de consulta — o span girante é injetado pelo
   SearchController#setLoading; sem este bloco a animação era morta (o ⏳ antigo
   também nunca girou). Plano 57: virou classe (era `style=` inline no JS) e
   passou de 1 s para 0,7 s por volta — spinner mais rápido faz a MESMA espera
   parecer menor. Fica de fora do `prefers-reduced-motion`: é indicador de
   estado, não decoração. */
@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  display: inline-block;
  animation: spin 0.7s linear infinite;
}

/* Examples / Chips */
.examples-section {
  margin-top: 1.5rem;
  text-align: left;
}

.examples-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
  margin-bottom: 0.5rem;
  display: block;
}

.chips-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.chip-btn {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  padding: 0.4rem 0.85rem;
  background-color: var(--bg-subtle);
  border: 1px solid var(--border-color);
  border-radius: 9999px; /* pílula — o único raio fora da escala, e é funcional */
  color: var(--text-primary);
  cursor: pointer;
  text-align: left;
  /* Plano 57 — as propriedades nomeadas, nunca `all`: `all` anima também o que
     ninguém pediu (e deixaria o `transform` do `:active` com a curva errada). */
  transition: background-color var(--dur-press) ease, border-color var(--dur-press) ease,
              color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}

/* O que o exemplo mostra, depois do endereço: menor e apagado, para o endereço continuar sendo o que se lê. */
.chip-rotulo {
  margin-left: 0.35rem;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

a.chip-btn {
  display: inline-block;
  text-decoration: none;
}

.chip-btn-mais {
  color: var(--accent);
}

@media (hover: hover) and (pointer: fine) {
  .chip-btn:hover {
    background-color: var(--bg-hover);
    border-color: var(--accent);
    color: var(--accent);
  }
}

/* ==========================================================================
   Results Page Header & Street Section
   ========================================================================== */

.results-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.back-link {
  font-size: var(--fs-base);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.street-header {
  margin-top: 2rem;
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border-color);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.street-title {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.02em;
}

.street-count {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* ==========================================================================
   Solid Cards
   ========================================================================== */

.card {
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  margin-top: 1.25rem;
  overflow: hidden;
}

.card-header {
  background-color: var(--bg-subtle);
  border-bottom: 1px solid var(--border-color);
  padding: 0.75rem 1.25rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Plano 57 — sem caixa alta: ela é a marca dos rótulos em `--fs-xs`. No tamanho
   de título, gritava ("RANKING DE BAIRROS…") e disputava com o `h1`. */
.card-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

.card-meta {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  margin-top: 0.15rem;
}

.card-meta strong {
  color: var(--text-primary);
}

.card-section-title {
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
  padding: 0.65rem 1.25rem 0.35rem 1.25rem;
  background-color: var(--bg-surface);
}

.card-footer {
  padding: 0.85rem 1.25rem;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  background-color: var(--bg-subtle);
  border-top: 1px solid var(--border-color);
}

/* ==========================================================================
   Tables
   ========================================================================== */

.table-responsive {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

table.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  text-align: left;
  background-color: var(--bg-surface);
}

table.table th {
  background-color: var(--bg-subtle);
  color: var(--text-secondary);
  font-weight: 700;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.6rem 1rem;
  border-top: 1px solid var(--border-color);
  border-bottom: 1px solid var(--border-color);
  white-space: nowrap;
}

/* 20/10/2026 — tabelas da ficha cujo cabeçalho pode quebrar linha. Com os rótulos longos em
   uma linha só elas passavam da moldura e cortavam a última coluna: a do IPTU (9 colunas desde
   a "Área ocupada") dava 1.316 px em 1.090, cortando "Valor usado para calcular o IPTU"; a de
   vendas (7 colunas) dava 1.296 px, cortando "Como a venda foi casada". Só nelas o cabeçalho
   quebra; o `nowrap` global continua valendo para as outras tabelas. */
table.table.cabecalho-quebra th {
  white-space: normal;
}
/* O selo da célula ("endereço idêntico nos dois cadastros") NÃO quebra: em duas linhas ele fica
   espremido dentro da própria borda. Quem cede espaço é o cabeçalho. Só no layout de tabela:
   abaixo de 720 px a `.table-stack` empilha, e um selo longo sem quebra estouraria a tela. */
@media (min-width: 720px) {
  table.table.cabecalho-quebra td .badge {
    white-space: nowrap;
  }
}

/* A frase logo abaixo de uma tabela do card, com o mesmo recuo lateral dos títulos de seção. */
.nota-da-tabela {
  margin: 0;
  padding: 0.5rem 1.25rem 0.75rem;
}

table.table td {
  padding: 0.65rem 1rem;
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-primary);
  background-color: var(--bg-surface);
}

/* Striped rows */
table.table-striped tbody tr:nth-child(even) td {
  background-color: var(--bg-stripe);
}

table.table-striped tbody tr:nth-child(odd) td {
  background-color: var(--bg-surface);
}

/* Hover state */
@media (hover: hover) and (pointer: fine) {
  table.table tbody tr:hover td {
    background-color: var(--bg-hover) !important;
  }
}

table.table tbody tr:last-child td {
  border-bottom: none;
}

/* Plano 57 — Inter com dígitos tabulares, não JetBrains Mono: a Inter já alinha
   os algarismos em coluna, e a mono punha duas vozes na mesma linha (e pegava os
   `<th>` alinhados à direita junto). Mono continua opt-in, via `.font-mono`. */
table.table .text-end {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

table.table .font-mono {
  font-family: var(--font-mono);
}

/* --------------------------------------------------------------------------
   Plano 47.1 — a frase da linha e o disclosure por candidato (/candidatos)

   O disclosure mora numa <tr> PRÓPRIA com colspan, não dentro do <td> da 1ª
   coluna: expandir dentro da célula empurra a largura das outras colunas
   (medido no DOM — ver a Verificação do plano). Cada candidato é um <tbody>,
   então a linha de dados é sempre :nth-child(1) e a do detalhe :nth-child(2) —
   a listra do .table-striped passa a marcar o PAR, sem tocar na regra que
   /mercado e /endereco compartilham.
   -------------------------------------------------------------------------- */

.candidato-frase {
  margin: 0 0 0.2rem;
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: var(--lh-tight);
  color: var(--text-primary);
}

/* Plano 63 — a razão do prédio, acima do índice de unidades. */
.razao-do-predio {
  margin-bottom: var(--space-4);
}

/* Plano 63 — a frase da rua concentrada, logo abaixo do rastro da razão.
   Plano 64 — a da área que parece já convertida, no mesmo lugar. */
.candidato-rua-concentrada,
.candidato-area-convertida {
  margin: 0.2rem 0 0;
}

/* Especificidade deliberada: `table.table tbody tr:last-child td` (0,2,4) tira a
   borda de TODA linha de detalhe, porque ela é a última do seu <tbody>. Estas
   duas empatam em 0,2,4 / vencem em 0,3,4 e vêm depois na cascata. */
table.table tbody tr.linha-detalhe > td {
  /* 🚨 Os DOIS verticais iguais, DE PROPÓSITO: fechado, o `<details>` é o ÚNICO
     conteúdo desta `<td>` — então o que centra o `<summary>` no rodapé É a
     simetria do padding. Antes era `0 1rem 0.5rem`: o summary nascia colado na
     borda que separa o disclosure do conteúdo de cima, parecendo um item
     pendurado no bloco anterior (medido em 375 px: 0 px acima, 8 px abaixo).

     ⚠️ Padding vertical DESIGUAL não centra nada — maior embaixo empurra o
     conteúdo para CIMA. Regra que quebrar a simetria daqui tem de vir com uma
     medição, não com a intuição. Há teste que compara os dois valores. */
  padding: var(--space-2) 1rem;
  border-bottom: 1px solid var(--border-color);
}

table.table tbody:last-of-type tr.linha-detalhe > td {
  border-bottom: none;
}

.disclosure > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  width: fit-content;
  cursor: pointer;
  padding: 0.15rem 0.4rem;
  margin-left: -0.4rem;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  list-style: none;
}

.disclosure > summary::-webkit-details-marker {
  display: none;
}

.disclosure > summary:focus-visible {
  color: var(--text-primary);
  background-color: var(--bg-subtle);
}

@media (hover: hover) and (pointer: fine) {
  .disclosure > summary:hover {
    color: var(--text-primary);
    background-color: var(--bg-subtle);
  }
}

.disclosure[open] > summary {
  color: var(--text-primary);
  margin-bottom: 0.35rem;
}

/* O único indicador de estado: `display: inline-flex` no <summary> tira o
   triângulo nativo, e sem ele nada diz se o bloco está aberto. */
.disclosure-seta {
  transition: transform var(--dur-press) var(--ease-out);
}

.disclosure[open] > summary .disclosure-seta {
  transform: rotate(90deg);
}

/* Plano 57 — o conteúdo do `<details>` entra em vez de aparecer de uma vez:
   4 px de deslocamento e opacidade, 180 ms. É keyframe (não transição) porque o
   filho de um `<details>` fechado não é renderizado — não há estado de partida
   para transicionar; a animação dispara quando o seletor passa a casar.

   🚨 Só na abertura PELO USUÁRIO (R4 da validação): com `[open]` sozinho, o
   "Refinar" e o "Mais filtros" que já nascem abertos (há filtro ativo) animavam a
   cada carga de página — movimento que ninguém pediu. O `data-aberto-pelo-usuario`
   é posto pelo listener de clique em `application.js`; sem JS o bloco só não anima. */
@keyframes revela {
  from { opacity: 0; transform: translateY(-4px); }
}

.disclosure[data-aberto-pelo-usuario][open] > :not(summary) {
  animation: revela 180ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .disclosure-seta {
    transition: none;
  }
}

.disclosure-corpo {
  /* O texto longo não acompanha a largura da tabela: linha de 100+ caracteres
     não se lê. O bloco é fluido e para de crescer num limite de leitura. */
  max-width: var(--measure);
  padding: 0.6rem 0.8rem;
  border-left: 2px solid var(--border-color);
  background-color: var(--bg-subtle);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  line-height: var(--lh);
  color: var(--text-secondary);
}

.disclosure-corpo p {
  margin: 0 0 0.5rem;
}

.disclosure-corpo p:last-child {
  margin-bottom: 0;
}

.disclosure-corpo strong {
  color: var(--text-primary);
}

/* ==========================================================================
   Badges & Status
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--fs-xs);
  font-weight: 600;
  padding: 0.25rem 0.55rem;
  border-radius: var(--radius-sm);
  line-height: 1;
}

.badge-neutral {
  background-color: var(--bg-subtle);
  color: var(--text-secondary);
  border: 1px solid var(--border-color);
}

/* Selo que é link (a consulta salva, o anúncio original): sem regra própria, o `a:hover` global o deixava azul e
   sublinhado. O hover é o do `.chip-btn`, o outro controle em forma de selo. */
@media (hover: hover) and (pointer: fine) {
  a.badge-neutral:hover {
    color: var(--accent-on-light);
    text-decoration: none;
    border-color: var(--accent);
  }
}

.badge-warning {
  background-color: var(--warning-bg);
  color: var(--warning);
  border: 1px solid var(--warning-border);
}

.badge-success {
  background-color: var(--success-bg);
  color: var(--success);
  border: 1px solid var(--success-border);
}

/* ==========================================================================
   Alerts
   ========================================================================== */

.alert {
  padding: 0.85rem 1.25rem;
  border-radius: var(--radius);
  font-size: var(--fs-base);
  margin-top: 1rem;
  border: 1px solid transparent;
}

.alert-warning {
  background-color: var(--warning-bg);
  color: var(--warning);
  border-color: var(--warning-border);
}

.alert-info {
  background-color: var(--accent-light);
  color: var(--accent-on-light);
  border-color: rgba(59, 130, 246, 0.25);
}

.alert-secondary {
  background-color: var(--bg-subtle);
  color: var(--text-secondary);
  border-color: var(--border-color);
}

/* ==========================================================================
   Visualizations: só o gráfico, e só em `/mercado`

   🚨 `.unit-visuals-grid`, `.map-box`, `.map-canvas` e `.grafico-altura` saíram
   em 18/09/2026 (Plano 49.2, desvio D13): o gráfico de evolução do IPTU e o
   minimapa foram removidos da ficha de unidade por decisão do autor — e CSS sem
   markup é a mesma família da classe sem regra que a guarda do
   `layout_responsivo_test` passou a pegar, do outro lado. `.chart-box` e
   `.visual-box-header` FICAM: são os dois que `/mercado` usa.
   ========================================================================== */

.chart-box {
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  padding: 0.85rem;
  background-color: var(--bg-surface);
}

.visual-box-header {
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
  margin-bottom: 0.5rem;
}

/* Plano 23 — era `style="height: 220px; position: relative;"` no markup. A CSP
   (`style-src 'self'` sem `'unsafe-inline'`) bloqueia atributo `style`, e o
   Chart.js com `maintainAspectRatio: false` precisa de pai com altura e posição. */
.chart-canvas { height: 220px; position: relative; }

/* ==========================================================================
   Utilities
   ========================================================================== */

.d-flex { display: flex; }
/* 🚨 `.flex-wrap` era usada em 9 lugares das views e NUNCA foi definida — todas
   aquelas linhas ficavam sem quebra, em silêncio. Pego na verificação do Plano 49
   em 375 px: a linha de campos opcionais da home (`d-flex gap-2 flex-wrap`) punha
   três campos de `min-width: 200px` na mesma linha e estourava o documento em
   **290 px** (scrollWidth 665 contra innerWidth 375 — medido,
   `script/verifica_plano_49.rb`). Classe usada e não definida é a mesma família
   do rótulo que só existe no `title=`: funciona onde ninguém mediu. */
.flex-wrap { flex-wrap: wrap; }
.align-items-center { align-items: center; }
.justify-content-between { justify-content: space-between; }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }
.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 0.75rem; }
.mt-4 { margin-top: 1rem; }
.mt-5 { margin-top: 1.5rem; }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: 0.25rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-3 { margin-bottom: 0.75rem; }
.mb-4 { margin-bottom: 1rem; }
/* Plano 23 — os dois abaixo substituem atributos `style` que a CSP bloqueia (o
   `font-weight: 600` das células de /mercado virou o `.peso-forte` que já existia). */
.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); }
.pb-4 { padding-bottom: var(--space-4); }
.w-100 { width: 100%; }
.text-muted { color: var(--text-muted); }
.text-secondary { color: var(--text-secondary); }

/* ==========================================================================
   Market Dashboard (/mercado)
   ========================================================================== */

.page-title {
  font-size: var(--fs-lg);
  font-weight: 700;
  margin: 0 0 0.25rem;
}

.page-subtitle {
  color: var(--text-secondary);
  margin: 0 0 1.25rem;
}

/* Plano 50 — a data do SNAPSHOT da lista de candidatos, logo abaixo do
   subtítulo. Fica VISÍVEL, fora de qualquer disclosure: é a única linha da tela
   que explica um link do portal que não abre mais, e o Plano 47 proíbe pôr
   correção de modelo mental atrás de um clique.
   ⚠️ Sem `display: flex` de propósito: a frase tem `<strong>` no meio, e cada
   pedaço de texto viraria um item de flex — a linha pararia de quebrar junto. */
.page-snapshot {
  color: var(--text-secondary);
  margin: 0 0 1.25rem;
}

.cards-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  margin-bottom: 1.5rem;
}

@media (max-width: 900px) {
  .cards-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 520px) {
  .cards-grid { grid-template-columns: 1fr; }
}

.stat-card {
  padding: 1rem 1.25rem;
}

/* Plano 57 (desvio D1) — dentro da grade quem espaça é o `gap`. O `margin-top`
   do `.card` se somava a ele, e no celular (uma coluna) os cards ficavam com o
   dobro do respiro entre si. */
.cards-grid > .card { margin-top: 0; }

.stat-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
}

.stat-value {
  font-size: var(--fs-xl);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  margin: 0.35rem 0 0.15rem;
}

.stat-meta {
  font-size: var(--fs-sm);
  /* 20/10/2026 — era `--text-muted` (#94a3b8 sobre branco, 2,56:1, abaixo do AA). A legenda do
     número é texto que se lê, não enfeite. */
  color: var(--text-secondary);
}

/* ==========================================================================
   Plano 49, Etapa 4 — foco visível e alvo de toque

   Até aqui só `.search-input` tinha estado de foco: quem navega por teclado não
   enxergava onde estava. Regra ÚNICA, com `:where()` de propósito (especificidade
   0,1,0 — não briga com nenhum bloco já escrito).
   ========================================================================== */

/* `textarea` também: sem ele, a caixa de comentário da denúncia fica com o anel do
   navegador, diferente de todo outro campo. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Alvo de toque: os `.filter-input` de hoje têm `padding: 0.3rem` (≈ 28 px de
   altura). 44 px é o mínimo que o dedo acerta sem zoom. */
@media (max-width: 719px) {
  :where(button, select, input):not([type="hidden"]) {
    min-height: 44px;
  }
}

/* O iOS dá zoom na página quando o foco cai num campo com fonte abaixo de 16 px, e não
   desfaz o zoom ao sair dele. Os campos usam `--fs-base` (15 px). Em toque, sobem para o
   degrau seguinte da escala (`--fs-md`, 17 px): não há degrau de 16 px, e a escala é fechada.
   ❌ Nunca `maximum-scale=1` no viewport: trava o zoom de quem precisa dele.
   Por CAPACIDADE (`pointer: coarse`), não por largura: um iPad com teclado é largo e é toque.
   Especificidade 0,1,1 de propósito: vence as classes de campo (`.search-input`,
   `.filter-input`, `.filter-select`, `.denuncia-comentario`, 0,1,0) e perde para as do login
   (`.search-input.campo-login`, `.campo-codigo`, 0,2,0), que já são maiores. No `select` e no
   `textarea`, que nunca têm `type="hidden"`, quem dá essa especificidade é o `:not()`: sem
   ele, 0,0,1 perderia para as classes. Botão (`type="submit"` e afins) fica de fora: não
   recebe foco de digitação, não dá zoom. */
@media (pointer: coarse) {
  input:not([type="hidden"], [type="submit"], [type="button"], [type="reset"], [type="checkbox"], [type="radio"]),
  :is(select, textarea):not([type="hidden"]) {
    font-size: var(--fs-md);
  }
}

/* ==========================================================================
   Plano 49, Etapa 2 — `.table-stack`: a tabela vira lista abaixo de 720 px

   Opt-in por CLASSE: `/mercado` compartilha `.table` e não entra neste plano —
   sem `.table-stack` ele renderiza exatamente como hoje.

   Contrato de markup: cada `<td>` carrega o rótulo da sua coluna como ELEMENTO
   real — `<td><span class="cel-rotulo">Ano</span>2026</td>`.

   🚨 Decisão D1 — NÃO usar `data-rotulo` + `content: attr(data-rotulo)`. Texto
   gerado por CSS é VISÍVEL ao usuário e INVISÍVEL a quem confere: a varredura de
   palavras proibidas (`Proibidas.call`) faz `gsub(/<[^>]+>/, " ")` — o atributo
   sai junto com a tag — e `assert_select` não enxerga pseudo-elemento. Um rótulo
   publicado só em `::before` seria uma afirmação na tela fora do alcance de
   todas as guardas do projeto. É o mesmo defeito que o Plano 47 proibiu no
   `title=`, com outra sintaxe.
   ========================================================================== */

.cel-rotulo { display: none; }

@media (max-width: 719px) {
  table.table-stack thead            { display: none; }
  table.table-stack tbody tr         { display: block; padding: var(--space-3) 0; }
  /* 🚨 `flex-wrap: wrap` + `overflow-wrap: anywhere` NÃO são enfeite de layout:
     o `min-content` de uma linha de `flex` é a SOMA dos itens, e o da tabela é o
     maior `min-content` entre as suas linhas — uma célula com rótulo + valor numa
     linha só faz a TABELA inteira pedir a largura dos dois juntos. Medido em
     15/09/2026 (Plano 49.1): `/candidatos?modo=a` em 375 px dava tabela de 421 px
     com 78 px de rolagem lateral DENTRO da moldura — o documento não estourava
     (`estouro == 0`), então só a medida de dentro da tabela pegava. A célula do
     modo A carregava rótulo + título + linha de preço: sem quebra, era ela que
     mandava. Com a quebra o `min-content` passa a ser o MAIOR item, e `anywhere`
     garante que uma palavra longa não volte a empurrar (ele só quebra quando não
     cabe). O modo A saiu no Plano 59, e a célula que entrou no lugar (preço
     pedido + quartos/vagas) pede 204 px com o rótulo: em 17/10/2026, 375 px,
     tabela de 343 px na moldura de 343, rolagem interna 0. A regra fica: é ela
     que garante isso para a próxima célula larga. */
  table.table-stack tbody td         { display: flex; align-items: baseline; flex-wrap: wrap;
                                       justify-content: space-between; gap: var(--space-1) var(--space-4);
                                       overflow-wrap: anywhere;
                                       padding: var(--space-1) var(--space-4);
                                       border: 0; background: none !important; }
  table.table-stack tbody td:empty   { display: none; }
  .cel-rotulo                        { display: inline; flex: none;
                                       color: var(--text-secondary);
                                       font-size: var(--fs-xs);
                                       text-transform: uppercase;
                                       letter-spacing: .04em; }
  table.table-stack .text-end        { text-align: right; }
  /* Plano 59 — o preço e a linha de quartos/vagas alinham à direita, como o
     valor das outras células do cartão; no desktop seguem a coluna. */
  table.table-stack .preco-pedido    { text-align: right; }
}

/* ==========================================================================
   Plano 49, Etapa 5 — o padrão de métrica (`.metric`), usado pelas duas telas

   O bloco Square: rótulo pequeno em caixa alta, número grande tabular, ressalva
   embaixo. Generaliza o `.stat-card` de `/mercado` — NÃO o substitui (o painel
   fica como está neste plano).

   🚨 Quem publica o número publica a ressalva: um tile sem a `.metric-meta` que
   o qualifica é o defeito que o Plano 47 existe para impedir. A `.metrics` só
   distribui; a honestidade mora em cada bloco.
   ========================================================================== */

.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: var(--space-3);
}

.metric-rotulo {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-secondary);
}

.metric-valor {
  font-size: var(--fs-lg);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

.metric-meta {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ==========================================================================
   Plano 49, Etapa 3 — cromo: botão de tema, rodapé, escala de tipo utilitária
   ========================================================================== */

/* O botão de tema era um `style=` inline de 6 declarações mais um `font-size`
   inline no rótulo. Abaixo de 480 px fica só o ícone, com `aria-label` — esconder
   o texto sem nome acessível deixaria um botão anônimo. */
.tema-btn {
  cursor: pointer;
  padding: 0.35rem 0.65rem;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--font-sans);
}

@media (max-width: 479px) {
  .rotulo-compacto { display: none; }
}

/* Plano 73.1: o número de salvos entrou na linha única do cabeçalho e faltavam ~36 px nas
   bordas de 720 e 960 px (V2.1b do `script/verifica_plano_57.rb`). Da linha única para cima o
   tema fica só com o ícone, como o "Sair" e os salvos; o nome continua no `aria-label`. Só com
   sessão (a navegação existe): o cabeçalho do login não muda. Entre 480 e 719 px o cabeçalho tem
   duas linhas e o rótulo cabe. */
@media (min-width: 720px) {
  .site-nav ~ .d-flex .rotulo-compacto { display: none; }
}

/* A única porta para aplicar um degrau da escala fora de um bloco — o degrau é
   o token, nunca um valor solto. (Plano 49, Etapa 1: os `font-size` inline das
   views saíram daqui, que é o que o critério de aceite conta.) */
.text-xs   { font-size: var(--fs-xs); }
.text-sm   { font-size: var(--fs-sm); }
.text-base { font-size: var(--fs-base); }
.text-md   { font-size: var(--fs-md); }
.text-lg   { font-size: var(--fs-lg); }
.text-xl   { font-size: var(--fs-xl); }

/* ==========================================================================
   Plano 49.1 — `/candidatos`: filtros, chips, modo, a lista no celular, o funil
   ========================================================================== */

/* --- Etapa 2 — a barra de filtros ----------------------------------------
   Rótulo ACIMA do campo, pequeno e em caixa alta (o par Linear/Square). Era isto
   que resolvia o "Preço (R$)" órfão: com o rótulo à esquerda, o `mín` e o `máx`
   caíam em linhas diferentes, cada um com um pedaço do rótulo. */

.filtros-card {
  padding: var(--space-4);
  margin-top: 0;
  margin-bottom: var(--space-5);
}

/* Ajuda curta colada embaixo do próprio controle que a motiva. `.campo` já é uma
   coluna de `flex`, então ela entra na coluna sem posicionamento próprio — e é
   `--fs-xs`/`--text-secondary` para não competir com o campo.

   🚨 Ela existe porque a correção que estava num `title=` do rótulo do filtro de
   idade sumiu no reescrito desta tela (R1 da validação de 15/09/2026). Correção
   que corrige uma crença errada mora na TELA, e colada no controle que a motiva —
   o disclosure da linha só a diz para quem tem anúncio antigo na frente. */
.campo-ajuda {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-secondary);
}

.filtros-grade {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

.campo {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.campo-rotulo {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-secondary);
}

.campo-par { display: flex; gap: var(--space-2); }
.campo-par input { min-width: 0; flex: 1; }

/* O bloco de avançados ocupa a grade inteira — ele não é um campo a mais na
   mesma linha dos primários. */
.filtros-mais { grid-column: 1 / -1; }
.filtros-mais[open] > summary { margin-bottom: var(--space-2); }

.filtros-acoes {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.filtros-limpar { font-size: var(--fs-sm); font-weight: 600; }

/* --- Etapa 3 — os chips do que está ativo -------------------------------- */

.chips-ativos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.chips-rotulo {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-secondary);
}

.chip-ativo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--border-color);
  border-radius: 9999px;   /* pílula: o único raio fora da escala, e é funcional */
  background-color: var(--bg-subtle);
  font-size: var(--fs-sm);
}

/* Plano 71: o texto do chip é UM item do flex. Com rótulo, valor e nota soltos,
   cada pedaço virava uma coluna quando o chip quebrava em 375 px. */
.chip-texto { min-width: 0; }
.chip-nota { color: var(--text-secondary); }

.chip-x {
  font-weight: 700;
  line-height: 1;
}

.chips-limpar { font-size: var(--fs-sm); }

/* --- Etapa 4 — a barra de resultados e o controle segmentado --------------
   A contagem subiu para antes da tabela. O seletor de modo A/B que morava ao
   lado dela saiu no Plano 59; o `.segmentado` fica, porque é o seletor de janela
   de `/mercado` (Plano 57). */

.resultados-topo {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

.resultados-contagem { font-size: var(--fs-base); color: var(--text-secondary); }

/* Plano 59 — "2 quartos · 1 vaga" é UMA leitura: na coluna estreita do desktop
   ela quebrava em "2 quartos · 1" / "vaga". A linha mais longa do dev real mede
   ~110 px (17/10/2026), folgada até no cartão de 343 px do celular. */
.preco-pedido .metric-meta { white-space: nowrap; }

.segmentado {
  display: inline-flex;
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  overflow: hidden;
}

.segmento {
  transition: transform var(--dur-press) var(--ease-out);
  padding: 0.35rem 0.75rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  background-color: var(--bg-surface);
  color: var(--text-secondary);
}

.segmento + .segmento { border-left: 1px solid var(--border-color); }

.segmento:focus { text-decoration: none; }

@media (hover: hover) and (pointer: fine) {
  .segmento:hover { text-decoration: none; color: var(--text-primary); }
  .segmento-ativo:hover { color: var(--accent-text); }
}

.segmento-ativo {
  background-color: var(--accent-fill);
  color: var(--accent-text);
}


/* --- Etapa 5 — os badges numa linha que quebra sozinha -------------------
   O `<br>` solto entre eles empilhava um por linha MESMO quando caberiam lado a
   lado. Vale em toda largura: no desktop os três badges de um card ocupavam três
   linhas de altura por nada. */

.badges-linha {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

/* --- Etapa 5 — cada candidato vira um cartão no celular ------------------ */

@media (max-width: 719px) {
  table.table-stack tbody.candidato {
    display: block;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background-color: var(--bg-surface);
    margin-block: var(--space-3);
    overflow: hidden;
  }

  table.table-stack tbody.candidato tr { padding: 0; }

  /* A posição é discreta, no topo: não é coluna com nome — é a posição do
     candidato na ordem, e a ordem já está explicada no cabeçalho da tela. */
  table.table-stack tbody.candidato td.posicao {
    display: block;
    padding: var(--space-2) var(--space-4) 0;
    text-align: right;
    font-size: var(--fs-xs);
    color: var(--text-muted);
  }

  /* A frase da razão lidera o cartão: ela é a resposta, o resto é evidência. */
  table.table-stack tbody.candidato td.comparacao {
    display: block;
    padding: var(--space-1) var(--space-4) var(--space-3);
    border-bottom: 1px solid var(--border-subtle);
  }

  table.table-stack tbody.candidato td.comparacao .candidato-frase {
    font-size: var(--fs-md);
  }

  table.table-stack tbody.candidato tr.linha-detalhe > td {
    display: block;
    /* Mesma simetria da regra de cima: é ela que centra o `<summary>` no rodapé
       do cartão. Não trocar por `var(--space-1) … var(--space-3)` — medido. */
    padding: var(--space-2) var(--space-4);
    border-bottom: 0;
  }
}

/* --- Etapa 6 — o funil ---------------------------------------------------
   Motivo e causa na MESMA entrada, uma linha por motivo. O rodapé continua
   visível sem clique (Plano 47): a densidade vem da estrutura, não de esconder. */

.funil { margin-top: var(--space-5); }

.funil-resumo {
  margin: var(--space-4) var(--space-4) 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.funil-lista { margin: var(--space-4) var(--space-4) 0; }

.funil-item + .funil-item {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

/* A coluna de dígitos que a Etapa 6 pedia, sem quebrar a âncora `data-motivo`. */
.funil-motivo { font-variant-numeric: tabular-nums; }

.funil-causa { margin: var(--space-1) 0 0; }

.funil-nota { margin: var(--space-4); }

/* ==========================================================================
   Plano 49.2 — `/endereco`: o índice de unidades e a ficha de uma unidade

   Etapa 6 — troca de veículo, não de conteúdo: os `style=` inline das parciais de
   `enderecos/` viraram classes com os tokens do Plano 49. Nenhuma palavra mudou
   (conferido comparando o conteúdo SEM tag entre o HEAD e este estado).
   ========================================================================== */

/* Blocos de conteúdo da ficha. O respiro mora numa classe só — antes era um
   `style=` repetido em cinco elementos, cada um podendo divergir do vizinho. */
.bloco-ref        { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-color); }
.bloco-ref-subtle { background-color: var(--bg-subtle); }
.bloco-fim        { padding: var(--space-3) var(--space-4); }
.bloco-pedido     { margin-top: var(--space-3); padding-top: var(--space-3);
                    border-top: 1px dashed var(--border-color); }

/* O título que ABRE um bloco dentro do card: a borda de cima é o que o separa do
   que veio antes. Sem ela, dois blocos de texto viram um parágrafo só. */
.secao-cabecalho       { border-top: 1px solid var(--border-color); background-color: var(--bg-subtle); }
.secao-cabecalho-linha { display: flex; align-items: center; justify-content: space-between;
                         gap: var(--space-2); }

.peso-forte       { font-weight: 600; }
.valor-destaque   { color: var(--success); font-weight: 600; }
.numeros-tabulares { font-variant-numeric: tabular-nums; }

/* Botão em forma de badge (Copiar link, Mostrar todos os anos): é afordância, não
   conteúdo — e o `font-family` volta ao da interface por cima do `.badge`, que a
   tabela formata em monoespaçada. */
.nav-badge { cursor: pointer; padding: 0.35rem 0.65rem; font-family: var(--font-sans); }

/* O alerta dentro de um bloco que tem respiro próprio. */
.alerta-espaco       { margin: var(--space-4); }
.alerta-espaco-baixo { margin: 0 var(--space-4) var(--space-4); }

/* ==========================================================================
   Plano 49.2 — `.visually-hidden`: esconde da TELA, não do leitor de tela

   🚨 Achado R1 da validação em sessão limpa (18/09/2026): a 49.2 usava esta
   classe no `<th>` da coluna de ação e ela **não existia em nenhuma folha do
   projeto**. Medido no DOM em 1024 px: o texto "Ficha completa da unidade", que
   deveria ser lido apenas por leitor de tela, era o **cabeçalho visível da 6ª
   coluna**. Não quebrava layout nenhum (`estouro == 0`), então nenhum número
   acusou — e a medição de tela dos três planos é em 375 px, onde
   `table.table-stack thead { display: none }` esconde justamente a linha onde o
   defeito mora.

   ⚠️ NÃO é `display: none`: isso tiraria o texto também do leitor de tela, que é
   o único leitor que ele tem. O clip-rect de 1 px mantém o nó acessível e
   invisível ao olho (é o padrão do `visually-hidden` do Bootstrap). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* O campo opcional que divide a linha do formulário com os irmãos. */
.chip-link  { text-decoration: none; }


/* --- Etapa 5 — os campos opcionais do formulário -------------------------
   A linha de cima fica com a busca e o botão (o que 100 % das consultas usa) e os
   três campos opcionais vão para o disclosure. Ele nasce ABERTO quando qualquer um
   dos três veio preenchido (ver `show.html.erb`) — filtro ativo escondido atrás de
   um controle fechado é o "filtro fantasma" da 49.1. */
.refinar { margin-top: var(--space-3); }

/* --- Etapa 3 — a faixa de métricas da ficha de UMA unidade --------------- */
.metricas-ficha {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-color);
  background-color: var(--bg-surface);
}

/* --- Etapa 1 — o índice de unidades -------------------------------------
   No desktop é uma tabela comum (`.table`). No celular, `.table-stack` empilharia
   as 6 células — a regra genérica dá ~29 px por célula, ~198 px por unidade, e 68
   unidades estouram o critério de 6.000 px do plano ao contrário. Aqui a LINHA do
   índice é 2–3 linhas de texto: o nome em bloco, os dados correndo inline.

   ⚠️ Os rótulos continuam sendo NÓ do DOM (`cel_rotulo`), na MESMA linha do valor;
   esconder a célula inteira no celular para encurtar a página seria trocar o
   número por silêncio — e deixaria a linha "Área" sem nome justamente onde ela
   não tem cabeçalho.   🚨 Correção do autor olhando a tela (18/09/2026, em DUAS rodadas): "os blocos das
   unidades estão todos colados um ao outro" e, depois, já com o fio visível,
   "se não dá pra separar as unidades, aumente o padding superior e inferior para o
   texto não ficar tão grudado nas bordas".

   A 1ª versão tinha trocado TODO o respiro vertical da linha por um fio de 1 px em
   `--border-subtle` — e `--border-subtle` (#1f2937) sobre `--bg-page` (#090d16) é
   quase invisível: as 68 unidades viravam uma parede de texto, sem onde o olho
   parar. O fio passou a `--border-color` (que se vê) e o padding foi para
   `--space-2` em cima e embaixo — **16 px entre o texto de uma unidade e o da
   próxima**, o dobro do meio-termo que ainda cabia no critério antigo.

   ⚠️ O que segura o crescimento é `line-height: var(--lh-tight)` nas células: a
   linha é densa por dentro (dado tabular em 12–13 px, 1,3 de entrelinha, que é
   entrelinha de tabela) e respira ENTRE uma unidade e a outra. Medido em 375 px no
   prédio de 68 unidades: **5.650 → 6.498 px**, ~15 px por linha de respiro ganho.

   🚨 **O critério de 6.000 px foi ABANDONADO de propósito** (desvio D12): ele valia
   enquanto altura e legibilidade apontavam para o mesmo lado, e o autor decidiu que
   não apontam. A 49.2 publica **~1/17** da altura de antes (112.374 px, 2.433 fichas)
   em vez de 1/20, e o script guarda **7.000 px** como rede de regressão — não como
   alvo. Número menor que isso aqui é sinal de que a linha voltou a ser ilegível. */
@media (max-width: 719px) {
  table.indice-unidades tbody tr      { padding: var(--space-2) var(--space-4);
                                        border-bottom: 1px solid var(--border-color); }
  table.indice-unidades tbody tr:last-child { border-bottom: 0; }
  table.indice-unidades tbody td      { display: inline; padding: 0; border: 0;
                                       background: none !important;
                                       margin-right: var(--space-2);
                                       /* O degrau de baixo da escala: o dado do
                                          índice é linha de tabela, e aqui a linha
                                          precisa caber. Medido — ver a nota de
                                          altura abaixo. */
                                       font-size: var(--fs-xs);
                                       line-height: var(--lh-tight); }
  table.indice-unidades tbody td.unidade { display: block; font-weight: 600;
                                       font-size: var(--fs-sm); }
  table.indice-unidades .cel-rotulo   { margin-right: 0.2rem; }
}

/* 🚨 O respiro acima do índice — o mesmo defeito na outra ponta ("a lista está
   colada no card que avisa que está vendo o prédio inteiro"). O aviso e a lista
   são duas coisas diferentes: o primeiro explica POR QUE a tela mostra 68
   unidades, a segunda é o dado. Colados, o aviso lê como título da tabela.
   Vale nas duas larguras — no desktop a lista também nascia encostada no aviso. */
table.indice-unidades { margin-top: var(--space-3); }

/* 20/10/2026 — o índice dentro de um card (`.indice-card`). A moldura agora é do card: a
   tabela encosta nele em cima (sem margem, e sem a borda de cima do cabeçalho, que somaria
   com a do card numa linha dupla), e o respiro entre a nota e a tabela passa a ser da nota. */
.indice-card table.indice-unidades { margin-top: 0; }
.indice-card > .table-responsive:first-child th { border-top: 0; }
.indice-card .indice-nota { margin: 0; padding: var(--space-3) var(--space-4); }

/* A nota que devolve o nome do prédio à lista (extra do 49.2): ele se repetia em
   TODAS as linhas e subiu para cá uma vez só. Fica em `--fs-xs` porque é nota, não
   dado — o dado é a tabela. */
.indice-nota {
  margin: var(--space-3) var(--space-4) 0;
  color: var(--text-secondary);
  font-size: var(--fs-xs);
}

.indice-nota strong { color: var(--text-primary); }
.indice-nota i      { margin-right: var(--space-1); }

/* 🚨 `display: block` na linha empilhada ANULA o `hidden` do IPTU.

   `[hidden]` é do CSS do navegador, e qualquer `display` declarado aqui o vence:
   a linha do exercício escondido passaria a APARECER só no celular, e o botão
   "Mostrar todos os anos" não faria nada visível. O defeito é invisível no
   desktop (onde a linha volta a ser `table-row` e o `hidden` vale) e é exatamente
   o tipo de coisa que a medição em 375 px pega. Medido: sem esta regra o
   `q=AV CONSELHEIRO AGUIAR, 5025, <unidade>` renderiza os 7 exercícios em vez de 3. */
table.table-stack tbody tr[hidden] { display: none; }

/* ==========================================================================
   Plano 57 — polimento: home, janela de `/mercado`, resposta ao clique
   ========================================================================== */

/* Os três campos opcionais (parcial `enderecos/_campos_opcionais`), na home e no
   "Refinar" de `/endereco`. Grade que vira coluna sozinha: `auto-fit` + o mínimo. */
.campos-opcionais {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-3);
  text-align: left;
}

.campos-opcionais-ajuda { margin-top: var(--space-2); text-align: left; }

/* Os fatos da home — o que a base cobre, sem número que não tenha fonte. */
.fatos-home {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-6);
}

.fato { margin-top: 0; padding: var(--space-4) var(--space-5); }

.fato-valor {
  font-size: var(--fs-lg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  margin: var(--space-1) 0;
}

/* A janela de `/mercado`: rótulo e o `.segmentado` na mesma linha. */
.janela-linha {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

/* ==========================================================================
   /mercado — revisão de 20/10/2026 (skills de Emil Kowalski; ver o cabeçalho da view)
   ========================================================================== */

/* A frase que diz o que o número é. `--text-secondary` (7,6:1 sobre branco), nunca o
   `--text-muted` que o índice de lançamentos usava (2,56:1). E com limite de leitura: frase
   de 180 caracteres numa linha de 1.090 px não se lê. */
.mercado-nota {
  margin: 0;
  /* Mais largo que o `--measure` (68ch): no `--fs-sm` ele dá ~530 px, e num card de 1.090 px
     o parágrafo virava uma coluna estreita de quatro linhas. 90ch ainda é linha de leitura. */
  max-width: 90ch;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.mercado-nota strong { color: var(--text-primary); }
.chart-box .mercado-nota { margin-bottom: var(--space-3); }

/* O card dos bairros: o título à esquerda e as abas (o `.segmentado`) à direita. */
.bairros-cabecalho { gap: var(--space-3); }
.bairros-cabecalho .card-title { margin: 0; }
.segmento-conta {
  margin-left: 0.3rem;
  font-size: var(--fs-xs);
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}
/* O `.segmento` nasceu como link; aqui é `<button role="tab">`, e o botão traz fundo e
   fonte do navegador. */
button.segmento {
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
}
button.segmento.segmento-ativo { background-color: var(--accent-fill); }

.bairros-painel-titulo {
  margin: 0;
  padding: var(--space-4) var(--space-4) var(--space-1);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--text-primary);
}
.bairros-painel > .mercado-nota { padding: 0 var(--space-4) var(--space-3); box-sizing: content-box; }
.mercado-disclosure { margin: 0 var(--space-4) var(--space-3); }
.bairros-fora-lista { margin: 0; padding-left: 1.1rem; }
/* Sem JS os dois painéis aparecem: o 2º ganha uma borda que o separa do 1º. */
.bairros-painel + .bairros-painel { border-top: 1px solid var(--border-color); }
.bairros-painel[hidden] + .bairros-painel { border-top: 0; }

/* O cabeçalho que ordena. O botão herda a aparência do `<th>` (caixa alta, cor, peso) e
   só ganha a seta; a coluna ordenada fica na cor do texto, as outras na secundária. */
.ordena {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: inherit;
  cursor: pointer;
  transition: transform var(--dur-press) var(--ease-out), color var(--dur-press) ease;
}
.text-end > .ordena { justify-content: flex-end; }
.ordena:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.ordena-desc, .ordena-asc { display: none; }
.ordena-neutro { opacity: 0.45; }
th[aria-sort] .ordena { color: var(--text-primary); }
th[aria-sort] .ordena-neutro { display: none; }
th[aria-sort="descending"] .ordena-desc,
th[aria-sort="ascending"] .ordena-asc { display: inline; }
@media (hover: hover) and (pointer: fine) {
  .ordena:hover { color: var(--text-primary); }
  .ordena:hover .ordena-neutro { opacity: 0.8; }
}

.col-posicao { width: 3rem; }

/* Unidade e valor não quebram no meio ("R$ 4.871/" numa linha e "m²" na outra, medido em 390 px). */
.sem-caixa-alta { text-transform: none; }
.sem-quebra { white-space: nowrap; }
.tabela-ordenavel td.text-end { white-space: nowrap; }
.tabela-ordenavel td.celula-quebra { white-space: normal; }

/* No celular a tabela ganha espaço tirando a coluna "#" (a ordem já é a da própria lista) e o
   recuo lateral das células. O que ainda não couber rola dentro da moldura, nunca a página. */
@media (max-width: 719px) {
  .tabela-ordenavel .col-posicao { display: none; }
  .tabela-ordenavel th, .tabela-ordenavel td { padding-left: var(--space-2); padding-right: var(--space-2); }
  .tabela-ordenavel th:first-child, .tabela-ordenavel td:first-child { padding-left: var(--space-4); }
}

/* "Mostrar todos os 76": a ação no pé da tabela, larga como ela, discreta. */
.tabela-rodape { border-top: 1px solid var(--border-color); }
.tabela-rodape:has(> .tabela-mais[hidden]) { display: none; }
.tabela-mais {
  display: block;
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent-on-light);
  cursor: pointer;
  /* Não afunda no `:active`: é uma faixa da largura da tabela, e 3 % dela são 30 px. */
  transition: background-color var(--dur-press) ease;
}
.tabela-mais:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
@media (hover: hover) and (pointer: fine) {
  .tabela-mais:hover { background-color: var(--bg-subtle); }
}

/* ==========================================================================
   Plano 58 — login por código no e-mail
   Nada de componente novo: `.card`, `.search-input`, `.search-btn` e `.alert`.
   ========================================================================== */

.login-card {
  max-width: 26rem;
  margin: var(--space-6) auto;
  padding: var(--space-5);
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.login-rotulo {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-secondary);
}

.login-alerta { margin: 0; }

.login-email { overflow-wrap: anywhere; }

/* 🚨 16 px no mínimo: o Safari do iOS dá zoom na página ao focar campo com menos
   que isso, e o `.search-input` usa `--fs-base` (15 px). Login é a tela mais usada
   no celular, porque o código chega no e-mail do telefone. `--fs-md` (17 px) é o
   menor degrau da escala que passa. ⚠️ A busca da home tem o mesmo problema
   (achado fora do escopo do Plano 58, não corrigido aqui). */
.search-input.campo-login {
  font-size: var(--fs-md);
}

/* Um campo só para o código, não 6 caixinhas: as caixinhas precisam de JS para
   foco, colar e apagar, e atrapalham o preenchimento automático do celular. */
.search-input.campo-codigo {
  font-size: var(--fs-xl);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.3em;
  text-align: center;
  padding-inline: var(--space-2);
}

.login-ajuda { margin-top: var(--space-4); }

.login-reenvio-form { margin-top: var(--space-2); }

.login-reenvio {
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: none;
  padding: var(--space-1) 0;
  cursor: pointer;
  transition: color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .login-reenvio:hover { color: var(--accent-hover); text-decoration: underline; }
}

.login-reenvio:disabled { opacity: 0.7; cursor: not-allowed; }

/* O `button_to` do "Sair" vem num <form>; ele não pode quebrar a linha do cabeçalho. */
.sair-form { display: inline-flex; }

/* Plano 61 — o "Sair" é só ícone em toda largura (rótulo em `.visually-hidden`),
   então o padding lateral do `.tema-btn`, pensado para ícone + texto, sobra: ele
   fica com o mesmo padding nos quatro lados. Recupera ~10 px da linha do cabeçalho. */
.sair-form .tema-btn { padding-inline: 0.35rem; }

/* --- Resposta ao clique ----------------------------------------------------
   Todo pressionável afunda 3 % enquanto o dedo/botão está em cima: é a
   confirmação de que a interface ouviu, antes de qualquer resposta do servidor.
   A transição de `transform` está declarada em cada componente, com o
   `--ease-out`. */
.tema-btn {
  transition: transform var(--dur-press) var(--ease-out);
}

:where(.search-btn, .filter-submit, .chip-btn, .segmento, .nav-badge, .tema-btn, .nav-link, .login-reenvio, .ordena,
        .salvar-btn, .salvo-remover, .salvo-desfazer, .botao-perigo, .botao-perigo-cheio, .botao-secundario):active {
  transform: scale(0.97);
}

/* "Link copiado!": o rótulo não troca de uma vez. O JS liga `.trocando`, troca o
   conteúdo no meio da transição e desliga — o desfoque funde os dois estados num
   só, em vez de mostrar um texto sumindo e outro aparecendo. */
.nav-badge {
  transition: transform var(--dur-press) var(--ease-out), filter 120ms ease, opacity 120ms ease;
}

.nav-badge.trocando {
  filter: blur(2px);
  opacity: 0.6;
}

/* Movimento reduzido: sai todo deslocamento (o afundar, a entrada do disclosure);
   fica a opacidade, que ajuda a ler a mudança sem mover nada. */
@keyframes revela-sem-movimento {
  from { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  :where(.search-btn, .filter-submit, .chip-btn, .segmento, .nav-badge, .tema-btn, .nav-link, .login-reenvio,
          .salvar-btn, .salvo-remover, .salvo-desfazer, .botao-perigo, .botao-perigo-cheio, .botao-secundario):active {
    transform: none;
  }

  .disclosure[data-aberto-pelo-usuario][open] > :not(summary) {
    animation-name: revela-sem-movimento;
  }
}

/* ==========================================================================
   "Isso está errado?": o formulário da denúncia, dentro do disclosure
   da linha da `/candidatos` e no rodapé dos cards da `/endereco`.
   ========================================================================== */
.denuncia {
  margin-top: 0.75rem;
}

.denuncia-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: flex-start;
}

.denuncia-campo {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  width: 100%;
}

/* O motivo e o comentário com a mesma largura: o `.filter-select` para em 16 rem, e a
   borda direita dos dois campos ficava desalinhada. */
.denuncia-campo .filter-select {
  width: 100%;
  max-width: 32rem;
}

.denuncia-comentario {
  width: 100%;
  max-width: 32rem;
  padding: 0.3rem 0.5rem;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background-color: var(--bg-surface);
  color: var(--text-primary);
}

.denuncia-erro {
  color: var(--text-primary);
  font-weight: 600;
}

/* No rodapé do card da `/endereco` o próprio `.card-footer` já dá o respiro. */
.card-denuncia .denuncia {
  margin-top: 0;
}

/* O formulário e o "Anotado. Obrigado." chegam pela rede, DEPOIS de o bloco já ter
   aberto (o frame é `loading="lazy"`): entram como o resto do disclosure (4 px e
   opacidade, 180 ms, `--ease-out`), em vez de trocar de uma vez. Transição com
   `@starting-style`, não keyframe: o elemento é novo no DOM a cada resposta, e a
   transição é interrompível se outra resposta chegar no meio. */
.denuncia-form,
.denuncia-anotada {
  transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
}

@starting-style {
  .denuncia-form,
  .denuncia-anotada {
    opacity: 0;
    transform: translateY(-4px);
  }
}

/* "Carregando o formulário…" só aparece se a rede demorar: antes de 300 ms o
   formulário já chegou, e um texto que pisca 50 ms e some é ruído. Depois disso,
   entra em opacidade. */
@keyframes aparece-se-demorar {
  from { opacity: 0; }
}

.denuncia-carregando {
  animation: aparece-se-demorar 150ms ease 300ms backwards;
}

/* O "Enviar" desabilitado pelo Turbo durante o envio (`data-turbo-submits-with`):
   o rótulo vira "Enviando…" e o botão perde o realce, o que impede a 2ª denúncia
   no clique duplo e diz que o pedido saiu. */
.filter-submit:disabled {
  opacity: 0.7;
  cursor: progress;
}

/* Movimento reduzido: sai o deslocamento, fica a opacidade (mesma regra do
   disclosure, no fim deste arquivo). */
@media (prefers-reduced-motion: reduce) {
  @starting-style {
    .denuncia-form,
    .denuncia-anotada {
      transform: none;
    }
  }
}

/* ==========================================================================
   Plano 73.1: "Salvar". O desenho está no `## Desenho` do plano, escrito com
   as skills de Emil Kowalski antes do código: o badge leve na linha da
   `/candidatos`, o botão principal no cabeçalho da ficha da `/endereco`, o
   número no cabeçalho do app e a tela `/salvos`.
   ========================================================================== */
.salvar-form {
  display: inline-flex;
  margin: 0;
}

/* A base dos dois: o estado é o `aria-pressed`. Na `/candidatos` é um badge (repete 100 vezes);
   salvo, fica tingido de acento, com o visto: estado, não sucesso (o verde é "bom"). */
.salvar-btn {
  position: relative;
  cursor: pointer;
  font-family: var(--font-sans);
  transition: background-color var(--dur-press) ease, color var(--dur-press) ease,
              border-color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}

.salvar-btn[aria-pressed="true"] {
  background-color: var(--accent-light);
  border-color: var(--accent);
  color: var(--accent-on-light);
}

@media (hover: hover) and (pointer: fine) {
  .salvar-btn:hover {
    border-color: var(--accent);
    color: var(--accent-on-light);
  }
}

/* Os dois rótulos, cada um com o seu ícone, ocupam a mesma célula: a largura é a do maior, e a
   troca não empurra os vizinhos. A troca é o crossfade de 120 ms do "Link copiado!" (Plano 57); o
   `visibility` tira o rótulo escondido também do leitor de tela, que lê o que está à vista e o
   `aria-pressed`. Dezenas de vezes por sessão: nada além disso, nem toast. */
.salvar-rotulos {
  display: inline-grid;
}

.salvar-rotulos > span {
  grid-area: 1 / 1;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  transition: opacity 120ms ease, visibility 120ms;
}

.salvar-btn[aria-pressed="false"] .salvar-rotulo-sim,
.salvar-btn[aria-pressed="true"] .salvar-rotulo-nao {
  opacity: 0;
  visibility: hidden;
}

/* O "Salvar endereço": a ação principal da ficha da `/endereco`, à direita de tudo no cabeçalho do
   card. As medidas do `.filter-submit` (15 px, ~36 px de altura) e o fundo de acento do "Consultar":
   o botão principal do app, não um terceiro desenho. O selo vizinho tem 22 px e é neutro; com outra
   altura e outra cor, ele não se lê como mais um selo. Salvo, fica tingido com a borda de acento: o
   estado continua à vista, e o botão deixa de chamar o clique. `text-transform` e `letter-spacing`
   voltam ao normal porque a faixa de título da razão do prédio é caixa alta. */
.salvar-destaque {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.45rem 1.1rem;
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.25;
  text-transform: none;
  letter-spacing: normal;
  white-space: nowrap;
  color: var(--accent-text);
  background-color: var(--accent-fill);
  border: 1px solid var(--accent-fill);
  border-radius: var(--radius);
}

@media (hover: hover) and (pointer: fine) {
  .salvar-destaque:hover {
    color: var(--accent-text);
    background-color: var(--accent-fill-hover);
    border-color: var(--accent-fill-hover);
  }
  .salvar-destaque[aria-pressed="true"]:hover {
    color: var(--accent-on-light);
    background-color: var(--accent-light);
    border-color: var(--accent-hover);
  }
}

/* No cabeçalho do card, o grupo que leva o botão ocupa o resto da linha, e o botão encosta na borda
   direita (`margin-left: auto`): os selos e links ficam juntos à esquerda, e a ação principal fica
   sempre no mesmo lugar, caiba o grupo na linha do título ou desça para a seguinte (medido: a 1280 px
   ele desce na ficha da unidade). Por `:has()`, sem mexer no markup dos grupos que já existem. */
.card-header > div:has(> .salvar-form .salvar-destaque) {
  flex: 1 1 auto;
}

.salvar-form:has(> .salvar-destaque) {
  margin-left: auto;
}

/* Abaixo do cabeçalho do card: os anúncios que a pessoa salvou deste número (`## Desenho`). Contexto, não
   ação: texto pequeno e secundário, só o ícone e o link em acento, sem fundo de alerta, para não competir
   com o "Salvar endereço" logo acima. */
.salvos-ligados {
  margin: 0;
  padding: var(--space-2) 1.25rem;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border-color);
}

.salvos-ligados .ti {
  color: var(--accent-on-light);
}

.salvos-ligados a {
  color: var(--accent-on-light);
  font-weight: 600;
  white-space: nowrap;
}

/* A faixa de título da razão do prédio vira linha flex só quando leva o botão (`:has()`, sem mexer
   no markup da faixa): o texto à esquerda, o botão à direita. */
.secao-cabecalho:has(> .salvar-form) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}

/* Abaixo de 720 px o cabeçalho do card quebra, e o "Salvar endereço" fecha o cabeçalho na largura
   toda, com 44 px: um alvo grande ao alcance do polegar (`mobile-native`). O badge da `/candidatos`
   fica com a altura da linha, e um pseudo-elemento invisível estende o alvo dele para 44 px. */
@media (max-width: 719px) {
  .salvar-btn { min-height: 0; }
  .card-header > div:has(> .salvar-form .salvar-destaque) { width: 100%; }
  .salvar-form:has(> .salvar-destaque) { flex-basis: 100%; }
  .salvar-destaque {
    width: 100%;
    min-height: 44px;
    justify-content: center;
  }
}

@media (max-width: 719px), (pointer: coarse) {
  .salvar-btn:not(.salvar-destaque)::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 44px;
    transform: translateY(-50%);
  }
}

/* `mobile-native`, só nos controles do Salvar: o toque não espera o duplo toque (o clique sai na
   hora), o rótulo não é selecionado ao segurar o dedo, e o realce cinza do navegador não briga com
   o `:active`. O resto da tela ainda não tem essa base. */
.salvar-btn,
.salvos-contador,
.salvo-remover,
.salvo-desfazer {
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.salvar-erro {
  display: block;
  flex-basis: 100%;
  color: var(--warning);
  font-weight: 600;
}

/* "Salvos" no menu, com o número numa etiqueta pequena ao lado do texto. */
.salvos-contador {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
}

.salvos-numero {
  display: inline-block;
  min-width: 1.25rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background-color: var(--bg-subtle);
  color: var(--text-primary);
  font-size: var(--fs-xs);
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}

.nav-link-ativo .salvos-numero {
  background-color: var(--bg-surface);
  color: var(--accent-on-light);
}

/* O número que acabou de mudar por um clique em Salvar (só a resposta do servidor o marca; a página
   que carrega não anima) entra de cima, 3 px, em 160 ms: liga o clique ao lugar para onde o item
   foi. Dezenas de vezes por sessão na `/candidatos`: quase imperceptível de propósito. */
.salvos-contador-novo .salvos-numero {
  transition: opacity 160ms var(--ease-out), transform 160ms var(--ease-out);
}

@starting-style {
  .salvos-contador-novo .salvos-numero {
    opacity: 0;
    transform: translateY(-3px);
  }
}

/* A tela `/salvos` usa a largura toda, como as outras telas (decisão do autor, 27/10/2026); era 52 rem, e a
   página destoava do cabeçalho. O que não pode correr a página inteira é o texto: ele para no `--measure`,
   logo abaixo. */
.salvos-lista {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-3);
}

/* Dentro do card, só o topo (título à esquerda, ações à direita) acompanha a largura; o resto para na linha de
   leitura, porque a 1.140 px a frase da razão corria o card inteiro. */
.salvo [data-remover-target="corpo"] > :not(.salvo-topo),
.salvos-vazio > p {
  max-width: var(--measure);
}

.salvos-lista > .card,
.salvos-vazio {
  margin-top: 0;
}

.salvo,
.salvos-vazio {
  padding: var(--space-4) 1.25rem;
}

/* O endereço à esquerda e o "Remover" à direita, num lugar fixo e fora do caminho da leitura. */
.salvo-topo {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

/* Um endereço longo quebra em linhas; o "Remover" não encolhe. */
.salvo-remover-form {
  flex-shrink: 0;
  margin: 0;
}

.salvo-titulo {
  font-size: var(--fs-md);
  font-weight: 600;
  margin: 0;
}

.salvo-meta {
  margin: var(--space-1) 0 0;
}

.salvo-numeros {
  margin: var(--space-3) 0 var(--space-1);
}

.salvo .candidato-frase {
  margin: 0;
}

.salvo .rastro {
  display: block;
  margin: var(--space-1) 0 0;
}

.salvo-links {
  margin-top: var(--space-3);
}

/* A nota mora num disclosure: sem nota, "Adicionar nota" fechado; com nota, aberto. */
.salvo-nota-bloco {
  margin-top: var(--space-3);
}

/* Em toque, o "Adicionar nota" tinha 24 px de altura (3ª validação): 44 px, como todo alvo de toque. */
@media (pointer: coarse) {
  .salvo-nota-bloco > summary { min-height: 44px; }
}

.salvo-nota {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
}

.salvo-nota-campo {
  width: 100%;
  padding: 0.3rem 0.5rem;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background-color: var(--bg-surface);
  color: var(--text-primary);
  resize: vertical;
}

.salvo-nota-acoes {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 1.25rem;
}

/* A linha de estado da nota fica sempre (`## Desenho`): "Salva automaticamente.", "Salvando…",
   "Salvo às HH:MM". Só a falha muda de cor. */
.salvo-nota-estado.salvo-nota-erro {
  color: var(--warning);
  font-weight: 600;
}

/* Remover e Desfazer são ações de texto: não competem com o conteúdo do item. */
.salvo-remover,
.salvo-desfazer {
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-secondary);
  background: none;
  border: none;
  padding: var(--space-1) 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
  transition: color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}

.salvo-desfazer {
  color: var(--accent-on-light);
}

@media (hover: hover) and (pointer: fine) {
  .salvo-remover:hover { color: var(--text-primary); }
  .salvo-desfazer:hover { text-decoration: underline; }
}

.salvo-removido {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  /* O aviso troca o item inteiro: entra em opacidade, com a duração e a curva do disclosure
     (180 ms, `--ease-out`). Só opacidade: a altura que o item perde não anima (custaria layout a
     cada quadro), e o movimento reduzido não tem o que tirar. */
  transition: opacity 180ms var(--ease-out);
}

@starting-style {
  .salvo-removido { opacity: 0; }
}

.salvos-vazio-titulo {
  font-size: var(--fs-md);
  font-weight: 600;
  margin: 0 0 var(--space-2);
}

.salvos-vazio-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* Movimento reduzido: o número novo do cabeçalho entra só em opacidade. */
@media (prefers-reduced-motion: reduce) {
  @starting-style {
    .salvos-contador-novo .salvos-numero { transform: none; }
  }
}

/* ==========================================================================
   Plano 73.2: o que mudou desde que salvou. O desenho está no `## Desenho` do plano. A mudança vem
   antes dos números do retrato; sem mudança, uma linha quieta. Nenhuma cor diz se a mudança é boa:
   o tingido de acento é "mudou", o de aviso é "o item perdeu o chão".
   ========================================================================== */
.salvo-mudancas {
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background-color: var(--accent-light);
  border-radius: var(--radius-sm);
  color: var(--accent-on-light);
}

.salvo-mudancas-titulo {
  margin: 0 0 var(--space-1);
  font-weight: 600;
}

.salvo-mudancas ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.salvo-mudancas li + li {
  margin-top: var(--space-1);
}

.salvo-mudanca-aviso {
  color: var(--warning);
  font-weight: 600;
}

.salvo-mudanca-link {
  margin-left: var(--space-1);
  font-weight: 600;
  text-decoration: underline;
  /* O alvo de toque de 44 px sem mudar a altura da linha de texto. */
  display: inline-block;
  padding-block: 0.7rem;
  margin-block: -0.7rem;
}

/* O bloco inteiro vira aviso quando o item perdeu o chão. */
.salvo-fora .salvo-mudancas {
  background-color: var(--warning-bg);
  border: 1px solid var(--warning-border);
}

.salvo-fora .salvo-numeros,
.salvo-fora .candidato-frase,
.salvo-fora .rastro {
  opacity: 0.75;
}

.salvo-sem-mudanca {
  margin: var(--space-3) 0 0;
}

/* O aviso do topo (Resumo ligado, Não foi possível salvar) não encosta na linha de ferramentas (D9 da 73.3). */
.salvos-cabeca-aviso {
  margin-bottom: var(--space-4);
}

/* D9 da 73.3: uma linha entre o subtítulo e a lista. À esquerda a contagem e a dica de comparar; à direita o
   resumo por e-mail, reduzido ao estado. */
.salvos-ferramentas {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-3);
}

.salvos-ferramentas-lista {
  display: grid;
  gap: 0.15rem;
}

.salvos-ferramentas .resultados-contagem,
.salvos-ferramentas .salvos-comparar-dica {
  margin: 0;
}

/* O painel flutua a partir daqui. */
.salvos-resumo {
  position: relative;
}

/* O gatilho tem peso de ação de texto, como "Comparar" e "Remover": é configuração, não conteúdo. */
/* A margem negativa alinha o texto (não o fundo do foco) com a borda da coluna: à direita no desktop, à
   esquerda no telefone, onde o gatilho desce para baixo da dica. */
.disclosure > summary.salvos-resumo-gatilho {
  gap: 0.4rem;
  margin-left: 0;
  margin-right: -0.4rem;
  font-weight: 600;
  white-space: nowrap;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}

/* O disclosure comum ganha margem embaixo do <summary> ao abrir; aqui ela empurraria a lista. */
.salvos-resumo[open] > .salvos-resumo-gatilho {
  margin-bottom: 0;
}

.salvos-resumo-gatilho strong {
  color: var(--text-primary);
}

.salvos-resumo-seta {
  transition: transform 180ms var(--ease-out);
}

.salvos-resumo[open] .salvos-resumo-seta {
  transform: rotate(180deg);
}

@media (pointer: coarse) {
  .disclosure > summary.salvos-resumo-gatilho { min-height: 44px; }
}

/* Flutua sobre a lista, ancorado embaixo do gatilho e alinhado à direita dele: abrir e fechar não move a
   página. */
.salvos-resumo-painel {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: 0;
  z-index: 10;
  width: min(20rem, calc(100vw - 2 * var(--space-4)));
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  transform-origin: top right;
}

/* Cresce a partir do gatilho, só quando aberto pelo clique (`data-aberto-pelo-usuario`); fecha sem animação.
   Troca a entrada genérica do disclosure pela de popover (`emil-design-eng`: 125 a 200 ms, da origem). */
.salvos-resumo[data-aberto-pelo-usuario][open] > .salvos-resumo-painel {
  animation: salvos-resumo-entra 150ms var(--ease-out);
}

@keyframes salvos-resumo-entra {
  from {
    opacity: 0;
    transform: scale(0.97);
  }
}

@media (prefers-reduced-motion: reduce) {
  .salvos-resumo[data-aberto-pelo-usuario][open] > .salvos-resumo-painel {
    animation-name: revela-sem-movimento;
  }

  .salvos-resumo-seta { transition: none; }
}

.salvos-resumo-texto {
  margin: 0;
  font-size: var(--fs-sm);
  overflow-wrap: anywhere;
}

.salvos-resumo-form {
  margin: 0;
}

/* Desligar não é o que se quer oferecer: botão secundário. */
.salvos-resumo-desligar {
  color: var(--text-primary);
  background-color: var(--bg-surface);
  border-color: var(--border-color);
}

@media (hover: hover) and (pointer: fine) {
  .salvos-resumo-desligar:hover { background-color: var(--bg-subtle); }
}

@media (pointer: coarse) {
  .salvos-resumo-form .filter-submit { min-height: 44px; }
}

:where(.salvos-resumo-gatilho):active {
  transform: scale(0.97);
}

@media (prefers-reduced-motion: reduce) {
  :where(.salvos-resumo-gatilho):active { transform: none; }
}

/* No telefone o gatilho desce para baixo da dica, à esquerda, e o painel abre alinhado à esquerda. */
@media (max-width: 479px) {
  .disclosure > summary.salvos-resumo-gatilho {
    margin-left: -0.4rem;
    margin-right: 0;
  }

  .salvos-resumo-painel {
    right: auto;
    left: 0;
    transform-origin: top left;
  }

  .salvos-resumo-form .filter-submit { width: 100%; }
}

/* ==========================================================================
   Plano 73.3: comparar salvos. O desenho está no `## Desenho` do plano.
   ========================================================================== */

/* As ações do item no topo do card: "Comparar" e "Remover", do mesmo peso. */
.salvo-acoes {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-shrink: 0;
}

/* No telefone, as duas ações ao lado do título o espremiam em três linhas (medido a 375 px): elas descem
   para baixo da meta, e o endereço fica com a largura toda. */
@media (max-width: 479px) {
  .salvo-topo:has(.salvo-comparar) {
    flex-direction: column;
    gap: var(--space-2);
  }
}

.salvos-comparar-dica {
  margin: var(--space-1) 0 0;
}

.salvo-comparar {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}

.salvo-comparar input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.salvo-comparar:has(input:checked) {
  color: var(--text-primary);
}

@media (hover: hover) and (pointer: fine) {
  .salvo-comparar:hover { color: var(--text-primary); }
}

/* Alvo de 44 px em toque sem crescer o topo do card. */
@media (pointer: coarse) {
  .salvo-comparar {
    padding-block: 0.7rem;
    margin-block: -0.7rem;
  }
}

/* A barra presa à borda de baixo enquanto a lista rola; no fim da lista, ela repousa no lugar. */
.comparar-barra {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-4);
  padding: var(--space-3) 1.25rem calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: 0 -4px 12px -6px rgba(0, 0, 0, 0.18);
  /* Entra de baixo, pela própria altura, quando a primeira caixa é marcada; some sem animação, porque
     quem desmarcou tudo já decidiu (`## Desenho`). */
  transition: transform 200ms var(--ease-out), opacity 200ms var(--ease-out);
}

.comparar-barra[hidden] {
  display: none;
}

@starting-style {
  .comparar-barra {
    transform: translateY(100%);
    opacity: 0;
  }
}

.comparar-barra-texto {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.comparar-botao {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.comparar-botao[aria-disabled="true"] {
  background-color: var(--bg-subtle);
  color: var(--text-muted);
  cursor: not-allowed;
}

@media (hover: hover) and (pointer: fine) {
  .comparar-botao[aria-disabled="true"]:hover { background-color: var(--bg-subtle); }
}

@media (pointer: coarse) {
  .comparar-botao { min-height: 44px; }
}

@media (max-width: 719px) {
  .comparar-botao { width: 100%; justify-content: center; }
}

:where(.salvo-comparar, .comparar-botao[aria-disabled="false"]):active {
  transform: scale(0.97);
}

@media (prefers-reduced-motion: reduce) {
  :where(.salvo-comparar, .comparar-botao[aria-disabled="false"]):active { transform: none; }

  @starting-style {
    .comparar-barra { transform: none; }
  }
}

/* O aluguel: rótulo, campo com a unidade dos dois lados, e o rendimento abaixo. */
.salvo-aluguel-corpo {
  margin-top: var(--space-2);
}

.aluguel-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
}

.aluguel-rotulo {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-secondary);
}

.aluguel-linha {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.aluguel-unidade {
  white-space: nowrap;
}

.aluguel-campo {
  flex: 0 1 8.5rem;
  min-width: 0;
  padding: 0.3rem 0.5rem;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
  text-align: right;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background-color: var(--bg-surface);
  color: var(--text-primary);
}

.aluguel-rendimento {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-sm);
}

.aluguel-rendimento .rastro {
  display: block;
  margin-top: var(--space-1);
}

/* O preço e a área informados num endereço salvo (Plano 73.5): dois campos empilhados, a unidade no rótulo (na
   coluna de 154 px do celular ela não cabe ao lado do campo). O campo é o do aluguel. */
.pedido-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
}

.pedido-campos {
  display: grid;
  gap: var(--space-2);
}

.pedido-campo-bloco {
  display: grid;
  gap: 0.15rem;
}

.pedido .pedido-campo {
  flex: none;
  width: 100%;
  max-width: 11rem;
}

.pedido-m2 {
  margin-top: var(--space-2);
}

.pedido-m2:empty {
  display: none;
}

.pedido-m2 .rastro {
  display: block;
  margin-top: var(--space-1);
}

/* A comparação (D10 e D11 da 73.3): um HTML só, duas apresentações. A partir de 720 px, uma coluna-card por item,
   com o rótulo dentro de cada célula e as linhas alinhadas entre os cards. Abaixo de 720 px, uma tabela: cada rótulo
   é uma linha de largura inteira, uma vez só, e com 3 ou 4 itens a grade rola na horizontal, de par em par. As linhas
   são `display: contents`: as células entram direto na grade, e as linhas ficam só para os papéis de tabela. */
.comparar-limite {
  color: var(--warning);
  font-weight: 600;
  margin: 0 0 var(--space-3);
}

.comparar-arraste {
  display: none;
  margin: 0 0 var(--space-2);
}

.cmp {
  position: relative;
}

:is(.cmp-grade, .cmp-fixo-trilho)[data-n="2"] { --cmp-n: 2; }
:is(.cmp-grade, .cmp-fixo-trilho)[data-n="3"] { --cmp-n: 3; }
:is(.cmp-grade, .cmp-fixo-trilho)[data-n="4"] { --cmp-n: 4; }

.cmp-grade,
.cmp-fixo-trilho {
  display: grid;
}

.cmp-linha {
  display: contents;
}

/* A célula é o bloco de referência do que ela posiciona: o rótulo `.visually-hidden` do aluguel é absoluto e,
   sem isto, escapava da rolagem da grade e alargava a página no celular (501 px a 375, medido na 73.5). */
.cmp-celula {
  position: relative;
}

/* A coluna de cada item é fixa: a célula do endereço que ocupa três linhas no desktop não desloca as outras. */
.cmp-c1 { grid-column: 1; }
.cmp-c2 { grid-column: 2; }
.cmp-c3 { grid-column: 3; }
.cmp-c4 { grid-column: 4; }

.cmp p {
  margin: 0;
}

.cmp .rastro {
  display: block;
  margin-top: var(--space-1);
}

.comparar-bloco-titulo {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}

.cmp-cabeca {
  display: grid;
  align-content: start;
  gap: 0.15rem;
}

/* Quebra entre palavras; no celular, também com a hifenização do português ("CONSELHEIR / O" quebrava no meio). */
.cmp-titulo {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: 600;
  overflow-wrap: break-word;
}

.comparar-posicao {
  font-variant-numeric: tabular-nums;
}

.cmp-numero {
  display: inline-flex;
  justify-content: center;
  min-width: 1.25rem;
  padding-inline: 0.25rem;
  border-radius: 999px;
  background-color: var(--bg-subtle);
  color: var(--text-primary);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.cmp-cabeca .salvo-mudanca-aviso {
  margin-top: var(--space-1);
}

.comparar-destaque {
  font-size: var(--fs-md);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.cmp-fora:not(.cmp-cabeca) {
  opacity: 0.75;
}

.cmp-informado .aluguel-campo {
  background-color: var(--bg-surface);
}

/* A forma longa no desktop, a curta abaixo de 720 px (D10). As duas ficam no HTML. */
.cmp-curta {
  display: none;
}

/* A faixa presa, só no celular: altura zero e grudada no topo (`--cmp-topo`, medido pelo
   `comparar_tabela_controller.js`: a altura do cabeçalho do site quando ele é fixo, senão 0); a faixa de verdade é
   absoluta dentro dela e só aparece quando o cabeçalho dos itens sai da tela. Só opacidade, 120 ms: aparece toda
   vez que se rola, então quase não se nota. */
.cmp-fixo {
  display: none;
  position: sticky;
  top: var(--cmp-topo, 0px);
  z-index: 20;
  height: 0;
}

.cmp-fixo-faixa {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  overflow: hidden;
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow-sm);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 120ms ease, visibility 0s linear 120ms;
}

.cmp-fixo-faixa.cmp-fixo-visivel {
  opacity: 1;
  visibility: visible;
  transition: opacity 120ms ease;
}

.cmp-fixo-item {
  min-width: 0;
  padding: var(--space-2);
  border-left: 1px solid var(--border-subtle);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A partir de 720 px: o formato de antes do D10 (devolvido no D11, a pedido do autor). Uma coluna-card por item, de
   até 24 rem, com o rótulo e o título de cada bloco dentro do card; as células de um item, empilhadas, formam o card. */
@media (min-width: 720px) {
  .cmp-grade {
    grid-template-columns: repeat(var(--cmp-n), minmax(0, 24rem));
    column-gap: var(--space-4);
  }

  .cmp-rotulo,
  .cmp-faixa-titulo {
    display: none;
  }

  .cmp-celula,
  .cmp-faixa-coluna {
    min-width: 0;
    padding: var(--space-3) 1.25rem 0;
    background-color: var(--bg-surface);
    border-inline: 1px solid var(--border-color);
  }

  .cmp-cabeca {
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-color);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }

  .cmp-faixa-coluna {
    padding-top: var(--space-4);
  }

  .cmp-links {
    padding-block: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-color);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  }

  /* Com o `.cmp` na frente: o `.cmp p` zera a margem e pesa mais que a classe sozinha. */
  .cmp .cmp-rotulo-celula {
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    margin-bottom: 0.15rem;
  }

  /* O endereço salvo não tem preço, área nem quartos de anúncio: uma célula só, nas três linhas. */
  .cmp-endereco-preco {
    grid-row: span 3;
  }

  .cmp-endereco-resto {
    display: none;
  }

  /* "Informado por você": uma caixa tracejada dentro de cada card. O título, o rótulo e o campo são três peças
     empilhadas que fecham a caixa juntas: tracejado em cima e dos lados na primeira, dos lados no meio, dos lados e
     embaixo na última. `--text-muted`, não `--border-color`: no tema claro a borda comum sobre o `--bg-subtle`
     quase sumia. */
  .cmp-faixa-informado .cmp-faixa-coluna {
    padding-bottom: 0;
  }

  .cmp-faixa-informado .cmp-faixa-coluna > p,
  .cmp-informado .cmp-rotulo-celula,
  .cmp-informado [data-comparar-informado] {
    padding-inline: var(--space-3);
    background-color: var(--bg-subtle);
    border-inline: 1px dashed var(--text-muted);
  }

  .cmp-faixa-informado .cmp-faixa-coluna > p {
    padding-block: var(--space-3) var(--space-2);
    border-top: 1px dashed var(--text-muted);
    border-radius: var(--radius) var(--radius) 0 0;
  }

  /* O miolo estica até a altura da linha: com três linhas de alturas diferentes entre as colunas, a caixa se rompia
     entre elas (medido na 73.5). */
  .cmp-informado .cmp-celula {
    display: flex;
    flex-direction: column;
    padding-top: 0;
  }

  .cmp-informado [data-comparar-informado] {
    flex: 1;
  }

  .cmp .cmp-informado .cmp-rotulo-celula {
    margin-bottom: 0;
    padding-bottom: 0.15rem;
    font-weight: 600;
  }

  /* Três linhas no bloco desde a 73.5: só a última fecha a caixa. */
  .cmp-informado [data-comparar-informado] {
    padding-bottom: var(--space-3);
  }

  .cmp-informado-fim [data-comparar-informado] {
    border-bottom: 1px dashed var(--text-muted);
    border-radius: 0 0 var(--radius) var(--radius);
  }
}

/* Abaixo de 720 px: a tabela. Cada rótulo é uma linha de largura inteira, presa à esquerda enquanto os valores
   rolam; com 3 ou 4 itens a grade rola de par em par (só as colunas ímpares encaixam), e a terceira coluna espia à
   direita. A rolagem é a nativa (`mobile-native`). */
@media (max-width: 719px) {
  .comparar-arraste { display: block; }

  .cmp-fixo { display: block; }

  .cmp-grade,
  .cmp-fixo-trilho {
    grid-template-columns: repeat(var(--cmp-n), var(--cmp-col, calc(50% - 0.75rem)));
  }

  :is(.cmp-grade, .cmp-fixo-trilho)[data-n="2"] { --cmp-col: 50%; }

  .cmp-grade {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    background-color: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
  }

  .cmp-impar {
    scroll-snap-align: start;
  }

  .cmp-rotulo-vazio,
  .cmp-fixo-vazio,
  .cmp-rotulo-celula,
  .cmp-faixa-coluna {
    display: none;
  }

  .cmp-rotulo,
  .cmp-faixa-titulo {
    grid-column: 1 / -1;
    min-width: 0;
    padding: var(--space-3) var(--space-2) 0;
    border-top: 1px solid var(--border-subtle);
  }

  .cmp-rotulo {
    font-size: var(--fs-xs);
    color: var(--text-secondary);
  }

  .cmp-faixa-titulo {
    padding-bottom: var(--space-1);
  }

  /* A faixa do título do bloco não leva fio em cima: ela abre o bloco, não separa uma linha. */
  .cmp-faixa > .cmp-faixa-titulo,
  .cmp-faixa + .cmp-linha > .cmp-rotulo {
    border-top-color: transparent;
  }

  .cmp-rotulo-texto,
  .cmp-faixa-titulo .comparar-bloco-titulo {
    position: sticky;
    left: var(--space-2);
    display: inline-block;
    max-width: calc(100vw - 4rem);
  }

  .cmp-faixa-titulo .comparar-bloco-titulo {
    display: inline-flex;
  }

  /* O rótulo de largura inteira já separa as linhas; os valores só se separam entre si. O respiro lateral é de
     8 px: a 360 px os 12 px de cada lado comiam a largura que a tabela ganhou. */
  .cmp-celula {
    min-width: 0;
    padding: var(--space-2) var(--space-2) var(--space-3);
    border-left: 1px solid var(--border-subtle);
  }

  .cmp-cabeca {
    padding-top: var(--space-3);
  }

  .cmp-linha > .cmp-celula:nth-child(2) {
    border-left: 0;
  }

  /* O que a pessoa informou: fundo sutil e tracejado em cima e embaixo da faixa. */
  .cmp-faixa-informado > .cmp-faixa-titulo,
  .cmp-informado > * {
    background-color: var(--bg-subtle);
  }

  .cmp-faixa-informado > .cmp-faixa-titulo {
    border-top: 1px dashed var(--text-muted);
  }

  .cmp-informado-fim > .cmp-celula {
    border-bottom: 1px dashed var(--text-muted);
  }

  .cmp-titulo {
    font-size: var(--fs-sm);
    hyphens: auto;
  }

  .comparar-destaque {
    font-size: var(--fs-base);
  }

  /* Com o `.cmp` na frente: o `.cmp .rastro` (que é `display: block`) também é forma longa e pesa mais. */
  .cmp .cmp-longa {
    display: none;
  }

  .cmp .cmp-curta {
    display: revert;
  }

  .cmp .aluguel-campo {
    flex: 1 1 0;
  }

  .cmp-fixo-vazio + .cmp-fixo-item {
    border-left: 0;
  }
}

/* ==========================================================================
   Administração (`/admin`). O layout é o do produto; a linha "Administração" do topo é o que diz onde se está,
   porque o cabeçalho do site não tem link para cá (decisão do autor). Sem cor de destaque: a tela inteira é dela.
   ========================================================================== */

.admin-topo { margin-bottom: var(--space-2); }

.admin-lugar {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0 0 var(--space-1);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

/* O papel da equipe: o par de cor do item ativo da navegação (AA nos dois temas, Plano 57). Verde é "bom" e
   amarelo é "atenção"; o papel não é nenhum dos dois. */
.badge-papel {
  background-color: var(--accent-light);
  color: var(--accent-on-light);
  border: 1px solid var(--border-color);
}

.pessoa-email .badge { margin-left: var(--space-2); vertical-align: 1px; }
/* "nunca entrou" quebrava em duas linhas na coluna do estado (medido a 1280 px). */
.pessoas-tabela .badge { white-space: nowrap; }
.pessoa-rotulo { display: none; }
/* "(há menos de 1 hora)" é uma expressão: quebrada no meio, a última palavra fica sozinha na linha de baixo
   (medido a 375 px). */
.pessoa-relativo { color: var(--text-secondary); white-space: nowrap; }
/* Na tabela, o "(há …)" vai para a linha de baixo da data: na mesma linha a coluna tinha 285 px, e a tabela passava da
   moldura até 1070 px de janela (validação da 75.2, R1). */
@media (min-width: 720px) {
  .pessoas-tabela .pessoa-relativo { display: block; }
}
/* Entre 720 e 1100 px os cabeçalhos ("Sessões ativas") e as datas ("17/10/2026 08:22") quebram linha, com a hora
   embaixo: sem isso a tabela ainda passava da moldura até ~830 px (medido). O texto do botão de ordenar quebra dentro
   dele, com a seta ao lado. Os números (sessões, salvos) continuam numa linha. */
@media (min-width: 720px) and (max-width: 1099px) {
  .pessoas-tabela th,
  .pessoas-tabela .ordena,
  .pessoas-tabela td[data-convidado-em],
  .pessoas-tabela td[data-ultimo-login] { white-space: normal; }
  .pessoas-tabela .ordena { text-align: right; }
}

/* Abaixo de 720 px cada pessoa é um bloco: o e-mail numa linha (e-mail não tem onde quebrar, daí o `anywhere`) e
   os números numa linha de metadados que quebra, com o rótulo de cada um. É a mesma tabela, para o texto não se
   repetir. `[hidden]` precisa da regra própria: o `display` de autor ganha da folha do navegador. */
@media (max-width: 719px) {
  .pessoas-tabela thead { display: none; }
  .pessoas-tabela,
  .pessoas-tabela tbody { display: block; }
  .pessoas-tabela tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-subtle);
    background-color: var(--bg-surface);
  }
  .pessoas-tabela tbody tr:nth-child(even) { background-color: var(--bg-stripe); }
  .pessoas-tabela tbody tr:last-child { border-bottom: 0; }
  .pessoas-tabela tbody tr[hidden] { display: none; }
  /* A listra passa da célula para a linha, que é o bloco. O seletor empata com o do `.table-striped` e vem depois. */
  table.table.pessoas-tabela tbody tr td {
    display: inline;
    padding: 0;
    border: 0;
    background-color: transparent;
    text-align: left;
    white-space: normal;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
  }
  table.table.pessoas-tabela tbody tr td.pessoa-email {
    flex: 1 0 100%;
    min-width: 0;
    overflow-wrap: anywhere;
    font-size: var(--fs-md);
    color: var(--text-primary);
  }
  .pessoa-rotulo { display: inline; }
}

/* O título e a ação primária da página na mesma linha; abaixo de 720 px o botão desce para a largura inteira, com
   44 px de altura (alvo de toque). */
.admin-topo-linha {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
}
.admin-topo-linha .page-title { overflow-wrap: anywhere; min-width: 0; }

.admin-acao-principal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  text-decoration: none;
}

@media (max-width: 719px) {
  .admin-acao-principal { width: 100%; min-height: 44px; }
}

/* O resultado de uma ação fica à vista até a próxima navegação, nunca num aviso que some. Sem animação: aparece com
   a página. */
.admin-confirmacao,
.admin-recusa,
.admin-aviso-neutro {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid;
  border-radius: var(--radius);
  font-size: var(--fs-base);
  max-width: var(--measure);
}
.admin-confirmacao { background-color: var(--success-bg); border-color: var(--success-border); color: var(--text-primary); }
.admin-confirmacao > p { margin: 0; }
.admin-recusa { background-color: var(--danger-bg); border-color: var(--danger-border); color: var(--danger); }
/* "Já tinha acesso" não é erro: fundo sutil, sem cor. */
.admin-aviso-neutro { background-color: var(--bg-subtle); border-color: var(--border-color); color: var(--text-secondary); }

/* O convite: uma página com um campo, a caixa do e-mail e um botão. */
.convite-form,
.apagar-form {
  display: grid;
  gap: var(--space-2);
  max-width: 28rem;
  margin-top: var(--space-4);
}
.convite-erro { margin: 0; font-size: var(--fs-sm); color: var(--danger); }
.convite-caixa {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-size: var(--fs-base);
  cursor: pointer;
}
.convite-caixa input { margin-top: 0.2rem; width: 1.1rem; height: 1.1rem; flex: none; }
.convite-caixa-frase { display: block; margin-top: var(--space-1); font-size: var(--fs-sm); color: var(--text-secondary); }
.convite-enviar { justify-self: start; margin-top: var(--space-2); }

@media (max-width: 719px) {
  .convite-enviar { justify-self: stretch; min-height: 44px; }
}

/* A frase para mandar por mensagem: texto selecionável, com o "Copiar" ao lado. */
.convite-frase {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-top: var(--space-3);
  padding: var(--space-3);
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
}
.convite-frase-texto { flex: 1; min-width: 0; overflow-wrap: anywhere; user-select: text; }
.convite-frase-texto p { margin: 0 0 var(--space-2); }
.convite-frase-texto p:last-child { margin-bottom: 0; }
.convite-copiar { flex: none; cursor: pointer; }
.admin-confirmacao .convite-frase-nota { margin-top: var(--space-2); font-size: var(--fs-sm); color: var(--text-secondary); }

@media (max-width: 479px) {
  .convite-frase { flex-direction: column; }
}

/* A ficha da pessoa. */
.ficha-selos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
}
.ficha-voltar { margin-left: auto; font-size: var(--fs-sm); color: var(--text-secondary); }
.badge-perigo {
  background-color: var(--danger-bg);
  color: var(--danger);
  border: 1px solid var(--danger-border);
}

/* Os fatos em uma coluna no celular e em duas a partir de 720 px; o rótulo em cima do valor. */
.ficha-fatos {
  display: grid;
  gap: var(--space-3) var(--space-6);
  margin: var(--space-5) 0 0;
}
.ficha-fatos > div { min-width: 0; }
.ficha-fatos dt { font-size: var(--fs-sm); color: var(--text-secondary); }
.ficha-fatos dd { margin: var(--space-1) 0 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
@media (min-width: 720px) {
  .ficha-fatos { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.ficha-atencao { color: var(--warning); }
.ficha-acao-form { display: inline; }
.ficha-acao-pequena { margin: var(--space-1) 0 0 var(--space-1); cursor: pointer; }

.ficha-secao { margin-top: var(--space-6); }
.ficha-titulo { margin: 0 0 var(--space-2); font-size: var(--fs-md); font-weight: 600; }
.ficha-nota { margin: 0 0 var(--space-2); font-size: var(--fs-sm); color: var(--text-secondary); max-width: var(--measure); }
.ficha-ver-todas { font-size: var(--fs-sm); }

.ficha-auditoria { list-style: none; margin: 0; padding: 0; }
.ficha-auditoria li {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.ficha-auditoria-quando { color: var(--text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; }

.ficha-salvos { list-style: none; margin: 0; padding: 0; }
.ficha-salvo { padding: var(--space-3) 0; border-bottom: 1px solid var(--border-subtle); }
.ficha-salvo p { margin: 0 0 var(--space-1); }
.ficha-salvo-titulo { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-weight: 600; overflow-wrap: anywhere; }
.ficha-salvo-meta { font-size: var(--fs-sm); color: var(--text-secondary); }
/* A nota inteira, com as quebras de linha dela: é o conteúdo que se veio ver. */
.ficha-salvo-nota { white-space: pre-wrap; overflow-wrap: anywhere; max-width: var(--measure); }

/* As sessões: uma linha por sessão, sem tabela (são poucas). O ícone da classe do aparelho, o texto, e o "Encerrar" à
   direita, em contorno neutro: encerrar só obriga a pessoa a pedir um código novo, não é perigo. */
.sessoes-topo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
}
.sessoes-topo .ficha-titulo { margin: 0; }
.sessoes-frase { margin: 0 0 var(--space-2); color: var(--text-secondary); }
.sessoes-lista { list-style: none; margin: 0; padding: 0; }
.sessao {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.sessao-icone { font-size: var(--fs-lg); color: var(--text-secondary); line-height: 1.4; }
.sessao-corpo { min-width: 0; }
.sessao-corpo p { margin: 0; }
.sessao-aparelho {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-base);
  font-weight: 600;
}
.sessao-datas { margin-top: var(--space-1); font-size: var(--fs-sm); color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.sessao-rede { margin-top: var(--space-1); }
.sessao-rede-corpo { margin: 0; }
.sessao-rede-corpo dt { color: var(--text-primary); }
.sessao-rede-corpo dd { margin: 0 0 var(--space-2); }
.sessao-rede-corpo dd:last-child { margin-bottom: 0; }
/* O user agent tem mais de 100 caracteres e nenhum espaço onde quebrar perto do fim: quebra em qualquer ponto. */
.sessao-rede-valor { font-family: var(--font-mono); overflow-wrap: anywhere; user-select: text; }
.sessao-encerrar-form { display: inline; margin: 0; }
.sessao-encerrada { display: block; color: var(--text-secondary); font-size: var(--fs-sm); }
.sessao-erro { display: block; margin-top: var(--space-1); font-size: var(--fs-sm); color: var(--danger); }
/* A frase entra depois do formulário (`encerra_controller.js`): na grade da linha ela ocuparia uma célula nova e
   alargaria a coluna do ícone; vai embaixo do texto. No título, numa linha própria. */
.sessao > .sessao-erro { grid-column: 2 / -1; margin-top: 0; }
.sessoes-topo > .sessao-erro { flex-basis: 100%; margin-top: 0; }
.botao-pequeno { padding: 0.3rem 0.8rem; font-size: var(--fs-sm); }
/* No celular, a coluna do botão espremia o texto e o user agent em ~190 px (medido a 375 px): o botão desce para
   baixo do texto, alinhado com ele. */
@media (max-width: 479px) {
  .sessao { grid-template-columns: auto minmax(0, 1fr); }
  .sessao > .sessao-encerrar-form { grid-column: 2; }
}

/* A entrada: os códigos das últimas 24 horas, uma linha cada, e o que fazer quando o código não chega. */
.entrada-lista { list-style: none; margin: 0; padding: 0; }
.entrada-lista li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--fs-sm);
}
.entrada-quando { color: var(--text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; }
.entrada-erro { font-family: var(--font-mono); overflow-wrap: anywhere; }
.entrada-saidas { margin-top: var(--space-4); max-width: var(--measure); }
.entrada-terminal { margin-top: var(--space-1); }
.entrada-saidas-titulo { margin: 0 0 var(--space-2); font-size: var(--fs-base); font-weight: 600; }
.entrada-passos { margin: 0 0 var(--space-3); padding-left: 1.25rem; }
.entrada-passos li { margin-bottom: var(--space-3); }
.entrada-email { overflow-wrap: anywhere; }
.entrada-emergencia-form { display: block; margin: var(--space-2) 0 0; }
/* O comando é texto selecionável e quebra em qualquer ponto: a 375 px ele não cabe numa linha. */
.terminal-linha {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}
.terminal-comando {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  color: var(--text-primary);
  overflow-wrap: anywhere;
  user-select: text;
}
.terminal-copiar { flex: none; cursor: pointer; }
@media (max-width: 479px) {
  .terminal-linha { flex-direction: column; }
}

/* A página do código de emergência: o código grande, em mono (os dígitos não se confundem), selecionável inteiro
   com um toque. */
.emergencia {
  margin-top: var(--space-4);
  padding: var(--space-4);
  max-width: var(--measure);
  background-color: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
}
.emergencia p { margin: 0 0 var(--space-2); }
.emergencia p:last-child { margin-bottom: 0; }
.emergencia-codigo {
  font-family: var(--font-mono);
  font-size: var(--fs-xl);
  font-weight: 600;
  letter-spacing: 0.15em;
  font-variant-numeric: tabular-nums;
  user-select: all;
}
.emergencia-validade { font-weight: 600; }
.emergencia-frase, .emergencia-nota { font-size: var(--fs-sm); color: var(--text-secondary); }
.emergencia-voltar { margin-top: var(--space-4); }

/* A operação (Plano 75.6): quatro cartões numa coluna, na mesma ordem em toda largura. O título de cada um é a frase de
   estado; o atraso pinta só o ícone e a palavra que atrasou (`--warning`), nunca o cartão. */
.operacao { display: grid; gap: var(--space-4); margin-top: var(--space-4); }
.operacao > .operacao-cartao { margin-top: 0; padding: var(--space-4) var(--space-5); }
@media (max-width: 479px) {
  .operacao > .operacao-cartao { padding: var(--space-4); }
}
.operacao-estado { margin: 0; font-size: var(--fs-md); font-weight: 600; line-height: 1.4; overflow-wrap: anywhere; }
.operacao-estado + .ficha-nota { margin-top: var(--space-2); }
.operacao-cartao .ficha-fatos { margin-top: var(--space-3); }
.operacao-aviso-icone { color: var(--warning); margin-right: var(--space-1); }
.operacao-atraso { color: var(--warning); font-weight: 600; }
.operacao-apoio { color: var(--text-secondary); font-size: var(--fs-sm); }
/* A mensagem e o comando do último alarme, cada um na sua linha, sob a data e o gatilho. */
.operacao-alarme-detalhe { display: block; margin-top: var(--space-1); }
.operacao-subtitulo { margin: var(--space-5) 0 var(--space-2); font-size: var(--fs-base); font-weight: 600; }
/* Nome de job, de erro, de tarefa, de tabela e de arquivo: vocabulário interno, em mono, quebrando em qualquer ponto
   (a 375 px `ExpiredLoginsCleanupJob` não cabe ao lado do erro). */
.operacao-codigo, .operacao-cartao code { font-family: var(--font-mono); overflow-wrap: anywhere; }
.operacao-tabela { white-space: nowrap; }

.operacao-acao { margin-top: var(--space-5); padding-top: var(--space-4); border-top: 1px solid var(--border-subtle); }
.operacao-acao .ficha-nota { margin: 0; }
.operacao-acao-frase { margin: 0 0 var(--space-3); max-width: var(--measure); }
.operacao-resumo-form { margin: 0; }
/* Desabilitado com o porquê escrito em cima: não é espera (o `cursor: progress` do `.filter-submit`), é "não agora". */
.operacao-resumo-form .filter-submit:disabled { cursor: not-allowed; opacity: 0.5; }
.operacao-na-fila { display: flex; align-items: baseline; gap: var(--space-2); margin: 0; }
@media (max-width: 719px) {
  .operacao-resumo-form .filter-submit { width: 100%; min-height: 44px; }
}

.operacao-recorrentes, .operacao-falhas { list-style: none; margin: 0; padding: 0; }
.operacao-recorrentes li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
  font-variant-numeric: tabular-nums;
}

/* Uma falha por linha: o texto e, à direita, as ações (como o "Encerrar" das sessões). No celular as ações descem. */
.operacao-falha {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.operacao-falha-corpo { min-width: 0; }
.operacao-falha-corpo > p { margin: 0 0 var(--space-1); }
.operacao-falha-titulo { display: flex; flex-wrap: wrap; gap: 0 var(--space-2); font-weight: 600; }
/* A primeira linha do erro, cortada com reticências; a inteira mora no disclosure, no DOM, nunca em `title`. */
.operacao-falha-linha {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}
.operacao-falha .disclosure { margin-top: var(--space-1); }
.operacao-pre {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: text;
}
.operacao-falha-acoes { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: flex-end; }
/* A frase da falha do pedido entra depois do formulário (`encerra_controller.js`), embaixo dos botões. */
.operacao-falha-acoes > .sessao-erro { flex-basis: 100%; margin-top: 0; text-align: end; }
.operacao-falha-feita { padding: var(--space-3) 0; border-bottom: 1px solid var(--border-subtle); color: var(--text-secondary); font-size: var(--fs-sm); }
@media (max-width: 479px) {
  .operacao-falha { grid-template-columns: minmax(0, 1fr); }
  .operacao-falha-acoes { justify-content: flex-start; }
}

/* A zona de ações: por último e separada por uma borda, como a área perigosa das configurações. */
.zona-acoes {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-color);
}
.acao { margin-bottom: var(--space-5); }
.acao form { display: inline; }
.acao-frase { margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--text-secondary); max-width: var(--measure); }
.acao-estado { margin: 0 0 var(--space-2); }
.acao-apagar { color: var(--danger); font-weight: 600; }

/* Botão perigoso é contorno; só o botão final da confirmação de apagar é cheio. O texto do cheio é a cor da
   superfície: branco sobre o vermelho do claro (6,47:1) e o fundo escuro sobre o vermelho claro do escuro (6,41:1),
   as mesmas contas do `--danger` sobre `--bg-surface`. */
.botao-perigo,
.botao-perigo-cheio,
.botao-secundario {
  padding: 0.45rem 1.1rem;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: 600;
  border: 1px solid;
  border-radius: var(--radius);
  transition: background-color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}
.botao-perigo { color: var(--danger); background-color: var(--bg-surface); border-color: var(--danger); }
.botao-perigo-cheio { color: var(--bg-surface); background-color: var(--danger); border-color: var(--danger); }
.botao-perigo-cheio:disabled { opacity: 0.45; cursor: not-allowed; }
.botao-secundario { color: var(--text-primary); background-color: var(--bg-surface); border-color: var(--border-color); }

@media (hover: hover) and (pointer: fine) {
  .botao-perigo:hover { background-color: var(--danger-bg); }
  .botao-secundario:hover { background-color: var(--bg-hover); }
  .pessoa-link:hover { text-decoration-color: currentColor; }
}

@media (pointer: coarse) {
  .botao-perigo, .botao-perigo-cheio, .botao-secundario, .acao .filter-submit { min-height: 44px; }
}

/* O e-mail da lista leva à ficha: sublinhado discreto, que acende no hover. */
.pessoa-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--border-color);
  text-underline-offset: 3px;
}

.apagar-listas { display: grid; gap: var(--space-4); margin-top: var(--space-4); max-width: 48rem; }
@media (min-width: 720px) {
  .apagar-listas { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.apagar-lista { margin: 0; padding-left: 1.25rem; }
.apagar-lista li { margin-bottom: var(--space-1); }
.apagar-acoes { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); margin-top: var(--space-2); }

@media (max-width: 719px) {
  .apagar-acoes .botao-perigo-cheio { width: 100%; }
}

/* --------------------------------------------------------------------------
   O uso (`/admin/uso` e a linha do tempo de uma pessoa). Contagem absoluta, sem barra nem cor de "subiu/desceu": com
   duas pessoas, seria veredito sobre ruído. Nada anima: trocar de janela e de filtro é navegação.
   -------------------------------------------------------------------------- */

/* A linha "Administração" e a sub-navegação, que são lugares (nunca o `.segmentado`). */
.admin-topo-nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-3); margin-bottom: var(--space-1); }
.admin-topo-nav .admin-lugar { margin: 0; }
.admin-nav { display: flex; gap: var(--space-1); }
.admin-nav .nav-link { flex: none; }

/* Os chips de desfecho e o "mais antigas" levam a `#consultas`: a partir de 720 px o cabeçalho do site é fixo e cobria o
   título da seção (R1 da validação da 75.4). */
@media (min-width: 720px) {
  #por-pessoa, #consultas, #semanas { scroll-margin-top: 5rem; }
}

.uso-controles { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); margin: var(--space-3) 0; }
.uso-equipe { display: flex; align-items: center; gap: var(--space-2); margin: 0; }
.uso-equipe-caixa { margin: 0; }
.uso-equipe button { cursor: pointer; }
/* O `display` do `.nav-badge` ganha da folha do navegador: o "Aplicar" escondido pelo JS precisa da regra própria. */
.uso-equipe button[hidden] { display: none; }

/* Zero é dado: aparece, mais apagado. `--text-secondary`, não `--text-muted`, que no claro fica abaixo do AA. */
.uso-zero { color: var(--text-secondary); }
.uso-pessoas { color: var(--text-secondary); }
.uso-tabela td, .uso-tabela th { white-space: nowrap; }

/* A coluna da pessoa (e a do nome da tela) fica presa quando a tabela rola de lado. O fundo opaco é o da linha, com a
   listra: sem ele, o número passaria por baixo do e-mail. */
.uso-tabela .uso-coluna-pessoa { position: sticky; left: 0; z-index: 1; box-shadow: inset -1px 0 0 var(--border-color); }
.uso-tabela thead .uso-coluna-pessoa { background-color: var(--bg-subtle); }
.uso-tabela tbody tr:nth-child(odd) th.uso-coluna-pessoa { background-color: var(--bg-surface); }
.uso-tabela tbody tr:nth-child(even) th.uso-coluna-pessoa { background-color: var(--bg-stripe); }
.uso-tabela td.uso-coluna-pessoa { white-space: normal; min-width: 11rem; max-width: 16rem; overflow-wrap: anywhere; }
.uso-tabela td.uso-coluna-pessoa .badge { margin-left: var(--space-1); }
.uso-semanas tbody th { text-align: left; font-weight: 600; color: var(--text-primary); text-transform: none;
                        letter-spacing: 0; font-size: var(--fs-sm); }
.uso-semanas tbody tr:last-child th { border-bottom: none; }
.uso-eventos { display: block; font-size: var(--fs-xs); font-weight: 400; color: var(--text-secondary); }

.uso-nunca { margin: var(--space-3) 0 0; font-size: var(--fs-sm); color: var(--text-secondary); max-width: var(--measure); }
.uso-nunca strong { color: var(--text-primary); }

/* Os chips de desfecho são filtros (links): o escolhido tem o par do item ativo da navegação, e os que não chegaram a
   um imóvel, a borda de atenção. */
.uso-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0 0 var(--space-3); }
.uso-chip { display: inline-flex; align-items: baseline; gap: var(--space-1); text-decoration: none; color: var(--text-primary); }
.uso-chip strong { font-variant-numeric: tabular-nums; }
.uso-chip-atencao { border-color: var(--warning-border); }
.uso-chip-ativo { background-color: var(--accent-light); border-color: var(--accent); color: var(--accent-on-light); }
.uso-chip:focus { text-decoration: none; }

.uso-consultas { list-style: none; margin: 0; padding: 0; }
.uso-consultas li { padding: var(--space-2) 0; border-bottom: 1px solid var(--border-subtle); }
.uso-consultas p { margin: 0; }
.uso-consulta-texto { font-size: var(--fs-base); overflow-wrap: anywhere; }
.uso-consulta-meta { margin-top: var(--space-1); font-size: var(--fs-sm); color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.uso-sem-imovel { margin-left: var(--space-1); }

/* A linha do tempo: um bloco por sessão, a hora em mono para alinhar sem tabela, e o detalhe quebrando à vontade. */
.ficha-voltar-esquerda { font-size: var(--fs-sm); color: var(--text-secondary); }
/* O "Copiar como texto" e o "Ver como texto" na mesma linha; aberto, o texto ocupa a largura toda embaixo. */
.uso-texto { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-4); }
.uso-texto .convite-copiar { cursor: pointer; }
.uso-texto details[open] { flex-basis: 100%; }
.uso-dados > summary { font-size: var(--fs-xs); color: var(--text-secondary); }
.uso-texto-pre,
.uso-dados-pre {
  margin: var(--space-2) 0 0;
  padding: var(--space-3);
  max-height: 24rem;
  overflow: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: text;
  background-color: var(--bg-subtle);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  color: var(--text-primary);
}
.uso-blocos { list-style: none; margin: 0; padding: 0; }
.uso-bloco { margin-bottom: var(--space-5); }
.uso-bloco-titulo { margin: 0 0 var(--space-2); font-size: var(--fs-sm); font-weight: 600; color: var(--text-secondary); }
.uso-eventos-lista { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border-subtle); }
.uso-evento { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3); padding: var(--space-2) 0;
              border-bottom: 1px solid var(--border-subtle); }
.uso-hora { font-size: var(--fs-sm); color: var(--text-secondary); line-height: 1.6; }
.uso-evento-corpo p { margin: 0; }
.uso-evento-linha { overflow-wrap: anywhere; }
.uso-evento-detalhe { margin-left: var(--space-2); color: var(--text-secondary); }
.uso-evento-caminho { margin-top: var(--space-1); font-size: var(--fs-xs); overflow-wrap: anywhere; }
.uso-caminho { color: var(--text-secondary); }
.uso-dados { margin-top: var(--space-1); }
.ficha-ver-uso { margin-left: var(--space-2); font-size: var(--fs-sm); }

@media (pointer: coarse) {
  .uso-controles .segmento,
  .uso-chip,
  .uso-texto .convite-copiar,
  .uso-equipe button,
  .admin-nav .nav-link { min-height: 44px; display: inline-flex; align-items: center; }
}

/* --------------------------------------------------------------------------
   As denúncias (`/admin/denuncias`, Plano 75.5). Um cartão por denúncia (o retrato e o comentário têm tamanho livre),
   com a mesma anatomia em toda largura. Nada se move: o que o Turbo Stream troca entra só com a opacidade.
   -------------------------------------------------------------------------- */
.denuncias-controles { display: grid; gap: var(--space-2); margin: var(--space-3) 0 var(--space-2); }
.denuncias-controles .segmentado { justify-self: start; }
.denuncias-controles .uso-chips { margin: 0; }

.denuncia { margin-top: var(--space-3); padding: var(--space-4); }
.denuncia-topo { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); }
.denuncia-motivo { margin: 0; font-size: var(--fs-md); font-weight: 600; }
.denuncia-meta { margin: 0; font-size: var(--fs-sm); color: var(--text-secondary); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
/* A voz de quem denunciou vale mais que o retrato: o texto primário, inteiro, com as quebras de linha dela. */
.denuncia-comentario {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-color);
  color: var(--text-primary);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-width: var(--measure);
}
.denuncia-retrato { display: grid; gap: var(--space-1); margin: var(--space-3) 0 0; font-size: var(--fs-sm); }
.denuncia-retrato > div { display: grid; grid-template-columns: 9rem minmax(0, 1fr); gap: var(--space-2); }
.denuncia-retrato dt { color: var(--text-secondary); }
.denuncia-retrato dd { margin: 0; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
/* No celular, a coluna do rótulo espremia o valor: o rótulo vai em cima. */
@media (max-width: 479px) {
  .denuncia-retrato > div { grid-template-columns: minmax(0, 1fr); gap: 0; }
}
.denuncia-links { margin-top: var(--space-3); }
/* "O anúncio hoje" é frase, embaixo dos links. */
.denuncia-hoje { margin: var(--space-2) 0 0; font-size: var(--fs-sm); max-width: var(--measure); }
/* A cor do aviso da `/salvos` (`.salvo-mudanca-aviso`), sem o negrito: o rótulo já é negrito. */
.denuncia-hoje-aviso { color: var(--warning); }
.denuncia-nota { margin: var(--space-1) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }

.denuncia-julgar { margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--border-subtle); max-width: 40rem; }
.denuncia-pergunta { margin: 0 0 var(--space-2); font-weight: 600; }
.denuncia-porque-rotulo { display: block; margin-bottom: var(--space-1); font-size: var(--fs-sm); color: var(--text-secondary); }
/* Uma classe só com `font-size`: o bloco de `pointer: coarse` dos campos ganha dela e sobe para 17 px no toque. */
.denuncia-porque-campo {
  width: 100%;
  padding: 0.3rem 0.5rem;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background-color: var(--bg-surface);
  color: var(--text-primary);
  resize: vertical;
}
.denuncia-porque-campo[aria-invalid="true"] { border-color: var(--danger); }
.denuncia-obrigatorio { margin: var(--space-1) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }
.denuncia-erro { margin: var(--space-1) 0 0; font-size: var(--fs-sm); color: var(--danger); }
.denuncia-botoes { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
/* No celular os três dividem a linha. Com o respiro lateral do botão, "Sim, fica" quebrava em duas linhas a 375 px
   (98 px de largura para ~105 de conteúdo, medido): menos respiro e nenhuma quebra. */
.denuncia-botoes .botao-secundario { white-space: nowrap; }
@media (max-width: 479px) {
  .denuncia-botoes .botao-secundario { flex: 1 1 0; padding-inline: var(--space-2); }
}

/* Julgada: o julgamento e o porquê no topo, numa faixa neutra (nenhum julgamento é "bom"). */
.denuncia-julgamento { margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background-color: var(--bg-subtle); }
.denuncia-julgamento p { margin: 0; }
.denuncia-julgamento .denuncia-porque-texto { margin-top: var(--space-1); white-space: pre-wrap; overflow-wrap: anywhere; max-width: var(--measure); }
.denuncia-julgar-de-novo { margin-top: var(--space-3); }
.denuncia-julgar-de-novo .denuncia-julgar { margin-top: var(--space-2); padding-top: 0; border-top: 0; }

/* A linha recolhida que fica no lugar do cartão julgado, até a próxima navegação. */
.denuncia-julgada {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--border-color);
  border-radius: var(--radius-lg);
  color: var(--text-secondary);
}
.denuncia-julgada p { margin: 0; }
.denuncia-julgada-qual { overflow-wrap: anywhere; }
.denuncia-desfazer-form { margin: 0; }

/* O que o Turbo Stream acabou de trocar entra com o crossfade de 120 ms (o do "Link copiado!"); o que sai sai na hora. Só
   opacidade, que o movimento reduzido mantém. */
.denuncia-chegou { transition: opacity 120ms ease; }
@starting-style {
  .denuncia-chegou { opacity: 0; }
}
