/* ═══════════════════════════════════════════════════════════════
   Reset & base
   ═══════════════════════════════════════════════════════════════ */
* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0;
  background: var(--bg-base); color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--fs-body); line-height: var(--lh-body);
  font-feature-settings: 'cv11', 'ss01', 'ss03';
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

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

::selection { background: var(--accent-muted); color: var(--text-primary); }

/* FOCO VISÍVEL — a única cor de marca que entra no conteúdo, e ela entra
   porque é transitória: aparece um anel por vez, só por teclado, e some.
   Não compete com o âmbar de dado (`--p-google`, `--conf-fallback`) porque
   nunca está parada na tela ao lado deles.

   `outline`, e não `box-shadow`: sombra é RECORTADA por qualquer ancestral com
   `overflow: hidden` — e a tabela, o painel da conversa e o seletor de cliente
   todos têm. O anel sumiria justamente onde mais se navega por teclado. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--marca);
  outline-offset: 2px;
  border-radius: var(--raio-sm);
}

/* NÚMERO SEMPRE TABULAR — em tabela, cartão, campo e etiqueta. Antes dependia
   da classe `.num` estar lembrada em cada lugar; digito que não alinha entre
   linhas é o que faz uma coluna de valores parecer desarrumada. */
.num, td, th, input, .valor, .badge, .card-stat, .conv-hora, .venda-valor {
  font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════
   Marca — a troca de arquivo por tema
   ═══════════════════════════════════════════════════════════════
   O logo completo tem a palavra "trakzo" desenhada, e as letras são de UMA
   cor só dentro do arquivo: `#ffffff` no branco, `#000000` no preto. Não há
   como fazê-las herdar `currentColor`, porque o logo entra como <img> e não
   inline — e entrar inline foi descartado (6 KB em toda página e ids de
   exportador que colidem).

   Então os DOIS arquivos vão no HTML e aqui se esconde um. Sem isto, um
   clique no #theme-toggle apaga a palavra: branco sobre `--bg-base` claro
   (#FAFAFA) some, e sobra o "o" laranja sozinho.

   O padrão sem `data-theme` é o ESCURO, igual ao script inline do layout —
   as duas regras precisam concordar, ou a marca pisca na primeira pintura.

   O `display` das duas mora AQUI, e não num `style=` no <img>: estilo inline
   vence folha de estilo, então um `display:block` no atributo anularia o
   `display:none` desta seção e os dois logos apareceriam lado a lado. Foi o
   que aconteceu na primeira tentativa, visto no navegador antes de subir. */
.marca-trakzo { display: block; width: auto; max-width: 100%; }
.marca-trakzo-claro  { display: none; }
[data-theme="light"] .marca-trakzo-escuro { display: none; }
[data-theme="light"] .marca-trakzo-claro  { display: block; }

/* ═══════════════════════════════════════════════════════════════
   Legacy layout (login, index) — retrocompatibilidade
   ═══════════════════════════════════════════════════════════════ */
.container { max-width: 1200px; margin: 0 auto; padding: var(--sp-5); }


/* ═══════════════════════════════════════════════════════════════
   Tipografia
   ═══════════════════════════════════════════════════════════════ */
h1, h2, h3, h4 { color: var(--text-primary); margin: 0; }
h1 { font-size: var(--fs-h1);      line-height: var(--lh-h1);      font-weight: 600; letter-spacing: var(--track-h1); }
h2 { font-size: var(--fs-h2);      line-height: var(--lh-h2);      font-weight: 600; }
h3 { font-size: var(--fs-body);    line-height: var(--lh-body);    font-weight: 600; }

code { font-family: var(--font-mono); font-size: 0.92em; }

/* ═══════════════════════════════════════════════════════════════
   Formulários
   ═══════════════════════════════════════════════════════════════ */
form { display: flex; flex-direction: column; gap: var(--sp-3); }
label {
  display: flex; flex-direction: column; gap: 6px;
  font-size: var(--fs-label); line-height: var(--lh-label);
  font-weight: 500; text-transform: uppercase; letter-spacing: var(--track-label);
  color: var(--text-tertiary);
}
input, select, textarea {
  padding: var(--sp-2) var(--sp-3); height: 36px;
  border: 1px solid var(--border); border-radius: var(--raio-sm);
  /* O campo AFUNDA. Ele era `--bg-elevated`, o mesmo tom do hover de linha:
     um campo mais claro que o painel que o contém lê como algo saliente, e
     campo é onde se escreve, não algo que se destaca. */
  background: var(--bg-afundado); color: var(--text-primary);
  font-family: var(--font-sans); font-size: var(--fs-body); line-height: var(--lh-body);
  transition: border-color var(--trans);
}
textarea { height: auto; padding: var(--sp-3); line-height: 1.5; }
input:hover, select:hover, textarea:hover { border-color: var(--border-hover); }
input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color: var(--border-hover);
}
select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23A1A1AA' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center;
  padding-right: var(--sp-6); cursor: pointer;
}
::placeholder { color: var(--text-tertiary); }

/* ═══════════════════════════════════════════════════════════════
   Botões
   ═══════════════════════════════════════════════════════════════ */
/* TRÊS PESOS, e o quarto é o destrutivo.
   O que estava invertido (apontado pelo dono em 19/08): `button[type=submit]`
   sem classe cai na regra do primário, então "Remover acesso" — ação
   SECUNDÁRIA e DESTRUTIVA — virava o elemento mais pesado da tela de Usuários,
   e "Sair" dominava a barra lateral pelo mesmo motivo. Os dois ganharam classe
   própria; a regra do primário não mudou, porque ela está certa. */
button {
  padding: var(--sp-2) 14px; height: 36px;
  border: 1px solid var(--border); border-radius: var(--raio);
  background: var(--bg-surface); color: var(--text-primary);
  font-family: var(--font-sans); font-size: var(--fs-body); font-weight: 500;
  cursor: pointer; transition: background var(--trans), border-color var(--trans), color var(--trans);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  white-space: nowrap;
}
button:hover { background: var(--bg-elevated); border-color: var(--border-hover); }
button:disabled { opacity: 0.4; cursor: not-allowed; }

/* A LISTA DE EXCEÇÕES do primário precisa cobrir TODA classe que já significa
   "não é a ação principal". `.perigo` e `.destrutiva` faltavam, e o resultado
   era invisível na leitura do CSS: `.zona-perigo .perigo` pesa 0,2,0 contra os
   0,3,1 desta regra, então "Apagar" e "Ignorar" — as duas ações mais graves do
   sistema — renderizavam como botão primário cheio, exatamente ao contrário do
   que a classe delas pedia. */
button.primario,
button[type=submit]:not(.secundario):not(.link-btn):not(.critico):not(.perigo):not(.destrutiva) {
  background: var(--accent); color: var(--accent-fg); border-color: var(--accent);
  font-weight: 600;
}
button.primario:hover,
button[type=submit]:not(.secundario):not(.link-btn):not(.critico):not(.perigo):not(.destrutiva):hover {
  background: var(--accent-strong); border-color: var(--accent-strong);
}
button.secundario {
  background: var(--bg-surface); color: var(--text-primary); border-color: var(--border-hover);
}
button.secundario:hover { background: var(--bg-elevated); border-color: var(--border-hover); }

/* DESTRUTIVA: aparência de texto. Ela não pede para ser clicada — pede para ser
   encontrada por quem a procura. A cor só aparece no hover, quando já não há
   dúvida de que o cursor está ali. Distinta de `.critico`, que é o botão cheio
   de confirmação dentro de um diálogo, onde apagar já foi decidido. */
button.destrutiva {
  background: transparent; border-color: transparent;
  color: var(--text-secondary); font-weight: 500;
}
button.destrutiva:hover {
  background: var(--danger-muted); border-color: transparent; color: var(--critico);
}
/* PROMOVER A DONO É ESCALONAMENTO, NÃO É NEUTRO (P0-2, 04/09). SEPARADA DE
   `.critico` DE NOVO em 05/09: o próprio parágrafo acima já dizia que
   preenchimento sólido é "o botão de confirmação dentro de um diálogo, onde a
   ação já foi decidida" -- e `.promover` mora na LINHA da tabela, em repouso,
   disponível o tempo todo em toda linha de operador. Vermelho cheio ali é
   alarme ligado sem incêndio: a primeira coisa que o olho pega ao abrir a
   tela são blocos vermelhos numa tabela onde nada está errado.
   A pausa visual que promover merece continua existindo -- contorno +
   texto em `--critico` já destaca a ação das duas vizinhas (`.secundario`
   neutro, `.destrutiva` que só ganha cor no hover). A intensidade sobe
   quando a decisão é REAL: dentro do diálogo de confirmação o botão usa
   `.critico`, preenchido, sem mudança nenhuma.
   AVISO: `.promover` NÃO está na lista de exceções do botão primário
   (`button[type=submit]:not(...)` na linha 145). Se reusar esta classe com
   `type="submit"` no futuro, será necessário adicioná-la à lista de `:not()`
   ou manter como `type="button"`. */
button.critico {
  background: var(--critico); color: #fff; border-color: var(--critico); font-weight: 600;
}
button.critico:hover { background: #dc2626; border-color: #dc2626; }
button.promover {
  background: transparent; color: var(--critico); border-color: var(--critico); font-weight: 600;
}
button.promover:hover { background: var(--danger-muted); }
button.link-btn {
  background: transparent; border: none; color: var(--text-primary); padding: 0; height: auto;
  font-weight: 500;
}
button.link-btn:hover { color: var(--accent); background: transparent; }
button.ghost {
  background: transparent; border: 1px solid transparent; color: var(--text-secondary);
}
button.ghost:hover { background: var(--bg-surface); color: var(--text-primary); }

/* ═══════════════════════════════════════════════════════════════
   Tabelas
   ═══════════════════════════════════════════════════════════════ */
table {
  width: 100%; border-collapse: collapse; margin-top: var(--sp-4);
}
th, td {
  /* Mais apertado: 14px de respiro vertical numa ferramenta que se opera o dia
     inteiro é altura de site, não de tabela de trabalho. */
  text-align: left; padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-body); line-height: var(--lh-body);
  color: var(--text-primary);
}
th {
  font-size: var(--fs-label); line-height: var(--lh-label);
  font-weight: 600; text-transform: uppercase; letter-spacing: var(--track-label);
  color: var(--text-tertiary); background: transparent;
  padding: var(--sp-2) var(--sp-4);
}
tbody tr { transition: background var(--trans); }
tbody tr:hover { background: var(--bg-elevated); }
tbody tr:last-child td { border-bottom: none; }
td .num, td.num, th.num { font-variant-numeric: tabular-nums; text-align: right; }
td a { color: var(--text-primary); text-decoration: none; }
td a:hover { text-decoration: underline; }
tr.linha-total td { font-weight: 600; border-top: 2px solid var(--border); }
.legenda-oculta { font-size: var(--fs-label); font-weight: 400; color: var(--text-tertiary); margin-top: 2px; }

/* O NÚMERO QUE RESUME A TELA (faturas, 02/09) -- o total do ciclo, ou o valor
   de uma fatura, maior que o corpo mas sem disputar com o `h1` do título.
   `<strong>` já entra em 600 nas duas telas que usam esta classe; o resto do
   texto ("fechados em", "recebido") fica no peso normal ao redor dele. */
.numero-destaque { font-size: var(--fs-xl); color: var(--text-primary); margin: var(--sp-1) 0 var(--sp-5); }
.numero-destaque strong { font-weight: 600; }
/* O PARCIAL DO MÊS (agencia.ejs, 02/09): a legenda ao lado do valor precisa
   ficar MENOR que o número, senão "parcial de MM/YYYY, calculado agora" briga
   de tamanho com o valor que ela está explicando -- mesma cor/peso apagados
   de `.legenda-oculta`, só que sem quebrar linha por padrão. */
.numero-destaque .legenda { font-size: var(--fs-label); font-weight: 400; color: var(--text-tertiary); margin-left: var(--sp-2); }

/* ═══════════════════════════════════════════════════════════════
   Erro (login e forms genéricos)
   ═══════════════════════════════════════════════════════════════ */
.erro {
  color: var(--critico); padding: var(--sp-3) var(--sp-4);
  border: 1px solid transparent; border-left: 3px solid var(--critico);
  border-radius: var(--raio); background: var(--danger-muted);
  font-size: var(--fs-body);
}
/* MESMA FORMA DO .erro, cor trocada -- para a confirmação de "senha
   redefinida" no login (03/09) não usar uma cor nova sem passar por revisão
   de paleta. Sem `background` dedicado: não existe `--success-muted` como
   `--danger-muted` tem, e inventar um token aqui não é o lugar. */
.sucesso {
  color: var(--sucesso); padding: var(--sp-3) var(--sp-4);
  border: 1px solid transparent; border-left: 3px solid var(--sucesso);
  border-radius: var(--raio); font-size: var(--fs-body);
}

/* ═══════════════════════════════════════════════════════════════
   Cards de resumo — o número antes da tabela crua
   Quem abre o painel quer saber "como está" antes de "o que aconteceu".
   ═══════════════════════════════════════════════════════════════ */
/* .cards-resumo e .cards (dashboard) compartilham a mesma aparência de
   propósito: dois sistemas de card no mesmo painel seria justamente a
   inconsistência que esta fase existe pra eliminar. A diferença é só a
   densidade da grade — a de leads cabe mais colunas. */
.cards-resumo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.card-stat {
  background: var(--bg-surface);
  border: var(--bord);
  border-radius: var(--raio-md);
  padding: var(--sp-4);
  display: flex; flex-direction: column; gap: 2px;
  box-shadow: var(--sombra-sm);
  transition: box-shadow var(--trans), border-color var(--trans);
}
/* O NÚMERO É O PONTO ALTO DA TELA e precisa parecer isso. "43,1%" e "4 min"
   são o que a pessoa veio ver; o rótulo acima só diz do que se trata. */
.card-stat .valor {
  font-size: var(--fs-metric); line-height: var(--lh-metric);
  font-weight: 600; letter-spacing: var(--track-metric);
  font-variant-numeric: tabular-nums;
}
/* Valor monetário sozinho ("R$ 553,33") transborda os 150px da coluna no
   mobile no tamanho métrico normal -- achado rodando a tela, não no teste. */
.card-stat .valor.valor-menor {
  font-size: var(--fs-2xl); line-height: 1.2;
}
.card-stat .rotulo {
  font-size: var(--fs-label); line-height: var(--lh-label);
  text-transform: uppercase; letter-spacing: var(--track-label); font-weight: 600;
  color: var(--text-tertiary);
}
/* A cor do número segue o estado que ele mede — mesma escala das badges,
   pra leitura bater o olho entre card e tabela. */
.card-stat.codigo   .valor { color: var(--conf-codigo-fg); }
.card-stat.manual   .valor { color: var(--conf-manual-fg); }
.card-stat.fallback .valor { color: var(--conf-fallback-fg); }
.card-stat.sem      .valor { color: var(--conf-sem-fg); }
/* Card que pede ação fica com uma faixa lateral — forma, não só cor,
   pra não depender de enxergar matiz. */
.card-stat.acao {
  border-left: 3px solid var(--conf-manual-fg);
}

/* ═══════════════════════════════════════════════════════════════
   Badges — pill sem borda, muted + cor semântica
   ═══════════════════════════════════════════════════════════════ */
.badge {
  display: inline-block; padding: 3px var(--sp-2);
  border-radius: var(--raio-sm); border: none;
  font-size: var(--fs-label); line-height: var(--lh-label);
  font-weight: 500; letter-spacing: 0; text-transform: none;
  background: var(--bg-elevated); color: var(--text-secondary);
  /* NUNCA QUEBRA LINHA (achado do dono, 07/09): `.badge` é `inline-block`
     sem largura própria -- dentro de uma linha flex apertada (o badge de
     Conexões do Painel geral ao lado do título/período) o navegador
     encolhia a caixa e o texto de duas palavras ("7 conectados") partia em
     duas linhas dentro da mesma pílula, visto em produção. Uma pílula que
     quebra o próprio texto não lê como pílula. `flex-shrink: 0` impede o
     mesmo aperto de encolher o badge quando ele é item de um flex row. */
  white-space: nowrap; flex-shrink: 0;
}
/* ── Os 4 estados de confiança ────────────────────────────────
   Únicos lugares do painel onde matiz carrega significado. As classes
   vêm de services/confianca.js — não escreva o ternário na view. */
.badge.codigo   { background: var(--conf-codigo-bg);   color: var(--conf-codigo-fg); }
.badge.manual   { background: var(--conf-manual-bg);   color: var(--conf-manual-fg); }
.badge.fallback { background: var(--conf-fallback-bg); color: var(--conf-fallback-fg); }
.badge.sem      { background: var(--conf-sem-bg);      color: var(--conf-sem-fg); }

/* ── Semânticos, fora da escala de confiança ──────────────────
   Erro não é nível de confiança: misturar os dois é como o painel perde
   significado. Ficam separados de propósito. */
.badge.info    { background: var(--info-muted);    color: var(--info); }
.badge.critico { background: var(--danger-muted);  color: var(--critico); }
/* `--text-tertiary` falhava AA (achado da revisão final, 05/09): medido contra
   `--bg-elevated`, 4,32:1 no tema claro e 4,13:1 no escuro, os dois abaixo de
   4,5:1 -- e essa classe é a que renderiza o badge "+N" de Usuários (Tarefa 8),
   a mesma tela cujo objetivo (Tarefa 1) era justamente corrigir contraste no
   tema claro. `--text-secondary` mede 5,25:1 claro / 6,27:1 escuro, os dois com
   folga (ver contraste-tema-claro-usuarios.test.js). */
.badge.neutro  { background: var(--bg-elevated);   color: var(--text-secondary); }
/* Aliases legados — status de conexão usa a marca WhatsApp, não confiança */
.badge.sucesso      { background: var(--conf-codigo-bg);   color: var(--conf-codigo-fg); }
.badge.aviso        { background: var(--conf-fallback-bg); color: var(--conf-fallback-fg); }
.badge.conectado    { background: var(--conf-codigo-bg);   color: var(--conf-codigo-fg); }
.badge.desconectado { background: var(--danger-muted);     color: var(--critico); }

/* ═══════════════════════════════════════════════════════════════
   Login (usa .container.login)
   ═══════════════════════════════════════════════════════════════ */
/* ═══════════ CONECTAR — a tela que o CLIENTE vê ═══════════
   Ela é a primeira coisa que o dono de um número vê do Trakzo, e é por ela que
   se reconecta um número que caiu. Até 19/08 tinha DUAS linhas de estilo:
   `text-align: center` e um teto na imagem. O resto era HTML nu.

   O defeito que o dono viu: `text-align: center` numa `<ul>` centraliza o TEXTO
   e não os marcadores — os bullets ficavam colados na margem esquerda com as
   frases soltas no meio. Centralizar é decisão de bloco, não de parágrafo.

   Ela NÃO usa o app-shell, e isso é certo: quem abre não faz login e não
   navega. Segue a família do `.login` — coluna estreita, cartão, marca em cima.
   O `data-theme` é fixo em `dark` no próprio HTML, então aqui não há par claro
   a manter. */
.conectar { max-width: 480px; margin: 0 auto;
            padding: var(--sp-7) var(--sp-4) var(--sp-8);
            display: flex; flex-direction: column; gap: var(--sp-4); }
.conectar .marca { display: flex; justify-content: center; }
.conectar h1 { text-align: center; margin: 0; }
/* A abertura é a única frase centralizada, e por ser uma linha só. */
.conectar .abertura { text-align: center; color: var(--text-secondary); margin: 0; }

.conectar .cartao { background: var(--bg-surface); border: var(--bord);
                    border-radius: var(--raio-md); padding: var(--sp-5);
                    box-shadow: var(--sombra-md); }
.conectar .cartao > :first-child { margin-top: 0; }
.conectar .cartao > :last-child { margin-bottom: 0; }
.conectar .cartao h2 { font-size: var(--fs-h2); line-height: var(--lh-h2);
                       margin: 0 0 var(--sp-3); }
.conectar .cartao h2 small { color: var(--text-tertiary); font-weight: 400;
                             font-size: var(--fs-caption); }
.conectar .cartao p { margin: 0 0 var(--sp-3); color: var(--text-secondary); }
/* A lista alinhada à esquerda, que é a correção do defeito. */
.conectar ul { margin: 0 0 var(--sp-4); padding-left: var(--sp-5);
               color: var(--text-primary); }
.conectar ul li + li { margin-top: var(--sp-2); }

/* O ACEITE é um alvo de clique, e o `<label>` inteiro faz parte dele: caixa
   pequena sozinha é o clique mais fácil de errar no celular.
   `flex-direction: row` EXPLÍCITO — a regra base `label { flex-direction:
   column }` é a mesma armadilha que `.filtros` e `.form-link-novo` já
   documentam, e eu caí nela de novo em 19/08: a caixa ficou EMPILHADA sobre o
   texto. Declarar `display: flex` sem declarar a direção é herdar a direção
   errada de alguém.
   E o resto desta regra desfaz o estilo de RÓTULO DE CAMPO que o `label` base
   aplica — caixa alta, 12px, cor terciária. Isto aqui não é rótulo de campo, é
   uma frase que a pessoa lê e aceita; em caixa alta ela lia como etiqueta. */
.conectar .aceite { display: flex; flex-direction: row; align-items: flex-start;
                    gap: var(--sp-3); cursor: pointer;
                    text-transform: none; letter-spacing: normal;
                    font-size: var(--fs-body); line-height: var(--lh-body);
                    font-weight: 400; color: var(--text-primary); }
/* A caixa não é campo de texto: sem a borda, o fundo afundado e a altura de
   36px que a regra base dá a todo `input`. Quem pinta é `accent-color`. */
.conectar .aceite input[type=checkbox] { flex: none; width: 18px; height: 18px;
                                         margin: 1px 0 0; padding: 0;
                                         border: 0; background: none;
                                         cursor: pointer;
                                         accent-color: var(--accent); }
.conectar form { gap: var(--sp-4); }
.conectar form button { width: 100%; }

/* O QR PRECISA DE FUNDO CLARO PARA SER LIDO — isto é função, não enfeite. A
   imagem chega com fundo transparente, e sobre o #0E1116 do tema escuro o
   leitor do celular perde o contraste entre os módulos.
   O `color` escuro acompanha o fundo branco porque o JavaScript escreve texto
   DENTRO desta caixa ("Gerando QR…", "Conectado com sucesso"): sem ele, herda
   o quase-branco do tema e some no papel. */
/* QUADRADO E CENTRADO. Sem o teto ele virava uma lousa branca da largura do
   cartão com um código pequeno no meio — o papel tem que ter o tamanho do que
   está impresso nele. `aspect-ratio` em vez de altura fixa para o quadrado
   sobreviver ao encolher no celular. */
.conectar #qr-box { background: #FFFFFF; color: #14181F;
                    border-radius: var(--raio-md); padding: var(--sp-4);
                    display: flex; align-items: center; justify-content: center;
                    max-width: 272px; width: 100%; aspect-ratio: 1;
                    margin: 0 auto; text-align: center; }
.conectar #qr-box p { margin: 0; color: inherit; }
.conectar #qr-img { max-width: 240px; width: 100%; height: auto; display: block; }
/* O teto de 300px era global e pegava TODA imagem da tela — inclusive a marca,
   que passou a existir aqui em 19/08. Escopado ao QR. */
.conectar .estado { text-align: center; color: var(--text-secondary);
                    font-size: var(--fs-caption); margin: 0; }
.conectar .estado strong { color: var(--text-primary); font-weight: 600; }
.login {
  max-width: 380px; margin: 10vh auto 0;
  padding: var(--sp-6);
  background: var(--bg-surface); border: var(--bord); border-radius: var(--raio-lg);
}
.login h1 { margin-bottom: var(--sp-4); }

/* ═══════════════════════════════════════════════════════════════
   Shell — app-shell (sidebar + topbar + main)
   ═══════════════════════════════════════════════════════════════ */
/* A MOLDURA FICA PARADA, e só o conteúdo rola.
   Era `min-height: 100vh` com `overflow: visible`: o shell crescia com o
   conteúdo (2405px numa lista de 30 linhas), a PÁGINA inteira rolava, e a barra
   lateral subia junto — o botão "Sair" simplesmente saía da vista. O
   `overflow-y: auto` do `.main` nunca entrava em ação, porque não havia altura
   nenhuma para ele respeitar.
   `100dvh` antes de `100vh`: no celular a barra de endereço entra e sai, e
   `100vh` é a altura COM ela escondida — o rodapé ficaria cortado. Navegador
   que não conhece `dvh` ignora a linha e fica com a anterior. */
.app-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: var(--altura-topo) 1fr;
  grid-template-areas: "sidebar topbar" "sidebar main";
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* ─── Sidebar ─────────────────────────────────────────── */
.sidebar {
  grid-area: sidebar;
  background: var(--bg-base);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  padding: 0;
  /* `min-height: 0` pelo mesmo motivo que o `.main` tem `min-width: 0`: item de
     grid não encolhe abaixo do próprio conteúdo por padrão, e sem isto um menu
     longo empurraria o rodapé para fora em vez de rolar. */
  min-height: 0;
}
/* O bloco do logo termina EXATAMENTE onde a barra de topo termina — as duas
   bordas horizontais são a mesma linha atravessando a tela. A altura vem do
   token, e não de `padding` mais a altura da imagem, que era o que produzia os
   13px de desencontro. */
/* `flex-direction: row` EXPLICITO. Este bloco virou um `<label>` em 19/08 (a
   barra da gaveta), e a regra base `label { flex-direction: column }` passou a
   se aplicar a ele -- sem esta linha o hamburguer EMPILHA sobre a marca. Quem
   achou foi o guarda `form-em-linha`, minutos depois de eu tê-lo ensinado a
   olhar `<label>`. A mesma armadilha, pela terceira vez neste arquivo. */
.sidebar .logo {
  display: flex; flex-direction: row; align-items: center; gap: 10px;
  height: var(--altura-topo); flex: none; box-sizing: border-box;
  padding: 0 var(--sp-4); border-bottom: 1px solid var(--border);
  color: var(--text-primary); font-weight: 600; font-size: var(--fs-lg);
}
.sidebar .logo .marca-trakzo { max-height: 24px; width: auto; }
.sidebar .logo svg { flex-shrink: 0; }

/* ─── A GAVETA DO CELULAR (19/08) ─────────────────────────
   Caixa de seleção, e NÃO `<details>` — a primeira versão usou `<details>` e
   custou dois defeitos graves, os dois medidos numa revisão:

   1. `nav` e `.rodape` dentro do `<details>` deixam de ser itens flex de
      `.sidebar`, então `nav { flex: 1 }` (abaixo) virava letra morta: o menu
      caiu de 746px para 173px e o "Sair" desgrudou do rodapé.
   2. O desktop dependia de forçar `display` em filho de `<details>` FECHADO,
      que é comportamento de navegador e não de especificação. Em Safari < 18.4
      e Firefox < 139 a barra lateral renderizava só o logo.

   Com a caixa, `nav` e `.rodape` continuam filhos DIRETOS de `.sidebar` e o
   desktop é exatamente o que sempre foi — não há uma regra sequer aqui que o
   altere. Tudo que existe abaixo é ou esconder a caixa, ou valer só no celular.

   Continua zero JavaScript. */

/* ESCONDIDA MAS FOCÁVEL. `display: none` ou `hidden` a tirariam da ordem de
   tabulação, e a gaveta ficaria impossível de abrir pelo teclado. */
.gaveta-check {
  position: absolute; width: 1px; height: 1px;
  opacity: 0; margin: 0; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%);
}
/* O anel de foco aparece na BARRA, que é o que a pessoa enxerga. */
.gaveta-check:focus-visible + .gaveta-barra {
  outline: 2px solid var(--marca); outline-offset: -2px;
}
/* No desktop a barra é a faixa da marca: sem hambúrguer e sem promessa de
   clique. Um cursor de mão anunciaria uma ação que não acontece. */
.sidebar .gaveta-abre, .sidebar .gaveta-fecha { display: none; }
.gaveta-barra { cursor: default; }

/* O menu rola por dentro se for longo; o rodapé com "Sair" fica ancorado. */
.sidebar nav {
  display: flex; flex-direction: column; flex: 1;
  padding: var(--sp-4) var(--sp-3); gap: 2px;
  overflow-y: auto; min-height: 0;
}
.sidebar .secao-label {
  font-size: var(--fs-label); line-height: var(--lh-label);
  text-transform: uppercase; letter-spacing: var(--track-label);
  color: var(--text-tertiary); font-weight: 600;
  padding: 0 var(--sp-3); margin: var(--sp-2) 0 var(--sp-1);
}
.sidebar nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 0 var(--sp-3); height: 36px; border-radius: var(--raio-sm);
  color: var(--text-secondary); font-size: var(--fs-body); font-weight: 500;
  position: relative; transition: background var(--trans), color var(--trans);
  border-left: none;
}
.sidebar nav a svg { flex-shrink: 0; opacity: 0.8; }
.sidebar nav a:hover {
  background: var(--bg-elevated); color: var(--text-primary); text-decoration: none;
}
.sidebar nav a:hover svg { opacity: 1; }

/* O ITEM ATIVO é um dos três lugares onde o laranja da marca aparece.
   A moldura (barra lateral e topo) nunca carrega dado, então aqui a cor não
   disputa significado com nada — é o que torna a regra por zona possível.
   Trilho de 2px E fundo: dois canais para a mesma informação, porque um item
   de menu ativo é o que ancora a pessoa na tela em que ela está. */
.sidebar nav a.ativo {
  background: var(--marca-fraca); color: var(--text-primary); font-weight: 600;
}
.sidebar nav a.ativo::before {
  content: ''; position: absolute; left: -12px; top: 6px; bottom: 6px;
  width: 2px; background: var(--marca); border-radius: 0 2px 2px 0;
}
.sidebar nav a.ativo svg { opacity: 1; color: var(--marca); }
.sidebar nav a .badge {
  margin-left: auto; background: var(--bg-elevated); color: var(--text-tertiary);
  font-size: var(--fs-label); padding: 1px 6px;
}
.sidebar nav a.ativo .badge { background: var(--bg-base); color: var(--text-secondary); }

.sidebar .rodape {
  flex: none;
  padding: var(--sp-3) var(--sp-3);
  border-top: 1px solid var(--border);
}
.sidebar .rodape form { margin: 0; }
/* "SAIR" NUNCA PREENCHIDO. Ele já era transparente aqui, mas a regra do
   primário (`button[type=submit]:not(...)`, peso 0,3,1) vencia esta (0,2,1) e o
   pintava de branco sólido — o elemento mais pesado da barra lateral, para a
   ação menos importante dela. `.destrutiva` no markup resolve na origem; esta
   linha continua como a definição de forma. */
.sidebar .rodape button {
  width: 100%; background: transparent; border: 1px solid transparent;
  color: var(--text-secondary); text-align: left; padding: 0 var(--sp-3); height: 36px;
  font-weight: 500; font-size: var(--fs-body);
  display: flex; align-items: center; gap: 10px; justify-content: flex-start;
}
.sidebar .rodape button:hover {
  color: var(--text-primary); background: var(--bg-elevated); border-radius: var(--raio-sm);
}

/* ─── Topbar ─────────────────────────────────────────── */
.topbar {
  grid-area: topbar;
  background: var(--bg-base);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--sp-6);
}
.topbar .breadcrumb {
  color: var(--text-tertiary); font-size: var(--fs-caption);
  display: flex; align-items: center; gap: 6px;
}
.topbar .breadcrumb a { color: var(--text-tertiary); }
.topbar .breadcrumb a:hover { color: var(--text-primary); text-decoration: none; }
.topbar .direita { display: flex; align-items: center; gap: var(--sp-3); }
.topbar .theme-toggle {
  background: transparent; border: 1px solid transparent;
  border-radius: var(--raio-sm); width: 32px; height: 32px; padding: 0;
  color: var(--text-secondary); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--trans), color var(--trans);
}
.topbar .theme-toggle:hover { background: var(--bg-surface); color: var(--text-primary); }
.topbar .theme-toggle .icone-tema-dark,
.topbar .theme-toggle .icone-tema-light { display: none; }
[data-theme="dark"] .topbar .theme-toggle .icone-tema-dark { display: inline-flex; }
[data-theme="light"] .topbar .theme-toggle .icone-tema-light { display: inline-flex; }

/* ─── Main ─────────────────────────────────────────── */
.main {
  grid-area: main; padding-block: var(--sp-6);
  background: var(--bg-base); overflow-y: auto;
  /* `min-height: 0` para o `overflow-y` acima valer: sem ele o item de grid
     cresce com o conteúdo e quem rola volta a ser a página. */
  min-height: 0;
  /* `min-width: 0` NÃO é decoração. Como item de grid na coluna `1fr`, o padrão
     é `min-width: auto`, que significa "nunca fique menor que meu conteúdo".
     Um único elemento largo — o textarea do pixel exige 1358px — então empurra
     a coluna e a página inteira vaza para a direita, cortando todo o resto.
     Medido em 18/08, depois que o dono mandou a captura da tela cortada. */
  min-width: 0;
}
/* LARGURA DE LEITURA.
   Sem teto, um formulário de dois campos se espalha por 1600px e abre um vazio
   no meio — foi o "mal dimensionado" que o dono descreveu. A referência que ele
   deu (Itaú, Nubank) limita a coluna de conteúdo e deixa o resto respirar.
   Tabelas largas não sofrem: elas rolam dentro de `.tabela-rolante`. */
/* A COLUNA é centralizada, e NÃO cada filho.
   Primeira tentativa: `.main > * { max-width: 1440px; margin-inline: auto }`.
   Ela centraliza cada filho DENTRO dos 1440 — então um bloco estreito
   (`.form-estreito`, 480px) flutua no meio enquanto a tabela ao lado começa na
   borda. Medido na tela de Usuários: três bordas esquerdas diferentes, 272,
   334 e 814. Teste verde, layout errado.
   O `padding-inline` centraliza a coluna inteira e deixa TODOS os filhos
   alinhados na mesma borda, que é o que se quer. O `max()` garante o respiro
   mínimo quando a janela é menor que 1440. */
.main { padding-inline: max(var(--sp-6), (100% - 1440px) / 2); }
.main > * { max-width: 1440px; }
/* ...MENOS o que sai do fluxo e cobre a tela.
   `.sobreposicao` é `position: fixed; inset: 0`, ou seja, a largura dela É a da
   janela -- e um teto de 1440px a corta. Numa tela de 1900px, medido: o fundo
   escuro ia de 0 a 1440 e o painel da conversa parava em 980-1440, no MEIO da
   tela, com um lado escurecido e o outro não. Foi assim que o dono viu, em
   20/08, e o defeito entrou junto com a largura de leitura (417a997, 18/08):
   ele só aparece em janela MAIOR que 1440, por isso passou.
   `.main > .sobreposicao` (0,2,0) vence `.main > *` (0,1,0) sem `!important`.
   O painel lateral do convite (Seção 3, 05/09) tem o mesmo problema: tanto
   `.painel-lateral-fundo` quanto `.painel-lateral` são `position: fixed` e
   filhos diretos de `.main`, no mesmo nível que `.sobreposicao` -- então
   herdavam o mesmo corte de 1440px. Numa tela maior que isso, o fundo
   escurecia só x∈[0,1440] e sobrava uma faixa clara e clicável sem nenhum
   `<label>` por baixo pra fechar o painel (achado da revisão final, 05/09). */
.main > .sobreposicao, .main > .painel-lateral-fundo, .main > .painel-lateral { max-width: none; }

.main h1 { font-size: var(--fs-h1); line-height: var(--lh-h1); font-weight: 600; margin: 0 0 var(--sp-1); letter-spacing: var(--track-h1); }
.main h1 + .subtitulo {
  color: var(--text-secondary); margin: 0 0 var(--sp-5);
  font-size: var(--fs-body); line-height: var(--lh-body);
}
.main .cabecalho {
  display: flex; align-items: start; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-5);
}
.main .cabecalho .acoes { display: flex; gap: var(--sp-2); align-items: center; }

/* ═══════════════════════════════════════════════════════════════
   Cards de KPI (dashboard)
   ═══════════════════════════════════════════════════════════════ */
.cards {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: var(--sp-3) 0 var(--sp-5);
}
@media (max-width: 1024px) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px)  { .cards { grid-template-columns: 1fr; } }

/* ═══════════════════════════════════════════════════════════════
   Responsivo — o suficiente pra conferir no celular
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  /* Sidebar vira faixa no topo; o conteúdo ocupa a largura toda. */
  .app-shell {
    grid-template-columns: 1fr;
    grid-template-areas: "sidebar" "topbar" "main";
    grid-template-rows: auto auto 1fr;
  }
  /* A BARRA LATERAL VIRA GAVETA, e não uma fileira de itens.
     O que havia antes era `flex-direction: row` com `overflow-x: auto` — e ele
     nunca chegava a rolar: os itens ENCOLHIAM e quebravam o texto, então
     "Painel geral" virava duas linhas e o terceiro item sobrava só com o ícone.
     Medido a 390px em 19/08: `scrollWidth === clientWidth`, ou seja, nada
     transbordava; estava tudo espremido. */
  .sidebar { border-right: 0; border-bottom: 1px solid var(--border); }
  .gaveta-barra { cursor: pointer; }
  .sidebar .gaveta-abre { display: inline-flex; }
  .gaveta-check:checked ~ .gaveta-barra .gaveta-abre { display: none; }
  .gaveta-check:checked ~ .gaveta-barra .gaveta-fecha { display: inline-flex; }
  /* Fechada, some tudo menos a barra. Aberta, o menu desce EM COLUNA e empurra
     o conteúdo — nunca em linha, que é o defeito que estamos consertando. */
  .gaveta-check:not(:checked) ~ nav,
  .gaveta-check:not(:checked) ~ .rodape { display: none; }
  .sidebar nav { flex-direction: column; }

  /* O MENU ROLA POR DENTRO, e o teto é o que garante o "Sair" alcançável.
     Sem isto, medido a 740×360 (celular deitado): a gaveta aberta media 289px
     numa barra de 283px, o `.app-shell` (que é `100dvh; overflow: hidden`)
     cortava, a página não rolava, e o único botão de sair ficava fora da tela.
     Com teto e rolagem própria, o menu rola e o rodapé fica sempre visível. */
  .sidebar nav { max-height: 50dvh; overflow-y: auto; min-height: 0; }

  /* Na gaveta há espaço, então o rótulo da seção e o "Sair" voltam a existir —
     eles só tinham sumido porque não cabiam na fileira. */
  .sidebar .secao-label { display: block; }
  .sidebar .rodape { display: block; }

  /* ─── ALVO DE TOQUE ───────────────────────────────────
     Medido a 390px em 19/08: 4 de 4 alvos no Painel geral e 18 de 18 no
     Rastreamento tinham MENOS de 44px de altura. A interface inteira estava
     dimensionada para ponteiro de mouse. 44px é o mínimo de toque, não gosto
     pessoal — e `min-height` para não encolher nada que já seja maior. */
  .sidebar nav a { min-height: 44px; height: auto; }
  .sidebar .rodape button { min-height: 44px; height: auto; }
  button, .link-btn { min-height: 44px; }
  input:not([type=checkbox]):not([type=radio]), select { min-height: 44px; }
  /* O BOTÃO DE TEMA É QUADRADO, e `min-height` sozinho o deformava: ele tem
     `height: 32px` fixo, e `min-height` vence `height` — virava 32 de largura
     por 44 de altura, uma pílula. Cresce nas DUAS medidas, que aliás é o que
     um alvo de toque precisa. */
  .topbar .theme-toggle { width: 44px; height: 44px; min-height: 44px; }

  /* A tabela rola DENTRO dela mesma. Sem isto, uma tabela de 8 colunas
     empurra a página inteira pro lado e o resto do layout quebra junto. */
  table { display: block; overflow-x: auto; white-space: nowrap; max-width: 100%; }

  .filtros { flex-wrap: wrap; }
  .cabecalho { flex-direction: column; align-items: flex-start; gap: var(--sp-3); }
}


/* ═══════════════════════════════════════════════════════════════
   Barra de filtros
   ═══════════════════════════════════════════════════════════════ */
.filtros {
  /* `flex-direction: row` EXPLICITO, e nao por omissao: a regra base
     `form { display:flex; flex-direction: column }` (linha ~60) se aplica aqui
     tambem, e sem esta linha os cinco filtros empilhavam na vertical -- com o
     `align-items: end` abaixo jogando todos para a direita da tela. Encontrado
     em 14/08 renderizando a tela; e defeito antigo, nao da tela de Conversas. */
  display: flex; flex-direction: row; flex-wrap: wrap; align-items: end; gap: var(--sp-3);
  padding: var(--sp-3); margin-bottom: var(--sp-4);
  background: var(--bg-surface); border: 1px solid var(--border);
  border-radius: var(--raio-lg);
}
.filtros label { flex: 0 0 auto; min-width: 140px; }
.filtros button[type=submit] {
  height: 36px;
}
.filtros a.limpar {
  color: var(--text-tertiary); font-size: var(--fs-caption); padding: 0 var(--sp-2);
  align-self: center;
}
.filtros a.limpar:hover { color: var(--text-primary); text-decoration: none; }

/* ═══════════════════════════════════════════════════════════════
   Banner topo (avisos)
   ═══════════════════════════════════════════════════════════════ */
.banner {
  padding: var(--sp-3) var(--sp-4); border-radius: var(--raio);
  margin-bottom: var(--sp-4);
  border: 1px solid transparent; border-left-width: 3px;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  font-size: var(--fs-caption); line-height: 1.5;
}
.banner.aviso   { background: var(--warning-muted); color: var(--aviso);   border-left-color: var(--aviso); }
.banner.critico { background: var(--danger-muted);  color: var(--critico); border-left-color: var(--critico); }
.banner.sucesso { background: var(--accent-muted);  color: var(--accent);  border-left-color: var(--accent); }
.banner.info    { background: var(--info-muted);    color: var(--info);    border-left-color: var(--info); }

/* ═══════════════════════════════════════════════════════════════
   Modais
   ═══════════════════════════════════════════════════════════════ */
dialog.modal {
  border: 1px solid var(--border); border-radius: var(--raio-lg);
  background: var(--bg-surface); color: var(--text-primary);
  padding: var(--sp-5); box-shadow: var(--sombra-lg);
  max-width: 480px; width: 90%;
}
dialog.modal::backdrop { background: rgba(0,0,0,0.6); backdrop-filter: blur(2px); }
dialog.modal form { gap: var(--sp-3); }
dialog.modal .modal-acoes {
  display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-3);
}

/* A busca de lead do painel "+ Nova venda" (Task 6, oportunidades.ejs) --
   mesmo dialog.modal de cima, só que sem precedente pra lista de resultados
   e pro "lead selecionado": as duas classes abaixo nasceram com esta
   tarefa, e nada no app.css cobria isso antes. */
.lista-busca-lead {
  list-style: none; margin: var(--sp-2) 0 0; padding: 0;
  max-height: 220px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--raio);
}
button.resultado-busca-lead {
  display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
  padding: var(--sp-2) var(--sp-3); border: none; border-bottom: 1px solid var(--border);
  border-radius: 0; background: transparent; color: var(--text-primary);
  font: inherit; height: auto;
}
.lista-busca-lead li:last-child button.resultado-busca-lead { border-bottom: none; }
button.resultado-busca-lead:hover { background: var(--bg-elevated); }
button.resultado-busca-lead span { color: var(--text-tertiary); font-size: var(--fs-caption); }
.lead-selecionado {
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border); border-radius: var(--raio);
  background: var(--bg-afundado);
}
.lead-selecionado span { color: var(--text-tertiary); font-size: var(--fs-caption); margin-left: var(--sp-1); }

/* ═══════════════════════════════════════════════════════════════
   Empty state
   ═══════════════════════════════════════════════════════════════ */
/* SEM BORDA TRACEJADA. Tracejado é a convenção de PLACEHOLDER — de coisa que
   deveria estar ali e não está —, e a lista vazia de um cliente novo não é uma
   falha, é o começo. O que sustenta o bloco agora é o respiro e o ícone. */
.tabela-vazia {
  padding: var(--sp-7) var(--sp-5); text-align: center;
  color: var(--text-secondary);
  border: none; background: transparent;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
}
.vazio-icone { color: var(--text-tertiary); opacity: .45; margin-bottom: var(--sp-1); }
.tabela-vazia .titulo {
  color: var(--text-primary); font-weight: 600; font-size: var(--fs-h2);
  line-height: var(--lh-h2); letter-spacing: var(--track-h2); margin: 0;
}
.tabela-vazia .desc {
  color: var(--text-secondary); font-size: var(--fs-body); max-width: 46ch; margin: 0;
}

/* `sobreposicao`, e NÃO `modal`: três telas do admin já têm
   `<dialog class="modal">` (clientes, detalhe do cliente, detalhe do lead), e
   uma regra de autor com `display: flex` vence o `display: none` que o
   navegador dá a `dialog` fechado — a classe compartilhada pintaria um overlay
   preto por cima daquelas telas, engolindo todo clique. Nome próprio resolve
   por construção; brigar por especificidade só adiaria o problema. */
.sobreposicao { position: fixed; inset: 0; background: rgba(0,0,0,.6); z-index: 50;
  display: flex; align-items: center; justify-content: center; padding: var(--sp-4); }
.sobreposicao[hidden] { display: none; }
.sobreposicao-fechar { background: none; border: 1px solid var(--border); border-radius: var(--raio-lg);
  color: var(--text-secondary); padding: var(--sp-1) var(--sp-3); cursor: pointer;
  align-self: flex-end; }

/* A MOLDURA DA CONVERSA, no meio da tela e com o resto escurecido em volta.
   Era uma gaveta colada na borda direita até 20/08. O dono pediu a moldura
   ("tipo um retângulo no meio da tela, todo o resto, dos lados, opaco") e ela
   diz melhor o que a coisa É: a tela do celular de onde a conversa veio.
   Herda `justify-content: center` de `.sobreposicao` -- não sobrescreve mais.
   O `padding` no fundo é o que garante escuro dos QUATRO lados: sem ele a
   moldura encosta em cima e embaixo e volta a parecer gaveta. */
.painel { padding: var(--sp-5); }
/* 420x760 é proporção de telefone, e ela não é enfeite: é a largura em que os
   balões caem em duas ou três palavras por linha, que é como a conversa foi
   escrita. `min(..., 100%)` para caber em janela baixa ou estreita sem cortar
   nada -- é o `.sobreposicao` (com o padding acima) que define esse 100%. */
.painel-caixa { background: var(--bg-surface); border: var(--bord);
  border-radius: var(--raio-xl); box-shadow: var(--sombra-lg);
  width: min(420px, 100%); height: min(760px, 100%); overflow: hidden;
  padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-3); }
.painel-topo { display: flex; align-items: center; justify-content: space-between; flex: none; }
/* DENTRO DA MOLDURA SÓ AS MENSAGENS ROLAM. Antes rolava a caixa inteira: o
   "Fechar" subia junto e sumia, e na fila de comprovantes os botões de decisão
   iam junto com ele -- justamente os dois que precisam estar sempre à mão.
   `min-height: 0` em cada nível porque item de flex não encolhe abaixo do
   próprio conteúdo por padrão, e sem isso `overflow-y` é decorativo (o mesmo
   motivo que `.main` e `.sidebar` já documentam). */
.painel-corpo { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.painel-corpo > .conversa { flex: 1; min-height: 0; overflow-y: auto; }
.painel-decisao { border-top: 1px solid var(--border); padding-top: var(--sp-3); margin-top: var(--sp-2); flex: none; }
/* Na tela do celular a moldura é a tela toda menos uma folga: manter 24px de
   escuro de cada lado deixaria 342px de conversa num aparelho de 390px. */
@media (max-width: 640px) { .painel { padding: var(--sp-3); } }
body.com-sobreposicao { overflow: hidden; }

/* O POPUP DO COMPROVANTE (15/09) -- caixa PRÓPRIA, não `.painel-caixa`: a
   caixa da conversa é 420x760, proporção de tela de celular, e uma imagem de
   comprovante não é balão de WhatsApp -- ela pede largura pra ler o valor
   sem precisar de zoom. `min(...)` pelos dois eixos, mesma disciplina da
   caixa da conversa, pra caber em janela baixa ou estreita sem cortar nada. */
.comprovante-caixa { background: var(--bg-surface); border: var(--bord);
  border-radius: var(--raio-xl); box-shadow: var(--sombra-lg);
  width: min(560px, 100%); max-height: min(90vh, 100%); overflow: hidden;
  padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-3); }
/* `overflow: hidden` (16/09, segunda rodada) -- NUNCA mais barra de rolagem,
   nem lateral nem inferior (pedido explícito do dono). Antes era `auto`: a
   primeira versão do zoom mostrava a imagem em tamanho real e rolava pra
   alcançar o resto. Agora quem posiciona é `transform` (JS), não scroll --
   ver `comprovante-popup.js`. */
.comprovante-corpo { flex: 1; min-height: 0; display: flex; align-items: center;
  justify-content: center; overflow: hidden; }
/* `max-height: 100%` NÃO FUNCIONAVA (achado 16/09: valor cortado no rodapé de
   comprovantes altos). Porcentagem de altura só resolve contra um ancestral
   de altura DEFINIDA -- `.comprovante-caixa` só tem `max-height`, então a
   cadeia inteira fica com altura "auto" e a porcentagem não tem contra o que
   resolver. `min(vh, px)` nos dois eixos -- mesma disciplina do comentário
   acima sobre `.comprovante-caixa` -- não depende de altura de ancestral. Este
   é o tamanho de BASE (escala 1); o zoom não mexe nele, só aplica `transform:
   scale()` por cima, então o layout nunca recalcula durante o zoom/arrasto. */
.comprovante-corpo img { max-width: 100%; max-height: min(65vh, 600px); border-radius: var(--raio-md);
  cursor: zoom-in; transition: transform .15s ease;
  /* Tira o pinça-pra-zoom e o arrastar-pra-rolar NATIVOS do navegador no
     toque -- sem isto, no celular o gesto de arrastar rola a PÁGINA ou tenta
     dar zoom do sistema, competindo com o pointermove que faz o pan aqui. */
  touch-action: none; }
.comprovante-corpo.ampliado img { cursor: zoom-out; }
/* A NAVEGAÇÃO ENTRE PROVAS -- só aparece com mais de uma (JS decide, via
   `[hidden]`). O contador fica no meio, mesma leitura de "página 2 de 5" que
   a paginação da lista já usa. */
.comprovante-nav { display: flex; align-items: center; justify-content: center;
  gap: var(--sp-3); flex: none; }
.comprovante-nav .ico:disabled { opacity: .35; cursor: not-allowed; }
/* `chevron-direita` reaproveitado pros dois lados -- girar 180° é mais barato
   que desenhar um segundo ícone só pra apontar pro outro lado. */
#comprovante-anterior svg { transform: rotate(180deg); }

/* Balões: lead à esquerda, empresa à direita — a convenção do WhatsApp, onde
   quem olha vê o outro à esquerda e a si mesmo à direita. */
.conversa { display: flex; flex-direction: column; gap: var(--sp-2); }
.balao { max-width: 82%; padding: var(--sp-2) var(--sp-3); border-radius: var(--raio-lg);
  display: flex; flex-direction: column; gap: 2px; }
.balao p { margin: 0; overflow-wrap: anywhere; }
.balao-lead { align-self: flex-start; background: var(--bg-elevated); }
.balao-empresa { align-self: flex-end; background: var(--conf-codigo-bg); color: var(--conf-codigo-fg); }
.balao-hora { font-size: var(--fs-label); opacity: .7; align-self: flex-end; }
.balao-midia { font-size: var(--fs-sm); font-style: italic; opacity: .8; }

/* A ETIQUETA DE DIA, entre os balões (20/08, pedido da equipe do dono).
   Centralizada e discreta: ela orienta, não compete com a mensagem. É o mesmo
   papel que a data cumpre no WhatsApp, e a forma segue o que o sistema já tem
   -- fundo de superfície elevada, tipo de etiqueta, sem cor de estado (dia não
   é bom nem ruim, então não empresta a paleta de confiança nem a de alerta). */
.conversa-dia { display: flex; justify-content: center; margin: var(--sp-2) 0; }
/* CONTORNO, E NAO PREENCHIMENTO. A primeira versão usava `--bg-elevated`, que
   é EXATAMENTE o fundo do balão do lead -- medido no navegador: as duas cores
   deram `rgb(238,240,243)`, então a etiqueta de dia parecia mais uma mensagem
   curta no meio da conversa. Vazada, ela lê como marcador. */
.conversa-dia span {
  background: transparent; color: var(--text-tertiary);
  border: 1px solid var(--border);
  font-size: var(--fs-label); line-height: var(--lh-label);
  padding: 1px var(--sp-3); border-radius: var(--raio-lg);
  white-space: nowrap;
}

/* Os gatilhos do comprovante e da conversa. Cinza, não verde: com o botão
   Confirmar verde ao lado, um ícone verde seria um segundo verde querendo
   dizer outra coisa na mesma linha. Verde é decisão; ícone é caminho. */
.ico {
  width: 30px; height: 30px; padding: 0; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: var(--raio-sm); cursor: pointer;
  transition: background var(--trans), color var(--trans), border-color var(--trans);
}
.ico:hover { background: var(--bg-elevated); color: var(--text-primary); border-color: var(--border-hover); }

/* ═══════════════════════════════════════════════════════════════
   Gerador v3 — fluxo único de pixel (mantido intacto)
   ═══════════════════════════════════════════════════════════════ */
.gerador-v3 { display: flex; flex-direction: column; gap: var(--sp-4); }

.gerador-v3 .campo-lp {
  padding: var(--sp-4); background: var(--bg-surface); border: 1px solid var(--border);
  border-radius: var(--raio-lg); border-left: 3px solid var(--accent);
}
.gerador-v3 .campo-lp input[type=url] { font-family: var(--font-mono); }

.gerador-v3 .passo-card {
  padding: var(--sp-4); background: var(--bg-surface); border: 1px solid var(--border);
  border-radius: var(--raio-lg); display: flex; flex-direction: column; gap: var(--sp-3);
}
.gerador-v3 .passo-cabecalho {
  display: flex; align-items: center; gap: var(--sp-2);
}
/* O h3 do cartão era `--fs-body`: do mesmo tamanho do texto que ele encabeça,
   então não lia como título. Sobe um degrau e ganha o peso. */
.gerador-v3 .passo-cabecalho h3 {
  margin: 0; font-size: var(--fs-lg); line-height: 22px;
  font-weight: 600; color: var(--text-primary); flex: 1;
}

.gerador-v3 .info-toggle { position: relative; }
.gerador-v3 .info-toggle summary {
  cursor: pointer; list-style: none; width: 22px; height: 22px; border-radius: 50%;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-tertiary);
  display: flex; align-items: center; justify-content: center; font-size: var(--fs-caption);
  transition: all var(--trans);
}
.gerador-v3 .info-toggle summary::-webkit-details-marker { display: none; }
.gerador-v3 .info-toggle summary:hover { color: var(--accent); border-color: var(--accent); }
.gerador-v3 .info-toggle[open] summary {
  color: var(--accent); border-color: var(--accent); background: var(--accent-muted);
}
.gerador-v3 .info-body {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 20;
  width: 340px; padding: var(--sp-3); background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: var(--raio); box-shadow: var(--sombra-lg);
  font-size: var(--fs-sm); color: var(--text-secondary); line-height: 1.55;
}
.gerador-v3 .info-body code {
  background: var(--bg-base); padding: 1px 5px; border-radius: 3px;
  font-size: var(--fs-caption); color: var(--text-primary);
}

.gerador-v3 .instrucao-caixa {
  padding: var(--sp-3) var(--sp-4);
  background: var(--accent-muted);
  border-left: 3px solid var(--accent);
  border-radius: var(--raio); color: var(--text-primary);
  font-size: var(--fs-body); line-height: 1.6;
}
.gerador-v3 .instrucao-caixa code {
  background: var(--bg-base); padding: 1px 6px; border-radius: 3px;
  font-size: var(--fs-sm); font-family: var(--font-mono);
}
.gerador-v3 .instrucao-caixa ol { margin: 0; padding-left: 20px; }
.gerador-v3 .instrucao-caixa ol li + li { margin-top: var(--sp-1); }
.gerador-v3 .instrucao-caixa a { color: var(--accent); }

.gerador-v3 .copy-field { display: flex; gap: var(--sp-2); align-items: stretch; }
.gerador-v3 .copy-field input[type=text], .gerador-v3 .copy-field textarea {
  flex: 1; padding: var(--sp-3); background: var(--bg-base); border: 1px solid var(--border);
  border-radius: var(--raio-sm); color: var(--text-primary);
  font-family: var(--font-mono); font-size: var(--fs-sm); word-break: break-all; height: auto;
}
/* AS URLS DO GOOGLE/META TRUNCAM COM RETICÊNCIAS (11/09, exceção nomeada, ver
   "Responsivo: nenhuma caixa corta texto"). O valor inteiro continua no campo
   -- é o que `data-copy-target` copia -- e no `title`, que `gerador.js`
   mantém sincronizado a cada tecla. */
.gerador-v3 .copy-field input[type=text] { overflow: hidden; text-overflow: ellipsis; }
/* `resize: none` (item 6.2): o único `<textarea>` deste componente é o script
   do pixel, uma linha que não cresce -- a alça de redimensionar convidava a
   arrastar um campo que nunca muda de tamanho. Sem `min-height: 100px`
   também: ela inflava o campo bem além do necessário para uma linha de
   `<script>`. */
.gerador-v3 .copy-field textarea { resize: none; white-space: pre; overflow-x: auto; }
.gerador-v3 .copy-field button { align-self: stretch; white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════
   Paginador — nenhuma tela carrega tabela inteira
   ═══════════════════════════════════════════════════════════════ */
.paginador {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap;
  margin-top: var(--sp-4); padding-top: var(--sp-4);
  border-top: var(--bord);
}
.paginador-info {
  font-size: var(--fs-caption); color: var(--text-tertiary);
  font-variant-numeric: tabular-nums; margin: var(--sp-3) 0 0;
}
.paginador .paginador-info { margin: 0; }
.paginador-controles { display: flex; gap: var(--sp-2); }

/* ── A marca da origem (14/08) ─────────────────────────────────────
   Desenhada por `views/partials/fonte.ejs`, um lugar só. Antes eram seis views
   repetindo o mesmo quadradinho com estilo inline.
   `color` herda e serve aos estados NEUTROS (não rastreada, link sem canal,
   outra fonte), que são desenhados com `currentColor` e traço fino. As logos
   de plataforma trazem cor própria e ignoram isto. */
.fonte-marca {
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: middle; color: var(--text-tertiary);
}

/* ═══════════════════════════════════════════════════════════════
   Tela de Conversas — bloco por conversa (14/08)
   Direção B do desenho: a pessoa vem primeiro, e o que não existe
   SOME em vez de virar um traço que o olho precisa ignorar. Com 57%
   das conversas sem origem, é essa regra que separa uma tela limpa
   de uma tela de riscos.
   ═══════════════════════════════════════════════════════════════ */
.conversas { border: var(--bord); border-radius: var(--raio-lg); overflow: hidden; background: var(--bg-surface); }
.conv {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--sp-4); align-items: center;
  padding: var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--border);
}
.conv:last-child { border-bottom: 0; }
.conv-quem { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.conv-nome { font-size: var(--fs-lg); font-weight: 600; color: var(--text-primary); text-decoration: none; }
.conv-nome:hover { text-decoration: underline; }
/* A ULTIMA MENSAGEM no lugar do telefone (direcao A, 14/08). Existe em 97,4%
   das conversas -- o dado mais presente da base -- contra uma etiqueta cinza
   de "sem origem" que aparecia em 57% e nao distinguia nada. Uma linha so, com
   reticencias: aqui ela e uma DICA do assunto, nao a conversa; quem quer ler
   abre o balao ao lado. */
.conv-msg {
  font-size: var(--fs-sm); color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.conv-sinais { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-1); }
.conv-anuncio { font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--text-tertiary); }
a.conv-anuncio:hover { color: var(--text-secondary); }

/* Data em cima, hora um degrau abaixo em tamanho e em cor: pedido do dono em
   14/08. A hora é o que distingue o que acabou de entrar; a data é o que ancora
   no calendário. Empilhar dá as duas sem gastar largura. */
.conv-dir { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.conv-venda { color: var(--conf-codigo-fg); font-weight: 700; font-size: var(--fs-lg); }
.conv-data { color: var(--text-secondary); font-size: var(--fs-caption); white-space: nowrap; }
.conv-hora {
  color: var(--text-tertiary); font-size: var(--fs-label); line-height: var(--lh-label);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}

@media (max-width: 900px) {
  .conv { grid-template-columns: minmax(0, 1fr) auto; }
  .conv-dir { grid-column: 2; }
  .conv > .ico { grid-column: 2; justify-self: end; }
}

/* ═══════════════════════════════════════════════════════════════
   O RESUMO do detalhe do lead (direção C, 14/08, modelo do dono)
   ═══════════════════════════════════════════════════════════════
   Painéis de linhas rótulo/valor. O rótulo tem largura fixa para os valores
   caírem numa coluna reta descendo a página -- a mesma razão da altura igual
   na fila de comprovantes: o que desalinha não é o valor, é o rótulo ao lado
   mudar de tamanho. */
.painel-info {
  background: var(--bg-surface); border: 1px solid var(--border);
  border-radius: var(--raio-lg); overflow: hidden; margin-bottom: var(--sp-4);
}
.painel-info > h2, .painel-info > summary > h3 {
  margin: 0; font-size: var(--fs-lg); font-weight: 600;
}
.painel-info > .secao-cabecalho { padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border); }
/* ⚠️ O `h2` do partial `secao` traz `margin-top` de título de PÁGINA, e a regra
   que o zera acima só pega `h2` filho DIRETO do painel -- dentro do cabeçalho
   ele escapava. Resultado medido em 24/08: 79px de altura para um título de
   22px, num cabeçalho que já paga o próprio respiro. Visto no navegador. */
.painel-info > .secao-cabecalho h2 { margin: 0; }
.campos-info { padding: var(--sp-2) var(--sp-5) var(--sp-4); }
.linha-info {
  display: grid; grid-template-columns: 210px minmax(0, 1fr);
  gap: var(--sp-4); padding: 9px 0; align-items: baseline; margin: 0;
}
.linha-info + .linha-info { border-top: 1px solid var(--border); }
.linha-info dt { color: var(--text-secondary); font-size: var(--fs-sm); }
.linha-info dd {
  margin: 0; font-size: var(--fs-md);
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
}
/* A DICA é onde a inferência se declara. A localização sai do DDD, que é a
   região do CHIP e não da pessoa -- e a regra travada desta casa é que palpite
   nunca se apresenta como certeza. */
.dica { color: var(--text-tertiary); font-size: var(--fs-caption); }
/* Quando a ausência é o próprio achado. Medido: em 38 conversas a empresa
   nunca respondeu -- esse é o caso que o dono quer ver, não um campo vazio. */
.dica-forte { color: var(--conf-fallback-fg); font-size: var(--fs-sm); }
.rot-inline { color: var(--text-secondary); font-size: var(--fs-sm); min-width: 190px; }
.rot-bloco {
  font-size: var(--fs-label); letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-tertiary); margin-bottom: var(--sp-2);
}
.bloco-params { border-top: 1px solid var(--border); padding-top: var(--sp-3); }
.valor-forte { color: var(--conf-codigo-fg); }
.msg-inicial { color: var(--text-secondary); }

.venda-reg {
  display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; padding: var(--sp-3) 0;
}
.venda-reg + .venda-reg { border-top: 1px solid var(--border); }
.venda-valor { font-size: var(--fs-xl); font-weight: 700; color: var(--conf-codigo-fg); }

/* O identificador do anúncio e os parâmetros de URL são para CONFERIR contra
   outro painel, não para ler -- monoespaçado é o que permite comparar dígito a
   dígito. `word-break` porque um gclid tem 90 caracteres e não pode empurrar a
   página para o lado. */
.mono-id, .param dd {
  font-family: var(--font-mono); font-size: var(--fs-caption); word-break: break-all;
}
.param {
  display: grid; grid-template-columns: 150px minmax(0, 1fr);
  gap: var(--sp-3); padding: 5px 0; font-size: var(--fs-sm); margin: 0;
}
.param dt { color: var(--text-tertiary); font-family: var(--font-mono); font-size: var(--fs-caption); }
.param dd { margin: 0; }

/* Ação de largura inteira no pé do painel, como no modelo que o dono mandou.
   Não é o botão primário: ela abre coisa, não decide nada. */
.bt-largo {
  display: block; width: calc(100% - var(--sp-5) * 2); margin: 0 var(--sp-5) var(--sp-5);
  padding: 11px; text-align: center; background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: var(--raio);
  color: var(--text-primary); font-size: var(--fs-md); font-family: inherit; cursor: pointer;
}
.bt-largo:hover { border-color: var(--border-hover); }

@media (max-width: 700px) {
  .linha-info { grid-template-columns: 1fr; gap: 2px; }
  .param { grid-template-columns: 1fr; gap: 0; }
}

/* Zona de perigo do detalhe do lead (14/08). Ela existe para dar PESO à ação:
   apagar aqui some com o lead e com todo o histórico de conversas, e o WhatsApp
   não devolve o que foi apagado. Fica no fim da página, separada por moldura
   vermelha discreta -- visível para quem procura, fora do caminho de quem não. */
.zona-perigo {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  flex-wrap: wrap; margin-top: var(--sp-6); padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--danger-muted); border-radius: var(--raio-lg);
  background: var(--danger-muted);
}
/* `> div > strong` E NAO `.zona-perigo strong`: o descendente pegava tambem o
   <strong> DENTRO do parágrafo, e ele virava bloco -- "muda os números do
   painel" quebrava a linha em três pedaços no meio da frase. Medido no
   navegador em 20/08, com o texto que separa apagar de anonimizar; o teste
   estava verde e o parágrafo, errado. O título continua alcançado: ele é filho
   direto do <div> que abre toda zona de perigo (as três da tela). */
.zona-perigo > div > strong { display: block; color: var(--text-primary); font-size: var(--fs-md); }
.zona-perigo p { margin: var(--sp-1) 0 0; color: var(--text-secondary); font-size: var(--fs-sm); max-width: 62ch; }
.zona-perigo .perigo {
  font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
  padding: var(--sp-2) var(--sp-4); border-radius: var(--raio); white-space: nowrap;
  background: transparent; color: var(--critico); border: 1px solid var(--critico);
  transition: background var(--trans), color var(--trans);
}
.zona-perigo .perigo:hover { background: var(--critico); color: #fff; }

/* Os números que o extrator não entendeu, listados um a um com o motivo. Fonte
   monoespaçada no número: eles vieram colados de uma planilha, e é comparando
   caractere a caractere com o original que a pessoa entende por que aquele
   ficou de fora. */
.lista-duvidosos { margin: var(--sp-2) 0 var(--sp-3); padding-left: var(--sp-5);
                   font-size: var(--fs-caption); color: var(--text-secondary); }
.lista-duvidosos code { font-family: var(--fonte-mono, monospace); }

/* A contagem de conversas ocultas, em voz baixa ao lado do filtro (14/08,
   redesenhada em 17/09). Era um banner laranja de faixa inteira; laranja
   neste painel quer dizer atenção, e conversas que o dono não quer analisar
   não são um alarme. O número fica porque contato escondido não pode sumir
   em silêncio. O link de texto sublinhado (14/08) foi pensado pra uma base
   pequena (~26); com a base maior (166) ele estourava a linha do formulário
   `.filtros` e caía sozinho, sem a altura de botão dos campos ao lado.
   Ganhou forma e altura de botão (mesmos 36px de `.filtros button[type=
   submit]`), mas sem cor de destaque — mesma paleta do badge "+N" de
   Usuários (`.badge.neutro`), pra continuar discreto ao lado do Filtrar.
   BORDA TRANSPARENTE na base, não ausente: testado no navegador (17/09), o
   primeiro hover trocava o fundo para `--bg-surface` -- a MESMA cor do
   cartão `.filtros` por baixo -- e o botão sumia inteiro, sobrando só o
   sublinhado padrão do link. A borda ficando visível no hover é o feedback,
   o fundo não muda mais. */
.ocultos-nota {
  display: inline-flex; align-items: center; justify-content: center;
  align-self: center; height: 36px; padding: 0 var(--sp-3);
  border-radius: var(--raio); border: 1px solid transparent;
  background: var(--bg-elevated);
  color: var(--text-secondary); font-size: var(--fs-body); font-weight: 500;
  text-decoration: none; white-space: nowrap;
}
.ocultos-nota:hover {
  border-color: var(--border-hover); color: var(--text-primary);
  /* Sem isto, a regra global `a:hover { text-decoration: underline }` (linha
     ~17) ganha: mesma especificidade de classe que esta regra, e o `a`
     desempata por elemento. Achado testando no navegador (17/09). */
  text-decoration: none;
}

/* "Decididas recentemente" recolhida (14/08, pedido do dono olhando a tela no
   ar). Ela ocupava mais altura que a própria fila, e a fila é o trabalho -- as
   decididas são consulta, e só quando algo deu errado.
   `<details>` nativo de propósito: abre e fecha sem uma linha de JavaScript,
   funciona com teclado e com leitor de tela de graça, e o estado padrão
   (fechado) é o que empurra a fila para cima. */
.recentes-caixa { padding: 0; }
.recentes-caixa > summary {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); cursor: pointer; list-style: none;
}
.recentes-caixa > summary::-webkit-details-marker { display: none; }
/* A seta é desenhada por nós para girar na abertura -- o marcador nativo não
   segue o tema e muda de forma entre navegadores. */
.recentes-caixa > summary::before {
  content: ""; width: 7px; height: 7px; flex: none;
  border-right: 1.5px solid var(--text-tertiary); border-bottom: 1.5px solid var(--text-tertiary);
  transform: rotate(-45deg); transition: transform var(--trans); margin-right: 2px;
}
.recentes-caixa[open] > summary::before { transform: rotate(45deg); }
.recentes-caixa > summary h3 { margin: 0; font-size: var(--fs-h2); line-height: var(--lh-h2); }
.recentes-caixa > summary:hover h3 { color: var(--text-primary); }
.recentes-contagem {
  font-size: var(--fs-caption); color: var(--text-tertiary);
  background: var(--bg-elevated); border-radius: 999px; padding: 1px var(--sp-2);
  font-variant-numeric: tabular-nums;
}

/* O SELETOR DE CLIENTE — `<details>` nativo, o mesmo recurso que "Decididas
   recentemente" usa na fila. Sem JavaScript: um menu de navegação que depende
   de script é um menu que some quando o script falha. */
.seletor-cliente { position: relative; }
.seletor-cliente > summary {
  cursor: pointer; list-style: none; font-weight: 600; color: var(--fg);
  border: var(--bord); border-radius: 8px; padding: 5px 11px; background: var(--bg-alt);
}
.seletor-cliente > summary::-webkit-details-marker { display: none; }
/* Mesma seta das outras duas: ver `.instalacao > summary::after`. */
.seletor-cliente > summary::after {
  content: ""; display: inline-block; width: 6px; height: 6px; margin-left: var(--sp-2);
  border-right: 1.5px solid var(--text-secondary); border-bottom: 1.5px solid var(--text-secondary);
  transform: rotate(45deg) translateY(-2px); transition: transform var(--trans);
}
.seletor-cliente[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
.seletor-cliente .opcoes {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 20; min-width: 250px;
  background: var(--bg-alt); border: var(--bord); border-radius: 10px; padding: 6px;
  box-shadow: var(--sombra-md); display: flex; flex-direction: column;
}
.seletor-cliente .opcoes a {
  padding: var(--sp-2) 10px; border-radius: 6px; color: var(--fg-mut);
  text-decoration: none; font-size: var(--fs-caption);
}
.seletor-cliente .opcoes a:hover { background: var(--bg); color: var(--fg); }
.seletor-cliente .opcoes a.atual { color: var(--fg); font-weight: 600; }
.seletor-cliente .opcoes hr { margin: 6px var(--sp-1); border: 0; border-top: var(--bord); }

/* A BARRA EMPILHADA do Painel geral: um pedaço por plataforma, sem texto ao
   lado. O vão de 2px entre pedaços é do fundo e NÃO é enfeite — é ele que
   separa duas cores vizinhas para quem enxerga cor de outro jeito. */
/* A BARRA DE ORIGEM — o elemento que este produto tem de mais próprio. Ela
   mostra, de um olhar, o quanto o cliente NÃO sabe.
   Até 19/08 ela fazia o oposto: "Não sabemos" tinha 49% e era o segmento mais
   escuro e mais sólido da barra — o vazio como protagonista. Agora o que se
   sabe é cheio e saturado, e o que não se sabe quase encosta no fundo.
   As pontas arredondadas ficam nas EXTREMIDADES da barra, e não em cada
   pedaço: cantos redondos por dentro transformariam uma barra contínua numa
   fileira de pastilhas, e a leitura de proporção se perde. */
.barra-origem {
  height: 10px; border-radius: 999px; display: flex; gap: 2px;
  min-width: 240px; overflow: hidden;
}
.barra-origem i {
  display: block; height: 100%; min-width: 2px;
  transition: filter var(--trans);
}
.barra-origem i:hover { filter: brightness(1.25); }

/* O QUE NÃO SE SABE recua para o fundo — mas RECUAR NÃO É SUMIR.
   A primeira versão usava `--bg-afundado` com hachura em `--border`, e medi
   1,04:1 contra o fundo: o segmento desaparecia. E aí a barra passava a mentir
   pior do que antes — mostrando só a parte conhecida, como se a soma fosse
   100%, quando o ponto inteiro desta peça é revelar o tamanho do que falta.
   A proporção é a informação; ela não pode ser invisível.
   Quem carrega a leitura agora é a HACHURA, em `--traco-sem-origem`, sobre o
   fundo afundado -- token PRÓPRIO desde 05/09, e não mais o genérico
   `--border-hover` (que media 1,34:1 no claro, o pior contraste da tela; ver
   o comentário do token em tokens.css). O segmento continua o mais quieto da
   barra e continua legível. */
.barra-origem i.p-desconhecida,
.barra-origem i.p-nada,
.barra-origem i.p-outra {
  background: var(--bg-afundado);
  background-image: repeating-linear-gradient(45deg,
    var(--traco-sem-origem) 0 1px, transparent 1px 4px);
  box-shadow: inset 0 0 0 1px var(--border);
}

/* O TRILHO VAZIO: cliente sem conversa nenhuma mantém a linha com a mesma
   altura, em vez de colapsar. Uma linha que some da tabela é uma linha que o
   olho não conta ao comparar clientes. */
.barra-origem.vazia {
  background: var(--bg-afundado);
  box-shadow: inset 0 0 0 1px var(--border);
}
/* A COR DA PLATAFORMA SOLTA DO CONTEXTO.
   Ela vivia presa em `.barra-origem i`, então a LEGENDA logo abaixo da barra
   não alcançava — e pintava as mesmas sete cores com `style=` no HTML,
   repetindo o mapeamento. Duas fontes para a mesma verdade: mudar a paleta
   consertava a barra e deixava a legenda mentindo. */
.p-google    { background: var(--p-google); }
.p-facebook  { background: var(--p-facebook); }
.p-instagram { background: var(--p-instagram); }
.p-link      { background: var(--p-link); }
.p-chatgpt   { background: var(--p-chatgpt); }
/* `--p-meta` passou a existir como token próprio em 25/08. Antes esta classe
   pintava com `--p-facebook`, e na mesma barra o balde "a Meta não disse a
   rede" saía com a tinta do balde "Facebook" -- dois significados numa cor só.
   A classe e o token agora concordam, e há um lugar só para mudar. */
.p-meta      { background: var(--p-meta); }
.p-outra,
.p-desconhecida, .p-nada { background: var(--p-nada); }

/* "OUTROS" (3.3, 05/09): composição de plataformas conhecidas, cada uma
   pequena demais para o próprio segmento -- NÃO é "não sabemos". Cor
   distinta de --p-nada de propósito: as duas juntas voltariam a misturar
   ausência de dado com dado que existe e só foi agrupado por tamanho.

   TOKEN PRÓPRIO desde 06/09 (achado da revisão final de branch) -- ANTES
   pintava com `--text-tertiary`, reaproveitando um token de TEXTO como
   tinta de DADO, o que furava a disciplina de conjunto fechado que o
   cabeçalho de `tokens.css` estabelece para a paleta `--p-*`. E na prática
   colidia: `--text-tertiary` contra `--p-meta` dava 1,04:1 de contraste no
   tema claro -- a mesma tinta -- e um balde `meta` (rota do `fbclid`) do
   lado de um balde `outros` ficava ilegível. `--p-outros` (#7C6F57 nos dois
   temas) foi calibrado contra o fundo E contra cada `--p-*` existente; ver
   o comentário do token em `tokens.css` para os números de contraste e ΔE. */
.p-outros { background: var(--p-outros); }

/* HACHURA = há incerteza. Nunca cor sozinha: a cor já está dizendo qual
   plataforma é, e um segundo significado na mesma tinta não se lê. */
.barra-origem i.incerto {
  background-image: repeating-linear-gradient(135deg,
    rgba(0,0,0,.55) 0 3px, transparent 3px 6px);
}

.legenda-plataformas { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
                       margin-top: var(--sp-3);
                       font-size: var(--fs-caption); color: var(--text-secondary); }
.legenda-plataformas span { display: inline-flex; align-items: center; gap: var(--sp-1); }

/* A bolinha de estado. Aqui cor SIGNIFICA estado, que é a regra normal desta
   casa — os tokens são os mesmos de sucesso e crítico. */
.ponto { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: none; margin-right: 6px; }
.ponto.on  { background: var(--sucesso); }
.ponto.off { background: var(--critico); }

/* O PULSO — decisão do dono em 19/08, ao desenhar o topo do Rastreamento.
   Não veio de acordo anterior nem de proposta minha: é instrução nova daquele
   dia, e fica registrado assim para o comentário não mentir sobre a origem.

   É EXCEÇÃO NOMEADA à regra de movimento de `tokens.css`, que diz que animação
   aqui só faz parecer amador. A exceção se sustenta porque este pulso não é
   enfeite — ele carrega o significado que a cor sozinha não carrega: verde
   parado diz "estava verde quando a página carregou", verde pulsando diz "isto
   está vivo AGORA".

   Ele mora em `.ponto`, e não numa classe do Rastreamento, porque A empresa
   quer dizer exatamente a mesma coisa com a mesma bolinha. As duas passam a
   pulsar, e isso é o resultado pretendido, não efeito colateral.

   `ease-in-out` e não a `--trans` da casa: aquela curva desacelera no fim, que
   é o que uma transição de ida e volta precisa. Um ciclo que respira é
   simétrico nas duas metades.

   Só o verde pulsa. Vermelho piscando seria alarme — outra mensagem. */
@keyframes pulso-vivo { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.ponto.on { animation: pulso-vivo 2.4s ease-in-out infinite; }
/* Movimento que repete para sempre é gatilho vestibular clássico. Quem pediu
   menos movimento continua vendo a COR, que é onde o estado realmente mora. */
@media (prefers-reduced-motion: reduce) { .ponto.on { animation: none; } }

/* ═══════════════════════════════════════════════════════════════
   A CAMADA DE COMPONENTES (18/08)
   ═══════════════════════════════════════════════════════════════
   Medido antes de escrever: 102 estilos dentro do HTML das views, nove telas
   com nove cabeçalhos diferentes. O sistema de tokens já existia e era bom — o
   que faltava era usá-lo. Faltavam QUATRO componentes, não vinte:
   cabeçalho de página, seletor de período, cartões em três, e grupo de ações.
   ═══════════════════════════════════════════════════════════════ */

/* O cabeçalho fica idêntico em toda tela porque agora é um partial só
   (partials/cabecalho-pagina.ejs). Estas regras completam o que faltava: o
   subtítulo colado no título, e a coluna de texto podendo encolher sem empurrar
   as ações para fora. */
.main .cabecalho-texto { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.main .cabecalho-texto h1 { margin: 0; }
.main .cabecalho-texto .subtitulo { margin: 0; }

/* O SELETOR DE PERÍODO — três links num controle segmentado.
   Substitui a caixa de filtro que ocupava a primeira dobra inteira. */
.periodo {
  display: inline-flex; border: var(--bord); border-radius: var(--raio);
  overflow: hidden; background: var(--bg-alt); flex: none;
}
.periodo a {
  padding: var(--sp-1) var(--sp-3); font-size: var(--fs-sm);
  color: var(--fg-mut); text-decoration: none; white-space: nowrap;
  border-right: var(--bord);
}
.periodo a:last-child { border-right: 0; }
.periodo a:hover { color: var(--fg); }
.periodo a.on { background: var(--bg); color: var(--fg); font-weight: 600; }
.periodo a:focus-visible { outline: 2px solid var(--fg); outline-offset: -2px; }

/* Cartão que é um link: mesmo desenho do cartão comum, mais o estado de foco e
   de hover que um destino navegável precisa ter. */
/* SOMBRA MAIOR NO HOVER SÓ SE FOR CLICÁVEL. Um cartão que só informa e sobe
   ao passar o mouse promete uma ação que não existe. */
a.card-stat { text-decoration: none; color: inherit; }
a.card-stat:hover { border-color: var(--border-hover); box-shadow: var(--sombra-md); }

/* GRUPO DE AÇÕES. O que ele conserta é a fila de botões do canto superior
   direito da tela do cliente — quatro botões do mesmo peso mais um menu
   "Mais", que o dono chamou de confuso e feio. A regra: no máximo UMA ação
   primária, e as demais em tinta secundária. */
.acoes-grupo { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.acoes-grupo form { flex-direction: row; gap: 0; }

/* GAP DE 24px ENTRE REBAIXAR/PROMOVER E REMOVER (P0-3) -- antes os três
   botões desta linha ficavam a ~8px (`--sp-2`) um do outro, o mesmo respiro
   de ações que fazem parte do mesmo gesto. Remover acesso é a ação mais
   destrutiva da tela; precisa de uma pausa visual antes dela, não só uma
   cor diferente. */
.acoes-escalonamento { gap: var(--sp-5); }

/* MESMO ACHADO, TERCEIRA VEZ HOJE NO MESMO ARQUIVO (revisão da Task 3,
   04/09) -- ver `.opcoes-marcar[hidden]`/`.opcao-marcar[hidden]` mais abaixo
   neste arquivo para o histórico completo. `form` já é `display: flex` de
   autor (regra global, no topo deste arquivo), e uma regra de autor com
   `display` sempre vence o `display: none` que o agente do usuário aplica a
   `[hidden]`, independente de especificidade. Sem esta regra, os dois
   `<form ... hidden>` que carregam a `action`/campos ocultos de "Tornar
   dono"/"Remover acesso" geravam caixas vazias reais dentro do flex: 4 itens
   (form, botão, form, botão) em vez de 2, com 3 gaps de `--sp-5` em vez de 1
   -- o respiro de 24px virava ~48px, e o grupo inteiro deslocava por causa do
   form fantasma antes do primeiro botão. Escopado em `.acoes-escalonamento`,
   e não `form[hidden]` solto, para não mudar outro formulário do sistema que
   use `hidden` por outro motivo. */
.acoes-escalonamento form[hidden] { display: none; }

/* Título de seção dentro de uma página. Ele existe para tirar
   `style="margin:var(--sp-6) 0 var(--sp-3)"` das views: a margem vinha escrita à mão em cada
   tela, com valor diferente em cada uma. */
/* O TÍTULO DE SEÇÃO PRECISA DE TIPO, e não só de margem.
   Até 18/08 esta regra declarava apenas `margin`, então o tamanho vinha do
   estilo base do `h2` e variava com o contexto. O dono descreveu o sintoma como
   "tamanho dos textos bem fora do esperado", e era isto: a tela tinha sete
   tamanhos entre 11px e 14px, três deles escritos na unha, fora da escala. */
.main .secao-titulo {
  /* -25% no respiro entre seções (sp-6 -> sp-5). Em Rastreamento os buracos
     entre Configuração, Pixel, Google Ads e Meta Ads eram grandes o bastante
     para o dono ler como defeito. Ferramenta profissional é mais densa.
     Continua na escala de 4px: só desce um degrau, não inventa valor. */
  margin: var(--sp-5) 0 var(--sp-3);
  font-size: var(--fs-h2); line-height: var(--lh-h2);
  font-weight: 600; color: var(--text-primary);
}
.main .secao-titulo:first-of-type { margin-top: var(--sp-5); }

/* ═══════════ RASTREAMENTO ═══════════ */
/* SEM `margin-bottom` AQUI. Medido no navegador em 19/08: com ela, o vão até o
   título seguinte dava 88px contra os 64px de todo outro par da tela — porque
   a margem do bloco somava com o `margin-top` de `.secao-titulo`. Duas margens
   escritas em lugares diferentes para o mesmo respiro é como o ritmo vertical
   se perde sem ninguém errar nada sozinho. Quem dá o respiro é o título. */
/* COLUNA ÚNICA (reforma 11/09, pedido do dono). Era grid de duas colunas: a
   da direita tinha campo extra (checkbox + dica), então as duas nunca
   pareavam de altura — o olho espera pareamento numa grade e não encontra.
   Os três formulários postam para TRÊS rotas diferentes (endereço, mensagem,
   mensagem da extensão), então continuam três `<form>`/três botões — unificar
   num só "Salvar" pediria mudar o backend, fora do escopo desta reforma
   (CSS e markup só).

   ⚠️ TRÊS TENTATIVAS NA TELA, mesmo dia, até achar a certa:
   1. `max-width` no CARD inteiro → sobrava um vazio enorme à direita.
   2. Card largura cheia, campos alinhados à ESQUERDA → o vazio some da
      direita, mas o texto cola na borda esquerda de uma caixa larga, sem
      intenção nenhuma -- ainda lia torto.
   3. Card largura cheia, campos CENTRALIZADOS → melhor, mas o problema não
      era o alinhamento: era a CAIXA. Borda + fundo cinza só para guardar um
      formulário estreito sempre vai ler como "moldura grande demais pro
      quadro", não importa como o conteúdo se posiciona lá dentro.

   A SAÍDA: tirar a caixa. "Links rastreáveis", duas seções abaixo, já não
   tem borda nem fundo -- é só o título e a tabela soltos na página. Um
   formulário é o mesmo tipo de conteúdo que uma tabela: a caixa fazia sentido
   nos cards de STATUS (`.bloco-linha`, que mostram um sinal ao vivo), não
   aqui. Sem caixa, o formulário só ocupa a largura que precisa -- igual a
   qualquer formulário de configuração normal.

   ⚠️ QUARTA RODADA, mesmo dia: sem caixa, o vazio do lado direito continuou
   incomodando -- e o dono pediu, direto, algo "bonito, harmônico". Dar
   função ao espaço em vez de tentar escondê-lo: uma prévia ao vivo da
   mensagem do WhatsApp (`.preview-whatsapp`) ocupa a segunda coluna, com dado
   real (o valor de `#mensagem-template`, sincronizado por `gerador.js` a
   cada tecla -- mesma técnica que já atualiza as URLs do Google/Meta). Junto
   com isso, a extensão do Google e o "texto pronto" que ela produz ganharam
   `.grupo-campo`, com respiro menor entre si: eram duas coisas com o mesmo
   peso visual de seções inteiras, quando uma é consequência da outra. */
.bloco-config { display: grid; grid-template-columns: minmax(0, 560px) minmax(240px, 320px);
                gap: var(--sp-6); align-items: start; }
.bloco-config-campos { display: flex; flex-direction: column; gap: var(--sp-5); min-width: 0; }
.grupo-campo { display: flex; flex-direction: column; gap: var(--sp-3); }
.bloco-config form { gap: var(--sp-2); align-items: flex-start; }
/* `align-items: flex-start` existe para o BOTÃO não esticar. Só que ele encolhe
   todo filho, inclusive o `<label>` do campo — e aí `input { width: 100% }`
   passa a valer 100% de um rótulo encolhido: medido, 276px dentro de uma coluna
   de 684px. O campo esticar é o que se quer; o botão, não. */
.bloco-config form > label { align-self: stretch; }
.bloco-config input { width: 100%; }
.bloco-config form > button { margin-top: var(--sp-2); }

/* SEM CAIXA por fora (achado 11/09, direto do dono: "não gostei dessa
   hierarquia... essa caixinha pode remover"). Uma segunda caixa ao lado do
   formulário já sem caixa criava hierarquia demais -- moldura dentro de
   moldura. Só a bolha em si guarda alguma borda/sombra: isso não é "uma
   caixa a mais", é o desenho de UMA MENSAGEM (o que ela representa), a
   mesma lógica de por que um balão de chat sempre parece levemente
   destacado do fundo em qualquer app de mensagem. Nenhuma cor de marca
   (nada de verde WhatsApp): a paleta de plataforma é reservada a
   ícone/etiqueta, não a preencher fundo de conteúdo. */
.preview-whatsapp { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.preview-rotulo {
  font-size: var(--fs-label); font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--track-label); color: var(--text-tertiary);
}
.preview-bolha {
  margin: 0; background: var(--bg-surface); border: 1px solid var(--border);
  border-radius: var(--raio-md) var(--raio-md) var(--raio-md) 2px;
  padding: var(--sp-3) var(--sp-4); font-size: var(--fs-body); line-height: var(--lh-body);
  color: var(--text-primary); overflow-wrap: anywhere; box-shadow: var(--sombra-sm);
}
@media (max-width: 860px) { .bloco-config { grid-template-columns: 1fr; } }

/* `flex-direction: row` EXPLÍCITO, e não por omissão — a mesma armadilha que
   `.filtros` já documenta, e na qual eu caí de novo em 18/08. A regra base
   `form { display:flex; flex-direction:column }` se aplica aqui também: sem
   esta linha, `align-items: flex-end` passa a significar "à direita" em vez de
   "embaixo", e `flex: 1 1 200px` faz os campos crescerem na VERTICAL. Uma
   declaração faltando, três sintomas. Hoje há teste cobrando (form-em-linha). */
/* A GAVETA DO "CRIAR LINK" (item 5.4, reforma 11/09) — o formulário deixa de
   ocupar espaço fixo no rodapé (usado raramente) e vira um painel que abre.
   Mesma técnica da gaveta da sidebar no celular: `<input type="checkbox">` +
   `~` no CSS, zero JS, sem os bugs de `<details>` no Safari/Firefox. O
   checkbox fica escondido do jeito visualmente-oculto de sempre (não
   `display:none`, que também tiraria do foco de teclado). */
.link-novo-check { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.link-novo-abrir {
  /* `align-self: flex-start` -- ACHADO NA TELA (11/09): `.gerador-v3` é
     `display: flex; flex-direction: column` sem `align-items` declarado, e o
     padrão do eixo cruzado é `stretch` -- todo filho direto (este `<label>`
     incluído) esticava pra 100% da largura da coluna, virando uma barra
     inteira em vez de um botão. */
  align-self: flex-start;
  display: inline-flex; flex-direction: row; align-items: center; justify-content: center; gap: 6px;
  padding: var(--sp-2) 14px; height: 36px; border: 1px solid var(--border-hover);
  border-radius: var(--raio); background: var(--bg-surface); color: var(--text-primary);
  font-family: var(--font-sans); font-size: var(--fs-body); font-weight: 500; cursor: pointer;
}
.link-novo-abrir:hover { background: var(--bg-elevated); }
.link-novo-check:focus-visible + .link-novo-abrir { box-shadow: 0 0 0 2px var(--accent); outline: none; }
.form-link-novo {
  display: flex; flex-direction: row; flex-wrap: wrap;
  gap: var(--sp-3); align-items: flex-end;
  max-height: 0; overflow: hidden; margin-top: 0;
  transition: max-height .2s ease;
}
.link-novo-check:checked ~ .form-link-novo { max-height: 160px; margin-top: var(--sp-4); }
.form-link-novo label { flex: 0 1 240px; gap: var(--sp-1); min-width: 0; }
.form-link-novo input { width: 100%; }
.form-link-novo button { flex: none; }
/* A instalação vem recolhida: instalar é uma vez na vida, copiar link é todo
   dia. Dar o mesmo espaço às duas fazia a tela parecer manual. */
/* `margin-top: var(--sp-5)`, e não `--sp-3` (reforma 11/09, item 8): o mesmo
   respiro que separa "Configuração" e "Links rastreáveis" das seções
   anteriores. "Instalação" não passa pelo componente `secao` (o próprio
   `<summary>` é o título), então precisa repetir a margem à mão em vez de
   herdar `.secao-cabecalho`. */
.instalacao { border: var(--bord); border-radius: var(--raio-lg); background: var(--bg-alt);
              margin-top: var(--sp-5); }
.instalacao > summary { cursor: pointer; padding: var(--sp-4); list-style: none;
                        display: flex; flex-direction: column; gap: 2px;
                        position: relative; padding-right: var(--sp-7); }
.instalacao > summary::-webkit-details-marker { display: none; }
/* O TÍTULO DENTRO DO `<summary>` NO MESMO PESO DE `.secao-titulo` (item 8):
   sem isto ele herda o corpo (15px, 400) e lê como um nível abaixo de
   "Configuração"/"Links rastreáveis", que são `<h2>` de verdade. */
.instalacao > summary strong {
  font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: 600; color: var(--text-primary);
}
/* A SETA É A MESMA DE "Decididas recentemente" (25/08, o dono olhando a tela no
   ar: "esse triângulo no canto direito é minúsculo, não dá nem pra saber").
   Era o caractere ▾, e o glifo desenha uma fração do próprio corpo: a 15px ele
   saía com ~5px de altura e cor de texto terciário -- some no canto de uma
   caixa de 80px de altura, e nada mais dizia que a linha abre.
   Desenhada com bordas, ela tem o tamanho que pedimos, gira na abertura e usa
   o mesmo desenho que a casa já tinha em `.recentes-caixa` -- inventar uma
   segunda seta seria dois vocabulários para o mesmo gesto. */
.instalacao > summary::after {
  content: ""; position: absolute; right: var(--sp-4); top: 50%; margin-top: -6px;
  width: 9px; height: 9px; border-right: 2px solid var(--text-secondary);
  border-bottom: 2px solid var(--text-secondary); transform: rotate(45deg);
  transition: transform var(--trans);
}
.instalacao { position: relative; }
.instalacao[open] > summary::after { transform: rotate(-135deg); margin-top: -2px; }
.instalacao > summary:hover::after { border-color: var(--text-primary); }
.instalacao-corpo { padding: 0 var(--sp-4) var(--sp-4); }

/* ═══════════ A EMPRESA ═══════════ */
/* Um bloco de linha: assunto à esquerda, ação à direita. Ele substitui a fila
   de quatro botões do canto superior direito, onde "Excluir cliente" dividia
   espaço com "Inspecionar webhook". Cada assunto passa a ter o seu lugar. */
.bloco-linha { border: var(--bord); border-radius: var(--raio-lg); background: var(--bg-alt);
               padding: var(--sp-4); display: flex; align-items: center;
               justify-content: space-between; gap: var(--sp-4); flex-wrap: wrap; }
.bloco-linha-titulo { font-size: var(--fs-md); font-weight: 600; display: flex; align-items: center; }
.bloco-linha form { flex-direction: row; gap: 0; align-items: center; }

/* A CONEXÃO COM A META -- estado à esquerda, o que se faz à direita.
   
   Reformado em 24/08 a pedido do dono, com as palavras dele: "no dia a dia o
   único botão útil é o de desconectar, o resto é inútil: trocar pixel, testar
   envio? Pelo menos não deveria estar ali na cara, poluindo."
   
   A regra que sustenta isso é a mesma que organiza o Rastreamento desde 19/08:
   FREQUÊNCIA. O estado se lê toda semana; trocar o pixel e testar o envio
   acontecem quando algo muda ou alguém desconfia. Antes os três dividiam a
   mesma faixa, e a raridade ocupava o lugar da rotina.
   
   O bloco anterior (`.linha-teste-meta`) morava FORA da caixa do estado, com
   padding próprio para fingir alinhamento -- e por isso flutuava. Agora as
   ferramentas moram DENTRO, separadas por uma linha. */
.conexao-acoes { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.conexao-acoes form { flex-direction: row; gap: 0; align-items: center; }

/* Ocupa a largura toda para cair na linha de baixo do `.bloco-linha`, que é
   flex com `wrap`. Sem isto ela disputaria a faixa com o estado. */
.ferramentas { flex-basis: 100%; border-top: var(--bord);
               margin-top: var(--sp-3); padding-top: var(--sp-3); position: relative; }
.ferramentas > summary { cursor: pointer; list-style: none; font-size: var(--fs-sm);
                         color: var(--text-secondary); }
.ferramentas > summary::-webkit-details-marker { display: none; }
/* Mesma seta da Instalação, no tamanho de uma linha de texto: ver o comentário
   de `.instalacao > summary::after`. */
.ferramentas > summary::after {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-left: var(--sp-2);
  border-right: 1.5px solid var(--text-secondary); border-bottom: 1.5px solid var(--text-secondary);
  transform: rotate(45deg) translateY(-2px); transition: transform var(--trans);
}
.ferramentas[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
.ferramentas > summary:hover::after { border-color: var(--text-primary); }
.ferramentas-corpo { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap;
                     gap: var(--sp-2); padding-top: var(--sp-3); }
.ferramentas-corpo form { flex-direction: row; gap: var(--sp-2); align-items: center; }
.ferramentas-corpo input { width: 12rem; }
/* Cada dica em sua própria linha: espremidas entre botões elas viram ruído. */
.ferramentas-corpo .dica { flex-basis: 100%; margin: 0; }

/* A TELA DE ESCOLHER O PIXEL (24/08). O estado do pixel se lê como estado, com
   a mesma bolinha do Rastreamento -- a casa já usa esse sinal para "está de pé"
   e "parou", e inventar outro para a mesma ideia seria dois vocabulários. */
.estado-pixel { display: flex; align-items: center; gap: 0; margin: 0 0 var(--sp-1); }
.estado-pixel .ponto { margin-right: var(--sp-2); }

/* O `<details>` das saídas manuais é um painel, então perde a borda de cima que
   o separador das ferramentas do Rastreamento usa -- ali ele divide uma caixa,
   aqui ele É a caixa. */
.painel-info.ferramentas { flex-basis: auto; border-top: var(--bord);
                           margin-top: var(--sp-4); padding: var(--sp-4); }
.painel-info.ferramentas > summary { padding: 0; }
.ferramentas-corpo.empilhado { flex-direction: column; align-items: stretch; gap: var(--sp-4); }
.ferramentas-corpo.empilhado input { width: 100%; }
.ferramentas-corpo.empilhado form { flex-direction: column; align-items: stretch; gap: var(--sp-3); }

/* O CORPO DE UM PAINEL. `.painel-info` dá respiro ao cabeçalho e a
   `.campos-info`, e nada a um `<form>` solto -- que por isso encostava nas duas
   bordas, com o botão virando uma barra de ponta a ponta. Visto no navegador,
   não nos testes. */
.painel-corpo { padding: var(--sp-4) var(--sp-5) var(--sp-5); }
/* Quando vem logo abaixo de `.campos-info`, que já pagou o respiro de cima. */
.painel-corpo.sem-topo { padding-top: 0; }
/* `form` é coluna com `stretch`: sem isto o botão ocupa a largura toda. O
   `.acoes-grupo` é linha, então cada botão volta ao tamanho do próprio rótulo. */
.painel-corpo .acoes-grupo { margin-top: var(--sp-1); }

.form-busca { flex-direction: row; align-items: flex-end; gap: var(--sp-3); flex-wrap: wrap; }
/* DENTRO DE `.bloco-linha`, `.bloco-linha form` (mais específico) vencia o
   `align-items` acima e centralizava o botão contra a altura do label
   INTEIRO (rótulo maiúsculo + campo) — o botão ficava visivelmente mais alto
   que o campo de busca. Precisa de mais uma classe para ganhar a disputa de
   especificidade e voltar a alinhar o botão pelo rodapé, rente ao campo. */
.bloco-linha .form-busca { flex-direction: row; align-items: flex-end; gap: var(--sp-3); }
.form-busca label { flex: 1 1 240px; gap: var(--sp-1); }
.form-busca input { width: 100%; }

/* A zona de perigo: separada, no fim, e visualmente terminal. Excluir apaga
   todas as conversas, cliques e vendas sem desfazer — ela não pode dividir
   espaço com um diagnóstico. */
.perigo-zona { border: 1px solid color-mix(in srgb, var(--critico) 45%, transparent);
               border-radius: var(--raio-lg); padding: var(--sp-4); }
.perigo-zona p { margin: 0 0 var(--sp-3); font-size: var(--fs-caption); color: var(--fg-mut); }
.link-btn.perigo { color: var(--critico); }

/* Cada lugar tem dois destinos: WhatsApp e site. Até 11/09 cada um vinha numa
   linha própria, com rótulo + URL visível + "Copiar" genérico -- três coisas
   pra dizer "isto aqui é o link do WhatsApp". Na reforma visual, o botão virou
   o rótulo (o texto já diz o destino) e a URL saiu da renderização -- ela
   segue inteira em `data-copiar` (o que de fato vai pra área de
   transferência) e em `title` (confere passando o mouse). Os dois destinos
   agora cabem numa linha só. */
.link-destino { display: flex; align-items: center; gap: var(--sp-2); padding: 3px 0; }
.link-destino button { flex: none; }

/* ─── O "i" de ajuda ──────────────────────────────────────
   Pedido do dono em 18/08: instrução some da tela e vive atrás de um "i" que
   abre ao passar o mouse. O que fica visível é estado e ação.

   O balão precisa de três cuidados que a versão ingênua não tem:
   - `position: fixed` não serve (rolagem descola), então é absoluto e o pai
     ganha `position: relative`;
   - largura em `min()` para nunca ser mais largo que a janela no celular;
   - `pointer-events: none` enquanto fechado, senão ele intercepta cliques de
     uma área invisível. */
/* A linha em que o rótulo e o "i" convivem. Sem ela o "i" cai numa linha só
   dele: o `<label>` herda `flex-direction: column` da regra base de formulário,
   e um `inline-flex` dentro de um flex container vira item de bloco. */
.rotulo-linha { display: inline-flex; align-items: center; gap: 0; }

.ajuda {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; flex: none; margin-left: 7px; vertical-align: middle;
  background: var(--bg-alt); border: 1px solid var(--text-tertiary); border-radius: 50%;
  color: var(--text-secondary); cursor: help; user-select: none;
  transition: background .12s ease, color .12s ease;
}
.ajuda:hover, .ajuda:focus-visible {
  background: var(--text-tertiary); color: var(--bg-base); border-color: var(--text-tertiary); outline: none;
}
.ajuda:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
/* `text-transform: none` pelo mesmo motivo do `.ajuda-balao` (11/09): dentro
   de um `<label>`, sem isto o "i" minúsculo vira "I" maiúsculo -- deixa de
   parecer a marca de informação e passa a parecer letra solta. */
.ajuda-marca { font-size: var(--fs-label); font-weight: 600; font-style: italic; line-height: 1;
               font-family: var(--font-serif, Georgia, serif); text-transform: none; }

/* ⚠️ `max-height: 0` + `overflow: hidden` no estado fechado (achado 11/09):
   sem isso, o balão -- `position: absolute`, mas com altura de verdade --
   segue contando pra "scrollable overflow" de qualquer ancestral com rolagem
   (ex.: `.tabela-rolante`, que vira rolável no eixo Y também por causa do
   `overflow-x: auto`), mesmo escondido por `opacity`/`visibility`. Numa
   tabela cuja última linha tem um "i" de ajuda, isso sobrava como um trecho
   de rolagem vazio abaixo do conteúdo -- espaço fantasma, sem nada pra
   mostrar. Com o balão colapsado a 0 quando fechado, ele para de inflar a
   área rolável do pai; ao abrir, o `max-height` generoso (maior que
   qualquer texto real de ajuda) libera o conteúdo sem cortar. */
.ajuda-balao {
  position: absolute; left: 0; top: calc(100% + 8px); z-index: 40;
  width: max-content; max-width: min(420px, calc(100vw - 32px));
  padding: var(--sp-3) var(--sp-3);
  background: var(--bg-surface); border: 1px solid var(--border);
  border-radius: var(--raio-md); box-shadow: var(--sombra-lg, 0 8px 24px rgba(0,0,0,.35));
  font-size: var(--fs-sm); line-height: 1.55; font-weight: 400; font-style: normal;
  color: var(--text-secondary); text-align: left; white-space: normal;
  /* ⚠️ ACHADO 11/09: dentro de um `<label>` (via `campo.ejs`), o balão herdava
     `text-transform: uppercase` da regra base de `label` -- o texto de ajuda
     inteiro saía em caixa alta, ilegível numa frase longa. `.ajuda` também
     entra por `secao.ejs`/`passo-cabecalho`, que não têm essa regra, então o
     defeito só aparecia em metade dos usos -- foi assim que passou despercebido
     até o dono ver com o próprio olho. */
  text-transform: none;
  opacity: 0; visibility: hidden; pointer-events: none;
  max-height: 0; overflow: hidden;
  transition: opacity .12s ease, visibility .12s;
}
.ajuda:hover .ajuda-balao, .ajuda:focus-visible .ajuda-balao,
.ajuda:focus-within .ajuda-balao {
  opacity: 1; visibility: visible; max-height: 480px; overflow: visible;
}
.ajuda-balao code { font-family: var(--font-mono); font-size: var(--fs-caption);
                    background: var(--bg-alt); padding: 1px var(--sp-1); border-radius: 3px;
                    color: var(--text-primary); overflow-wrap: anywhere; }
.ajuda-balao strong { color: var(--text-primary); }

/* Perto da borda direita o balão sairia da tela; esta variante ancora à direita. */
.ajuda.ajuda-fim .ajuda-balao { left: auto; right: 0; }

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

/* ─── Responsivo: nenhuma caixa corta texto ───────────────
   O dono pediu em 18/08: "o sistema precisa ser responsivo, o que tem de caixa
   cortando texto não está escrito". As regras abaixo atacam as três causas
   reais, e não o sintoma.

   ⚠️ EXCEÇÃO NOMEADA, 11/09: as URLs do Google/Meta Ads na Instalação
   voltaram a truncar com reticências (pedido explícito do dono na reforma
   visual, depois de eu apontar o conflito com esta regra de 18/08 -- ele
   confirmou que queria a versão truncada mesmo assim: são URLs pra COPIAR,
   não pra ler, e o `title` nativo resolve a conferência sem gastar largura).
   O valor completo nunca sai do campo -- só some da RENDERIZAÇÃO. Ver
   `.gerador-v3 .copy-field input[type=text]`, mais abaixo.
   Na tabela de links rastreáveis a URL foi além: saiu de vez da tela (nem
   truncada aparece) -- o botão passou a se chamar pelo destino em vez de
   "Copiar", e a URL vive só em `data-copiar`/`title`. Ver `.link-destino`,
   também mais abaixo. */

/* 1. O textarea do pixel é o elemento mais largo da aplicação (1358px de
      largura mínima). Sem isto ele dita a largura da página inteira. */
.gerador-v3 .copy-field textarea { width: 100%; min-width: 0; resize: none; }
.gerador-v3 .copy-field { min-width: 0; }

/* 2. Campo cujo VALOR é mais longo que a caixa. O input não quebra linha, então
      o texto some à direita — foi o que apareceu em "https://cchdistribuidora.com.b|"
      e "Olá! Vim pelo site e gostari|". Largura cheia resolve os dois. */
.bloco-config label, .bloco-config form { min-width: 0; }
.bloco-config input { width: 100%; min-width: 0; }

/* 3. Toda tabela rola DENTRO de si mesma, em qualquer largura — não só no
      celular, onde esta regra já vivia. Uma tabela larga num desktop estreito
      quebrava o layout do mesmo jeito.

      CUIDADO COM A ESPECIFICIDADE, e eu caí nela em 18/08: a primeira versão
      escrevia `.main .tabela-rolante { max-width: 100% }`, que pesa 0,2,0
      contra os 0,1,0 de `.main > *` — e portanto ANULAVA a largura de leitura
      em toda tabela, silenciosamente. O seletor de classe simples resolve: ele
      empata com `.main > *` e perde por vir antes, que é o que se quer. */
.tabela-rolante { overflow-x: auto; }
.tabela-rolante table { width: 100%; }

@media (max-width: 1100px) {
  .link-destino { flex-wrap: wrap; }
}
@media (max-width: 900px) {
  .main { padding: var(--sp-4); }
  .form-link-novo label { flex: 1 1 100%; }
  /* ⚠️ AQUI HAVIA UM `.ajuda-balao { left: auto; right: 0; }` E ELE FOI EMBORA
     (01/09). A regra ancorava TODO balão à direita abaixo de 900px, e com isso
     apagava a decisão de `interacoes.js`, que mede a borda de verdade no
     instante de abrir -- o `classList.remove('ajuda-fim')` de lá não tem como
     desfazer uma media query que não olha a classe.

     Enquanto todo "i" morava num cabeçalho de seção, encostado à direita, as
     duas concordavam por acidente. O "i" da tabela de campanhas do Meta mora na
     PRIMEIRA COLUNA: a regra fixa jogava o balão para -90px num celular de 360.
     Quem decide o lado é o JS, nos dois eixos, em qualquer largura. */
}

/* O CABEÇALHO DE SEÇÃO: título à esquerda, ações à direita, na mesma linha.
   Antes disto, uma tela que precisava de um botão ao lado do título resolvia
   com `style=` — foi a origem do único `<h2 style="margin:var(--sp-6) 0 var(--sp-3);">` que
   a base tinha. */
.secao-cabecalho {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3); margin: var(--sp-5) 0 var(--sp-3);
}
.main > .secao-cabecalho:first-of-type { margin-top: var(--sp-5); }
.secao-cabecalho .secao-titulo { margin: 0; }
.secao-acoes { display: flex; align-items: center; gap: var(--sp-2); flex: none; }

/* A SEÇÃO NUMERADA (25/08), do componente e não de uma tela: a ordem das
   perguntas é INFORMAÇÃO no Relatório -- cada uma só existe porque a anterior
   foi respondida --, e é isso que o número escrito ao lado do título diz.
   Fora do Relatório nenhuma tela passa `numero` nem `nota`, e o cabeçalho de
   seção continua exatamente como era. */
.secao-cabecalho.numerada { gap: var(--sp-3); justify-content: flex-start; }
.secao-cabecalho .secao-numero {
  font-size: var(--fs-label); font-weight: 600; color: var(--text-tertiary);
  font-variant-numeric: tabular-nums; min-width: 14px; flex: none;
}
/* A NOTA encosta na margem direita e diz como LER o bloco ("duas portas, não um
   funil"). Não é subtítulo: subtítulo desceria uma linha e competiria com o
   título. */
.secao-cabecalho .secao-nota {
  font-size: var(--fs-sm); color: var(--text-tertiary);
  margin-left: auto; text-align: right;
}

/* O botão do estado vazio. Ele existia como `<a><button>` aninhado no partial
   antigo — botão dentro de link é marcação inválida, e o teclado tropeça nela:
   dois alvos focáveis para uma ação só. Aqui é um link com cara de botão. */
.botao-vazio {
  display: inline-block; margin-top: var(--sp-3);
  padding: var(--sp-2) var(--sp-4); border-radius: var(--raio-md);
  background: var(--accent); color: var(--bg-base);
  font-size: var(--fs-body); font-weight: 500; text-decoration: none;
}
.botao-vazio:hover { filter: brightness(1.08); }

/* O estado vazio quando ele É a página inteira: centralizado, com largura de
   leitura própria. */
.tabela-vazia-pagina { max-width: 520px; margin: var(--sp-8) auto; }

/* A variante COMPACTA: uma linha, sem caixa. Era a classe solta `.vazio-linha`.
   Uma caixa tracejada dentro de um painel de quatro linhas seria pesada demais
   — a densidade do vazio tem de acompanhar a densidade do que ele substitui. */
.tabela-vazia.tabela-vazia-compacta {
  padding: var(--sp-2) 0; border: none; text-align: left;
  display: block;
}
.tabela-vazia-compacta .titulo {
  color: var(--text-tertiary); font-weight: 400; font-size: var(--fs-sm); margin: 0;
}

/* AS PÁGINAS DE ERRO não são estado vazio — elas pegavam emprestada a aparência
   dele (a caixa tracejada), e por isso carregavam quatro `style=` cada uma para
   desfazer o que não servia. São outra coisa: um código grande, uma frase, e uma
   saída. */
.pagina-erro { max-width: 520px; margin: var(--sp-8) auto; text-align: center; }
.pagina-erro .codigo { font-size: 64px; line-height: 1; margin: 0 0 var(--sp-2); }
.pagina-erro .frase { font-size: var(--fs-h2); margin: 0 0 var(--sp-2); }
.pagina-erro .detalhe { color: var(--text-secondary); margin: 0 0 var(--sp-5); }

/* BOTÃO DENTRO DE CÉLULA não estica. A regra base `form { display: flex;
   flex-direction: column }` faz do formulário um container de coluna, e o
   `align-items: stretch` do padrão espicha o botão para a largura inteira da
   célula — 418px medidos na tela de Usuários, contra os ~120px que o texto
   pede. A mesma armadilha do `<label>` que o `campo.ejs` fecha. */
td form { align-items: flex-start; }
td form button, td > button { width: auto; }

/* A legenda: a mesma cor da barra, pelas mesmas classes. O quadradinho é
   pequeno de propósito — quem carrega a informação é o rótulo ao lado. */
.legenda-plataformas i {
  display: inline-block; width: 8px; height: 8px; border-radius: 2px;
  vertical-align: middle; margin-right: var(--sp-1); flex: none;
}
.legenda-plataformas i.incerto {
  background-image: repeating-linear-gradient(135deg, rgba(0,0,0,.55) 0 3px, transparent 3px 6px);
}
/* A LEGENDA TEM DE DIZER O QUE A BARRA MOSTRA. `--p-nada` sólido era o desenho
   antigo; hoje o desconhecido é hachura recuada, e o quadradinho tinha ficado
   mentindo — um bloco escuro na legenda para uma faixa quase vazia na barra.
   Medido em 19/08: 1,81:1 no escuro, onde ele mal aparecia, e 9,74:1 no claro,
   onde virava um quadrado preto. */
.legenda-plataformas i.p-nada,
.legenda-plataformas i.p-desconhecida {
  background: var(--bg-afundado);
  background-image: repeating-linear-gradient(45deg,
    var(--traco-sem-origem) 0 1px, transparent 1px 4px);
  box-shadow: inset 0 0 0 1px var(--border);
}

/* ─── Do `style=` para a intenção ─────────────────────────
   As classes abaixo substituem os 41 estilos que estavam escritos dentro do
   HTML. Cada nome diz o QUE a coisa é, e não como ela parece: `.form-estreito`
   e não `.mw-480`. A diferença importa — nome de aparência é classe utilitária
   disfarçada, e reintroduz a mesma deriva com outro rótulo. */

/* Formulário de configuração: linha de leitura curta. Campo de 900px de largura
   para digitar um e-mail não ajuda ninguém a ler o que digitou. */
.form-estreito { max-width: 480px; }

/* O PAINEL LATERAL DO CONVITE (Seção 3, 05/09) -- convidar é usado talvez
   uma vez por mês, e o formulário vivia empilhado no rodapé da tela que
   as pessoas abrem todo dia. Mesma técnica da gaveta mobile da sidebar:
   <input type="checkbox"> + CSS, ZERO JS, e sem os bugs de <details>
   cross-browser que já derrubaram aquela primeira tentativa (ver
   gaveta-do-celular.test.js). Classes próprias -- .painel-lateral*, nunca
   .sobreposicao/.modal -- porque aquelas duas já têm semântica própria
   noutro lugar (JS por tela; <dialog>, respectivamente).

   ⚠️ O CHECKBOX NÃO É IRMÃO DO PAINEL (achado do review, 05/09). A primeira
   versão pôs `#convite-toggle` do lado do `<aside>`, e usava `~` (irmão
   geral) pra ligar os dois -- mas isso deixava o gatilho visível
   ("Convidar pessoa") sozinho lá no cabeçalho, um `<label>` sem nenhum
   controle de formulário do lado, e por isso INALCANÇÁVEL por teclado (um
   `<label>` sozinho nunca entra na ordem de tabulação nativa). O conserto:
   o checkbox virou vizinho ADJACENTE do próprio rótulo visível, dentro do
   `acoes` do cabeçalho (ver `usuarios.ejs`) -- mesma forma exata da gaveta
   mobile. Isso tira o checkbox de perto do fundo/`<aside>`, então o
   liga-desliga deles não pode mais usar `~`: usa `:has()` num ancestral
   comum (`.main`, que contém tanto o cabeçalho quanto o painel) -- mesma
   técnica que `.cartao-radio:has(input:checked)` já usa neste arquivo. */
.painel-lateral-check {
  position: absolute; opacity: 0; width: 1px; height: 1px; overflow: hidden;
}
/* `.painel-lateral-abrir` e `.painel-lateral-fechar` são <label>, e a regra
   base de `label { display: flex; flex-direction: column }` (ver
   form-em-linha.test.js) valeria por cima se não declarássemos a direção
   aqui -- sem isto o ícone e o texto empilhavam em vez de ficar lado a
   lado. */
.painel-lateral-abrir {
  display: inline-flex; flex-direction: row; align-items: center; justify-content: center; gap: 6px;
  padding: var(--sp-2) var(--sp-3); height: 36px;
  border: 1px solid var(--accent); border-radius: var(--raio);
  background: var(--accent); color: var(--accent-fg);
  font-family: var(--font-sans); font-size: var(--fs-body); font-weight: 600;
  cursor: pointer; white-space: nowrap;
}
.painel-lateral-abrir:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
/* Agora `+` de verdade funciona: checkbox e rótulo são irmãos adjacentes no
   HTML (ver comentário acima), então o anel de foco aparece no botão que a
   pessoa enxerga -- mesma solução da gaveta (`.gaveta-check:focus-visible +
   .gaveta-barra`). */
.painel-lateral-check:focus-visible + .painel-lateral-abrir {
  outline: 2px solid var(--marca); outline-offset: 2px;
}

.painel-lateral-fundo {
  position: fixed; inset: 0; background: rgba(0,0,0,.6); z-index: 60;
  opacity: 0; pointer-events: none; transition: opacity var(--trans);
}
.painel-lateral {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 100vw);
  background: var(--bg-surface); border-left: var(--bord); box-shadow: var(--sombra-lg);
  z-index: 61; padding: var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-4);
  overflow-y: auto;
  transform: translateX(100%); visibility: hidden;
  transition: transform var(--trans), visibility var(--trans);
}
/* `:has()` no ancestral comum, e não `~` -- o checkbox mora dentro do
   cabeçalho, o fundo/painel moram soltos mais abaixo em `.main`; os dois
   são descendentes de `.main`, mas não são irmãos um do outro. */
.main:has(.painel-lateral-check:checked) .painel-lateral-fundo {
  opacity: 1; pointer-events: auto;
}
/* `visibility: hidden` no estado fechado (achado da revisão final, 05/09):
   `transform: translateX(100%)` move o painel pra fora da tela, mas não tira
   ele da ordem de tabulação nem da árvore de acessibilidade -- então em TODA
   carga de `/admin/usuarios`, quem navega por teclado caía direto nos ~10
   campos invisíveis do formulário de convite (e leitor de tela expunha um
   "Convidar pessoa" permanentemente presente). `visibility` participa da
   transição junto com `transform` pra não sumir o painel de golpe no meio do
   slide. */
.main:has(.painel-lateral-check:checked) .painel-lateral {
  transform: translateX(0); visibility: visible;
}
.painel-lateral-topo { display: flex; align-items: center; justify-content: space-between; flex: none; }
.painel-lateral-topo h3 { margin: 0; }
.painel-lateral-fechar {
  display: inline-flex; flex-direction: row; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: var(--raio-sm); cursor: pointer;
  color: var(--text-secondary);
}
.painel-lateral-fechar:hover { background: var(--bg-elevated); color: var(--text-primary); }

@media (max-width: 640px) { .painel-lateral { width: 100vw; padding: var(--sp-4); } }

/* Título de bloco dentro de um painel ou diálogo — abaixo da seção, acima do
   conteúdo. O `h3` base zera a margem (a regra `h1,h2,h3,h4 { margin: 0 }`),
   então cada uso reinventava o respiro com `style=`. */
.titulo-bloco { margin: 0 0 var(--sp-3); }

/* Coluna de ações da tabela: alinhada à direita, encostada na borda. */
td.acoes, th.acoes { text-align: right; }

/* Um `.acoes-grupo` dentro de `.acoes` é um `<div>` de bloco -- o
   `text-align: right` da célula não alcança um filho de bloco, só texto e
   caixas em linha. Sem isto, os botões voltariam a colar na borda esquerda. */
td.acoes .acoes-grupo { justify-content: flex-end; }

/* COLUNA DE AÇÕES COM MAIS DE UM BOTÃO (fix de 1400px, 02/09 -- achado no
   navegador da lista de agências: Suspender cortado no meio, Apagar fora da
   tela). `max-width`, e NÃO `width`: um `width` em `<td>` sob
   `table-layout: auto` é só um PALPITE -- o navegador ainda cresce a coluna
   se o conteúdo pedir mais (foi o que `col-data` já ensinou, sem nunca ter
   sido posto à prova por um conteúdo que pedisse). `max-width` é teto de
   verdade: quando os botões (dentro de `.acoes-grupo`, que já é flex e já
   quebra linha) não cabem nele, eles EMPILHAM dentro da própria célula em
   vez de esticar a tabela inteira para fora do viewport. 230px cobre o botão
   isolado mais largo desta tela ("Reenviar convite") sem caber os três lado
   a lado -- é o que força a quebra. */
td.col-acoes { max-width: 230px; }

/* Coluna de nome: largura fixa para os nomes se alinharem entre as linhas. */
td.col-nome { width: 200px; font-weight: 500; }

/* PAINEL GERAL: nome de cliente não pode quebrar linha (2.2) -- quebrar
   desalinha o nome em relação à barra na mesma linha. min-width em vez de
   width fixo: a coluna cresce até caber o nome mais longo, e devolve a
   diferença para a coluna de Composição (que é onde sobra espaço hoje).
   Escopada por `.tabela-origens` -- um `<div>` que envolve só o `include`
   desta tabela em `painel-geral.ejs` -- e não pelo `td.col-nome` genérico: a
   tabela de Conexões, na mesma view, usa a mesma classe de coluna e não
   precisa da largura maior.
   ⚠️ NÃO É PARÂMETRO NOVO NO PARTIAL: `tabela.ejs` não recebe classe extra
   nenhuma. Um `classeTabela` no `<table>` foi tentado e revertido -- quebrava
   DUAS guardas estáticas que já existiam: `marcacao-fechada` (o atributo
   condicional tirava o espaço logo após `<table`, e a tag parava de casar a
   regex `<table(?=[\s>])`) e `nada-orfao` (o nome da classe só existia como
   VALOR de variável JS, nunca como texto literal dentro de um `class="..."`,
   então o scanner de classes órfãs nunca via `tabela-origens` como usada). O
   `<div>` escreve a classe como texto literal, e resolve os dois de graça. */
.tabela-origens td.col-nome { min-width: 220px; white-space: nowrap; }

/* 6.3 (06/09): título da seção ("De onde vieram...") e a primeira coluna da
   tabela ("Cliente"/nomes) DESALINHADOS em var(--sp-4) = 16px -- medido lendo
   CSS, não no navegador (sem um disponível nesta sessão): `.secao-titulo` não
   tem padding lateral (`margin: 0`, regra de `.secao-cabecalho .secao-titulo`
   em 2023), mas `th`/`td` (regra global, linha ~209) têm
   `padding: var(--sp-3) var(--sp-4)`. O título começa em x=0 do contêiner; a
   primeira célula, 16px depois. Zerar o padding-left só da primeira coluna
   alinha os dois no mesmo x.
   Escopado a `.tabela-origens`/`.tabela-conexoes` -- não ao `td`/`th`
   genérico: mudar a base moveria a primeira coluna de toda tabela do
   sistema (30+ pares título+tabela em outras 11 telas), uma decisão de
   design maior que o "conserto barato" que 6.3 pede aqui.
   `.tabela-conexoes` (06/09, addendum pós-review) escopa a tabela
   "Conexões com problema", mais abaixo na mesma view -- tinha o MESMO
   desvio quando visível (algum cliente desconectado), só que sem classe
   própria até este commit envolver o `include` dela em `painel-geral.ejs`
   com um `<div>`, o mesmo padrão que `.tabela-origens` já usava. */
.tabela-origens th:first-child,
.tabela-origens td:first-child,
.tabela-conexoes th:first-child,
.tabela-conexoes td:first-child {
  padding-left: 0;
}

/* Coluna de data (formato fixo, "dd/mm/aaaa"): não precisa da largura que uma
   coluna de texto livre pede. Encolhê-la é o que sobra de espaço para a ação
   da linha (achado no navegador, tela da agência -- ver `td.acoes` acima e o
   comentário em `sistema/agencia.ejs`). */
td.col-data { width: 100px; white-space: nowrap; }

/* Formulário que vive dentro de uma linha (célula, barra de ações): não é
   bloco, não empurra nada. */
.form-inline { display: inline; margin: 0; }

/* Linha inativa — cliente desativado. Opacidade, e não cor: a cor já significa
   plataforma e confiança nesta base. */
tr.inativa { opacity: 0.6; }

/* Ícone e texto na mesma linha, centrados entre si. Aparecia duas vezes com
   `gap` diferente (9px e 6px) sem motivo; ficou um. */
.linha-icone { display: inline-flex; align-items: center; gap: var(--sp-2); vertical-align: middle; }

/* O espaço que empurra o resto para a direita numa linha flex. */
.espacador { flex: 1; }

/* Ação destrutiva escrita como link. A cor é o único aviso, então ela é
   explícita e vem do token. */
.link-btn.perigo { color: var(--critico); }

/* Separador dentro de um bloco: uma linha e respiro dos dois lados. */
.separador { border-top: var(--bord); margin-top: var(--sp-4); padding-top: var(--sp-3); }

/* URL longa que precisa caber: quebra em qualquer ponto, e mantém a cor de
   quem a contém em vez de virar azul de link. */
.link-longo { color: inherit; text-decoration: underline; overflow-wrap: anywhere; }

/* Mensagem de erro que o JavaScript revela. Nasce escondida.
   A cor era `#f66` — hexadecimal cru, a única cor da base fora dos tokens, e
   ela não acompanhava o tema. */
.erro-inline { display: none; color: var(--critico); margin-top: var(--sp-2); font-size: var(--fs-body); }
.erro-inline.visivel { display: block; }

/* ── FINALIZAR O CADASTRO DA AGÊNCIA (01/09) ─────────────────────────────
   A tela que o dono da agência abre pelo convite. Ela é a PRIMEIRA coisa que
   um cliente pagante vê do produto, e por isso não pode parecer um formulário
   de sistema interno.

   ⚠️ `flex-direction: row` EXPLÍCITO, pela armadilha que `.form-link-novo` já
   documenta e na qual eu caí de novo hoje: a regra base
   `form { flex-direction: column }` alcança qualquer filho, e uma classe que
   quer conteúdo em linha precisa declarar a direção. */
/* ⚠️ A BASE É 150px, E O NÚMERO SAIU DA TELA MONTADA. Com 200px, CNPJ e
   WhatsApp somavam 412px num cartão de ~400 e quebravam para linhas separadas
   -- funcionava, mas por acidente, e o par de baixo (cidade/estado) pareava
   porque o estado é `.curto`. Layout que depende de um campo ser estreito por
   outra razão quebra no dia em que alguém mexe naquele outro campo. */
.campos-par { display: flex; flex-direction: row; flex-wrap: wrap; gap: var(--sp-3); }
.campos-par label { flex: 1 1 150px; gap: var(--sp-1); min-width: 0; }
.campos-par label.curto { flex: 0 1 90px; }
.campos-par input { width: 100%; }

/* CAMPOS SOLTOS NUMA LINHA, do tamanho do próprio conteúdo -- uma data, um
   número de dois dígitos --, sem a moldura de `.filtros` (pensada para ficar
   acima de tabela) e sem esticar como `.campos-par` faz de propósito (que
   CRESCE para preencher o cartão estreito de um formulário de cadastro).
   ⚠️ ACHADO NO NAVEGADOR (02/09): a tela da agência usava `.form-inline` --
   `display: inline; margin: 0` -- numa dessas linhas. Essa classe existe para
   o botão "Salvar" SOZINHO dentro de uma célula de tabela (ver o comentário
   dela abaixo), não para um formulário com campos visíveis fora de uma
   célula: `display: inline` no `<form>` não muda que `label` continua
   `display: flex; flex-direction: column` (regra base) -- um flex container
   BLOCO dentro de uma caixa inline blocifica sozinho, e cada campo passou a
   ocupar a largura inteira da coluna de leitura (até 1440px) numa linha
   própria. Mesma mecânica de `.filtros label` (`flex: 0 0 auto`), que já
   resolve exatamente isto acima de uma tabela -- aqui sem o resto do
   figurino de barra. */
.campos-linha { display: flex; flex-direction: row; flex-wrap: wrap; align-items: end; gap: var(--sp-3); }
.campos-linha label { flex: 0 0 auto; min-width: 0; }

/* O QUE JÁ SE SABE, e por que como `<dl>` e não como campo desabilitado: campo
   cinza convida a tentar editar e frustra; uma lista de definição diz "isto é
   contexto, não é o seu trabalho aqui". */
.ja-sabemos { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-1) var(--sp-4);
              margin: 0 0 var(--sp-4); padding-bottom: var(--sp-4);
              border-bottom: 1px solid var(--border); }
.ja-sabemos dt { color: var(--text-secondary); font-size: var(--fs-label);
                 text-transform: uppercase; letter-spacing: .04em; align-self: center; }
.ja-sabemos dd { margin: 0; color: var(--text-primary); }

/* O ERRO COLADO NO CAMPO, e não um banner no topo: com quatro campos, um aviso
   lá em cima obriga a procurar qual deles. `.erro` (bloco) e `.erro-inline`
   (do pixel) já existem e são outra coisa -- aquele é caixa com borda, este é
   uma linha sob o rótulo. */
.erro-campo { display: block; margin-top: var(--sp-1); color: var(--critico);
              font-size: var(--fs-caption); }

/* Respiros que pertencem ao bloco, e não ao elemento solto. */
.banner.espacado { margin-bottom: var(--sp-4); }
.dica.espacada { margin: 0 0 var(--sp-4); }
.legenda-oculta.afastada { margin-top: var(--sp-3); }
.acoes-grupo.afastado { margin-top: var(--sp-3); }
.bloco-linha.empilhado { display: block; }
.titulo-login { margin-bottom: var(--sp-5); }

/* AS CAIXAS DE CLIENTE DO CONVITE (Tarefa 10, 20/08) -- `fieldset` sem a borda
   e o padding que o navegador dá de graça, porque o desenho já tem os
   próprios. Cada `<label>` de opção desfaz o estilo de RÓTULO DE CAMPO que o
   `label` base impõe (caixa alta, 12px, cor terciária) pela mesma razão que
   `.conectar .aceite` já desfez em 18/08: isto não é rótulo, é uma frase que
   a pessoa lê e marca. */
.opcoes-marcar { border: 1px solid var(--border); border-radius: var(--raio-sm);
                 padding: var(--sp-3); margin: 0; display: flex; flex-direction: column;
                 gap: var(--sp-2); }
.opcoes-marcar legend { padding: 0 0 var(--sp-2); font-size: var(--fs-label);
                        font-weight: 500; text-transform: uppercase;
                        letter-spacing: var(--track-label); color: var(--text-tertiary); }
.opcao-marcar { display: flex; flex-direction: row; align-items: center; gap: var(--sp-2);
                text-transform: none; letter-spacing: normal; font-size: var(--fs-body);
                line-height: var(--lh-body); font-weight: 400; color: var(--text-primary);
                cursor: pointer; }
.opcao-marcar input[type=checkbox] { flex: none; width: 18px; height: 18px; margin: 0;
                                      padding: 0; border: 0; background: none;
                                      cursor: pointer; accent-color: var(--accent); }

/* O RÓTULO ACIMA DO CAMPO, para o par de radios de escopo (P0-1, 04/09) --
   mesmo degrau visual do `<label>` de E-mail/Papel, mas sem `<label>`
   porque não há um único campo para associar (são dois radios). */
.rotulo-campo { font-size: var(--fs-label); font-weight: 500; text-transform: uppercase;
                letter-spacing: var(--track-label); color: var(--text-tertiary);
                margin: 0 0 var(--sp-2); }

/* OS DOIS RADIOS DE ESCOPO (P0-1). Mesma desconstrução de `.opcao-marcar`:
   o `<label>` base da casa é rótulo de campo (caixa alta, cor terciária), e
   isto é uma frase que a pessoa lê e escolhe. NENHUM vem marcado por padrão
   -- é o próprio ponto do item: permissão nunca por omissão. */
.opcao-radio { display: flex; flex-direction: row; align-items: center; gap: var(--sp-2);
               font-size: var(--fs-body); line-height: var(--lh-body); font-weight: 400;
               color: var(--text-primary); cursor: pointer; margin-bottom: var(--sp-2);
               /* DESCONSTRÓI O <label> BASE (achado 1.1, 05/09): `label` da casa é
                  rótulo de campo (caixa alta, cor terciária) -- .opcao-marcar já
                  desfaz isto duas linhas abaixo, e .opcao-radio tinha ficado de
                  fora por engano: a frase de escopo do convite renderizava em
                  CAIXA ALTA por herança. */
               text-transform: none; letter-spacing: normal; }
.opcao-radio input[type=radio] { flex: none; width: 18px; height: 18px; margin: 0;
                                  padding: 0; accent-color: var(--accent); cursor: pointer; }

/* A LISTA QUE ESCALA (P0-1, ">10 clientes"): busca, marcar/limpar, e rolagem
   interna em vez de a fieldset crescer sem fim. Só entra no HTML quando
   `clientes.length > 10` -- abaixo disso os controles seriam ruído para uma
   lista de 3 nomes. */
.opcoes-marcar-busca { width: 100%; margin-bottom: var(--sp-2); }
.opcoes-marcar-acoes { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.opcoes-marcar-rolante { max-height: 240px; overflow-y: auto; display: flex;
                          flex-direction: column; gap: var(--sp-2); padding-right: var(--sp-1); }

/* ACHADO DA REVISÃO DA TASK 1 (04/09), mesma raiz de `.sobreposicao[hidden]`
   logo acima neste arquivo: `.opcoes-marcar` já é `display: flex` (regra de
   autor), e uma regra de autor com `display` sempre vence o `[hidden] {
   display: none }` do agente do usuário -- o atributo `hidden` sozinho (seja
   estático no HTML de `#lista-clientes-convite`, seja setado por
   `interacoes.js`) não escondia nada. Nome próprio resolveria por
   construção como `.sobreposicao` fez, mas aqui a classe É a coisa reutilizada
   entre a lista do convite e telas futuras -- então a regra explícita
   `[hidden]` é o jeito de manter a classe compartilhada sem reabrir este bug. */
.opcoes-marcar[hidden] { display: none; }

/* MESMO ACHADO, SELETOR IRMÃO (revisão da Task 1, 04/09): a busca de
   `interacoes.js` esconde cada `.opcao-marcar` que não bate com o termo
   digitado fazendo `linha.hidden = true`, mas `.opcao-marcar` também é
   `display: flex` de autor -- sem esta regra, a linha ficava com o atributo
   `hidden` no DOM (e "Marcar todos"/`:not([hidden])` já respeitava isso por
   baixo dos panos) mas continuava pintada na tela, dando a impressão de que a
   busca e o "Marcar todos" não faziam nada. */
.opcao-marcar[hidden] { display: none; }

/* O RADIO CARD DO PAPEL (P0-5): 2 opções nunca justificam dropdown, e o
   card deixa a explicação DENTRO da escolha -- antes ela morava num
   parágrafo no rodapé, depois do botão Convidar, onde quem já decidiu não
   volta pra ler. */
.cartao-radio { display: flex; flex-direction: row; align-items: flex-start; gap: var(--sp-3);
                 border: 1px solid var(--border); border-radius: var(--raio-sm);
                 padding: var(--sp-3); cursor: pointer; margin-bottom: var(--sp-2); }
.cartao-radio:has(input:checked) { border-color: var(--accent); }
.cartao-radio input[type=radio] { flex: none; width: 18px; height: 18px; margin: 3px 0 0;
                                   accent-color: var(--accent); cursor: pointer; }
.cartao-radio span {
  font-size: var(--fs-body); line-height: var(--lh-body); color: var(--text-primary);
  /* MESMO DEFEITO DE .opcao-radio, achado 1.1 (05/09): `.cartao-radio` é o
     <label> em si, e o `<span>` filho herdava `text-transform: uppercase` +
     letter-spacing de rótulo de campo. A explicação de Operador/Dono -- ~40
     palavras cada -- renderizava inteira em caixa alta, mais lenta de ler e
     com cara de termo de uso em vez de ajuda. */
  text-transform: none; letter-spacing: normal;
}

/* AS SUBABAS DE CONFIGURACOES (03/09). Neutro de proposito: `--marca` (o
   laranja) e a cor da MOLDURA (sidebar/topbar) -- ver `reforma_visual`, "cor
   e dado e nao se move por marca". Isto e navegacao DENTRO do conteudo, entao
   o estado ativo usa `--text-primary`, nao a marca. */
.subabas { display: flex; gap: var(--sp-4); border-bottom: 1px solid var(--border);
           margin-bottom: var(--sp-4); }
.subabas a { padding: var(--sp-2) 0; color: var(--text-tertiary); text-decoration: none;
             font-size: var(--fs-body); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.subabas a:hover { color: var(--text-primary); }
.subabas a.ativo { color: var(--text-primary); border-bottom-color: var(--text-primary);
                    font-weight: 600; }


/* ═══════════════════════════════════════════════════════════════
   O PAINEL DO CLIENTE (25/08, redesenhado 07/09) — a única tela que SAI DA
   EMPRESA
   ═══════════════════════════════════════════════════════════════
   Tudo aqui vive sob `.relatorio`, e o escopo não é zelo: `.painel`, `.ponto`,
   `.cartao` e `.rodape` já existem nesta folha com outro sentido.

   A tela é MEIO; o PDF é o produto. O `@media print` que transforma um no
   outro é da task de impressão -- aqui está só o que se vê na tela. */

/* ── FAIXA DE CONTROLES (1.2) -- período, abas e PDF numa linha só, 13px,
   "chrome parece chrome". Reaproveita `.periodo-relatorio` (com o modificador
   `.compacto`) e `.subabas`, só adiciona o layout em linha e a hairline. ── */
.relatorio .faixa-controles {
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  padding-bottom: var(--sp-3); margin-bottom: var(--sp-6); border-bottom: 1px solid var(--border);
}
.relatorio .faixa-controles .subabas { border-bottom: none; margin: 0; }
.divisor-controles { width: 1px; height: 14px; background: var(--border-hover); flex: none; }

/* ── PERÍODO E PDF -- DE-ESCOPADOS DE `.relatorio` DESDE 05/09 (o Painel
   geral também usa `periodo-relatorio.ejs`, sem ancestral `.relatorio`). ── */
.periodo-relatorio {
  display: flex; align-items: center; gap: var(--sp-3);
  flex-wrap: wrap; margin-bottom: var(--sp-4);
}
.periodo-datas {
  display: flex; flex-direction: row; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-sm); color: var(--fg-mut); margin: 0;
}
.periodo-datas label {
  display: inline-flex; flex-direction: row; align-items: center; gap: var(--sp-2);
  text-transform: none; letter-spacing: normal; font-weight: 400;
  font-size: var(--fs-sm); color: var(--fg-mut); margin: 0;
}
.periodo-datas input[type=date] {
  font: inherit; padding: var(--sp-1) var(--sp-2); border-radius: var(--raio);
  border: var(--bord); background: var(--bg-afundado); color: var(--text-primary);
  width: auto;
  /* O NAVEGADOR NÃO CENTRALIZA O CONTEÚDO INTERNO (dia/mês/ano) sozinho: sem
     isto, o texto do input fica mais alto que o dos botões ao lado, mesmo com
     o mesmo padding — só `display:flex` faz o Chrome centralizar os campos
     internos do date picker verticalmente. */
  display: flex; align-items: center;
}
.periodo-datas button {
  font: inherit; font-weight: 500; cursor: pointer;
  padding: var(--sp-1) var(--sp-3); border-radius: var(--raio);
  border: var(--bord); background: var(--bg-surface); color: var(--fg-mut);
}
/* 6.1/3.4 (07/09): o PDF é `--accent` (tinta sólida), não mais o laranja da
   marca -- laranja é exclusivo da moldura (sidebar/topo), nunca do conteúdo. */
.botao-pdf {
  font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
  margin-left: auto; padding: var(--sp-1) var(--sp-3); border-radius: var(--raio);
  background: var(--accent); border: 1px solid var(--accent); color: var(--accent-fg);
}
.botao-pdf:hover { filter: brightness(1.06); }
.periodo-relatorio :focus-visible { outline: 2px solid var(--marca); outline-offset: 2px; }

/* O `.compacto` do período (07/09): mesma marcação de sempre, só em 13px e
   sem a margem que a faixa isolada tinha -- ela agora é um item de flex ao
   lado das abas, não uma linha própria. */
.periodo-relatorio.compacto { font-size: var(--fs-caption); gap: var(--sp-3); margin-bottom: 0; }
.periodo-relatorio.compacto .periodo a { padding: var(--sp-1) var(--sp-2); }
.periodo-relatorio.compacto .periodo-datas { font-size: var(--fs-caption); gap: var(--sp-1); }
.periodo-relatorio.compacto .periodo-datas input[type=date] { padding: 2px var(--sp-1); }
.periodo-relatorio.compacto .periodo-datas button { padding: 2px var(--sp-2); }
.periodo-relatorio.compacto .botao-pdf { font-size: var(--fs-caption); padding: var(--sp-1) var(--sp-3); margin-left: 0; }

/* ── CABEÇALHO COMPACTO (1.2/1.4) -- a sobrancelha é o `<h1>`, o lide é a
   frase gerada por `services/relatorio-lide.js`, e a linha de destaque fica
   logo abaixo. 6.1: sem régua de marca -- o cabeçalho antigo tinha uma barra
   laranja de 3px; este não tem nenhuma cor de marca no conteúdo.
   ⚠️ `--fs-2xl` (24px), NÃO os 26px do desenho original: a escala desta casa
   é fechada (h1 32 / h2 17 / corpo 15 / métrica 36, mais os degraus de
   `--fs-*`), e o guarda estático recusa `font-size` em px cru fora do número
   da página de erro. 24px é o degrau mais próximo que já existe -- inventar
   um degrau novo pra um uso só é a deriva que a regra existe pra impedir. */
.relatorio .cabecalho.compacta { margin-bottom: var(--sp-6); }
.relatorio .cabecalho.compacta .sobrancelha {
  font-size: var(--fs-caption); font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--track-label); color: var(--text-tertiary); margin: 0 0 var(--sp-2);
}
.relatorio .cabecalho.compacta .lide {
  font-size: var(--fs-2xl); line-height: 1.35; font-weight: 400; color: var(--text-secondary);
  max-width: 600px; margin: 0;
}
.relatorio .cabecalho.compacta .lide b { font-weight: 500; color: var(--text-primary); }
.relatorio .cabecalho.compacta .lide.estado-vazio { color: var(--text-tertiary); font-style: italic; }
.relatorio .cabecalho.compacta .linha-destaque {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-body); color: var(--text-secondary); margin: var(--sp-3) 0 0;
}
.relatorio .cabecalho.compacta .linha-destaque b { color: var(--text-primary); font-weight: 600; }
.relatorio .cabecalho.compacta .linha-destaque.alerta,
.relatorio .cabecalho.compacta .linha-destaque.alerta b { color: var(--conf-fallback-fg); }
.relatorio .cabecalho.compacta .linha-destaque .fonte-marca { flex: none; line-height: 0; }
.relatorio .cabecalho.compacta .linha-destaque .fonte-marca svg { display: block; }
.relatorio .cabecalho.compacta .assinatura {
  margin: var(--sp-4) 0 0; color: var(--text-tertiary);
  font-size: var(--fs-label); letter-spacing: var(--track-label); text-transform: uppercase;
}

/* ── ÍCONE DE IDENTIDADE DO CARTÃO (07/09) -- chip de tinta suave, mesmo
   princípio do `.badge` (fundo fraco + cor cheia), aplicado ao ícone. ── */
.cabeca-card { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-1); }
.cabeca-card .ic { display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; flex: none; }
.cabeca-card .ic svg { width: 14px; height: 14px; }
.ic-neutro { background: var(--bg-elevated); color: var(--text-secondary); }
.ic-vendas { background: var(--conf-codigo-bg); color: var(--conf-codigo-fg); }
.ic-espera { background: var(--conf-fallback-bg); color: var(--conf-fallback-fg); }

/* ── CARD DE AÇÃO (topo, 1.2) -- fundo de aviso, so-tela. Não usa o
   `partial card` porque o desenho é horizontal (ícone + texto + seta), uma
   forma que o cartão de número não tem. ── */
.card-acao {
  display: flex; align-items: center; gap: var(--sp-3); background: var(--conf-fallback-bg);
  border-radius: var(--raio-md); padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-6);
  color: var(--text-primary); transition: filter var(--trans);
}
.card-acao:hover { text-decoration: none; filter: brightness(0.98); }
.ic-acao { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  border-radius: 50%; background: var(--bg-surface); color: var(--conf-fallback-fg); flex: none; }
.ic-acao svg { width: 17px; height: 17px; display: block; }
.card-acao-texto { flex: 1; font-size: var(--fs-body); font-weight: 600; }
.card-acao > svg { width: 18px; height: 18px; color: var(--conf-fallback-fg); flex: none; }

/* ── BARRA DEITADA de recebidas → rastreados (seção 1, pedido do dono,
   07/09) -- reforça que "rastreados" é PARTE de "recebidas": subconjunto de
   verdade, seguro de desenhar assim (diferente do funil, que não é linear). ── */
.barra-subconjunto { margin-top: var(--sp-4); }
.trilha-subconjunto { height: 12px; background: var(--bg-elevated); border-radius: 6px; overflow: hidden; }
.preenchido-subconjunto { height: 100%; background: var(--conf-codigo-fg); border-radius: 6px; }
.legenda-subconjunto {
  display: flex; align-items: center; gap: var(--sp-5); margin-top: var(--sp-2); flex-wrap: wrap;
  font-size: var(--fs-caption); color: var(--text-secondary);
}
.legenda-subconjunto span { display: inline-flex; align-items: center; gap: 6px; }
.ponto-legenda { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.ponto-legenda.cheio { background: var(--conf-codigo-fg); }
.ponto-legenda.vazio { background: var(--bg-elevated); border: 1px solid var(--border-hover); }
.total-subconjunto { margin-left: auto; color: var(--text-tertiary); }

/* ── O PAINEL DO RELATÓRIO -- nome próprio porque `.painel` já existe nesta
   folha como o painel da conversa. Ganhou sombra (07/09), igual aos
   cartões de número, pra ter a mesma profundidade. ── */
.relatorio .rel-painel {
  background: var(--bg-surface); border: var(--bord);
  border-radius: var(--raio-md); padding: var(--sp-5); box-shadow: var(--sombra-sm);
}
.relatorio .rel-rotulo {
  font-size: var(--fs-label); line-height: var(--lh-label); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--track-label);
  color: var(--text-tertiary); margin: 0 0 var(--sp-3);
}

/* A NOTA EXPLICATIVA (07/09) -- explica o ESCOPO do dado (por que só um
   canal aparece aqui), nunca uma decisão de CSS ou de paleta. Fundo
   levemente afundado, pra ler como contexto e não como alerta. */
.nota-explicativa {
  font-size: var(--fs-sm); color: var(--text-secondary); background: var(--bg-afundado);
  border-radius: var(--raio); padding: var(--sp-3) var(--sp-4); margin: 0 0 var(--sp-4);
}
.nota-explicativa b { color: var(--text-primary); }

/* ── "QUEM CLICOU E ESCREVEU" (seção 3, antes "Por onde entraram") --
   FUNIL HONESTO (1.1): a caixa fecha nela mesma, sem seta pro total. ── */
.funil-porta {
  background: var(--bg-afundado); border: 1px solid var(--border); border-radius: var(--raio-md);
  padding: var(--sp-4) var(--sp-5); display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
}
.funil-porta + .funil-porta { margin-top: var(--sp-3); }
.funil-porta .rotulo-porta {
  display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-sm);
  font-weight: 500; color: var(--text-secondary); min-width: 180px;
}
.funil-porta .rotulo-porta svg { flex: none; }
.funil-conta { display: flex; align-items: center; gap: var(--sp-2); }
.funil-conta .n { font-size: var(--fs-2xl); font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.funil-conta .rotulo-n { font-size: var(--fs-sm); color: var(--text-tertiary); margin-right: var(--sp-2); }
.funil-taxa {
  margin-left: auto; background: var(--conf-codigo-bg); color: var(--conf-codigo-fg);
  border-radius: var(--raio); padding: var(--sp-2) var(--sp-4);
  display: inline-flex; align-items: baseline; gap: 6px;
}
.funil-taxa b { font-size: var(--fs-lg); }
.funil-taxa span { font-size: var(--fs-label); color: inherit; opacity: .8; }

/* ── "DE ONDE VIERAM" -- 3.3: BARRAS RANQUEADAS, não pilha. A pilha compara
   composição ENTRE clientes (papel do Painel geral); aqui é um cliente só, e
   magnitude se lê melhor em barra do que em segmento de poucos pixels. ── */
.barras-origem { display: flex; flex-direction: column; gap: var(--sp-2); }
.linha-barra { display: grid; grid-template-columns: 190px 1fr 56px 52px; align-items: center; gap: var(--sp-3); }
.linha-barra .trilha { height: 10px; background: var(--bg-afundado); border-radius: 6px; overflow: hidden; }
.linha-barra .preenchido { height: 100%; border-radius: 6px; min-width: 3px; }
/* ⚠️ "SEM ORIGEM IDENTIFICADA" NA PALETA DE CONFIANÇA (1.3), não em
   `--p-nada`: é o mesmo conceito de "sem" que o resto do produto já usa --
   reaproveita o token, não inventa cor nova. Nunca preto, nunca a fatia mais
   pesada por peso de cor (mesmo sendo, aqui, frequentemente a maior). */
.linha-barra .preenchido.sem-origem { background: var(--conf-sem-fg); }
.texto-sem-origem { color: var(--conf-sem-fg); }
.linha-barra .n { font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }
.linha-barra .pct { color: var(--text-tertiary); font-size: var(--fs-caption); text-align: right; font-variant-numeric: tabular-nums; }
.linha-plataforma { display: inline-flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.linha-plataforma .fonte-marca { flex: none; line-height: 0; }
.linha-plataforma .fonte-marca svg { display: block; }
@media (max-width: 640px) { .linha-barra { grid-template-columns: 130px 1fr 44px 44px; } }
/* 3.1: dispositivo reaproveita `.linha-barra`, só com uma coluna de rótulo
   mais estreita (o nome do aparelho é mais curto que o de plataforma). */
.linha-dispositivo { grid-template-columns: 110px 1fr 44px 48px; }
.relatorio .links-proprios { margin-top: var(--sp-3); }
.relatorio .links-proprios .rel-rotulo { margin-bottom: var(--sp-3); }

/* ── TABELAS DE DETALHAMENTO -- total sempre visível, diferença explicada,
   fora de área rolável (1.2/2.1). ── */
.tabela-cabecalho { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-1); flex-wrap: wrap; }
.tabela-cabecalho .titulo { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-body); font-weight: 600; }
.tabela-cabecalho .fechamento { font-size: var(--fs-caption); color: var(--text-tertiary); display: flex; align-items: center; gap: var(--sp-2); }
.tabela-cabecalho .fechamento b { color: var(--text-secondary); font-weight: 600; }

/* DESTAQUES DE MARKETING (12/09) -- substitui "Campanhas do Meta"/"Anúncios
   do Meta" no Painel: só a campanha com mais leads de cada bloco, com link
   pra tela /marketing. `h3` segue o mesmo tamanho do título de bloco da tela
   /marketing (`.bloco-marketing h3`), pra não inventar um terceiro nível de
   título nesta página. */
.relatorio .destaques-marketing { margin-bottom: var(--sp-4); }
.relatorio .destaques-marketing h3 { font-size: var(--fs-md); margin: 0 0 var(--sp-2); }
.relatorio .destaque-linha { font-size: var(--fs-body); margin: 0 0 var(--sp-1); }
.relatorio .destaque-linha .rotulo { color: var(--fg-mut); }
.relatorio .destaques-marketing .ver-tudo { font-size: var(--fs-caption); }

/* CAMPANHA → BUSCA / PÚBLICO / ANÚNCIO. A campanha é a linha forte; os
   filhos recuam sob ela, com um traço vertical de agrupamento (4.2). */
.relatorio .rel-sub .secao-cabecalho { margin: var(--sp-5) 0 var(--sp-3); }
.relatorio .rel-sub .secao-titulo { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-body); line-height: var(--lh-body); }
.relatorio .rel-sub .fonte-marca { flex: none; line-height: 0; }
.relatorio .rel-sub .fonte-marca svg { display: block; }
.relatorio tr.campanha td { font-weight: 600; border-top: 1px solid var(--border-hover); }
.relatorio tr.busca td {
  border-top: none; padding-top: 6px; padding-bottom: 6px; color: var(--fg-mut); font-size: var(--fs-sm);
}
.relatorio tr.busca td:first-child {
  padding-left: calc(var(--sp-4) + var(--sp-4)); border-left: 2px solid var(--border-hover);
}
.relatorio .nota-tabela { font-size: var(--fs-caption); color: var(--fg-mut); margin-top: var(--sp-3); }
.relatorio .nota-tabela.cobertura { padding-top: var(--sp-3); border-top: 1px solid var(--border); }

/* MARKETING -- funil por campanha/conjunto/anuncio */
.pagina-marketing { max-width: 960px; }
.aviso-conexao {
  border: 1px dashed var(--border); border-radius: var(--raio-md); padding: var(--sp-3) var(--sp-4);
  color: var(--fg-mut); margin-bottom: var(--sp-4);
}
.origem-macro { margin: var(--sp-4) 0 var(--sp-2); }
.bloco-marketing { margin-bottom: var(--sp-4); }
.bloco-marketing h3 { font-size: var(--fs-md); margin: 0 0 var(--sp-2); }
table.funil { width: 100%; border-collapse: collapse; background: var(--bg-surface); border: var(--bord); border-radius: var(--raio-md); overflow: hidden; }
table.funil th { text-align: right; font-size: var(--fs-caption); color: var(--fg-mut); padding: var(--sp-2) var(--sp-3); border-bottom: var(--bord); }
table.funil th.nome-col, table.funil td.nome-col { text-align: left; }
table.funil td { padding: var(--sp-2) var(--sp-3); border-bottom: var(--bord); text-align: right; font-variant-numeric: tabular-nums; }
table.funil tr:last-child td { border-bottom: none; }
table.funil tr.abre { cursor: pointer; }
table.funil tr.abre:hover { background: var(--bg-afundado); }
table.funil .chevron { display: inline-block; margin-right: var(--sp-1); transition: transform .15s ease; color: var(--fg-mut); }
/* A ETIQUETA "WhatsApp"/"Site" (14/09, tabela única de Meta) só entra na
   linha de nível 0 -- o `.badge` já tem `display:inline-block` sem margem
   própria (pensado pra caber dentro de linha de texto corrida em qualquer
   canto do app), então esta tela precisa da própria margem pra não colar no
   nome logo depois. */
table.funil .nome-col .badge { margin-right: var(--sp-2); }
/* O NOME que virou link (prévia do criativo, resolvida pela Meta) usa a
   MESMA cor do badge azul ao lado -- as duas coisas nascem do mesmo dado
   (Graph API) e a cor amarra visualmente "isto veio da Meta e é clicável",
   em vez do preto padrão de `a` (app.css:16) que não distingue link de texto
   nesta linha específica. */
table.funil .nome-col a { color: var(--info); }
table.funil tr.aberta .chevron { transform: rotate(90deg); }
table.funil tr.nivel-1 td.nome-col { padding-left: calc(var(--sp-3) + var(--sp-4)); }
table.funil tr.nivel-2 td.nome-col { padding-left: calc(var(--sp-3) + var(--sp-6)); color: var(--fg-mut); }
/* FECHADO POR CSS, e não só por JS: um `tr.hidden` setado no carregamento da
   página dependeria do script rodar antes de qualquer repaint -- medido em
   produção (12/09) que isso falhava (nível 1/2 apareciam abertos no primeiro
   load, mesmo com o script correto e sem erro nenhum). CSS aplica no PRIMEIRO
   frame, sem depender de JS ter rodado; o script só alterna a classe
   `.mostrar`, nunca a visibilidade inicial. */
table.funil tr.nivel-1, table.funil tr.nivel-2 { display: none; }
table.funil tr.nivel-1.mostrar, table.funil tr.nivel-2.mostrar { display: table-row; }
table.funil th.col-sortavel { cursor: pointer; user-select: none; }
table.funil th.col-sortavel .seta { opacity: .35; margin-left: var(--sp-1); }
table.funil th.col-sortavel.ativo { color: var(--fg); }
table.funil th.col-sortavel.ativo .seta { opacity: 1; color: var(--accent); }
.em-breve-marketing {
  border: 1px dashed var(--border); border-radius: var(--raio-md); padding: var(--sp-3) var(--sp-4);
  color: var(--fg-mut); display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
}

/* ── "COMO E QUANDO FALAM COM VOCÊ" -- horários, dispositivo (3.1) e já era
   da casa (3.2), agrupados numa seção própria (6.3). ── */

/* O HISTOGRAMA. 5.3: ganhou eixo Y; 2.6: o contêiner do eixo+grade precisa
   de `flex:1` -- sem isso ele fica do tamanho mínimo do conteúdo em vez de
   esticar até a largura do card. */
.relatorio .grade-horas-wrap { display: flex; gap: var(--sp-2); }
.relatorio .eixo-y {
  display: flex; flex-direction: column; justify-content: space-between; height: 130px; padding-bottom: var(--sp-4);
  font-size: var(--fs-label); color: var(--text-tertiary); text-align: right; font-variant-numeric: tabular-nums;
}
.relatorio .grade-horas-corpo { flex: 1; min-width: 0; }
.relatorio .grade-horas { display: flex; align-items: flex-end; gap: 2px; height: 130px; }
.relatorio .hora { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.relatorio .hora .col { background: var(--so-4); border-radius: 4px 4px 0 0; min-height: 2px; }
.relatorio .hora.fora .col { background: var(--conf-sem-fg); opacity: .5; }
.relatorio .eixo { display: flex; gap: 2px; margin-top: var(--sp-2); }
.relatorio .eixo span { flex: 1; text-align: center; font-size: var(--fs-label); color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.relatorio .pico { margin-top: var(--sp-3); font-size: var(--fs-caption); color: var(--fg-mut); }
.relatorio .pico b { color: var(--text-primary); }

/* OS DOIS BLOCOS LADO A LADO -- dispositivo (3.1, barras) e já era da casa
   (3.2, número grande). `auto-fit` porque um dos dois pode não existir. */
.relatorio .dois-blocos { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px,1fr)); gap: var(--sp-3); margin-top: var(--sp-3); }

/* 3.2: "JÁ ERA DA CASA?" VIROU NÚMERO GRANDE -- duas fatias é o caso
   canônico de "não use gráfico, use um número". */
.relatorio .numero-grande-bloco .valor-grande { font-size: var(--fs-metric); font-weight: 700; letter-spacing: var(--track-metric); font-variant-numeric: tabular-nums; }
.relatorio .numero-grande-bloco .sub { font-size: var(--fs-sm); color: var(--text-secondary); margin: 2px 0 var(--sp-3); }
.relatorio .trilha-simples { height: 10px; background: var(--bg-afundado); border-radius: 6px; overflow: hidden; display: flex; margin-bottom: var(--sp-3); }
.relatorio .trilha-simples i { display: block; height: 100%; }
/* `.resto` NUNCA leva `style=` -- `flex:1` completa o que sobrar da primeira
   fatia (que é a única com largura calculada), sem precisar de um segundo
   `width:` calculado pra "100 - pct". */
.relatorio .trilha-simples i.resto { background: var(--p-nada); opacity: .5; flex: 1; }
.relatorio .voltaram { font-size: var(--fs-caption); color: var(--fg-mut); margin: var(--sp-4) 0 0; padding-top: var(--sp-3); border-top: 1px solid var(--border); }

/* AS RAMPAS, aplicadas -- uma declaração por passo, num lugar só. TEAL
   (`so-*`) é o dado que não é plataforma (dispositivo, hora do dia, a barra
   de anúncio contra o campeão). ÍNDIGO (`ja-*`) é só "gente nova". */
.relatorio .so-1 { background: var(--so-1); }
.relatorio .so-2 { background: var(--so-2); }
.relatorio .so-3 { background: var(--so-3); }
.relatorio .so-4 { background: var(--so-4); }
.relatorio .so-5 { background: var(--so-5); }
.relatorio .ja-1 { background: var(--ja-1); }
.relatorio .ja-2 { background: var(--ja-2); }

/* ── "FATURAMENTO POR ORIGEM" -- só quem vendeu vira linha (regra mantida:
   origem sem venda já tem a fatia dela em "De onde vieram"; uma linha "0
   vendas" aqui seria cobrança ao cliente, não dado novo). ── */
.relatorio .rendeu { margin-top: var(--sp-3); }
.relatorio .rendeu .fonte-marca { flex: none; line-height: 0; }
.relatorio .rendeu .fonte-marca svg { display: block; }
.relatorio .rendeu .ressalva {
  font-size: var(--fs-caption); color: var(--fg-mut);
  margin: var(--sp-4) 0 0; padding-top: var(--sp-3); border-top: 1px solid var(--border);
}

/* ═══════════════════════════════════════════════════════════════
   INSPEÇÃO DE OPORTUNIDADES (Task 5, 08/09)
   ═══════════════════════════════════════════════════════════════ */
/* O FUNIL COMPACTO -- uma linha, não um gráfico grande. Uma versão anterior
   com um SVG de funil ocupando a tela foi recusada pelo dono ("grande demais,
   não dá visão geral num relance"); o pedido era ler em 2 segundos, e uma
   barra fina de 4 segmentos com legenda ao lado cabe nisso. NÃO É
   `.funil-porta`/`.funil-conta` (acima, do Relatório) -- aquela é outra forma
   de funil (lista vertical, uma "porta" por linha); esta é a barra
   empilhada única desta tela, e por isso ganha nomes próprios. */
.funil-card {
  background: var(--bg-alt); border: var(--bord); border-radius: var(--raio-md);
  padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-4);
  display: flex; align-items: center; gap: var(--sp-6); flex-wrap: wrap;
}
.funil-rotulo { flex: 0 0 auto; }
.funil-rotulo .funil-t { font-weight: 600; font-size: var(--fs-sm); }
.funil-rotulo .funil-d { font-size: var(--fs-caption); color: var(--fg-mut); margin-top: 1px; }
.funil-meio { flex: 1 1 320px; min-width: 260px; }
.funil-barra { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--bg-elevated); }
/* `min-width` evita que um grupo com 1 item vire uma fatia invisível --
   `flex-grow` sozinho deixaria um segmento de largura zero, e ele já não
   aparece nem no hover (`title`). */
.funil-seg { min-width: 3px; }
/* MESMAS TINTAS DA COLUNA "ESTÁGIO" AO LADO (as variantes de `badge.ejs`
   usadas em `_rotuloEstagio`) -- vendida=sucesso, aguardando=aviso,
   recusada=crítico, baixo sinal=sem. Barra e etiqueta contam a mesma
   história com a mesma cor, de propósito. */
.funil-seg.vendida { background: var(--conf-codigo-fg); }
.funil-seg.aguardando { background: var(--conf-fallback-fg); }
.funil-seg.orcamento { background: var(--info); }
/* "Em contato" (15/09) -- reaproveita o azul de confiança "manual" (não usado
   em nenhum outro selo desta tela), de propósito: é o degrau logo antes de
   Orçamento, e a família de cor próxima (dois azuis, tons diferentes) segue a
   progressão sem inventar uma cor nova só pra isto. */
.funil-seg.em-contato { background: var(--conf-manual-fg); }
.funil-seg.lead { background: var(--text-tertiary); }
.funil-seg.recusada { background: var(--critico); }
.funil-seg.baixo { background: var(--conf-sem-fg); }
.funil-legenda { display: flex; gap: var(--sp-4); margin-top: var(--sp-2); font-size: var(--fs-caption); color: var(--text-secondary); flex-wrap: wrap; }
.funil-legenda span { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }
.funil-legenda i { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.funil-legenda .vendida i { background: var(--conf-codigo-fg); }
.funil-legenda .aguardando i { background: var(--conf-fallback-fg); }
.funil-legenda .orcamento i { background: var(--info); }
.funil-legenda .em-contato i { background: var(--conf-manual-fg); }
.funil-legenda .lead i { background: var(--text-tertiary); }
.funil-legenda .recusada i { background: var(--critico); }
.funil-legenda .baixo i { background: var(--conf-sem-fg); }
.funil-legenda b { color: var(--text-primary); font-variant-numeric: tabular-nums; font-weight: 600; }

/* OS CHIPS DE ESTÁGIO -- neutros (sem preenchimento colorido: cor já é a
   barra acima e a etiqueta de cada linha; um terceiro lugar pintado repetiria
   o mesmo dado numa terceira cor). O ativo se destaca por FUNDO CLARO +
   sombra, não por matiz -- mesmo raciocínio de `.periodo a.on` ao lado. */
nav.estagios {
  display: flex; gap: 4px; align-items: center; margin-bottom: var(--sp-4);
  background: var(--bg-elevated); padding: var(--sp-1); border-radius: var(--raio-md);
  width: fit-content; max-width: 100%; flex-wrap: wrap;
}
nav.estagios a {
  padding: var(--sp-2) var(--sp-4); border-radius: var(--raio-sm); font-size: var(--fs-sm);
  color: var(--text-secondary); text-decoration: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
}
nav.estagios a:hover { color: var(--text-primary); }
nav.estagios a.on { background: var(--bg-surface); color: var(--text-primary); box-shadow: var(--sombra-sm); font-weight: 600; }
nav.estagios a .conta { font-size: var(--fs-label); color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
nav.estagios a.on .conta { color: var(--text-secondary); }
/* "SEM INTERESSE"/"INDETERMINADO" SEPARADOS por um divisor -- os dois têm
   0%/quase-0% de acerto medido (ver `medicao_kanban_da_para_classificar.md`),
   e por isso ganham peso visual menor (fonte um degrau abaixo) em vez de
   competir pela atenção dos três estágios que de fato viram decisão. */
nav.estagios .divisor { width: 1px; align-self: stretch; background: var(--border-hover); margin: var(--sp-1) 2px; }
nav.estagios a.baixo-sinal { color: var(--text-tertiary); font-size: var(--fs-caption); }
nav.estagios a.baixo-sinal.on { background: var(--bg-afundado); color: var(--text-secondary); box-shadow: none; }

/* A BARRA DE FERRAMENTAS -- período + origem + busca, uma linha, sempre um
   degrau menor que os chips (a navegação principal é o estágio; isto é
   filtro fino). `.periodo`/`.periodo a` já vêm do seletor de período do
   produto inteiro (acima); só o resto da linha é novo aqui.
   `flex-direction: row` EXPLÍCITO, e não por acaso -- a regra base de
   `form { display: flex; flex-direction: column }` empilharia select/busca/
   botão na vertical se só `display: flex` fosse redeclarado aqui (a mesma
   armadilha que `tests/unit/form-em-linha.test.js` cobra da folha inteira). */
.toolbar-oportunidades {
  display: flex; flex-direction: row; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  margin-bottom: var(--sp-4); padding-bottom: var(--sp-4); border-bottom: 1px solid var(--border);
  font-size: var(--fs-caption);
}
.toolbar-oportunidades select, .toolbar-oportunidades input[type=search] {
  font-size: var(--fs-caption); padding: var(--sp-1) var(--sp-2); height: auto;
}
.toolbar-oportunidades input[type=search] { min-width: 260px; }
.toolbar-oportunidades .contagem { margin-left: auto; color: var(--text-tertiary); }
nav.periodo { margin-bottom: var(--sp-4); }

/* AÇÕES EMPILHADAS NA LINHA (mockup, decisão 7 do spec de 08/09): dois botões
   de decisão lado a lado convidam ao clique errado numa tela que decide
   dinheiro -- empilhados, cada um pede um gesto deliberado. MESMOS TOKENS que
   pintavam a fila antiga de comprovantes (`--dec-ok-*`/`--dec-no-*`, hoje só
   aqui -- `decisao-form.ejs`/`.decisao-acoes` saíram na revisão final de
   08/09, junto com a fila que substituíam): é o mesmo par semântico (verde
   aprova, vermelho recusa), só com rótulo e layout novos para a tela
   consolidada. */
.acoes-linha { display: flex; flex-direction: column; gap: 5px; min-width: 122px; }
.acoes-linha form { width: 100%; }
.acoes-linha button { width: 100%; font-size: var(--fs-caption); padding: var(--sp-1) var(--sp-2); height: auto; }
.acoes-linha .btn-ok { background: var(--dec-ok-bg); color: var(--dec-ok-fg); border-color: var(--dec-ok-bg); font-weight: 600; }
.acoes-linha .btn-ok:hover { opacity: .88; }
.acoes-linha .btn-no { background: transparent; color: var(--dec-no-bg); border-color: var(--border); }
.acoes-linha .btn-no:hover { background: var(--dec-no-bg); color: var(--dec-no-fg); border-color: var(--dec-no-bg); }

/* A CÉLULA DE VALOR -- números alinham em coluna (`tabular-nums`), e "sem
   valor lido" é itálico e apagado em vez de zero: zero afirmaria um valor que
   ninguém leu. */
td.valor { font-variant-numeric: tabular-nums; font-weight: 500; white-space: nowrap; }
td.valor.sem { color: var(--text-tertiary); font-weight: 400; font-style: italic; }
td.valor.sem em { font-style: inherit; }
.metodo { color: var(--text-secondary); }
.traco { color: var(--text-tertiary); }
