/* ============================================================
   CPIDM Admin — design system
   Paleta da Camping Pousada Ilha do Mel (campingpousadailhadomel.com):
   azul costeiro #5EA1D2, azul profundo #2E5E84, verde #50CC60,
   laranja areia #F18B23 (accent), fonte Source Sans Pro.
   ============================================================ */

/* ⚠️ [hidden] TEM de vencer o CSS de autor. A regra `[hidden]{display:none}` do
   user-agent perde por ORIGEM — não por especificidade — para QUALQUER `display`
   de autor: `.btn{inline-flex}`, `.chat{flex}`, `.gravador-botoes{flex}`. O
   atributo continua no DOM, o navegador não reclama, e o elemento aparece.

   Sem esta linha, tanto `{% if %}hidden{% endif %}` no template quanto
   `elemento.hidden = true` no JS renderizam SEM EFEITO, em silêncio. Custou 4
   defeitos em `contato_detalhe.html`, dois deles de botão mentindo sobre o
   estado da conversa ("Encerrar atendimento" em conversa já encerrada, com o
   `conversa-polling.js` inerte) — todos mudos sob 1506 testes verdes. O 5º caso
   chegou a ser remendado classe a classe (`.nav-badge[hidden]`), que é o
   sintoma de quem tropeça no defeito sem ver a classe inteira.
   Medição e cenários: docs/2026-08-29-gap-padroes-fala.md §14.
   O irmão FALA mediu o MESMO defeito no MESMO componente (o gravador) e chegou
   à mesma linha em 09/08/2026.

   Todo `hidden` do painel é semântico (show/hide por JS ou por condição do
   template); não existe `hidden` decorativo que dependa de ser anulado.
   NÃO remover, e não criar regra que dispute com ela.

   Fica ANTES do `:root` de propósito: o canário da barra lateral proíbe
   `!important` no trecho das variáveis até o `.content`, e a posição não muda
   nada aqui (o `!important` vence em qualquer ordem).
   Canários: tests/test_paleta_marca.py. */
[hidden] { display: none !important; }

:root {
  /* ---- A MARCA. Só estas cinco são "a marca"; tudo abaixo é PAPEL. ----
     O resto do arquivo NÃO consome estas variáveis: consome os papéis. É o que
     permite trocar a paleta (tema por propriedade, modo escuro) mexendo aqui e
     em nenhum outro lugar — enquanto o CSS consumir `--brand-*` direto, toda
     troca vira busca-e-substitui, que é onde o design degrada em silêncio. */
  --brand-primary: #5EA1D2;        /* azul céu */
  --brand-primary-hover: #4A8CC0;
  --brand-deep: #2E5E84;           /* azul costeiro profundo */
  --brand-deep-2: #234A6B;
  --brand-accent: #F18B23;         /* laranja areia */

  /* ---- PAPÉIS. O CSS consome ESTES. ----
     O papel é a pergunta que a tela faz ("qual é a cor de ação?"), não a
     resposta ("azul céu"). Quem escreve tela escolhe papel; a marca já decidiu
     a cor. */
  --acento: var(--brand-primary);          /* ação: CTA, links, foco, item ativo */
  --acento-hover: var(--brand-primary-hover);
  --escuro: var(--brand-deep);             /* barra lateral, títulos, texto sobre tom claro */
  --escuro-2: var(--brand-deep-2);         /* fim do gradiente do login */
  --destaque: var(--brand-accent);         /* ASSINATURA (logo), nunca ação */

  /* ---- Os mesmos papéis em CANAIS, para dentro de `rgba()`. ----
     `rgba()` não aceita `var()` no lugar dos canais, então sombra, anel de foco
     e backdrop viviam com a cor da marca escrita à mão — `rgba(46, 94, 132,
     .08)` é `--brand-deep`, e `rgba(94,161,210,.18)` é `--brand-primary`. É a
     forma que ESCAPA de qualquer renomeação de token: trocar a paleta deixaria
     seis sombras e um anel de foco na cor antiga, sem nada acusar. (O canário
     `test_nenhuma_cor_da_marca_do_tivolli_sobreviveu` já vigia a marca ANTIGA
     em rgb() justamente porque foi assim que ela sobreviveu no irmão.)

     ⚠️ Duplicam o hex do papel de propósito, e o canário
     `test_todo_canal_bate_com_o_hex_do_papel` resolve a cadeia
     papel → marca → hex → canais e reprova a divergência. Sem ele isto seria só
     mais uma cópia — com ele, é uma cópia que não consegue envelhecer.

     A alternativa moderna (`rgb(from var(--escuro) r g b / .08)`) faria o mesmo
     sem duplicar, mas é sintaxe de cor relativa: um navegador que não a entenda
     descarta a declaração INTEIRA e a sombra some, que é exatamente o modo de
     falha silenciosa que esta camada existe para não ter. */
  --acento-canais: 94, 161, 210;           /* = --brand-primary #5EA1D2 */
  --escuro-canais: 46, 94, 132;            /* = --brand-deep    #2E5E84 */
  --escuro-2-canais: 35, 74, 107;          /* = --brand-deep-2  #234A6B */

  --bg: #FDFDFD;
  --bg-soft: #FAFAFA;
  --surface: #FFFFFF;
  --surface-alt: #EBEBEB;
  --border: #E2E5E9;
  --linha-hover: #FCFDFF;          /* realce da linha da tabela sob o ponteiro */
  /* Véu do modo gaveta da barra lateral. Escurece MAIS que o backdrop dos modais
     (`--escuro-2-canais` a .42) porque ali o conteúdo continua sendo a tela, e
     aqui ele fica atrás de um menu que ocupa a lateral inteira. */
  --veu: rgba(20, 34, 48, .45);
  --text: #2B3035;
  --text-mute: #6B7682;
  /* Escala SEMÂNTICA. Fica FORA da marca de propósito: verde = deu certo,
     vermelho = quebrou. Forçá-las na cor da propriedade apaga a informação. */
  --danger: #DB011D;
  --danger-hover: #B00018;         /* o vermelho um degrau abaixo, p/ :hover de botão */
  --success: #3F9E4D;              /* verde pousada (#50CC60) escurecido p/ contraste em texto */
  --warning: #C97A00;
  /* ⚠️ O âmbar tem DOIS textos, e não é redundância. `--warning` faz 3,11:1
     sobre `--tom-ambar-bg` — serve a pastilha e à borda, não a um parágrafo.
     `.banner-leitura` é parágrafo (14px, e diz à pessoa que a tela inteira está
     em leitura), então usa este, medido em 5,81:1 sobre o mesmo fundo. Trocá-lo
     por `--warning` "para ficar tokenizado" derrubaria o contraste pela metade
     — token não é desculpa para piorar a tela. Medido em 29/08/2026. */
  --warning-texto: #7A5C00;

  /* ---- TONS: pares (fundo + texto) das ~20 pastilhas. ----
     Antes daqui cada badge trazia o par cravado em hex, e QUATRO pares já
     apareciam em dois ou três lugares — `.cotacao_enviada` e `.col-tipo` com o
     mesmo roxo, `.novo`/`.col-pms`/`.ativo` com o mesmo azul. Repetição por
     cópia não é só ruído: a badge seguinte não tem de onde reusar, então
     inventa, e a paleta abre em leque sem ninguém decidir nada. Nove decisões
     no lugar de vinte declarações.

     Os dois lados do par ficam na MESMA linha, para serem lidos de uma vez —
     meio par (fundo novo, texto da paleta anterior) é pastilha ilegível, e é
     o que o canário `test_todo_par_de_tom_tem_fundo_e_texto` proíbe.

     ⚠️ A razão de contraste anotada foi MEDIDA nesta paleta, em 29/08/2026
     (`docs/2026-08-29-gap-padroes-fala.md` §2.2) — nenhuma foi copiada do
     projeto-irmão, que tem outras cores. O texto da pastilha é 12px/600, ou
     seja **texto normal** pela WCAG: o piso é 4,5:1, não 3:1. Cinco pares não
     alcançam, e um (`--tom-verde`) não alcança nem o piso de texto grande.
     Ficam como estão porque este bloco é REFATOR, não redesenho — mudar a cor
     é decisão de design, e agora está medida em vez de invisível.

     ⚠️ O TERCEIRO lado (`-bd`) existe só nos cinco tons que algum componente
     desenha com borda (as três `.flash`, o balão `.msg.humano`, o
     `.banner-leitura`). Não é "meio par": borda é decoração de componente, e
     inventar uma para os outros quatro seria escolher cor que ninguém pediu.
     O canário cobra o contrário — todo `-bd` tem de ter a família `-bg`/`-fg`
     de que ele é a borda. */
  --tom-azul-bg:     #E8F0F7;  --tom-azul-fg:     var(--escuro);    --tom-azul-bd:     #C9DCEF;  /* 5,98:1 ✓ */
  --tom-roxo-bg:     #EDE7F6;  --tom-roxo-fg:     #6A3FB5;          /* 5,80:1 ✓ */
  --tom-teal-bg:     #E0F2F1;  --tom-teal-fg:     #00796B;          /* 4,60:1 ✓ */
  --tom-mostarda-bg: #FDEFD0;  --tom-mostarda-fg: #9A6700;          /* 4,27:1 ⚠️ */
  --tom-vermelho-bg: #FBE0E2;  --tom-vermelho-fg: var(--danger);    --tom-vermelho-bd: #F3C0C4;  /* 4,18:1 ⚠️ */
  --tom-neutro-bg:   var(--surface-alt); --tom-neutro-fg: var(--text-mute); /* 3,88:1 ⚠️ */
  --tom-areia-bg:    #FDEBD9;  --tom-areia-fg:    #B5651A;          --tom-areia-bd:    #F5D9B5;  /* 3,73:1 ⚠️ */
  --tom-ambar-bg:    #FFF6E0;  --tom-ambar-fg:    var(--warning);   --tom-ambar-bd:    #E6C96A;  /* 3,11:1 ⚠️ */
  --tom-verde-bg:    #E7F3D9;  --tom-verde-fg:    var(--success);   --tom-verde-bd:    #C9E5A8;  /* 2,93:1 ❌ */

  --radius: 8px;
  --shadow: 0 1px 3px rgba(var(--escuro-canais), 0.08), 0 1px 2px rgba(var(--escuro-canais), 0.06);
  /* Largura CORRENTE da barra: a única alavanca dos três modos. Quem a consome
     são `.sidebar{width}` e `.main{margin-left}` — mudá-la aqui move os dois
     juntos, e é por isso que o modo é uma classe no <html> e não um punhado de
     regras espalhadas.
     ⚠️ NENHUMA @media pode redefini-la: media query tem a mesma especificidade
     de `:root` e vence por ordem de fonte, travando a barra na largura da faixa
     e fazendo o botão "não funcionar" abaixo de 1100px. Quem decide o modo é o
     JS; o CSS só reage (canário: test_nenhuma_media_query_mexe_na_largura_da_barra). */
  --sidebar-w: 232px;
  /* Largura da barra ABERTA. Sobrevive ao modo gaveta, onde `--sidebar-w` vai a
     zero (o conteúdo ocupa a tela toda) mas a barra precisa continuar tendo
     232px para deslizar por cima. */
  --sidebar-full: 232px;
  --sidebar-icones-w: 64px;

  /* ---- largura do conteúdo (docs/barra-lateral.md §Parte B) ----
     Sem esta metade a barra recolhível seria cosmética: os 168px devolvidos
     virariam margem branca à direita e o operador concluiria, com razão, que o
     botão não faz nada. */
  --content-pad: 26px;
  --content-max: 1180px;   /* teto PADRÃO — telas de trabalho pedem `conteudo-largo` */
  --card-pad: 20px;        /* consumido pelo .card E pela sangria da .acoes-form */
  /* ⚠️ 240/340, e não 260/460 (31/08/2026, medido na janela do dono, ~1080px).
     `--campo-min` decide QUANTAS trilhas cabem; `--campo-max` decide quanto cada
     uma engorda quando sobra espaço. Com 460 e duas trilhas, um "Código" de
     cinco caracteres (limitado a 190px pelo `.campo-curto`) ficava numa trilha
     de 460 — **270px de vão morto ao lado dele**, que é o "espaçamento grande /
     campos maiores do que precisariam" do relato. Baixar o TETO tira o vão;
     baixar o MÍNIMO junto é o que faz caber uma terceira coluna em vez de
     apenas encolher as duas. 340px comportam ~45 caracteres — folgado para nome
     e e-mail, e o que é longo de verdade (endereço, nome do mensalista) tem
     `.campo-largo`, que continua valendo duas trilhas. */
  --campo-min: 240px;      /* trilha mínima: é ela que decide o nº de colunas */
  --campo-max: 340px;      /* teto da trilha: campo largo demais não fica melhor */
  --medida: 78ch;          /* faixa de LEITURA — prosa, ajuda, textarea */

  /* ---- escala de espaçamento (guia-design-de-telas do FALA, §5) ----
     A ausência dela é o que produz `style="margin-top:7px"`: sem degrau
     nomeado, cada tela inventa um número — eram 231 `style=` inline em 46
     telas quando a escala nasceu (31/08/2026, catraca em
     test_contrato_campo.py). Base 4px; quem escreve regra NOVA escolhe um
     degrau, nunca um px avulso.
     ⚠️ NÃO retrofitar: trocar os valores soltos já existentes reflui todas as
     telas de uma vez. O que já existe migra por tela, com a catraca do
     `style=` inline puxando o número para baixo. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* ---- trilho de conversas do detalhe (G8, docs/conversas-detalhe-whatsapp.md) ----
     ⚠️ O PADRÃO de 300px mora aqui, no CSS, e não no JS: com `localStorage`
     bloqueado (modo privativo) a tela continua correta em vez de cair para
     `auto`. O script inline do template só SOBRESCREVE `--conv-w` quando há
     preferência gravada.
     ⚠️ 240/560 estão espelhados em `window.CONV_LARGURA` (template), e o
     espelho é conferido por canário: dois pares de números divergindo fariam o
     clamp do arrasto e o da restauração discordarem em silêncio. */
  --conv-w: 300px;         /* largura CORRENTE do trilho */
  --conv-min: 240px;
  --conv-max: 560px;
  --conv-alca: 8px;        /* a divisória é COLUNA da grid, não um ::after */
  --conv-alt: calc(100vh - var(--space-5));
}

/* Os três modos. A classe é escrita no <html> pelo snippet síncrono do <head>
   (base.html) e alternada por static/js/sidebar.js. */
html.sidebar-icones { --sidebar-w: var(--sidebar-icones-w); }
html.sidebar-oculta { --sidebar-w: 0px; }

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: var(--bg-soft);
  color: var(--text);
  font-size: 15px;
  line-height: 1.5;
}

a { color: var(--acento); text-decoration: none; }
a:hover { color: var(--acento-hover); }

/* ---------- Layout ---------- */
.layout { display: flex; min-height: 100vh; }

/* Empilhamento (docs/barra-lateral.md §2):
     .acoes-form 5 < .topbar 10 < .sidebar-overlay 30 < .sidebar 40
       < #qv-painel 41 < .barra-sessao 42 < [top layer]
   A "top layer" (o <dialog> de showModal() e os popovers do menu "⋮") pinta
   ACIMA de qualquer z-index — por isso NÃO existe camada de modal aqui, e dar
   `z-index` a `.modal`/`.menu-acoes` seria erro (há canário nos dois sentidos).
   ⚠️ `.barra-sessao` (P-PNL-004) é o topo da escada de propósito: a faixa diz
   que a tela parou de funcionar, e uma barra `sticky`/`fixed` por cima dela a
   esconderia justamente de quem está rolado. Ela NÃO usa top layer — o porquê
   está na regra dela. */
.sidebar {
  width: var(--sidebar-w);
  background: var(--escuro);
  color: #fff;
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  padding: 0;
  transition: width .18s ease, transform .18s ease, visibility .18s;
}
.sidebar .logo {
  padding: 20px 22px;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: .5px;
  border-bottom: 1px solid rgba(255,255,255,.12);
  white-space: nowrap;
}
.sidebar .logo b { color: var(--destaque); }
/* ⚠️ `overflow-x: hidden` explícito: pela spec, um `overflow-y: auto` sozinho
   promove o eixo X a `auto` também. Sem esta linha, no meio da transição para
   64px o rótulo ainda largo faria nascer uma barra de rolagem HORIZONTAL dentro
   do menu — que pisca a cada clique e some quando se vai olhar. */
.sidebar nav { padding: 12px 0; overflow-y: auto; overflow-x: hidden; flex: 1; }
.sidebar nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 22px; color: rgba(255,255,255,.82);
  font-size: 14.5px; border-left: 3px solid transparent;
  position: relative;          /* âncora do .nav-badge no modo só-ícones */
  white-space: nowrap;
}
.sidebar nav a:hover { background: rgba(255,255,255,.06); color: #fff; }
.sidebar nav a.active {
  background: rgba(255,255,255,.10);
  border-left-color: var(--acento);
  color: #fff; font-weight: 600;
}
.sidebar nav .grupo {
  padding: 14px 22px 6px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 1px; color: rgba(255,255,255,.45);
}
/* Tela sem permissão: aparece esmaecida e não-clicável (V023). */
.sidebar nav .item-off {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 22px; color: rgba(255,255,255,.32);
  font-size: 14.5px; border-left: 3px solid transparent;
  cursor: default; user-select: none;
  position: relative;
  white-space: nowrap;
}

/* ---------- Ícones (sprite templates/_icones.html) ----------
   UMA regra para todos os estados. É `stroke: currentColor` que faz o ícone
   herdar de graça normal/hover/ativo/sem-permissão — e é por isso que nenhum
   <symbol> pode trazer `fill`, `stroke` ou `stroke-width` próprios. */
.ic {
  width: 20px; height: 20px; flex: 0 0 20px;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ---------- Modo só-ícones ---------- */
/* O rótulo some por VISUALLY-HIDDEN, nunca por `display: none`: sem texto
   acessível dentro do <a>, o leitor de tela anuncia só "link" e o menu inteiro
   fica ilegível para quem navega por áudio. O tooltip visual é o `title` que o
   sidebar.js escreve a partir do `data-rotulo` (flyout em CSS seria recortado
   pelo overflow do <nav>).

   ⚠️ ESCOPADO à `.sidebar` de propósito. `.rotulo` não é exclusivo do menu: o
   dashboard usa `.kpi .rotulo` no texto de cada indicador. Sem o escopo, entrar
   em modo só-ícones apagaria os rótulos dos KPIs — numa TELA QUE NEM TEM MENU
   por perto, o que ninguém ligaria ao botão da barra. Há canário
   (test_rotulo_escondido_e_so_o_do_menu). */
html.sidebar-icones .sidebar .rotulo {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
html.sidebar-icones .sidebar .logo {
  padding: 20px 0; text-align: center; font-size: 17px; letter-spacing: 0;
}
html.sidebar-icones .logo-sufixo { display: none; }
html.sidebar-icones .sidebar nav a,
html.sidebar-icones .sidebar nav .item-off {
  justify-content: center; padding: 11px 0; gap: 0;
}
/* Cabeçalho de grupo vira um filete: a 64px o texto não cabe, mas a separação
   entre Atendimento/Configuração/Sistema é justamente o que ainda orienta. */
html.sidebar-icones .sidebar nav .grupo {
  height: 1px; padding: 0; margin: 10px 14px; overflow: hidden;
  color: transparent; background: rgba(255,255,255,.16);
}
/* O contador de hand-offs mora DENTRO do link: em fluxo normal ele empurraria o
   ícone e desalinharia a coluna inteira. Absoluto, ele passa a marcar o ícone. */
html.sidebar-icones .nav-badge {
  position: absolute; top: 4px; right: 8px; margin: 0;
}

/* ---------- Modo gaveta (janela estreita) ----------
   `--sidebar-w: 0` já entregou a tela toda ao conteúdo; a barra continua com
   232px e sai de cena por transform. `visibility: hidden` (e não só o
   translate) é o que tira os links do Tab enquanto a gaveta está fechada. */
html.sidebar-oculta .sidebar {
  width: var(--sidebar-full);
  transform: translateX(-100%);
  visibility: hidden;
}
html.sidebar-oculta.sidebar-gaveta-aberta .sidebar {
  transform: none;
  visibility: visible;
  box-shadow: 0 0 24px rgba(0,0,0,.35);
}
/* Véu: sempre no DOM (base.html), escondido por CSS. */
.sidebar-overlay { display: none; }
html.sidebar-gaveta-aberta .sidebar-overlay {
  display: block; position: fixed; inset: 0; z-index: 30;
  background: var(--veu);
}
/* ⚠️ Risco declarado: isto desliga o `sticky` da .topbar e da .acoes-form
   enquanto a gaveta está aberta. É transitório (fechar a gaveta devolve) e o
   preço de não deixar a página rolar atrás do véu. */
html.sidebar-gaveta-aberta body { overflow: hidden; }

.main {
  margin-left: var(--sidebar-w); flex: 1; display: flex; flex-direction: column;
  min-width: 0;                /* deixa o conteúdo encolher em vez de estourar */
  transition: margin-left .18s ease;
}

/* Único @media da folha — e ele não toca em `--sidebar-w` (ver o aviso no
   :root). Quem pediu menos movimento recebe a troca de modo instantânea. */
@media (prefers-reduced-motion: reduce) {
  .sidebar, .main { transition: none; }
}

.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 10px var(--content-pad);   /* mesmo recuo lateral da .content */
  display: flex; align-items: center; justify-content: space-between;
  position: sticky; top: 0; z-index: 10;
}
/* `.topbar` é `space-between` com dois filhos. O botão de menu entra agrupado
   com o seletor de propriedade: solto, viraria um terceiro filho e jogaria o
   seletor para o meio da barra. */
.topbar-esq { display: flex; align-items: center; gap: 12px; }
#btn-menu { padding: 5px 7px; }
.topbar .hotel-switch { display: flex; align-items: center; gap: 8px; }
.topbar .hotel-switch label { font-size: 13px; color: var(--text-mute); }
.topbar .user { display: flex; align-items: center; gap: 14px; font-size: 14px; }
.topbar .user .nome { color: var(--escuro); font-weight: 600; }

/* Três famílias de tela, por OPT-IN no template (base.html):
     1 · trabalho  → `conteudo_largo`   — lista/tabela/conversa, sem teto
     2 · formulário → o padrão + `.campos`, que já limita cada campo
     3 · leitura   → `conteudo_leitura` — prosa, presa à medida tipográfica
   A família 3 é DECLARADA, nunca presumida: largar uma tela de texto corrido em
   largura total piora a leitura. Nenhum dos dois opt-ins redefine o padding —
   é o que mantém a sangria da .acoes-form correta em qualquer família. */
.content { padding: var(--content-pad); max-width: var(--content-max); }
.content.conteudo-largo   { --content-max: none; }
.content.conteudo-leitura { --content-max: var(--medida); }
h1 { font-size: 22px; color: var(--escuro); margin: 0 0 4px; }
.subtitle { color: var(--text-mute); margin: 0 0 22px; font-size: 14px; }

/* ---------- Título + as ações sobre o REGISTRO daquela tela ----------
   O título diz o que a página é e o badge ao lado diz em que estado ela está;
   estes botões são o que dá para FAZER com esse estado. Ficam colados a ele de
   propósito — na ficha do contrato eles moraram, por um dia, na mesma faixa dos
   links "Faturas · Medições", e o dono relatou a confusão: o olho lê a faixa
   como um grupo, e um "Encerrar" com a mesma forma de um "Medições" sugere que
   os dois levam a algum lugar. Navegação e mudança de estado são papéis
   opostos, e a fila de `.acoes-inline` continua sendo a da navegação.

   `align-items: baseline` alinha o texto do botão com o do título (o `center`
   deixaria o botão flutuando alto ao lado de um h1 de 22px); `flex-wrap` põe os
   botões abaixo do título numa janela estreita, em vez de espremer os dois. */
.titulo-acoes {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-2) var(--space-4); flex-wrap: wrap;
  margin-bottom: var(--space-1);
}
/* A margem do bloco é do wrapper — o `0 0 4px` do `h1` somaria a ela. */
.titulo-acoes > h1 { margin-bottom: 0; }
.acoes-titulo { display: flex; gap: var(--space-2); flex-wrap: wrap; }
h2 { font-size: 17px; color: var(--escuro); margin: 26px 0 12px; }

/* ---------- Cards / KPIs ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: var(--card-pad);
  margin-bottom: 18px;
}
/* ⚠️ O primeiro título do cartão NÃO soma a sua margem ao padding do cartão.
   `h2 { margin: 26px 0 12px }` é a métrica do título SOLTO na página, onde ele
   separa dois blocos. Dentro de um `.card` já existe `--card-pad` fazendo esse
   trabalho, e os dois somados davam **46px** entre a borda do cartão e o texto
   do título — o "espaçamento grande entre o topo de cada form e o título" do
   relato de 31/08/2026. O mesmo vale para o `<p class="subtitle">` que vem logo
   abaixo: 22px é a distância certa entre o subtítulo da PÁGINA e o conteúdo,
   não entre a linha de apoio de um cartão e o primeiro campo dele.
   `:first-child` e não `.card h2`: um `<h3>` no MEIO do cartão (a divisão de
   "Dados pessoais" dentro da Identificação) continua precisando do respiro. */
.card > :is(h2, h3):first-child { margin-top: 0; }
.card > .subtitle { margin-bottom: var(--space-3); }
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin-bottom: 22px; }
.kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px 20px;
}
.kpi .valor { font-size: 30px; font-weight: 700; color: var(--escuro); line-height: 1.1; }
.kpi .rotulo { font-size: 13px; color: var(--text-mute); margin-top: 4px; }
.kpi.destaque .valor { color: var(--acento); }

/* ---------- Tabelas ---------- */
table { width: 100%; border-collapse: collapse; background: var(--surface); }
/* `overflow-x: auto`, e não `hidden`: em modo gaveta (700px) as colunas da
   direita ficavam INALCANÇÁVEIS — não havia como rolar até elas. O recorte do
   `border-radius` não se perde (qualquer overflow ≠ visible recorta) e o menu
   "⋮" das linhas continua inteiro, porque popover pinta na top layer. */
.table-wrap {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow-x: auto; box-shadow: var(--shadow);
}
th, td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--border); font-size: 14px; }
th { background: var(--bg-soft); color: var(--text-mute); font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: .4px; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--linha-hover); }

/* Cabeçalho de coluna ORDENÁVEL. Era `style="color:inherit;text-decoration:none"`
   repetido à mão em **sete** listas (contratos, clientes, faturamento, medições,
   reajustes, unidades e as faturas da ficha) — a mesma decisão escrita sete
   vezes, que é como sete telas passam a divergir na primeira manutenção. O `th`
   já define cor e peso; o link só precisa parar de se pintar de azul e de
   sublinhar. */
.table-wrap th a { color: inherit; text-decoration: none; }
.table-wrap th a:hover { color: var(--acento); }

/* Alçada por perfil na tela de Reserva (`P-PRC-001`): uma LINHA por perfil, com o campo do %
   na última coluna. A coluna do campo tem largura de dado curto — o input herda `width:100%`
   da regra base e, sem isto, esticaria até metade da tabela. */
.alcada-perfis { margin-bottom: var(--space-3); }
.alcada-perfis .col-alcada { width: 9rem; }

/* Fila de ações acima de uma tabela ou seção (o "➕ Adicionar taxa" das seções
   da ficha, os atalhos Faturas/Medições). Não é `.toolbar`: aquela é a barra de
   FILTRO, com `margin-bottom` próprio e regras de largura de campo dentro de
   `.filtros`. Aqui é só uma fila de botões — e o espaçamento sai da escala. */
.acoes-inline {
  display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center;
  margin-bottom: var(--space-3);
}
.acoes-inline.espacada { margin: var(--space-3) 0; }

/* ---------- Botões ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--acento); color: #fff; border: none;
  padding: 9px 16px; border-radius: var(--radius); font-size: 14px;
  font-weight: 600; cursor: pointer; font-family: inherit; line-height: 1.2;
}
.btn:hover { background: var(--acento-hover); color: #fff; }
.btn.secondary { background: var(--escuro); }
.btn.secondary:hover { background: var(--escuro-2); }
.btn.ghost { background: transparent; color: var(--escuro); border: 1px solid var(--border); }
.btn.ghost:hover { background: var(--bg-soft); color: var(--escuro); }
.btn.danger { background: var(--danger); }
.btn.danger:hover { background: var(--danger-hover); color: #fff; }
.btn.sm { padding: 5px 11px; font-size: 13px; }

/* Estado :disabled genérico (radios travados do cap, botões desabilitados) —
   sem isto um controle disabled renderiza idêntico a um habilitado. */
.btn:disabled, button:disabled,
input:disabled, select:disabled, textarea:disabled {
  opacity: .5; cursor: not-allowed;
}

/* Texto de célula que AGE — o rótulo da unidade abrindo o modal de edição.
   É `<button>` porque não há URL para onde ir (a edição é um `<dialog>`), e um
   `<a href="#">` rolaria a página ao topo e sujaria o histórico. Mas tem de
   PARECER link, senão ninguém descobre que clica: o botão do navegador vem com
   fundo, borda e fonte próprios, e um deles sozinho já mata a pista.
   `text-align: left` porque `<button>` centraliza por padrão — numa coluna de
   texto o rótulo sairia desalinhado das outras células. */
.link-acao {
  background: none; border: none; padding: 0; font: inherit;
  color: var(--acento); cursor: pointer; text-align: left;
}
.link-acao:hover { color: var(--acento-hover); text-decoration: underline; }

/* ---------- Modo somente-leitura (guia v2 §10) ----------
   Aplicado pelo base.html quando pode_editar=False (nível 'leitura' na tela).
   Convenção: form[method="post"] dentro de .content = escrita (filtros usam
   GET; o seletor de propriedade fica na .topbar, fora de .content).
   Domínios disjuntos do JS local: pointer-events aqui + tabIndex/capture no
   script do base.html — nunca `disabled` em massa. */
body.somente-leitura .content form[method="post"] { cursor: not-allowed; }
body.somente-leitura .content form[method="post"] :is(input,select,textarea,button) {
  pointer-events: none; opacity: .5; filter: grayscale(.75);
}
.banner-leitura {
  background: var(--tom-ambar-bg); border: 1px solid var(--tom-ambar-bd); color: var(--warning-texto);
  border-radius: var(--radius); padding: 10px 14px; margin-bottom: 14px;
  font-size: 14px;
}

/* ---------- Faixa do atendimento manual (`P-AGE-001`, base.html) ----------
   Irmã da `.banner-leitura` (mesma cor âmbar: AVISO, não erro — nada quebrou, alguém
   escolheu calar a Helena). Estática no topo do `.content`: é estado da propriedade, e
   não precisa perseguir a rolagem como a de sessão expirada. */
.faixa-manual {
  background: var(--tom-ambar-bg); border: 1px solid var(--tom-ambar-bd); color: var(--warning-texto);
  border-radius: var(--radius); padding: var(--space-2) var(--space-3); margin-bottom: var(--space-3);
  font-size: 14px;
}

/* ---------- Faixa de sessão expirada (`P-PNL-004`, static/js/sessao.js) ----------
   Irmã visual da `.banner-leitura` — e é dela que herda a COR, não do irmão FALA, onde
   esta faixa é vermelha.

   ⚠️ **AVISO ≠ ERRO.** Nada quebrou: a sessão venceu, a tela parou de atualizar e há um
   caminho de volta a um clique. Pintá-la de vermelho gastaria o vermelho, que é o que faz
   o erro de verdade ser visto. Por isso `--tom-ambar-bg` + `--tom-ambar-bd`.
   ⚠️ E o texto é `--warning-texto` (5,81:1), NÃO `--warning` (3,11:1): a faixa é
   PARÁGRAFO, como a `.banner-leitura` e o `.flash.aviso`. `--warning` serve pastilha e
   borda; num parágrafo de 14px ele derruba o contraste pela metade.

   ⚠️ `position: fixed` e não estática: quem está lendo uma conversa está ROLADO, e um
   aviso no topo do documento ficaria fora da viewport — exatamente o silêncio que esta
   faixa existe para acabar.

   ⚠️ `z-index: 42` CONTINUA a escada documentada em §Layout (5 < 10 < 30 < 40 barra < 41
   painel do quick view), e não é a exceção que a escada proíbe: o veto documentado ali é a
   `.modal`/`.menu-acoes`, que pintam na TOP LAYER e cujo z-index reabriria o recorte pelo
   `.table-wrap`. Aqui a top layer foi CONSIDERADA e descartada: a faixa é criada por JS, e
   um `showPopover()` que falhe (navegador sem suporte, elemento ainda não conectado) deixa
   o nó no DOM e INVISÍVEL pelo `display:none` do atributo `popover` — o silêncio de volta,
   pela porta que este arquivo existe para fechar. Nada a recorta: ela é filha direta do
   `<body>` e já é `fixed`.

   ⚠️ Ela NÃO rouba foco e NÃO bloqueia a página: quem atende pode ter texto digitado no
   compositor, e um modal por cima transformaria "a sessão caiu" em "perdi o que escrevi".
   Avisar e sair da frente. */
.barra-sessao {
  position: fixed; top: 0; left: 0; right: 0; z-index: 42;
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4); font-size: 14px;
  background: var(--tom-ambar-bg); color: var(--warning-texto);
  border-bottom: 1px solid var(--tom-ambar-bd);
}
.barra-sessao a { color: inherit; font-weight: 600; text-decoration: underline; }

/* ---------- Forms ---------- */
label.field { display: block; margin-bottom: 14px; }
label.field > span { display: block; font-size: 13px; color: var(--text-mute); margin-bottom: 5px; font-weight: 600; }
label.chk { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; margin: 6px 14px 6px 0; cursor: pointer; }
label.chk input { width: auto; }
/* ⚠️ LISTA BRANCA, com especificidade (0,1,1) — e um canário no lugar da fé.

   Esta regra já errou para os dois lados, e as duas cicatrizes importam:

   1ª versão — lista de cinco tipos (`text`, `password`, `number`, `url`,
   `date`): deixou **53 campos do painel sem estilo nenhum** (`month`, `email`,
   `time` e — a maioria — o `<input name="valor">` sem atributo `type`, que o
   navegador trata como texto mas que `input[type=text]` não casa). O sintoma
   não era um erro: era uma caixinha cinza do navegador ao lado de campos
   bonitos, em oito telas.

   2ª versão (31/08/2026, durou UM dia) — lista negra com dez `:not([type=…])`:
   fechou a cobertura e quebrou a cascata. Dez seletores de atributo levam a
   especificidade a **(0,10,1)**, e a regra base passou a VENCER os overrides
   legítimos por classe — três defeitos vivos, medidos aqui: o `width: auto`
   dos filtros de lista (0,2,1) virou código morto, o `width: 70px` do grid do
   tarifário (0,2,1) idem, e `input:focus { border-color: var(--acento) }`
   (0,1,1) perdeu a borda de foco — a base declara `border`, que expande para
   `border-color`, e só o anel de `box-shadow` sobrevivia.

   A lista branca volta com as DUAS coberturas que faltavam na 1ª versão:
   `input:not([type])` casa os campos sem atributo (eram 82), e o canário
   `test_contrato_campo.py::test_todo_tipo_de_campo_tem_regra_no_css` cobre o
   tipo futuro — um `type=` novo em template reprova a suíte nomeando o tipo e
   as telas, em vez de renderizar cru em silêncio.

   Ficam FORA de propósito: checkbox/radio têm tamanho próprio, file desenha o
   botão do SO, submit/button/image/reset são botões (e `.btn` os veste),
   hidden não pinta, range e color trazem controle nativo que 100% de largura
   estraga. (`test_a_regra_base_e_lista_branca` impede a volta do `:not`
   encadeado.) */
input:not([type]),
input[type=text], input[type=password], input[type=email], input[type=url],
input[type=number], input[type=date], input[type=month], input[type=time],
input[type=search],
select, textarea {
  width: 100%; padding: 9px 12px; border: 1px solid var(--border);
  border-radius: var(--radius); font-size: 14px; font-family: inherit;
  background: #fff; color: var(--text);
}
/* O placeholder NUNCA se parece com valor (P-PNL-012, 25/10/2026). Sem esta regra
   o navegador o pintava num cinza tão perto do texto digitado que o campo VAZIO de
   e-mail e telefone parecia preenchido (o dono mostrou o cadastro de usuário do
   FALA). Por isso ele só sobra onde é convite — busca, filtro, o compositor e a
   réplica do chamado —, e o itálico diz "isto não é o que está gravado" mesmo a
   quem não distingue o tom. `--text-mute` sobre o fundo branco do campo faz
   4,63:1 (AA para texto normal). Exemplo de formato, valor herdado e instrução
   saíram para o `<small>`, o "?" e a linha `efeito()`; o canário
   `test_placeholder.py` confere a lista branca nos dois sentidos. */
::placeholder { color: var(--text-mute); font-style: italic; opacity: 1; }
/* Texto de ajuda DENTRO do campo: abaixo do controle, como linha própria.
   Sem isso o `<small>` flutua ao lado do input — foi o que fez "Mês a que a
   variação se refere." aparecer emendado à direita da competência.
   O contrato é ESTRUTURAL (`label.field > small`), sem classe: dentro de um
   campo não existe outro papel para um <small>, e classe que ninguém é
   obrigado a pôr é classe que falta na tela seguinte. `max-width: 60ch`
   porque fora da faixa `.campos` (cujas trilhas já limitam a ~340px) um campo
   pode ocupar a medida inteira, e ajuda que atravessa a tela perde a volta da
   linha. Canário: test_contrato_campo.py::test_ajuda_de_campo_e_bloco_com_teto. */
label.field > small { display: block; margin-top: 5px; font-size: 12px; line-height: 1.45; max-width: 60ch; }
/* ---------- Ajuda longa: o botão "?" com painel (port do FALA, 31/08/2026) ----------
   Para o texto de auxílio LONGO demais para viver no fluxo (o <small> serve à
   linha curta; um parágrafo de regra empurra o formulário inteiro). O botão
   vive dentro do <span> do rótulo; o painel abre por POPOVER NATIVO — top
   layer, nada de z-index, nenhum overflow de ancestral o recorta, Esc e
   clique-fora fecham de graça, zero JS. Macros em _ajuda.html (botão e painel
   são DOIS macros de propósito: o painel tem de ficar FORA do <label>, senão
   todo clique dentro dele ativa o rótulo e manda o foco para o campo).
   ⚠️ Fundo do forte em `--escuro`, DIVERGINDO do irmão (lá é o acento): aqui o
   acento é o azul céu #5EA1D2, que a identidade visual mediu a 3,16:1 sobre
   branco — não serve para texto, e o "?" é texto de 11px. O navy (13,48:1
   inverso) é marca tanto quanto, e lê.
   ⚠️ `cursor: pointer`, não `help`: clicar FAZ algo — `help` prometeria só a
   dica de hover, que não existe no toque. */
.ajuda {
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; padding: 0; margin-left: 4px;
  border: 1px solid var(--border); border-radius: 50%;
  font-size: 11px; font-weight: 700; line-height: 1;
  vertical-align: middle; cursor: pointer;
}
/* O "?" ao lado do TÍTULO da tela (`P-TNT-004` L3): o `margin-left` de 4px do rótulo é
   apertado para um `<h1>` de 22px — o respiro sai da escala. */
.content h1 > .ajuda { margin-left: var(--space-2); }
.ajuda-forte { border-color: var(--escuro); background: var(--escuro); color: #fff; }
.ajuda-forte:hover, .ajuda-forte:focus-visible { border-color: var(--escuro-2); background: var(--escuro-2); }
/* O painel. Sombra e backdrop são OS MESMOS do .modal — é a mesma camada de
   atenção, e dois pares de números aqui seriam dois lugares para divergir. */
.ajuda-pop {
  max-width: 560px; width: calc(100% - 32px); padding: 20px 22px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  box-shadow: 0 10px 40px rgba(var(--escuro-2-canais), 0.28);
}
.ajuda-pop::backdrop { background: rgba(var(--escuro-2-canais), 0.42); }
.ajuda-pop h3 { margin: 0 0 12px; font-size: 16px; color: var(--escuro); }
.ajuda-pop p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.55; }
.ajuda-pop .btn { margin-top: 6px; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--acento);
  box-shadow: 0 0 0 3px rgba(var(--acento-canais), .18);
}
textarea { resize: vertical; min-height: 120px; max-width: var(--medida); }
/* Monoespaçado (prompt, JSON): 78ch de fonte proporcional é curto demais para
   uma linha de código, e aqui a quebra atrapalha mais que a largura. */
textarea.mono {
  font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace; font-size: 13px;
  max-width: 100ch;
}
.form-row { display: flex; gap: 14px; flex-wrap: wrap; }
.form-row > * { flex: 1; min-width: 180px; }
/* `.grid-3` e o `.grid-2` do tarifário morreram em 31/08/2026: as telas de
   cotação e do tarifário migraram para a faixa `.campos` (a junção que este
   comentário prometia — só que na direção certa: colunas viraram conta do
   navegador, não de `repeat(N, 1fr)`). */
/* Valor de pacote no tarifário: uma LINHA por duração — o período inteiro e
   cada opção (V104, P-PRC-003) — e uma coluna por linha de preço do modo de
   cobrança. Um teto fixo (era 420px, para rótulo + valor) espremeria o
   por_ocupacao, que tem uma coluna por adulto/criança; a tabela fica com a
   largura do que contém e rola dentro do `.tabela-scroll`. O resto do estilo da
   grade de preços vive no <style> da própria tela; estas regras moram aqui
   porque tela MIGRADA não pode conter a string max-width (canário das
   migradas). A dupla classe vence o `width:100%` da grade de lá. */
table.tarifa-grid.tarifa-pacote { width: auto; }
table.tarifa-pacote td:first-child { text-align: left; white-space: nowrap; }
table.tarifa-pacote input.pacote-noites { width: 8ch; }
.pacote-titulo { margin: var(--space-3) 0 var(--space-1); }
button[data-pacote-adicionar] { margin-top: var(--space-2); }
/* A LISTA do tarifário (23/10/2026): intervalos e bloqueios em linha, o formulário em
   modal largo, e o "Ocultar … passados" na barra da lista. A barra junta o título (com o
   "?") à esquerda e as ações à direita, e quebra de linha no estreito. A linha encerrada
   ou inativa fica esmaecida — menos a célula das ações, que continua clicável à vista. */
.lista-barra { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
               gap: var(--space-3); margin: var(--space-3) 0; }
.lista-barra h3 { margin: 0; }
.lista-barra-acoes { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.lista-barra-acoes label.chk { margin: 0 var(--space-2) 0 0; }
table.lista-periodos td { vertical-align: top; }
table.lista-periodos .sem-quebra { white-space: nowrap; }
tr.linha-apagada > td { opacity: .55; }
tr.linha-apagada > td:last-child { opacity: 1; }
.tarifa-legenda { margin: var(--space-2) 0 var(--space-1); }
.prod-tabs-grupo { margin-left: var(--space-2); }
.copia-lista { display: flex; flex-direction: column; align-items: flex-start; margin: var(--space-2) 0; }
.copia-lista label.chk { margin: var(--space-1) 0; }
.copia-lista p.muted { margin: var(--space-1) 0; }
/* O modal do intervalo em duas linhas FIXAS (pedido do dono, 23/10/2026): Nome e as duas datas
   na 1ª, as estadias mín./máx. — ou, no pacote, a noite obrigatória — na 2ª. Trilha explícita, e
   não o `auto-fill` da `.campos`: no modal largo ele dava duas colunas de 340px e espalhava os
   seis campos em três linhas. `minmax(0, …)` deixa a trilha encolher no estreito em vez de
   vazar de lado. `align-items: end` alinha os campos quando o rótulo de data do pacote ("(1ª
   noite do pacote)") quebra em duas linhas e o do Nome não. */
.modal .campos.campos-intervalo {
  grid-template-columns: minmax(0, 340px) repeat(2, minmax(0, 170px)); align-items: end;
}
.modal .campos.campos-estadia { grid-template-columns: repeat(3, minmax(0, 200px)); align-items: end; }
.modal .campos.campos-estadia > .campo-noite { grid-column: span 2; }
/* Preview do logo no config do voucher. Fundo branco DE PROPÓSITO, nos dois
   temas: o logo vai impresso num PDF branco, e é assim que ele tem de ser
   julgado (#fff está em CORES_SEM_DONO no canário da paleta). */
.preview-logo {
  max-height: 80px; max-width: 220px;
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px; background: #fff;
}
/* Célula de tabela com identificador longo (linha do extrato na conciliação):
   trunca com reticências em vez de alargar a coluna. O `nowrap` faz parte do
   idioma — sem ele o `text-overflow` nunca mostra as reticências. */
td.trunca { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Decisão de reajuste dentro da LINHA da tabela (mensalista_reajustes): o par
   valor+motivo é micro de propósito — o contexto é a célula, não a página. */
.decisao-reajuste { display: flex; gap: 6px; align-items: end; flex-wrap: wrap; }
.decisao-reajuste label.field { margin: 0; }
.decisao-reajuste .campo-valor { max-width: 130px; }
.decisao-reajuste .campo-motivo { max-width: 180px; }
.toolbar { display: flex; gap: 10px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }

/* ---------- Card de filtros de lista (docs/tiv/guia-filtro-lista.md) ----------
   Básicos sempre à vista; avançados num <details> recolhido que abre sozinho
   quando algum está aplicado. A última toolbar de dentro do card perde a
   margem inferior — quem dá o respiro é o padding do próprio card, senão
   sobra uma faixa vazia embaixo do formulário. */
.filtros-card { margin-bottom: 16px; }
/* ⚠️ Os controles de filtro NÃO herdam o `width: 100%` dos campos de
   formulário. Herdando, cada um vira uma linha inteira e o cartão de filtro
   passa a ocupar a tela toda empilhado — que é como as listas de mensalistas
   nasceram. Aqui o dado é curto e a leitura é horizontal: `busca | status |
   unidade | Filtrar | Limpar` cabe numa linha só.
   As larguras vivem no CSS e não em `style=` por tela, senão cada lista nova
   reinventa o número — e foi assim que a tela de auditoria acabou com três
   `max-width` diferentes escritos à mão. */
.filtros .toolbar > :is(input, select) { flex: 0 1 auto; width: auto; }
.filtros .toolbar > input[type=text] { flex: 1 1 220px; min-width: 180px; max-width: 320px; }
.filtros .toolbar > select { min-width: 150px; max-width: 250px; }
.filtros .toolbar > input[type=date] { max-width: 165px; }
.filtros .toolbar:last-child { margin-bottom: 0; }
.filtros > .toolbar { margin-bottom: 0; }
.filtros details > summary { cursor: pointer; font-size: 13px; }
.filtros details[open] > summary { margin-bottom: 2px; }
.filtros label { display: inline-flex; align-items: center; gap: 6px; margin: 0; }

/* ---------- Faixa de campos (docs/barra-lateral.md §Parte B) ----------
   Quantas colunas cabem é conta do navegador; o formulário não decide.

   ⚠️ `auto-fill`, NUNCA `auto-fit`. `auto-fit` colapsa as trilhas vazias e
   distribui a sobra entre as que restaram: um cartão de 2 campos a 2560px
   viraria dois campos de ~1200px — exatamente o defeito que a Parte B existe
   para corrigir, agora com grid. Pelo mesmo motivo o teto da trilha é
   `--campo-max` e não `1fr`. (`.kpi-grid` continua com `auto-fit` de propósito:
   cartão de indicador DEVE preencher a linha. Os dois convivem, e há canários
   espelhados justamente porque copiar um ao escrever o outro é o erro fácil.)

   `gap` só na horizontal: o espaçamento vertical já vem do
   `margin-bottom: 14px` do `label.field`, e somar os dois abre um buraco. */
.campos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--campo-min), var(--campo-max)));
  gap: 0 18px;
  align-items: start;
}
/* Item da grade = o WRAPPER `.campo`, não o `label.field` cru. É ele que mantém
   o `label` colado à linha de estado das macros de _campos.html (`efeito()`,
   `segredo()`, `apaga()`), cujo `margin-top: -9px` só encaixa se os dois forem
   irmãos no MESMO item. Soltos na grade, cada um vira uma célula e o estado do
   token cai sob o campo da ESQUERDA — o defeito corrigido no acerto (1) da
   Leva 3 dos tenants ("estado embaixo do seu campo, sempre"). */
.campos > .campo { margin-bottom: 14px; }
.campos > .campo > label.field { margin-bottom: 0; }
/* Hora, percentual, quantidade, sigla, UF: o CONTROLE diz o tamanho do dado.
   O teto é só do input/select, nunca do `.campo` inteiro — encolher o item da
   grade encolheria junto o rótulo, e "Remarcações permitidas por reserva" em
   190px vira três linhas de texto acima de uma caixinha de dois dígitos. */
.campos > .campo-curto :is(input, select) { max-width: 190px; }
/* URL, endereço, chave de API: o valor é longo e cortá-lo esconde informação —
   mas "linha inteira" sem teto seria a armadilha B1 de volta (um campo de
   2400px num monitor largo). Ocupa a linha e para em duas trilhas. */
.campos > .campo-largo {
  grid-column: 1 / -1;
  max-width: calc(var(--campo-max) * 2 + 18px);
}
/* Linha inteira, mas presa à medida de leitura. Serve a prosa dentro do
   formulário e ao PAR de campos que só faz sentido lido junto (valor+parcelas),
   que continua usando `.form-row` DENTRO do `.campo`: quebrar o par em trilhas
   independentes deixaria "3 parcelas" órfão numa coluna. */
.campos > .campo-linha { grid-column: 1 / -1; max-width: var(--medida); }
/* Faixa COMPACTA (`P-51`, a Nova cotação): onze campos curtos que o balcão preenche de uma
   vez, numa tela de trabalho. Só redefine a trilha — a mecânica da grade é a da `.campos`.
   ⚠️ No `auto-fill` o NÚMERO de colunas sai do TETO da trilha (o `max` do `minmax`, quando é
   definido), não do mínimo: o 150/220 do plano dava 4 por linha a 1366 px (medido na captura
   do CHECKPOINT A, 22/10/2026). Com teto 160 cabem 6 nos ~1065 px úteis de uma tela de 1366
   com a barra aberta e a barra de rolagem do Windows — os onze ficam em duas linhas. */
.campos.campos-compacta { --campo-min: 130px; --campo-max: 160px; }
/* A cotação rápida na coluna lateral da conversa (`P-51` L2c, ~250 px a 1366): DUAS trilhas
   fixas, não o `auto-fill` — com ele cabia um campo por linha e os sete viravam uma coluna
   (medido no CHECKPOINT C, 25/10/2026). O serviço e as datas (`.campo-largo`) ocupam a linha;
   os quatro números dividem-na. Nas opções, o nome, o valor e a pastilha quebram como itens
   inteiros, em vez de espremer o nome numa coluna de duas palavras. */
#cotacao-rapida .campos { --campo-max: 150px; grid-template-columns: repeat(2, minmax(0, var(--campo-max))); }
#cotacao-rapida .opcoes-cotacao label.chk { flex-wrap: wrap; row-gap: 0; }
.cotacao-rapida-resultado:not(:empty) { margin-top: var(--space-3); }
/* As opções da cotação: uma por linha (o `label.chk` nasce em linha, com margem à direita),
   o valor em destaque e a sugerida — as `max_opcoes` do topo da ordem de oferta — marcada. */
.opcoes-cotacao {
  display: grid;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
  max-width: var(--medida);
}
.opcoes-cotacao label.chk { margin: 0; gap: var(--space-2); }
.opcoes-cotacao .muted { margin: 0; }
.opcao-sugerida { font-weight: 600; }
/* Campo IRRELEVANTE no estado atual da tela (V070: o app Meta de uma
   propriedade que não é `meta_cloud`). Cinza e sem clique — **nunca
   `disabled`**: campo desabilitado não viaja no POST, e um extrator que
   reconstrói o registro inteiro leria a ausência como "vazio" e apagaria a
   coluna gravada, em silêncio. A regra do `design-telas/SKILL.md`, com casa
   no CSS. */
.campo-inerte { pointer-events: none; opacity: .55; }

/* ---------- Dados em LEITURA (`<dl class="dados">`) ----------
   A grade da `.campos` é para campo do POST — o contrato está escrito no
   checklist da skill ("estado, badge e botão de ação não entram nela fingindo
   ser campo"), e o cartão do mensalista é o caso limite: ele mora DENTRO do
   `<form>` do cadastro e não submete uma linha. Reusar `.campos` ali poria
   `<span>` de leitura na mesma grade dos inputs e apagaria a diferença entre o
   que se edita e o que só se lê.

   Mesmos tokens de trilha da `.campos`, de propósito: as duas grades ficam
   alinhadas quando aparecem no mesmo cartão, e a largura continua saindo do
   `:root` em vez de um px inventado aqui. */
.dados {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--campo-min), var(--campo-max)));
  gap: var(--space-3) 18px;
  margin: 0;
}
.dados dt {
  font-size: 12px;
  color: var(--text-mute);
  margin-bottom: var(--space-1);
}
/* `min-height` porque o valor pode estar vazio (o cartão do cadastro nasce em
   branco e é o JS que escreve): sem ele a linha colapsa e a grade "pula" no
   instante em que a pessoa é escolhida. */
.dados dd { margin: 0; min-height: 1.2em; word-break: break-word; }

/* Rede de segurança: numa tela de família 1/2 que declarou `conteudo-largo`
   mas ainda não migrou os campos, o `width: 100%` do input esticaria até a
   borda da janela. Escopada a `label.field` de propósito — não alcança
   toolbars, filtros nem tabelas, onde largura cheia é o certo. Dentro de
   `.campos` é redundante (a trilha já limita). */
/* ⚠️ **As duas isenções olhavam o lugar errado, e por isso nunca funcionaram.**
   Até 03/09/2026 esta regra era
   `label.field:not(.campo-largo):not(.campo-linha)` — e o contrato da faixa diz,
   três blocos acima, que o modificador vive no **WRAPPER `.campo`**, nunca no
   label (`.campos > .campo-largo`, `.campos > .campo-curto`). Medido: **65**
   sítios põem a classe no wrapper e **1** no label. Ou seja, os `:not()`
   praticamente nunca casavam, a rede de segurança pegava o label de dentro do
   `.campo-largo` e o prendia a `--campo-max`: o campo declarado LARGO saía com
   340px, o oposto exato do que a classe pede — em **22 telas**.
   Ninguém viu porque o sintoma é "estreito demais", que se parece com decisão de
   design, e porque `.campos > .campo-largo` está lá, correto, dando 698px de
   área de grade a um label que se recusava a ocupá-la.
   Achado pelo dono na 1ª sessão de navegador (passo 90: o `textarea` da persona
   não usava a largura do formulário em resolução nenhuma).
   A isenção passa a olhar o wrapper; o 2º seletor mantém de pé o único sítio que
   põe a classe no próprio label (`busca_mensagens.html`). */
.content.conteudo-largo label.field { max-width: var(--campo-max); }
.content.conteudo-largo :is(.campo-largo, .campo-linha) > label.field,
.content.conteudo-largo label.field:is(.campo-largo, .campo-linha) {
  max-width: none;
}

/* Faixa de LEITURA — vale em qualquer família. Texto de ajuda que atravessa um
   monitor de 2560px é ilegível por outro motivo que o campo largo: o olho perde
   a linha na volta. */
.content p.muted, .content .subtitle, .content .card > p { max-width: var(--medida); }
/* Prosa CURTA de apoio que usa a largura do cartão, por decisão da tela. O teto de 78ch acima é
   para o bloco de texto que se LÊ; um aviso de uma ou duas frases num cartão de 1180px quebrado
   a 78ch só obriga a rolar mais — foi a queixa do dono no E2E de `/admin/plataforma/llm`
   (14/09/2026). Opt-in por classe: o default da casa continua sendo a medida de leitura. */
.content p.muted.nota-larga, .content .card > p.nota-larga { max-width: none; }

/* Texto da ABA logo abaixo do subtítulo da ÁREA (telas de Plataforma). Com as
   pastilhas no topo os dois viraram vizinhos, e a margem default do <p> somada
   aos 22px do `.subtitle` abria 35px — o bastante para lerem como seções
   diferentes quando são o mesmo cabeçalho. */
.content .subtitle + p.muted { margin: 0 0 var(--space-5); }

/* Estado de um campo de configuração (macros de _campos.html).
   Fica ABAIXO do input, como texto de verdade — o antecessor disso era uma
   frase no `placeholder`, que some ao digitar, não pode ser copiada e é
   indistinguível de um exemplo de formato. A borda colorida à esquerda dá a
   origem do valor num relance; a cor sozinha nunca carrega o sentido, porque a
   frase ao lado já diz a mesma coisa por escrito. */
.efeito {
  margin: -9px 0 0; padding: 4px 0 0 9px;
  border-left: 3px solid var(--border);
  font-size: 12px; color: var(--text-mute); line-height: 1.5;
}
/* ⚠️ DENTRO da faixa `.campos` o recuo acima ENTRAVA no campo. Os -9px foram calculados para
   um `label.field` com os 14px de margem de baixo — e `.campos > .campo > label.field` zera essa
   margem (quem dá o respiro ali é o `.campo`). Somados, a linha "Em vigor" subia 9px por cima
   do input, e por cima do `<small>` quando havia um. Pego pelo dono no E2E de
   `/admin/plataforma/llm` (14/09/2026): "o texto abaixo dos campos está colado ao campo, e
   parte sobreposto". Vale para toda tela com `.campos`, e é por isso que mora na primitiva e
   não na tela. */
.campos > .campo > .efeito { margin-top: var(--space-1); }
.efeito strong { color: var(--text); font-weight: 600; }
.efeito .origem { color: var(--text-mute); }
.efeito .origem::before { content: "· "; }
.efeito-tenant { border-left-color: var(--success); }   /* gravado nesta propriedade */
.efeito-env    { border-left-color: var(--warning); }   /* .env global — vale para TODAS */
.efeito-env strong { color: var(--tom-mostarda-fg); }
.efeito-padrao  { border-left-color: var(--acento); }
/* padrão gravado em Plataforma → Modelos de IA (P-INF-007 leva 3): global como o .env,
   editável pela tela — a mesma cor de alerta do .env, porque vale para TODAS que herdam. */
.efeito-plataforma { border-left-color: var(--warning); }

/* Parágrafo de apoio DENTRO de uma seção de formulário, entre uma faixa `.campos` e a
   seguinte (P-INF-007 leva 3). Substitui os `style="margin:…;font-size:13px"` inline da seção
   "Comportamento do agente" da ficha do tenant — o tamanho já vem do `.muted`, e o espaço sai
   da escala. `-fim` é o último parágrafo da seção, sem margem embaixo. */
.nota-secao     { margin: var(--space-2) 0 var(--space-3); }
.nota-secao-fim { margin: var(--space-2) 0 0; }
.efeito-ausente { border-left-color: var(--border); }
.efeito-apaga   { border-left-color: var(--danger); }

/* A sugestão de prompt da ficha de pendência (P-PNL-012). Morava no placeholder — quatro
   linhas acinzentadas que pareciam o campo preenchido. Agora é texto de verdade logo abaixo
   do campo (copiável), com as quebras de linha da sugestão. NÃO é a linha `.efeito`: ela diz
   o valor que está valendo e de onde veio; esta é uma proposta que ninguém gravou. */
.prompt-sugerido {
  margin: var(--space-1) 0 0; padding-left: var(--space-2);
  border-left: 3px solid var(--border);
  font-size: 12px; color: var(--text-mute); line-height: 1.5;
  white-space: pre-line; max-width: var(--medida);
}

/* ---------- Faixa de estado do pareamento (P-TNT-002) ----------
   ⚠️ **NÃO é a linha "Em vigor" (`.efeito`), e reusá-la foi o defeito que o dono
   pegou no primeiro pareamento real (09/09/2026).** Aquela primitiva tem
   `margin-top:-9px` de propósito, porque nasce COLADA ao input que ela descreve,
   dentro de um `.campo` — o recuo negativo fecha o vão do `label`. Aqui a faixa é
   um bloco solto no fluxo do cartão, entre dois botões, e os -9px a puxavam POR
   CIMA do "Salvar número".

   É exatamente a sobreposição que o dono já tinha proibido na leva de 08/09
   (*"em tela administrativa até é aceitável, jamais em tela de usuário final"*), e
   esta é tela de usuário final: quem pareia é o tenant. Herdar a aparência sem
   herdar a POSIÇÃO era o erro — a linha vive por si, então tem espaçamento por si.

   ⚠️ Os modificadores também mudam de vocabulário, e não é cosmético: `.efeito-apaga`
   quer dizer *"este campo vai apagar o valor gravado"*, e aqui ele estava marcando
   *"conectou com o número errado"*. Classe que mente sobre o que significa é a
   próxima pessoa lendo errado. */
.estado-canal {
  margin: var(--space-4) 0 var(--space-3);
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  border-left: 3px solid var(--border);
  font-size: 13px; color: var(--text-mute); line-height: 1.5;
}
.estado-canal-ok     { border-left-color: var(--success); color: var(--text); }
.estado-canal-alerta { border-left-color: var(--danger);  color: var(--text); }
.estado-canal-neutro { border-left-color: var(--border); }

/* ---------- Pareamento do canal de saída (P-TNT-002) ----------
   O QR fica em 240px com fundo branco EXPLÍCITO: o PNG que a Evolution devolve
   é transparente, e num tema escuro os módulos pretos sobre fundo escuro deixam
   de ser legíveis pela câmera — o quadrado aparece e simplesmente não lê.
   O código é grande e espaçado porque ele é LIDO DA TELA e digitado no celular,
   às vezes por duas pessoas: o `letter-spacing` é o que separa 0/O e 1/l. */
.pareamento { margin-top: var(--space-4); }
.qr-pareamento {
  display: block; width: 240px; height: 240px;
  background: #fff; padding: var(--space-2); border-radius: 6px;
}
/* O quadrado que o servidor parou de renovar: esmaecido para ninguém apontar a
   câmera para ele. A faixa diz o que fazer; isto diz o que NÃO adianta mais.
   ⚠️ Esta classe é posta pelo JS, e o canário de classe desconhecida varre só
   TEMPLATES — se ela não existisse aqui, o esmaecimento simplesmente não
   aconteceria, sem erro no console e sem teste vermelho. É a mesma porta pela
   qual a faixa de estado chegou a produção com a classe errada em 09/09/2026. */
.qr-pareamento-expirado { opacity: .35; filter: grayscale(1); }
.codigo-pareamento {
  margin: var(--space-3) 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 30px; letter-spacing: .18em; color: var(--text);
}

/* ---------- Barra fixa Salvar/Cancelar (macro _acoes.html) ----------
   Sticky (nunca `fixed`: `fixed` ignoraria o teto do contêiner e a barra
   escaparia da coluna de conteúdo). Dentro de um .card ela SANGRA até as bordas
   do cartão — e o que ela compensa é o padding do CARTÃO, `--card-pad`, não o
   da .content. Casar os dois números à mão era a armadilha: mudar o padding do
   card deixava um filete branco de cada lado que ninguém associava à causa.
   O canário test_acoes_form_sangra_pelo_token_do_card_e_nao_por_20px_na_mao
   olha só os componentes HORIZONTAIS — os verticais (18px/14px) são espaçamento
   próprio da barra e não têm de casar com nada. */
.acoes-form {
  position: sticky; bottom: 0; z-index: 5;
  display: flex; align-items: center; gap: 10px;
  margin: 18px 0 0; padding: 12px 0;
  background: var(--bg-soft); border-top: 1px solid var(--border);
}
.card .acoes-form {
  margin: 18px calc(-1 * var(--card-pad)) calc(-1 * var(--card-pad));
  padding: 14px var(--card-pad);
  background: var(--surface);
  border-radius: 0 0 var(--radius) var(--radius);
}

/* ---------- Seção colapsável (<details class="card secao">) ----------
   Macro _secoes.html. Sem JS: o conteúdo fechado continua no DOM e é
   submetido com o form (e visto pela guarda de não-salvo). */
.card.secao > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
.card.secao > summary::-webkit-details-marker { display: none; }
.card.secao > summary::before {
  content: "▸"; color: var(--text-mute); font-size: 13px; line-height: 1;
}
.card.secao[open] > summary::before { content: "▾"; }
.card.secao > summary h2 { margin: 0; }
.card.secao > summary .subtitle { margin: 0; }
.card.secao[open] > summary { margin-bottom: 14px; }
.card.secao > summary:hover h2 { color: var(--acento); }

/* ---------- Badges / chips ---------- */
.badge {
  display: inline-block; padding: 2px 9px; border-radius: 20px;
  font-size: 12px; font-weight: 600;
  background: var(--tom-neutro-bg); color: var(--tom-neutro-fg);
}
/* Status do funil hoteleiro — um TOM por estado */
.badge.novo               { background: var(--tom-azul-bg);     color: var(--tom-azul-fg); }
.badge.em_atendimento     { background: var(--tom-areia-bg);    color: var(--tom-areia-fg); }
.badge.cotacao_enviada    { background: var(--tom-roxo-bg);     color: var(--tom-roxo-fg); }
.badge.aguardando_retorno { background: var(--tom-ambar-bg);    color: var(--tom-ambar-fg); }
.badge.reserva_criada     { background: var(--tom-teal-bg);     color: var(--tom-teal-fg); }
.badge.convertido         { background: var(--tom-verde-bg);    color: var(--tom-verde-fg); }
.badge.perdido,
.badge.bloqueado,
.badge.urgente            { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); }
.badge.excecao            { background: var(--tom-mostarda-bg); color: var(--tom-mostarda-fg); }
/* Prioridade da pendência (V060). ⚠️ As quatro CLASSES existiam no template e
   nenhuma regra existia aqui: Crítica e Baixa saíam com o mesmo cinza, e só o
   emoji distinguia — quem varre a lista por cor não via nada, e nada falhava.
   Achado da revisão cega. Os tons vêm da escala já medida acima; a ordem é a da
   gravidade, não a do alfabeto. */
.badge.prio-critica       { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); }
.badge.prio-alta          { background: var(--tom-areia-bg);    color: var(--tom-areia-fg); }
.badge.prio-media         { background: var(--tom-ambar-bg);    color: var(--warning-texto); }
.badge.prio-baixa         { background: var(--tom-neutro-bg);   color: var(--tom-neutro-fg); }
/* Situação da pendência (V060 + V061). ⚠️ A lista pintava `<span class="badge">`
   SEM classe nenhuma até 07/09/2026: os cinco estados saíam com o mesmo cinza
   do `.badge` base, e a coluna "Situação" deixava de informar de relance —
   nada falhava, e só quem olha a tela vê. Pedido do dono.
   Os tons são os MESMOS da fila de chamados (V058), e a repetição é deliberada:
   as duas telas são filas de trabalho lidas pelas mesmas pessoas, e a leitura
   pretendida é a mesma — azul = entrou, areia = mexendo, âmbar = a bola é de
   outro, verde = fechou, neutro = arquivado. Cores diferentes para o mesmo
   significado em telas irmãs custam mais que a duplicação de quatro linhas.
   ⚠️ Classe ASCII vinda de `repo_pendencias.CLASSE_STATUS`, nunca do valor do
   banco: `.badge.Concluído` seria classe não-ASCII e `.badge.Em Andamento`
   seriam duas classes, uma inventada. */
/* P-INF-006 leva 2 — onde a conversa está na cadência (detalhe e lista). */
.badge.followup-agendado  { background: var(--tom-ambar-bg);    color: var(--warning-texto); }
.badge.followup-etapa     { background: var(--tom-azul-bg);     color: var(--tom-azul-fg); }
.badge.followup-quitado   { background: var(--tom-neutro-bg);   color: var(--tom-neutro-fg); }
/* V098 (P-INF-006 leva 3) — a PAUSA de follow-up da conversa: badge na lista e no detalhe, e
   o bloco do detalhe que junta o badge, o Pausar/Retomar e o carimbo do encerramento. */
.badge.followup-pausado   { background: var(--tom-areia-bg);    color: var(--tom-areia-fg); }
/* `P-MEN-008` — o PRAZO do contrato de mensalista, ao lado do badge de status. O
   vencido em vermelho: saiu do fechamento e alguém precisa decidir. O "vence em"
   em âmbar: é antecedência, ainda dá tempo. */
.badge.contrato-vencido   { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); }
/* A origem da cotação no card (D4 do dono, `P-51` L2b): a Helena ou a recepção. */
.badge.origem-agente      { background: var(--tom-azul-bg);     color: var(--tom-azul-fg); }
.badge.origem-recepcao    { background: var(--tom-teal-bg);     color: var(--tom-teal-fg); }
.badge.contrato-vence     { background: var(--tom-ambar-bg);    color: var(--warning-texto); }
.ficha-estado { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
                margin: 0 0 var(--space-3); }
.followup-estado { margin: 0 0 var(--space-3); }
.followup-nota { margin: var(--space-2) 0 0; font-size: 13px; }
.badge.sit-pendente       { background: var(--tom-azul-bg);     color: var(--tom-azul-fg); }
.badge.sit-andamento      { background: var(--tom-areia-bg);    color: var(--tom-areia-fg); }
.badge.sit-aguardando     { background: var(--tom-ambar-bg);    color: var(--tom-ambar-fg); }
/* ⚠️ `Entregue` (V069) em TEAL, vizinho do verde e distinto dele: o código está
   no ar mas a validação não passou, e as duas situações não podem ler igual numa
   varredura de coluna. Era o único `--tom-*` ainda sem uso. */
.badge.sit-entregue       { background: var(--tom-teal-bg);     color: var(--tom-teal-fg); }
.badge.sit-concluido      { background: var(--tom-verde-bg);    color: var(--tom-verde-fg); }
.badge.sit-cancelada      { background: var(--tom-neutro-bg);   color: var(--tom-neutro-fg); }
/* Sigla do tenant (V035). UMA regra para TODAS as siglas — a sigla é conteúdo,
   nunca nome de classe: `badge {{ codigo }}` faria um tenant novo nascer sem
   estilo, e uma sigla com espaço viraria duas classes. Monoespaçada porque ela
   é lida em coluna, comparando caractere a caractere. */
.badge.hotel-tenant {
  background: var(--tom-neutro-bg); color: var(--text); letter-spacing: .5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* Escopo GLOBAL (jobs). Classe própria, e não a mesma da sigla: "roda para todas
   as propriedades" e "roda para esta" são parecidos de olhar e opostos de
   significado — se as duas pastilhas forem iguais, a coluna deixa de informar.
   Sem monoespaço de propósito: "Global" é palavra, não sigla comparada caractere
   a caractere. */
.badge.hotel-global { background: var(--tom-mostarda-bg); color: var(--tom-mostarda-fg); }
/* Colunas de classificação da lista de tenants: tipo · canal · PMS · gateway.
   Uma cor por COLUNA, nunca por valor — mesma lição da sigla acima: `badge
   {{ pms_tipo }}` faria um PMS novo nascer sem estilo. O que a cor diz é "esta
   pastilha responde a esta pergunta", e é isso que deixa a linha legível de
   relance quando são quatro pastilhas lado a lado. */
.badge.col-tipo    { background: var(--tom-roxo-bg);  color: var(--tom-roxo-fg); }
.badge.col-canal   { background: var(--tom-teal-bg);  color: var(--tom-teal-fg); }
.badge.col-pms     { background: var(--tom-azul-bg);  color: var(--tom-azul-fg); }
.badge.col-gateway { background: var(--tom-areia-bg); color: var(--tom-areia-fg); }
/* Sem PMS/gateway configurado: cinza, não vermelho — é um cadastro incompleto,
   não um defeito. */
.badge.col-pms.vazio, .badge.col-gateway.vazio,
.badge.col-tipo.vazio, .badge.col-canal.vazio {
  background: var(--tom-neutro-bg); color: var(--tom-neutro-fg); font-weight: 500;
}
/* Cap de cobrança de teste ativo (R1/R2): dinheiro na mesa, cor de aviso. */
.badge.cap-teste { background: var(--tom-mostarda-bg); color: var(--tom-mostarda-fg); }
/* Apps Meta (V070) — PREFIXADAS de propósito. `.badge.ativo` já existe e é o
   status da CONVERSA; um `.badge.inativo` solto nesta tela colidiria com ele na
   próxima vez que alguém mexesse na cor de um dos dois, e a colisão não parece
   defeito: a badge continua pintada, só que da cor da outra coisa. */
.badge.appmeta-ativo   { background: var(--tom-verde-bg);    color: var(--tom-verde-fg); }
.badge.appmeta-inativo { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); }
.badge.appmeta-neutro  { background: var(--tom-neutro-bg);   color: var(--tom-neutro-fg); }
.badge.appmeta-legado  { background: var(--tom-mostarda-bg); color: var(--tom-mostarda-fg); }
/* Status da conversa */
/* Registro de hóspedes no governo (P-02) — PREFIXADAS, pelo mesmo motivo das de
   Apps Meta acima: `pendente`, `cancelada` e `ativa` já são estado de outra coisa. */
.badge.gov-pendente     { background: var(--tom-azul-bg);     color: var(--tom-azul-fg); }
.badge.gov-bloqueada    { background: var(--tom-ambar-bg);    color: var(--tom-ambar-fg); }
.badge.gov-transmitindo { background: var(--tom-areia-bg);    color: var(--tom-areia-fg); }
.badge.gov-incerta      { background: var(--tom-mostarda-bg); color: var(--tom-mostarda-fg); }
.badge.gov-transmitida  { background: var(--tom-verde-bg);    color: var(--tom-verde-fg); }
.badge.gov-erro         { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); }
.badge.gov-cancelada    { background: var(--tom-neutro-bg);   color: var(--tom-neutro-fg); }
.badge.gov-ambiente     { background: var(--tom-roxo-bg);     color: var(--tom-roxo-fg); }
.badge.ativo, .badge.ativa { background: var(--tom-azul-bg); color: var(--tom-azul-fg); }
.badge.encerrada { background: var(--tom-neutro-bg); color: var(--tom-neutro-fg); }
/* Ação humana urgente — fundo vermelho sólido para não passar despercebido */
.badge.aguardando_humano {
  background: var(--danger);
  color: #fff;
  font-weight: 700;
  animation: badge-pulse 1.8s ease-in-out infinite;
}
@keyframes badge-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: .72; }
}
/* Contador de hand-offs pendentes na sidebar — vermelho sólido, pulsa */
.nav-badge {
  display: inline-block;
  min-width: 18px;
  padding: 0 5px;
  margin-left: 6px;
  border-radius: 9px;
  background: var(--danger);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  vertical-align: middle;
  animation: badge-pulse 1.8s ease-in-out infinite;
}
/* (o `.nav-badge[hidden]` que vivia aqui saiu: era o remendo pontual do defeito
   que a regra global `[hidden]` do topo deste arquivo agora fecha por inteiro.) */

/* ---------- Chat / histórico ---------- */
.chat { display: flex; flex-direction: column; gap: 8px; max-height: 540px; overflow-y: auto; padding: 6px; }
/* `min(74%, 62ch)`: com a conversa em família 1, 74% de uma janela de 1800px
   daria um balão de 1300px — e balão de mensagem é texto, não painel. */
.msg { max-width: min(74%, 62ch); padding: 9px 13px; border-radius: 12px; font-size: 14px; }
.msg .meta { display: block; font-size: 11px; color: var(--text-mute); margin-top: 4px; }
/* Texto formatado da mensagem (filtro `wa_texto` + a gêmea `waTexto` do poll).
   ⚠️ O `pre-wrap` fica no `.txt`, NUNCA no `.msg`: o `.msg` envolve o Jinja com
   a indentação do template, e o pre-wrap a transformaria em espaço VISÍVEL —
   toda bolha nasceria com um recuo que ninguém escreveu.
   Antes disto o painel não tinha `white-space` nenhum nas mensagens: uma
   mensagem de várias linhas chegava do WhatsApp e virava um parágrafo corrido,
   perdendo a única formatação que o hóspede tinha aplicado à mão.
   `overflow-wrap: anywhere` porque link e código não têm espaço onde quebrar. */
.msg .txt { white-space: pre-wrap; overflow-wrap: anywhere; }
/* Figurinha na bolha (04/09/2026): o ramo `sticker` FALTAVA nos dois
   renderizadores, e o arquivo ficava no disco sem nada aparecer na tela.
   Tamanho fixo pequeno porque figurinha não tem "tamanho real" para abrir —
   é assim que o WhatsApp a mostra, e é o que evita dar à recepção um clique
   que não leva a lugar nenhum. */
.msg img.fig { width: 120px; height: auto; display: block; }
/* Rótulo INTERNO de uma mídia (o título do item da biblioteca), e não a legenda
   que o hóspede recebeu. As duas viviam no mesmo `.txt` e eram indistinguíveis;
   aqui a diferença é de forma, não só de cor — colchete + apagado + menor. */
.msg .midia-rotulo {
  margin-top: var(--space-1); font-size: 12px; font-style: italic;
  color: var(--text-mute); overflow-wrap: anywhere;
}
.msg .legenda-midia, .msg .legenda-audio { margin-top: var(--space-1); }
/* P-CNV-003 (V092) — a mensagem saiu como MODELO aprovado da Meta. Mesma forma do
   rótulo interno da mídia (apagado, menor): é metadado sobre o envio, não fala. */
.msg .modelo-rotulo {
  margin-top: var(--space-1); font-size: 12px; font-style: italic;
  color: var(--text-mute); overflow-wrap: anywhere;
}
.msg code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px;
            white-space: pre-wrap; overflow-wrap: anywhere; }
.msg.cliente { align-self: flex-start; background: var(--surface-alt); border-bottom-left-radius: 3px; }
.msg.bot { align-self: flex-end; background: var(--tom-azul-bg); border-bottom-right-radius: 3px; }
.msg.humano { align-self: flex-end; background: var(--tom-areia-bg); border-bottom-right-radius: 3px; border: 1px solid var(--tom-areia-bd); }
.msg img { display: block; max-width: 100%; max-height: 320px; border-radius: 8px; cursor: pointer; }
.msg audio { display: block; width: 240px; max-width: 100%; margin: 2px 0; }

/* ---------- Busca no texto das mensagens (docs/busca-mensagens.md, V055) ---------- */
/* A célula do trecho é a única PROSA da tabela: ela recebe o teto de leitura,
   e não a tela (as outras três colunas são dado curto). */
.busca-trecho { max-width: var(--medida); white-space: normal; overflow-wrap: anywhere; }
/* O <mark> vem do servidor, já escapado. Fundo do papel "destaque neutro" — o
   par âmbar tem contraste medido e serve a texto pequeno sobre ele. */
.busca-trecho mark {
  background: var(--tom-ambar-bg); color: var(--text); padding: 0 2px; border-radius: 3px;
}

/* ---------- Tique de entrega na bolha (docs/status-entrega.md, V054) ---------- */
/* O <span> existe SEMPRE nas bolhas de saída, mesmo vazio: é ele que o poll
   preenche quando o callback da Meta chega — e ele chega depois, numa bolha já
   pintada. Vazio não ocupa espaço nem desloca o `.meta`. */
.msg .entrega { margin-left: var(--space-1); font-weight: 600; letter-spacing: -1px; }
/* `enviado`/`entregue` ficam no tom do `.meta` (é informação de canto de bolha);
   `lido` sobe para o acento; `falhou` é o único que pede atenção. */
.msg .entrega-lido   { color: var(--tom-azul-fg); }
.msg .entrega-falhou { color: var(--tom-vermelho-fg); }
/* ⚠️ O motivo da falha é TEXTO para a recepção agir, não um badge decorativo —
   por isso `--tom-vermelho-fg` (4,18:1 sobre branco, anotado ⚠️ no bloco de
   tokens) e não o `--tom-vermelho-bg`, que é fundo de pastilha. */
.msg .entrega-motivo {
  display: block; margin-top: var(--space-1); font-size: 11px;
  color: var(--tom-vermelho-fg); overflow-wrap: anywhere;
}

/* O MESMO estado, dito no card do documento (V059, P-44) — a bolha diz que a
   mensagem falhou, e o card diz que o DOCUMENTO não chegou. Enquanto o card
   afirmava "entregue" com base no carimbo do 2xx, as duas telas descreviam o
   mesmo evento de formas opostas, e quem opera não tinha como saber qual valia.
   ⚠️ A regra é separada da `.msg .entrega-falhou` de propósito: aqui é parágrafo
   de card, não canto de bolha — mesmo TOKEN (que é o que faz as duas lerem como
   o mesmo estado), tamanho e peso do contexto em que cada uma vive. */
.card > p.entrega-falhou {
  margin: 0 0 var(--space-2); color: var(--tom-vermelho-fg); font-weight: 600;
  max-width: var(--medida);
}

/* ---------- Lista de conversas viva (docs/conversas-lista-viva.md) ---------- */
/* Linha do título: h1+subtítulo à esquerda, controle de auto-refresh à direita.
   O controle vive FORA do card de filtros de propósito (com ele dentro, o foco
   no próprio <select> fazia `ocupado()` pular todo tique — defeito medido no
   FALA) e fora da URL: não é filtro, não entra no submit. */
.titulo-linha { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-4); flex-wrap: wrap; }
#ar-barra { margin: var(--space-2) 0 var(--space-4); }
#ar-barra label { display: flex; align-items: center; gap: 6px; margin: 0; }
#ar-custom { width: 72px; }
/* O pulso ↻ é só opacidade (o JS a anima: .15 desligado · .35 armado · 1 no flash) */
#ar-pulso { opacity: .15; transition: opacity .2s; }
/* Coluna de ações: encosta na direita e não cresce com a tabela */
td.col-acoes { width: 1%; white-space: nowrap; }
/* Respiro do <details> "Filtros avançados" dentro do card de filtros — no guia
   ele aparece como style= inline, que a catraca desta tela (teto 0) proíbe. */
.filtros > details { margin-top: var(--space-2); }
.filtros details > .toolbar { margin-top: var(--space-2); }

/* Quem falou por último — badge na lista e no quick view. A CLASSE usa o valor
   do BANCO (`fonte-bot`), não o rótulo traduzido ("agente"): o rótulo tem ç/ã,
   e classe não-ASCII é convite a typo silencioso; a tradução vive uma vez só,
   em admin/deps.py::ROTULO_FONTE. Pares de TOM, nunca hex cru (os do FALA eram
   hex fora do sistema — reprovariam o canário de cor). `teal` para o agente em
   vez de `verde`: o par verde está anotado ❌ para texto (2,93:1). */
.badge.fonte-cliente { background: var(--tom-areia-bg); color: var(--tom-areia-fg); }
.badge.fonte-bot     { background: var(--tom-teal-bg);  color: var(--tom-teal-fg); }
.badge.fonte-humano  { background: var(--tom-azul-bg);  color: var(--tom-azul-fg); }

/* Quick view (👁): drawer lateral da lista. Prefixo `qv-` OBRIGATÓRIO: `.msg`,
   `.txt` e `.meta` já significam a bolha do chat do detalhe, e reusar aqueles
   nomes faria a espiada herdar o layout do chat inteiro. Nenhuma cor nova —
   tudo sai dos tokens existentes. O estado escondido é a regra global
   `[hidden]` do topo do arquivo. */
#qv-fundo { position: fixed; inset: 0; background: var(--veu); z-index: 40; }
#quick-view {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 92vw);
  background: var(--surface); border-left: 1px solid var(--border);
  box-shadow: -8px 0 24px rgba(var(--escuro-2-canais), .18); z-index: 41;
  display: flex; flex-direction: column;
}
#quick-view > header {
  display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3) 14px;
  border-bottom: 1px solid var(--border);
}
/* O título empurra os botões para a direita — dispensa o <span> espaçador */
#qv-titulo { flex: 1; }
#qv-corpo { flex: 1; overflow-y: auto; padding: var(--space-3) 14px; }
.qv-msg { margin-bottom: 10px; }
.qv-meta { display: flex; align-items: center; gap: 6px; font-size: 12px; margin-bottom: 2px; }
/* pre-wrap no .qv-txt (nunca no .qv-msg): a quebra vem do conteúdo, não do template */
.qv-txt {
  white-space: pre-wrap; word-break: break-word; font-size: 13.5px; line-height: 1.45;
  background: var(--bg-soft); border-radius: 8px; padding: 6px 9px;
}
.qv-cliente .qv-txt { background: var(--bg-soft); }
.qv-bot .qv-txt, .qv-humano .qv-txt { background: var(--tom-azul-bg); color: var(--tom-azul-fg); }

/* ---------- Diversos ---------- */
.flash { padding: 12px 16px; border-radius: var(--radius); margin-bottom: 18px; font-size: 14px; }
.flash.ok   { background: var(--tom-verde-bg);    color: var(--tom-verde-fg);    border: 1px solid var(--tom-verde-bd); }
/* ⚠️ `erro`, não `err`. A regra chamava-se `.flash.err` e 12 mensagens em três
   telas de configuração escreviam `class="flash erro"` — num código todo em
   pt-BR, é a palavra que a mão alcança. Resultado: doze erros renderizavam com
   o padding do `.flash` e MAIS NADA — sem fundo vermelho, sem borda, sem cor de
   texto. "O intervalo não foi salvo" e "Não foi possível copiar" apareciam com
   cara de observação. Nada quebrava, nada logava; só a informação sumia.
   O nome canônico passou a ser o que a mão alcança, e o canário
   `test_nenhuma_classe_de_template_falta_no_css` impede a terceira grafia. */
.flash.erro { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); border: 1px solid var(--tom-vermelho-bd); }
/* Aviso neutro (informação, não resultado): o mesmo tom azul das pastilhas.
   Vivia como `style="background:#E8F0F7;…"` inline em três telas — a quarta
   cópia do par azul, e a única fora do CSS. */
.flash.info { background: var(--tom-azul-bg); color: var(--tom-azul-fg); border: 1px solid var(--tom-azul-bd); }
/* AVISO ≠ ERRO: "isto vai mexer no acesso de 12 pessoas" não é falha, é
   consequência — pintá-lo de vermelho gasta o vermelho, que é o que faz o erro
   de verdade ser visto. Nasceu tarde (29/08/2026) porque a única tela que
   precisava dele o escreveu em `style=` inline, e caixa inline não pede nome.
   Texto em `--warning-texto`, não `--warning`: 5,81:1 contra 3,11:1, e aqui é
   parágrafo. */
.flash.aviso { background: var(--tom-ambar-bg); color: var(--warning-texto); border: 1px solid var(--tom-ambar-bd); }
/* Lista DENTRO de um aviso (os usuários divergentes de perfis.html): vivia num
   `style="margin:6px 0 8px 18px"` inline. Por classe, e não `.flash ul`, para não
   mexer nas listas de erro das telas de configuração. */
.flash-lista { margin: var(--space-2) 0 var(--space-2) var(--space-4); }
/* O "?" no título da tela ou do grupo (o subtítulo longo virou painel, `P-TNT-004`):
   o botão do `_ajuda.html` foi feito para o rótulo de campo, e no título precisa de ar. */
.content :is(h1, h2) > .ajuda { margin-left: var(--space-2); }
.empty { text-align: center; color: var(--text-mute); padding: 40px 20px; }
.muted { color: var(--text-mute); font-size: 13px; }
/* Pastilhas de NAVEGAÇÃO — vêm ANTES do <h1> da tela (docs/design-de-telas.md
   §"Pastilhas acima do título"). Por isso a margem de baixo separa a barra do
   TÍTULO, e não do conteúdo: quem dá a folga final é o `.subtitle`. */
.pill-nav { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: 0 0 var(--space-4); }
.pill-nav a { padding: 7px 14px; border-radius: 20px; background: var(--surface); border: 1px solid var(--border); font-size: 13.5px; color: var(--escuro); }
.pill-nav a.active { background: var(--escuro); color: #fff; border-color: var(--escuro); }
hr { border: none; border-top: 1px solid var(--border); margin: 22px 0; }

/* ---------- Paginação das listas (macros em _paginacao.html) ---------- */
.galeria-resumo { margin: 8px 0 12px; }
.paginacao { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: center; margin: 22px 0 8px; }
.paginacao a, .paginacao .atual, .paginacao .desabilitado { padding: 6px 11px; border-radius: 8px; border: 1px solid var(--border); font-size: 13.5px; text-decoration: none; color: var(--escuro); background: var(--surface); }
.paginacao .atual { background: var(--escuro); color: #fff; border-color: var(--escuro); font-weight: 600; }
.paginacao .desabilitado { opacity: .4; }
.paginacao .reticencias { padding: 6px 4px; color: var(--text-mute); }
code.k { background: var(--surface-alt); padding: 1px 6px; border-radius: 4px; font-size: 13px; }

/* ---------- Login ---------- */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, var(--escuro), var(--escuro-2)); }
.login-card { background: #fff; border-radius: 14px; padding: 38px 34px; width: 360px; box-shadow: 0 12px 40px rgba(0,0,0,.25); }
.login-card .logo { font-size: 26px; font-weight: 700; color: var(--escuro); text-align: center; margin-bottom: 4px; }
.login-card .logo b { color: var(--destaque); }
.login-card .sub { text-align: center; color: var(--text-mute); font-size: 13px; margin-bottom: 24px; }
.login-card .btn { width: 100%; justify-content: center; margin-top: 6px; }

/* ---------- Gravador ao vivo (áudio/foto/vídeo) ---------- */
.gravador-botoes { display: flex; gap: 8px; margin: 4px 0 14px; flex-wrap: wrap; }
.gravador-painel { border: 1px dashed var(--border); border-radius: var(--radius); padding: 14px; margin: 4px 0 14px; background: var(--surface-alt); }
.gravador-live { width: 100%; max-width: 360px; border-radius: 8px; background: #000; display: block; }
.gravador-timer { font-weight: 600; color: var(--escuro); }
.gravador-timer.aviso { color: var(--danger); }
.gravador-previa img, .gravador-previa video { max-width: 100%; border-radius: 8px; display: block; margin-bottom: 8px; }
.gravador-previa audio { width: 100%; }
.gravador-erro { color: var(--danger); font-size: 13px; margin-top: 6px; }

/* ---------- Lista reordenável (ordem de oferta dos quartos) ---------- */
.lista-ordem { list-style: none; margin: 0 0 10px; padding: 0; max-width: 520px; }
.lista-ordem li { display: flex; align-items: center; justify-content: space-between; gap: 10px; background: var(--surface-alt); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; margin-bottom: 6px; }
.lista-ordem li .pos { color: var(--text-mute); font-size: 13px; min-width: 20px; }
.lista-ordem li button { padding: 2px 9px; line-height: 1.4; }

/* ---------- Modal (<dialog> nativo) — 1º uso: biblioteca de mídia (V033) ---------- */
/* `display:flex` no <form> é o que faz o corpo rolar com header/footer fixos:
   sem ele o modal cresce além da viewport e o botão Salvar sai da tela. */
.modal { border: none; border-radius: var(--radius); padding: 0; width: min(680px, 94vw); max-height: 88vh; box-shadow: 0 10px 40px rgba(var(--escuro-2-canais), 0.28); color: var(--text); }
.modal::backdrop { background: rgba(var(--escuro-2-canais), 0.42); }
.modal > form { display: flex; flex-direction: column; max-height: 88vh; margin: 0; }
.modal header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 20px; border-bottom: 1px solid var(--border); }
.modal header h2 { margin: 0; font-size: 18px; color: var(--escuro); }
.modal .modal-corpo { padding: 18px 20px; overflow-y: auto; }
/* Modal LARGO (`modal(..., largo=true)`): a grade de preço por dia do Tarifário tem nove
   colunas e não cabe nos 680px — espremida, ela rolaria de lado dentro do modal. */
.modal.modal-largo { width: min(980px, 94vw); }
/* O modal usa a MESMA faixa `.campos` das telas — um segundo sistema de
   distribuição (era `.form-row`, com `flex: 1` em tudo) dava a mesma largura a
   uma data e a um endereço, e o formulário do modal deixava de parecer do mesmo
   painel. Só a trilha mínima encolhe: em 640px úteis, 260px daria duas colunas
   e sobrariam três campos curtos brigando por duas. */
.modal .campos { --campo-min: 190px; }
.modal .modal-corpo label.field:last-child { margin-bottom: 0; }
.modal footer { display: flex; gap: 10px; padding: 14px 20px; border-top: 1px solid var(--border); background: var(--bg-soft); }

/* Modal SEM <form>: a coluna flex vem do próprio <dialog>, senão o corpo não
   rola e o rodapé sai da viewport. 1º uso: o picker da biblioteca no chat.
   ⚠️ O `[open]` NÃO é decoração — é o que mantém o diálogo FECHADO. A regra do
   modal com form põe o flex no `<form>`, então o `dialog:not([open]){display:none}`
   do user-agent segue valendo no elemento. Aqui o flex está no próprio
   `<dialog>`, e declaração de autor vence a do UA por ORIGEM: sem o `[open]` o
   picker da biblioteca aparecia no meio do detalhe, com a conversa aberta e o
   diálogo fechado — e `close()` não o escondia, porque a regra não olhava o
   atributo. Mesmo mecanismo do `[hidden]{display:none!important}` no topo desta
   folha. (Achado nº 1 da revisão cega da fatia de tela, 02/09/2026.) */
.modal.modal-simples[open] { display: flex; flex-direction: column; }

/* ---------- Trilho de conversas no detalhe (G8) ----------
   Grid de três colunas: trilho | alça | conversa.
   ⚠️ O `minmax(0, 1fr)` da última é OBRIGATÓRIO — sem o `0`, conteúdo largo
   (tabela de cotação, vídeo, bolha comprida) estoura a grid em vez de rolar
   dentro dela. */
.conversa-grid {
  display: grid; gap: var(--space-1); align-items: start;
  grid-template-columns: var(--conv-w) var(--conv-alca) minmax(0, 1fr);
}
.conv-list {
  position: sticky; top: var(--space-2); max-height: var(--conv-alt);
  overflow-y: auto; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
}
/* Fundo OPACO obrigatório: sticky sobre conteúdo que rola por baixo. */
.conv-topo-fixo {
  position: sticky; top: 0; z-index: 1; background: var(--surface);
  border-bottom: 1px solid var(--border); padding: var(--space-2);
}
/* O campo de busca herda `width:100%` da regra base de campo (input[type=search]
   está na lista branca) — declarar de novo aqui criaria uma 2ª regra de largura
   de input na folha, que é exatamente o que os canários do contrato de campo
   proíbem para não haver duas verdades sobre a largura. */
.conv-busca { margin: var(--space-2) 0; }
.conv-chips { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.conv-item {
  display: block; padding: var(--space-2); text-decoration: none;
  color: var(--text); border-bottom: 1px solid var(--border);
  /* A barra do item ativo vive AQUI, transparente, e não só na `.ativo`:
     senão trocar de conversa deslocaria o conteúdo do trilho inteiro em 3px. */
  border-left: 3px solid transparent;
}
.conv-item:hover { background: var(--linha-hover); }
/* ⚠️ O item ATIVO tem de se separar do hover A OLHO NU, e não se separava:
   `--bg-soft` (#FAFAFA) sobre branco dá **1,02:1** e o hover `--linha-hover`
   (#FCFDFF) dá 1,01:1 — dois cinzas quase brancos indistinguíveis entre si,
   num trilho de 25 linhas. Achado pelo dono na 1ª sessão de navegador (02/09):
   "mal dá para identificar qual conversa está ativa".
   O conserto não é tinta mais forte — nenhum fundo claro passa de ~1,2:1 contra
   branco, e depender de percepção de contraste é o que falhou. É FORMA: uma
   barra de 3px em `--escuro`, **6,88:1** sobre branco, que se lê antes de o
   olho comparar fundos. A tinta `--tom-azul-bg` entra junto porque a linha
   inteira também precisa de corpo, e o par tinta+`--escuro` já é medido e
   sancionado pela paleta (`--tom-azul-fg` É `--escuro`, 5,98:1).
   O `.conv-prev` vai junto: `--text-mute` sobre a tinta cai a 4,02:1, abaixo
   do piso de 4,5 — sobre fundo tingido quem serve é o fg do próprio tom. */
.conv-item.ativo {
  background: var(--tom-azul-bg);
  border-left-color: var(--escuro);
}
.conv-item.ativo .conv-nome { color: var(--escuro); }
.conv-item.ativo .conv-prev { color: var(--tom-azul-fg); }
.conv-topo { display: flex; justify-content: space-between; gap: var(--space-2); align-items: baseline; }
.conv-nome { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-hora { font-size: 11px; color: var(--text-mute); flex: none; }
.conv-prev { display: block; margin-top: 2px; font-size: 12px; color: var(--text-mute); }
.conv-texto { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-humano { font-size: 10px; }
.conv-mais { display: block; width: 100%; margin: var(--space-2) 0; }
.conv-resize {
  position: sticky; top: var(--space-2); height: var(--conv-alt);
  cursor: col-resize; border-radius: var(--radius);
}
.conv-resize:hover, .conv-resize:focus-visible { background: var(--border); }
/* Sem isto, arrastar a divisória SELECIONA o texto do chat ao lado. */
body.conv-arrastando { user-select: none; cursor: col-resize; }
.conv-titulo { margin-top: var(--space-2); }
.conv-itens { display: block; }
.conv-voltar-trilho { display: inline-block; font-size: 12px; }
/* A coluna da conversa dentro da grid do trilho: `min-width:0` pelo mesmo
   motivo do `minmax(0,1fr)` — sem ele um filho largo empurra a coluna. */
.conv-main { min-width: 0; }
.conversa-chat { min-width: 0; }
.conversa-lado { min-width: 0; }
/* O card Cotações agrupado por CENÁRIO (`P-51` L2b, `_cotacoes_card.html`). Os 11 `style=` que
   o card tinha viraram estas regras e as `.nota-secao*` de sempre. */
.cenario { margin-bottom: var(--space-4); }
.cenario-titulo { font-weight: 600; margin-bottom: var(--space-2); }
.cotacao-linha { margin-bottom: var(--space-3); }
.cotacao-linha summary,
.cenario-mais > summary,
.cenarios-anteriores > summary { cursor: pointer; }
.cenario-mais { margin-bottom: var(--space-3); }
.cenario-mais[open] > summary,
.cenarios-anteriores[open] > summary { margin-bottom: var(--space-2); }
/* A 2ª cópia do "← voltar", só na faixa estreita (ver o comentário no template). */
.conv-voltar-estreito { display: none; }

/* As duas colunas do detalhe. Substituiu `form-row` + `style="flex:2"/"flex:1"`. */
.conversa-colunas {
  display: grid; gap: var(--space-4); align-items: start;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}

/* ⚠️ Esta @media NÃO toca em `--sidebar-w` — ver o aviso no `:root`
   (canário: test_nenhuma_media_query_mexe_na_largura_da_barra). Alinhada ao
   breakpoint da navegação colapsável. */
@media (max-width: 1099px) {
  .conversa-grid { display: block; }
  .conv-list, .conv-resize { display: none; }
  .conversa-colunas { grid-template-columns: minmax(0, 1fr); }
  .conv-voltar-estreito { display: inline-block; }
}

/* ---------- Picker da biblioteca dentro do chat (G10) ---------- */
.bib-filtros {
  display: grid; gap: var(--space-3); padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.bib-filtros label.field { margin-bottom: 0; }
.bib-lista {
  display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
/* A linha inteira é o alvo do clique (é um <label>): mirar a caixinha de 13px
   num modal de escolha rápida é o gesto que mais erra. */
.bib-item {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2); border: 1px solid var(--border);
  border-radius: var(--radius); cursor: pointer; background: var(--surface);
}
.bib-item:hover { background: var(--bg-soft); }
.bib-item:has(input:checked) { border-color: var(--acento); background: var(--bg-soft); }
.bib-item img { width: 40px; height: 40px; object-fit: cover; border-radius: 6px; flex: none; }
/* O seletor em modo ÚNICO (follow-up, leva 1b): rádio no lugar da caixinha, mesmo item. */
.bib-item input[type=radio] { flex: none; margin: 0; }
.bib-glifo { font-size: 22px; width: 40px; text-align: center; flex: none; }
.bib-titulo { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bib-lista .muted { grid-column: 1 / -1; margin: 0; }

/* ---------- Anexos escolhidos no compositor (G10) ---------- */
.compositor-acoes { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-1) 0 0; }
/* P-CNV-003 — a faixa "janela de 24 h fechada" sobre o compositor: o aviso e o atalho
   para o modelo aprovado na mesma linha, quebrando quando não cabe. ⚠️ Sem `display`
   próprio em estado nenhum que não seja o flex: o `[hidden]` global do topo vence. */
.janela-fechada { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
/* O modal do modelo: os campos das variáveis na faixa do modal, e a prévia como uma
   bolha de saída — é o que o hóspede vai ler, então tem a cara de mensagem. */
.modelo-variaveis { display: grid; gap: 0 var(--space-3);
                    grid-template-columns: repeat(auto-fill, minmax(var(--campo-min, 190px), 1fr)); }
.modelo-previa { margin-top: var(--space-2); }
.modelo-previa-txt {
  margin-top: var(--space-1); padding: var(--space-2) var(--space-3);
  background: var(--tom-areia-bg); border: 1px solid var(--tom-areia-bd); border-radius: 8px;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.modelo-previa-botoes { margin-top: var(--space-1); font-size: 13px; color: var(--text-mute); }
.anexos-escolhidos { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-2) 0 0; }
.anexo-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 6px 3px 10px; border-radius: 20px;
  background: var(--tom-neutro-bg); color: var(--tom-neutro-fg);
  font-size: 12px; font-weight: 600;
}
.anexo-x {
  border: none; background: none; cursor: pointer; padding: 0 2px;
  font: inherit; color: inherit; line-height: 1;
}
.anexo-x:hover { color: var(--danger); }

/* ---------- Cartão do anexo e progresso do envio (port fala:CNV-036) ----------
   O chip ganha tipo · tamanho · origem e o selo do limite do WhatsApp; o `flex-wrap`
   deixa o selo descer de linha no celular em vez de estourar a largura. */
.anexo-chip { flex-wrap: wrap; max-width: 100%; }
.anexo-meta { font-weight: 400; color: var(--text-mute); }
.anexo-selo {
  padding: 1px 6px; border-radius: 10px; border: 1px solid transparent;
  font-size: 11px; font-weight: 600;
}
.anexo-selo-ok { background: var(--tom-verde-bg); color: var(--tom-verde-fg); border-color: var(--tom-verde-bd); }
.anexo-selo-alerta { background: var(--tom-ambar-bg); color: var(--tom-ambar-fg); border-color: var(--tom-ambar-bd); }
.anexo-selo-recusa { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); border-color: var(--tom-vermelho-bd); }
.envio-progresso {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin: var(--space-2) 0 0;
}
.envio-progresso progress { flex: 1 1 12rem; max-width: 100%; height: 8px; accent-color: var(--acento); }
.envio-progresso-txt { font-size: 13px; font-variant-numeric: tabular-nums; }
.envio-progresso-aviso { flex-basis: 100%; font-size: 12px; }

/* ---------- Respostas rápidas do compositor (P-CNV-005, port FALA CNV-023) ----------
   O popup do "/" e do ⚡. Abre ACIMA da caixa, ancorado na CAIXA (largura dela),
   não no caret. Absoluto e não popover+anchor-name: anchor-name exigiria
   `style=` inline (catraca) e só existe em Chromium. `.compositor-caixa` é o
   wrapper posicionado (o nome `.compositor-acoes` já era da faixa de botões).
   `.abaixo` é o flip, ligado por JS quando não há espaço acima. Cores só por
   token de papel: o par selecionado é o MESMO do `.conv-item.ativo`. */
.compositor-caixa { position: relative; }
.respostas-rapidas-pop {
  position: absolute; left: 0; right: 0; bottom: calc(100% + var(--space-1)); z-index: 30;
  margin: 0; padding: var(--space-1);
  max-height: min(280px, 40vh); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.respostas-rapidas-pop.abaixo { bottom: auto; top: calc(100% + var(--space-1)); }
.respostas-rapidas-opt {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-2) var(--space-3); border-radius: 6px;
  border-left: 3px solid transparent; cursor: pointer; font-size: 14px;
}
.respostas-rapidas-opt:hover { background: var(--surface-alt); }
.respostas-rapidas-opt[aria-selected="true"] { background: var(--tom-azul-bg); border-left-color: var(--acento); }
.respostas-rapidas-atalho { font-weight: 600; color: var(--tom-azul-fg); }
.respostas-rapidas-atalho::before { content: "/"; opacity: .6; }
.respostas-rapidas-previa {
  color: var(--text-mute); font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- Biblioteca de mídia ---------- */
.midia-thumb img { width: 64px; height: 64px; object-fit: cover; border-radius: 6px; border: 1px solid var(--border); display: block; background: var(--surface-alt); }
/* `.midia-acoes` foi removida em ago/2026: a biblioteca passou a usar
   `.acoes-lista` (abaixo), como as demais listas do painel. */

/* ---------- Ações de linha: primária + menu "⋮" (popover nativo) ----------
   Componente: templates/_acoes_lista.html · docs/ui-acoes-de-linha.md
   ⚠️ Não confundir com `.acoes-form` (barra Salvar/Cancelar de formulário, bem
   acima): nomes vizinhos, papéis opostos. */
.acoes-lista { display: flex; gap: 6px; align-items: center; justify-content: flex-end; }
.acoes-lista form { margin: 0; }
/* `.btn` na frente de propósito: `.btn.sm` tem especificidade 0,2,0 e venceria
   uma classe sozinha — o padding do "⋮" voltaria ao do botão de texto. */
.btn.acoes-kebab { padding: 5px 9px; line-height: 1; font-size: 15px; }

/* O menu está na TOP LAYER (é `popover`): NÃO ganha `position` nem `z-index`.
   Um `position:absolute` aqui reintroduz exatamente o recorte do
   `.table-wrap { overflow:hidden }` que o popover existe para resolver, e ele
   volta cortado na última linha da coluna da direita — onde ninguém testa. */
.menu-acoes {
  border: 1px solid var(--border); border-radius: var(--radius); padding: 6px;
  background: var(--surface); color: var(--text); min-width: 200px;
  box-shadow: 0 10px 40px rgba(var(--escuro-2-canais), 0.28);
}
.menu-acoes form { margin: 0; }
.menu-acoes .menu-item {
  display: block; width: 100%; text-align: left; white-space: nowrap;
  padding: 7px 10px; border: none; border-radius: 6px; background: none;
  font: inherit; font-size: 14px; color: var(--text); cursor: pointer; text-decoration: none;
}
.menu-acoes .menu-item:hover:not(:disabled) { background: var(--bg-soft); }
/* Destrutivo = COR DA PALAVRA, nunca `.btn.danger`: um bloco vermelho dentro de
   um menu de três itens vira o item mais chamativo da linha. */
.menu-acoes .menu-item.perigo { color: var(--danger); }
.menu-acoes .menu-item.perigo:hover:not(:disabled) { background: var(--tom-vermelho-bg); }

/* Sem anchor positioning, o popover cai no default do navegador: folha
   centralizada na viewport. Aí o título é a única coisa que diz de qual linha o
   menu é, e o backdrop escurecido é o que separa a folha da tabela atrás. */
.menu-acoes-titulo {
  margin: 2px 6px 6px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
  font-size: 12px; font-weight: 600; color: var(--text-mute);
}
.menu-acoes::backdrop { background: rgba(var(--escuro-2-canais), 0.42); }

/* ⚠️ `block-end`, JAMAIS `bottom`. A gramática de `position-area` proíbe
   misturar palavra física com lógica (`span-inline-start`), e o valor inválido
   está DENTRO da condição que o guarda: o erro não degrada, apaga a feature em
   silêncio, em todo navegador, sem aviso e sem teste vermelho. O sintoma é o
   menu centralizado — o MESMO sintoma de esquecer o par explícito
   anchor-name/position-anchor (que vai inline no template). Cada armadilha
   esconde a outra; prova é geometria medida, nunca captura de tela.
   O @supports fica mesmo com Chromium 129+/Firefox 147+/Safari 26+ já
   suportando: ainda há navegador corporativo travado em versão antiga, e o que
   ele guarda é a diferença entre "menu no lugar certo" e "menu no meio da tela",
   não entre funcionar e não funcionar. */
@supports (position-area: block-end span-inline-start) {
  .menu-acoes {
    position-area: block-end span-inline-start;
    position-try-fallbacks: flip-block, flip-inline;
    inset: auto; margin: 4px 0 0;
  }
  .menu-acoes::backdrop { background: transparent; }
  .menu-acoes-titulo { display: none; }
}

/* ── Estágio do funil editável no detalhe da conversa (G14) ───────────────────
   O badge VIRA o gatilho: o estado e o controle no mesmo pixel, sem linha nova
   no cabeçalho. Herda a cor do estado das regras `.badge.<status>` acima — por
   isso o botão traz as DUAS classes, e aqui só se desfaz o que o <button> traz
   de fábrica (borda, fonte do sistema, cursor de texto).

   ⚠️ O par anchor-name/position-anchor mora AQUI, e não inline como em
   `_acoes_lista.html`. A diferença não é estilo: lá o nome da âncora sai da
   CHAVE DA LINHA e só o servidor sabe escrevê-lo; aqui há exatamente um chip
   por página, então o nome é constante e cabe na folha — e a catraca de
   `style=` da tela não sobe por causa de decisão que é do CSS. */
.funil-chip {
  border: none; font-family: inherit; cursor: pointer;
  line-height: inherit; anchor-name: --funil-anc;
}
.funil-chip:hover { filter: brightness(0.95); }

/* Mesmo tratamento visual do `.menu-acoes` (é o mesmo gesto: um painel que abre
   ancorado num gatilho da linha), com largura de formulário em vez de menu. */
.funil-pop {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-3); background: var(--surface); color: var(--text);
  min-width: var(--campo-min);
  box-shadow: 0 10px 40px rgba(var(--escuro-2-canais), 0.28);
}
.funil-pop form { margin: 0; }
.funil-pop .campo { margin-bottom: var(--space-3); }
.funil-pop::backdrop { background: rgba(var(--escuro-2-canais), 0.42); }

/* Mesma armadilha do `.menu-acoes`: sem o par explícito de ancoragem — ou com
   `bottom` no lugar de `block-end` — o popover não degrada, ele abre
   CENTRALIZADO na viewport, longe do chip que o pediu. */
@supports (position-area: block-end span-inline-start) {
  .funil-pop {
    position-anchor: --funil-anc;
    position-area: block-end span-inline-end;
    position-try-fallbacks: flip-block, flip-inline;
    inset: auto; margin: 4px 0 0;
  }
  .funil-pop::backdrop { background: transparent; }
}

/* ── Nome de contato ainda NÃO confirmado (docs/nome-perfil-contato.md) ───────
   Vem do perfil do WhatsApp: a pessoa escreveu, ninguém confirmou. O nome é
   mostrado — na tela ele é útil, quem olha entende sozinho o que é "Desentope
   Tudo 24h" — mas precisa PARECER palpite, senão a recepção liga chamando
   alguém pelo nome errado.
   ⚠️ **Aqui viveu uma decisão que a tela desmentiu.** Este comentário dizia, em
   03/09/2026: "sublinhado pontilhado e NÃO cor/itálico — cor sozinha some para
   quem não a distingue e itálico se confunde com ênfase". Os dois argumentos
   estavam certos isoladamente e a conclusão estava errada, porque o sublinhado
   pontilhado é o **mesmo desenho que o Word usa para palavra fora do
   dicionário**: quem o vê aprendeu a ignorá-lo. O dono, executando a P-04,
   levou um tempo para achar qual nome era confirmado e não achou — que é o
   defeito exato que a marca existia para evitar.
   O que fica é o padrão do FALA, e ele não é "cor sozinha": são DOIS canais
   somados, e é isso que responde à objeção antiga. Confirmado = cor da paleta,
   normal (no `<a>` da lista, o próprio `--acento`). Não confirmado = **itálico
   + cinza**, forma e cor ao mesmo tempo — em preto e branco o itálico sobrevive,
   e para quem não distingue cinza de azul o itálico também.
   `--text-mute` sobre branco = **4,63:1**, acima do piso de 4,5.
   ⚠️ No item ATIVO do trilho a tinta faz `--text-mute` cair a 4,02:1, e a regra
   de `.conv-item.ativo .conv-nome` (mais específica) leva a cor para `--escuro`
   — ali o **itálico é o canal que sobra**, e basta: a linha ativa é uma só e já
   está sob o olho; o que precisa funcionar de relance é a comparação ENTRE
   linhas. Nenhum hex novo entra aqui só para essa linha.
   O `title` da macro continua sendo quem EXPLICA; isto só marca que há o que
   explicar. */
.nome-perfil {
  font-style: italic;
  color: var(--text-mute);
  cursor: help;
}

/* ── Busca de cliente no formulário de contrato (V048) ───────────────────────
   O resultado é uma lista de BOTÕES, não um <datalist>: o datalist não carrega
   o id da opção, e casar a escolha de volta por texto escolheria o cliente
   errado quando dois têm o mesmo nome — num formulário que gera boleto.
   Cor nenhuma literal aqui: só os tokens de papel, como manda o canário de
   coerência (tests/test_paleta_marca.py). */
.busca-resultados {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
  margin-top: 6px;
  max-height: 240px;
  overflow-y: auto;
}
.busca-item {
  text-align: left;
  padding: 8px 12px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.busca-item:last-child { border-bottom: 0; }
.busca-item:hover,
.busca-item:focus-visible { background: var(--linha-hover); color: var(--acento); }

/* ⚠️ `.campo-com-acao` foi REMOVIDA em 05/09/2026, e o motivo fica registrado
   porque a tentação de recriá-la volta toda vez que alguém quiser um botão ao
   lado de um campo.

   Ela punha o botão "Cadastrar cliente" ao lado da busca de mensalista com
   `align-items: end`, para alinhar pela base do input. Só que o item do flex era
   o `label.field` INTEIRO — e o label tinha um `<small>` de ajuda embaixo do
   campo. O botão então alinhava com o fim do TEXTO DE AJUDA, uns 20px abaixo da
   caixa: exatamente o "botão desalinhado do campo de busca" que o dono relatou.
   Alinhar por `align-items` sobre uma caixa de altura variável é frágil por
   construção, e o conserto certo não era outro valor: era tirar o par
   campo+botão do formulário e pôr a busca num `<dialog>`
   (`_cliente_picker.html`). Regra que sobrou sem uso reprova o canário de
   classes desconhecidas, então ela sai de vez. */

/* ══════════════════════════════════════════════════════════════════════════
   Suporte e testes guiados (V058)
   ══════════════════════════════════════════════════════════════════════════ */

/* Situação do chamado. Os tons saem da mesma paleta de PAPEL do resto do painel
   — nenhum hex novo, nenhum `rgba()` da marca solto. A leitura pretendida é:
   azul = chegou, areia = mexendo, âmbar = a bola é sua, verde = fechou,
   neutro = arquivado. */
.badge.aberto             { background: var(--tom-azul-bg);     color: var(--tom-azul-fg); }
.badge.em_analise         { background: var(--tom-areia-bg);    color: var(--tom-areia-fg); }
.badge.aguardando_cliente { background: var(--tom-ambar-bg);    color: var(--tom-ambar-fg); }
.badge.resolvido          { background: var(--tom-verde-bg);    color: var(--tom-verde-fg); }
.badge.cancelado          { background: var(--tom-neutro-bg);   color: var(--tom-neutro-fg); }

/* ---- A conversa do chamado ---- */
.chamado-thread { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-5); }

.chamado-msg {
  border: 1px solid var(--border);
  border-left: 3px solid var(--acento);
  border-radius: var(--radius);
  padding: var(--space-3);
  background: var(--bg-soft);
}
/* A nossa resposta se distingue pela BORDA, não pelo lado: alinhar mensagem à
   direita é idioma de aplicativo de bate-papo e aqui atrapalha a leitura de um
   relato que pode ter dez linhas. */
.chamado-msg.suporte { border-left-color: var(--destaque); background: var(--surface); }

.chamado-msg-cab { display: flex; gap: var(--space-2); align-items: baseline; margin-bottom: var(--space-2); }
.chamado-msg-texto { white-space: pre-wrap; }

/* ---- Colar print ---- */
.colar-area {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 88px;
  padding: var(--space-4);
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  background: var(--bg-soft);
  color: var(--text-mute);
  cursor: pointer;
  text-align: center;
}
/* O foco tem de ser VISÍVEL: sem foco não há evento `paste`, então o anel é a
   única coisa que diz à pessoa que o Ctrl+V vai funcionar agora. */
.colar-area:focus-visible { outline: 2px solid var(--acento); outline-offset: 2px; }
.colar-area.arrastando { border-color: var(--acento); background: var(--tom-azul-bg); }
.colar-dica { display: block; }
.colar-alt { font-size: .9em; }
.colar-arquivo { display: none; }
.colar-estado { margin: var(--space-2) 0; min-height: 1.2em; color: var(--text-mute); }
.colar-estado.erro { color: var(--danger); }

.prints { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-3); }
.print-item { margin: 0; max-width: 260px; }
.print-item img {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* ---- Checklist do roteiro ---- */
.qa-barra {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.qa-bloco { margin-bottom: var(--space-6); }
.qa-progresso { margin: var(--space-2) 0; }

.passo {
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--surface);
}
.passo.respondido { border-left-color: var(--success); }
.passo.achado     { border-left-color: var(--warning); }
/* O passo travado fica À VISTA, apagado. Sumir faria a pessoa ver um buraco na
   numeração e não saber se pulou alguma coisa. */
.passo.travado { background: var(--bg-soft); color: var(--text-mute); }

.passo-cab { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline; }
.passo-cab h3 { margin: 0; }
.passo-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--surface-alt);
  color: var(--text-mute);
  border-radius: var(--radius);
  padding: 0 var(--space-2);
}
.passo-estado { margin-left: auto; }
.passo-corpo { margin: var(--space-3) 0; }
.passo-onde, .passo-esperado, .passo-desfazer, .passo-importa { margin: var(--space-2) 0; }
.passo-fazer { margin: var(--space-2) 0; padding-left: var(--space-5); }
.passo-fazer li { margin-bottom: var(--space-1); }
.passo-travado-aviso {
  margin: var(--space-2) 0;
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--warning);
  background: var(--tom-ambar-bg);
  color: var(--tom-ambar-fg);
}
.passo-acoes { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.passo-achado {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}
.passo-chamado { margin: 0 0 var(--space-2); }
.passo-rascunho { align-self: center; }

/* Prosa de pendência (V060) — o texto vem do `docs/pendencias.md` e é a única
   carga de LEITURA de uma tela que, no resto, é ficha. Por isso a medida vive
   AQUI e não em `conteudo_leitura`: declarar a tela inteira como leitura
   estreitaria a tabela de chamados e o formulário junto, e o canário de
   família 3 recusa — com razão — que a lista de exceções cresça por conveniência. */
pre.prosa {
  max-width: var(--medida);
  white-space: pre-wrap;
  word-break: break-word;
  font-family: inherit;
  margin: 0;
}

/* ════════════════════════════════════════════════════════════════════════════
   Base de conhecimento publicável (V062)
   ════════════════════════════════════════════════════════════════════════════ */

/* Escolha entre dois modos que NÃO cabem numa linha: os rótulos são frases
   ("Sob consulta (o agente busca pelo título)"), e lado a lado eles se
   embolam justamente no ponto em que a diferença importa. `label.chk` é
   inline-flex por padrão — aqui a coluna vem do container, sem classe nova no
   próprio rótulo. */
fieldset.radios-coluna {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  margin: var(--space-4) 0;
}
fieldset.radios-coluna > legend { font-size: 14px; font-weight: 600; padding: 0 var(--space-2); }
fieldset.radios-coluna label.chk { margin: 0; }

/* O diff do "Revisar e publicar". Monoespaçado porque a comparação é linha a
   linha, e com rolagem PRÓPRIA — sem ela uma linha longa do texto curado
   empurra a página inteira na horizontal. */
.diff {
  font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;
  font-size: 12.5px;
  line-height: 1.55;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-2) 0;
  margin: var(--space-2) 0 var(--space-4);
  overflow-x: auto;
  background: var(--surface-alt, var(--tom-neutro-bg));
}
.diff .ln { display: block; padding: 0 var(--space-3); white-space: pre; }
/* Tons semânticos, nunca a marca: adicionar/remover é certo/errado de leitura,
   não identidade visual — a mesma razão pela qual `--danger` e `--success` não
   seguem a paleta. */
.diff .ln.add  { background: var(--tom-verde-bg);    color: var(--tom-verde-fg); }
.diff .ln.del  { background: var(--tom-vermelho-bg); color: var(--tom-vermelho-fg); }
.diff .ln.meta { color: var(--text-mute); }
.diff .ln.hunk { color: var(--tom-azul-fg); background: var(--tom-azul-bg); }

/* O textarea do bloco curado nasce alto: o conteúdo típico tem dezenas de
   linhas, e os 120px do padrão fariam o curador editar por uma fresta. Era um
   `style="min-height:340px"` inline até a V062. */
#txt-conteudo { min-height: 340px; }

/* O corpo de um bloco publicado, na tela de leitura da publicação: texto do
   usuário, escapado pelo autoescape, com quebra preservada e sem estourar a
   medida. */
/* Prosa de AJUDA dentro de um card de tela larga. `.content .card > p` já prende
   o parágrafo à medida, mas não alcança `<ul>`/`<ol>` nem parágrafo aninhado — e
   numa tela `conteudo_largo` (que é o que a lista de conhecimento é) a lista de
   ajuda esticaria sozinha por 1900px. Mesmo idioma do `pre.prosa`: a medida
   tipográfica é do TEXTO, e mora onde o texto está, não na `.content` inteira. */
.prosa-ajuda { max-width: var(--medida); }
.prosa-ajuda ul, .prosa-ajuda ol { padding-left: var(--space-5); margin: 0 0 var(--space-3); }
.prosa-ajuda li { margin-bottom: var(--space-2); }

.card > pre.mono {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 13px;
  margin: 0;
}

/* ---------- Gráfico de série temporal (V064 — Custo de LLM) ----------
   ⚠️ O SVG é montado no SERVIDOR (`admin/grafico.py`) e embutido no HTML. O
   módulo emite CLASSES e nenhuma cor: hex em `.py` escaparia do
   `test_nenhuma_cor_vive_fora_do_token`, que lê CSS e templates, e a paleta
   abriria em leque sem ninguém decidir nada — foi assim que a cor da marca
   sobreviveu no projeto irmão.

   As quatro séries são as quatro origens de `db.llm_uso.ORIGENS`. Os tokens
   foram escolhidos para se distinguirem lado a lado numa barra de ~6px: dois
   azuis da marca (claro × profundo), o âmbar e o cinza de texto secundário.
   Cada regra pinta as DUAS superfícies da mesma série — a barra do SVG (`fill`)
   e o quadradinho da legenda no HTML (`background`) —, e é isso que impede a
   legenda de dizer uma cor e o gráfico outra. */
.grafico-wrap { overflow-x: auto; }
.grafico-legenda {
  display: inline-block; width: 10px; height: 10px;
  border-radius: 2px; vertical-align: middle; margin-right: var(--space-1);
}
.grafico-serie-atendimento, .grafico-legenda.grafico-serie-atendimento {
  fill: var(--acento); background: var(--acento);
}
.grafico-serie-followup, .grafico-legenda.grafico-serie-followup {
  fill: var(--warning); background: var(--warning);
}
.grafico-serie-vigia, .grafico-legenda.grafico-serie-vigia {
  fill: var(--escuro); background: var(--escuro);
}
.grafico-serie-interno, .grafico-legenda.grafico-serie-interno {
  fill: var(--text-mute); background: var(--text-mute);
}
.grafico-eixo { stroke: var(--border); }
.grafico-rotulo { fill: var(--text-mute); }

/* ── Horário 7×2 e janela de atendimento humano (`P-AGE-005`) ─────────────────────────
   A grade (`templates/_horario.html`) é UMA para duas telas: o horário de trabalho do
   usuário e a janela da propriedade (Configurações › Hand-off). Prefixo `horario-` para
   a grade (compartilhada) e `janela-` para o que só a tela de hand-off tem (o selo). */
.horario-grade { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-2) 0 var(--space-3); }
.horario-dia { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.horario-dia-rotulo { flex: 0 0 90px; font-size: 14px; }
.horario-periodo { display: flex; align-items: center; gap: var(--space-1); }
.horario-periodo input[type=time] { width: 120px; }
.horario-tracinho, .horario-ligacao { color: var(--text-mute); font-size: 13px; }
/* Os dois botões de cópia da segunda (dias úteis · todos os dias, V107) numa fileira. */
.horario-acoes { display: flex; gap: var(--space-2); flex-wrap: wrap; align-self: flex-start; }
.janela-estado { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); flex-wrap: wrap; }
.badge.janela-desligada { background: var(--tom-neutro-bg); color: var(--tom-neutro-fg); }
.badge.janela-humanos   { background: var(--tom-verde-bg);  color: var(--tom-verde-fg); }
.badge.janela-agente    { background: var(--tom-azul-bg);   color: var(--tom-azul-fg); }
/* `P-AGE-001`: o atendimento manual VENCE a janela — âmbar, é estado de exceção. */
.badge.janela-manual    { background: var(--tom-ambar-bg);  color: var(--warning-texto); }

/* ── Follow-up cadenciado (`P-INF-006` leva 1, V095) — /admin/config/followup ─────────────
   A lista de CONTATOS da sequência: cada card é DADO em hiddens + um resumo pintado pelo
   JS (`config-followup.js`), com o interruptor do contato à esquerda e Editar/Remover à
   direita. Não é `.campos`: o card não é campo do POST fingindo ser linha de formulário —
   os hiddens viajam, o resto é leitura (checklist da skill `design-telas`). */
.degraus { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-3); }
.degrau {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
}
.degrau label.chk.degrau-interruptor { margin: 0; flex: 0 0 auto; }
/* O bloco dos hiddens não pinta nada, mas como item de flex ganharia um `gap` à esquerda.
   `display: none` não tira ninguém do POST: campo oculto por CSS é submetido igual. */
.degrau > [data-hiddens] { display: none; }
.degrau-corpo { flex: 1 1 18rem; display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); min-width: 0; }
.degrau-resumo { font-size: 14px; overflow-wrap: anywhere; }
.degrau-acoes { display: flex; gap: var(--space-2); flex: 0 0 auto; }
/* Contato DESLIGADO: continua na lista (guarda texto, modelo e instrução para religar —
   decisão 2 do dono), só que esmaecido. Classe aplicada pelo JS junto da badge. */
.degrau.desligado .degrau-resumo { opacity: .6; }
/* O grupo "Fora da janela de 24 h" do modal: os campos do modelo aprovado e do assunto
   lidos JUNTOS — soltos na grade do modal, o assunto parecia um campo do texto livre. */
/* Leva 1b: o grupo "Mídia (opcional)" tem a mesma moldura — nome da mídia + Escolher/Remover
   são AÇÃO e ESTADO, nunca campo da faixa. */
fieldset.grupo-modelo,
fieldset.grupo-midia {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-3); margin: var(--space-3) 0;
}
fieldset.grupo-modelo > legend,
fieldset.grupo-midia > legend { font-size: 14px; font-weight: 600; padding: 0 var(--space-2); }
/* "Conferir modelo na Meta" e o resultado: AÇÃO e ESTADO, fora da faixa `.campos`. */
.modelo-conferir,
.midia-escolha { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin: 0 0 var(--space-3); }
.modelo-estado { font-size: 13px; color: var(--text-mute); }
.modelo-estado.serve { color: var(--success); }
.modelo-estado.nao-serve { color: var(--danger); }

/* ── Login com a sigla na frente (`P-TNT-004` L3) ─────────────────────────────────────
   A sigla da casa é composta pelo SERVIDOR (`login_casa.compor`); o prefixo na caixa só
   mostra o que vai acontecer — `scds.` grudado à parte local que a pessoa digita. Vazio
   (superadmin, ou casa ainda não escolhida) ele some, e a caixa volta a ser a comum. */
.login-prefixado { display: flex; align-items: stretch; }
.login-prefixo {
  display: flex; align-items: center; padding: 0 var(--space-2);
  border: 1px solid var(--border); border-right: 0;
  border-radius: var(--radius) 0 0 var(--radius);
  background: var(--tom-neutro-bg); color: var(--text-mute); font-size: 14px;
}
.login-prefixo:empty { display: none; }
.login-prefixo:not(:empty) + input { border-top-left-radius: 0; border-bottom-left-radius: 0; }
