/*
 * Primitivos de UI do sdk-ui, traduzindo (não importando) os variants do
 * ZardUI usados pelas telas-alvo (Contratos, Evolution, Meta-WABA):
 * button.variants.ts, badge.variants.ts, alert.variants.ts,
 * input.variants.ts, table.variants.ts (b2b-v2-site/src/app/shared/components).
 *
 * São classes CSS puras (sem Tailwind) porque o apps-proxy não tem
 * pipeline de build de front-end — mantém a filosofia atual do projeto
 * (scripts simples, sem bundler novo).
 */

/* ---------- Button (zr-btn) ---------- */
.zr-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  white-space: nowrap;
  border-radius: var(--radius-md);
  font-size: 0.875rem;
  font-weight: 500;
  height: 2.25rem;
  padding: 0.5rem 1rem;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color 0.15s, opacity 0.15s;
  outline: none;
}
.zr-btn:disabled {
  pointer-events: none;
  opacity: 0.5;
}
.zr-btn:focus-visible {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}
.zr-btn--sm { height: 2rem; padding: 0 0.75rem; gap: 0.375rem; }
.zr-btn--lg { height: 2.5rem; padding: 0 1.5rem; }
/* 🔴 Mesma armadilha do `.zr-alert[hidden]` abaixo — achada em verificação na
   tela (T-B1, "Configurações do funil"): o botão "Tornar padrão" continuava
   visível e clicável com `hidden` posto pelo JS, porque `display:inline-flex`
   é regra de AUTOR e vence o `[hidden] { display:none }` do navegador. Vale
   para todo `.zr-btn` escondido por `elemento.hidden = true` no projeto —
   quem usa `style.display='none'` explícito nunca bateu nisso. */
.zr-btn[hidden] { display: none; }

.zr-btn--default {
  background: var(--primary);
  color: var(--primary-foreground);
}
.zr-btn--default:hover { opacity: 0.9; }

.zr-btn--secondary {
  background: var(--secondary);
  color: var(--secondary-foreground);
}
.zr-btn--secondary:hover { opacity: 0.8; }

.zr-btn--outline {
  background: var(--background);
  color: var(--foreground);
  border-color: var(--border);
}
.zr-btn--outline:hover { background: var(--accent); color: var(--accent-foreground); }

.zr-btn--destructive {
  background: var(--destructive);
  color: var(--destructive-foreground);
}
.zr-btn--destructive:hover { opacity: 0.9; }

.zr-btn--ghost {
  background: transparent;
  color: var(--foreground);
}
.zr-btn--ghost:hover { background: var(--accent); color: var(--accent-foreground); }

/* ---------- Badge (zr-badge) ---------- */
.zr-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  width: fit-content;
  white-space: nowrap;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  padding: 0.125rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 500;
}
.zr-badge--pill { border-radius: 9999px; }
/* Mesma armadilha de `.zr-btn[hidden]` acima, mesmo achado — `.zr-badge`
   também fixa `display` como regra de autor. */
.zr-badge[hidden] { display: none; }

.zr-badge--default { background: var(--primary); color: var(--primary-foreground); }
.zr-badge--secondary { background: var(--secondary); color: var(--secondary-foreground); }
.zr-badge--destructive { background: var(--destructive); color: #fff; }
.zr-badge--outline { background: transparent; color: var(--foreground); border-color: var(--border); }
.zr-badge--warning { background: var(--warning-bg-soft); color: var(--warning-strong); }

/* ---------- Alert / status message (zr-alert) ---------- */
.zr-alert {
  position: relative;
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
  border-radius: var(--radius-lg);
  padding: 0.875rem 1rem;
  font-size: 0.875rem;
  border: 1px solid var(--border);
}
/* 🔴 `display:flex` acima é regra de AUTOR e vence o `[hidden] { display:none }`
   do navegador — então um alerta vazio, com o atributo `hidden` posto pelo JS,
   continua ocupando espaço como uma caixa com borda e nada dentro. Foi assim
   que o painel de BI apareceu com um retângulo vazio no topo, achado em
   conferência na tela. Vale para os 34 `.zr-alert[hidden]` do projeto, não só
   para aquele — `PainelBase.anunciar()` esconde por `el.hidden`, que é o
   caminho certo, e era o CSS que não deixava. Mesma armadilha já registrada
   para `.modal[hidden]` em `public/styles.css`. */
.zr-alert[hidden] { display: none; }

.zr-alert--info    { color: var(--info);    background: var(--info-bg-soft);    border-color: color-mix(in srgb, var(--info) 35%, transparent); }
.zr-alert--success { color: var(--success); background: var(--success-bg-soft); border-color: color-mix(in srgb, var(--success) 35%, transparent); }
.zr-alert--warning  { color: var(--warning);  background: var(--warning-bg-soft);  border-color: color-mix(in srgb, var(--warning) 35%, transparent); }
.zr-alert--error   { color: var(--destructive); background: var(--error-bg-soft); border-color: color-mix(in srgb, var(--destructive) 35%, transparent); }

/* ---------- Input / select / textarea (zr-input) ---------- */
.zr-input,
.zr-select,
.zr-textarea {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--input);
  background: var(--background);
  color: var(--foreground);
  border-radius: var(--radius-md);
  font-size: 0.875rem;
  padding: 0 0.75rem;
  height: 2.5rem;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.zr-textarea { height: auto; min-height: 5rem; padding: 0.5rem 0.75rem; resize: vertical; }
.zr-input::placeholder, .zr-textarea::placeholder { color: var(--muted-foreground); }
.zr-input:focus-visible,
.zr-select:focus-visible,
.zr-textarea:focus-visible {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}
.zr-input:disabled, .zr-select:disabled, .zr-textarea:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.zr-input--error, .zr-select--error { border-color: var(--destructive); }
.zr-input--success, .zr-select--success { border-color: var(--success); }

.zr-checkbox {
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-sm);
  accent-color: var(--primary);
}

.zr-label {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--foreground);
  display: block;
  margin-bottom: 0.25rem;
}

/* 🔴 `.zr-help` era usada e NUNCA foi declarada — 9 vezes na Central de
   Ingestão BI e 3 no `index.html`, desde que aquelas telas nasceram. Classe
   inexistente não dá erro: o texto de ajuda simplesmente saía do tamanho e da
   cor do corpo, competindo com o rótulo do campo em vez de se apagar atrás
   dele. Achado por `public/js/__tests__/bi-titulos-classes.test.js` na
   primeira execução, contra código que já estava em produção.
   Irmã de `.zr-label`: o par rótulo/ajuda de um campo de formulário. */
.zr-help {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.4;
  color: var(--muted-foreground);
}
/* Mesma armadilha de `.zr-btn[hidden]` acima: `display: block` é regra de AUTOR e vence o `[hidden]` do navegador (a linha da soma das parcelas do CRM nasce escondida). */
.zr-help[hidden] { display: none; }

/* ---------- Table (zr-table) ---------- */
.zr-table-wrap { overflow: auto; }
.zr-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
  color: var(--foreground);
}
.zr-table thead tr { border-bottom: 1px solid var(--border); }
.zr-table th {
  height: 2.5rem;
  padding: 0.5rem;
  text-align: left;
  font-weight: 500;
  color: var(--muted-foreground);
}
.zr-table td { padding: 0.5rem; vertical-align: middle; }
.zr-table tbody tr { border-bottom: 1px solid var(--border); transition: background-color 0.15s; }
.zr-table tbody tr:last-child { border-bottom: 0; }
.zr-table tbody tr:hover { background: color-mix(in oklch, var(--muted) 50%, transparent); }

/* ---------- Tabs (zr-tabs) ---------- */
.zr-tabs {
  display: flex;
  gap: 0.25rem;
  border-bottom: 1px solid var(--border);
  /* 🔴 Sem isto, uma barra com rótulos longos empurra a PÁGINA para o lado em
     tela estreita — o piso de qualidade do DESIGN.md proíbe scroll horizontal
     do body. Medido na sessão 124 a 375px: as três abas do BI somam 307px num
     container de 278px, e a página inteira passava a rolar. O mockup daquela
     tela já declarava esta linha na cópia local dele, o que mostra que a falta
     era do primitivo, não da tela. `flex-shrink: 0` no botão é o par: sem ele,
     o flex espreme os rótulos em vez de deixar rolar. */
  overflow-x: auto;
}
.zr-tab {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 0.5rem 0.875rem;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted-foreground);
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}
.zr-tab:hover { color: var(--foreground); }
.zr-tab.active { color: var(--foreground); border-bottom-color: var(--primary); }
/* Aba desabilitada (ex.: Geografia sem carga de cobertura) — visível, sem
   clique, com cadeado. O `:disabled` já bloqueia o clique nativo. */
.zr-tab:disabled { cursor: not-allowed; opacity: 0.5; }
.zr-tab:disabled:hover { color: var(--muted-foreground); }
.zr-tab--travada::before { content: "\1F512"; margin-right: 0.3rem; font-size: 0.85em; }

/* ---------- Card / panel container ---------- */
.zr-card {
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

/* ---------- Indicador (zr-kpi) ----------
   O cartão de indicador ÚNICO do projeto — o padrão do Cockpit de BI, aprovado
   pelo Eder em 17/10/2026 (`docs/specs/kpi-padrao-bi-mockup.html`, D1–D5 do item
   9 de TAREFAS_PRO_LOOP.md). Substitui os seis estilos que existiam (`cr-kpi`,
   `os-kpi`, `fo-kpi`, o `kpi` da revisão do plano, `bi-kpi-*` e `bi-mt-kpi-*`).

   🔴 Nenhuma tela escreve esta marcação à mão: o HTML sai da função pura
   `htmlDoCartao()` (`public/js/cartao-indicador.js`, D-P2). O CSS daqui é só a
   aparência; a estrutura mora naquele arquivo.

   🔴 O rótulo NUNCA quebra linha (D2): "nunca desalinhar" — rótulo de duas
   linhas empurra o valor para baixo e desalinha a fileira inteira. Texto curto;
   se não couber, reticências e o texto inteiro no `title` (o parâmetro `dica`
   da função). Portão do desalinhamento: os testes de tela (`valoresAlinhados`,
   KP2 adiante) — este CSS não tem portão em Jest.

   Medidas copiadas do `.bi-kpi-*` (o modelo, `public/styles.css`): rótulo
   0,72rem/500 maiúsculas espaçado, valor 1,5rem/700 tabular, apoio 0,74rem. */
.zr-kpi-grade {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.85rem;
}
@media (max-width: 900px) {
  .zr-kpi-grade { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
  .zr-kpi-grade { grid-template-columns: minmax(0, 1fr); }
}
/* D5 — as variantes da GRADE: cinco colunas (Contas a receber, Metas do BI) e
   o cartão único de largura fixa (a fila A faturar). */
.zr-kpi-grade--cinco { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 900px) {
  .zr-kpi-grade--cinco { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
  .zr-kpi-grade--cinco { grid-template-columns: minmax(0, 1fr); }
}
.zr-kpi-grade--unico { grid-template-columns: minmax(0, 18rem); }

.zr-kpi {
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.95rem 1.05rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  /* `min-width: 0` porque item de grid/flex nasce com `min-width: auto` (= o
     tamanho do CONTEÚDO): sem ele, um valor longo estica a coluna e a lista
     passa a rolar de lado — o mecanismo do DESIGN.md, "overflow-x: auto não
     basta". */
  min-width: 0;
  text-align: left;
  font: inherit;
}
/* 🔴 Sétima ocorrência da armadilha do `[hidden]` (ver `.zr-btn[hidden]` acima):
   `display: flex` é regra de AUTOR e vence o `[hidden]` do navegador. O cartão
   nasce com o override. */
.zr-kpi[hidden] { display: none; }

.zr-kpi__rotulo {
  display: block;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--muted-foreground);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* A seta do cartão-botão (D4): o ícone Phosphor `arrow-right` do sprite, que a
   função `icone()` (com `aria-hidden`) põe dentro do rótulo — nunca um
   caractere nem SVG à mão. */
.zr-kpi__seta { margin-left: 0.3rem; }

.zr-kpi__valor {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* D3 — os tons. Quem decide se o número MERECE o tom é a tela, nunca este CSS
   nem a função: a conta é do servidor. */
.zr-kpi__valor--negativo { color: var(--error-strong); }
.zr-kpi__valor--alerta { color: var(--warning-strong); }
.zr-kpi__alvo { font-size: 0.8rem; font-weight: 500; color: var(--muted-foreground); }
.zr-kpi__apoio { font-size: 0.74rem; color: var(--muted-foreground); }

/* D4 — cartão que abre algo É botão (a fila "A faturar" das Ordens de
   serviço): foco de teclado e Enter/Espaço nativos, sem reimplementar nada. */
.zr-kpi--botao { cursor: pointer; }
.zr-kpi--botao:hover { border-color: var(--primary); }
.zr-kpi--botao:focus-visible {
  outline: 3px solid color-mix(in oklch, var(--ring) 50%, transparent);
  outline-offset: 2px;
}

/* D5 — as variantes do CARTÃO. */
.zr-kpi--cabecalho { flex: 0 0 auto; min-width: 12rem; }
/* A matriz dos indicadores por consultor (Loop E, KI): o mesmo cartão, valor
   menor — a fileira mostra várias colunas de números. */
.zr-kpi--matriz .zr-kpi__valor { font-size: 1.15rem; }
.zr-kpi__barra {
  height: 0.45rem;
  border-radius: 999px;
  background: var(--muted);
  overflow: hidden;
  margin: 0.15rem 0;
}
.zr-kpi__barra-cheia { height: 100%; border-radius: 999px; background: var(--success); }
.zr-kpi__barra-cheia--atencao { background: var(--warning); }
/* "Crítico" é a faixa abaixo da atenção nas réguas das Metas (antes, o
   `.bi-mt-barra--critico`): vermelho de erro, não amarelo. */
.zr-kpi__barra-cheia--critico { background: var(--destructive); }

/* ---------- Moldura do "Modo IA" (zr-moldura-ia) ----------
   A borda multicolor que gira, do "Modo IA" do Google (D8 do item 9; era a
   `.zr-card.filtro-card.modo-ia` em `public/styles.css`, que só o filtro de
   Representante do BI usava). Vira primitivo: envolve a faixa de filtros de
   TODA tela de lista, com os tokens `--ia-1..4` de `sdk-ui/tokens.css` (fonte
   única — hex aqui derruba `paleta-modo-ia.test.js`).

   Só a BORDA: o miolo fica no `--card`, sem halo nem preenchimento (pedido do
   Thiago). Movimento reduzido: NÃO precisa de regra própria — o bloco global
   `*` do fim deste arquivo zera `animation-duration` e fixa
   `animation-iteration-count`, então a borda fica parada sozinha. */
@property --ia-angulo { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@keyframes ia-girar { to { --ia-angulo: 360deg; } }
.zr-moldura-ia {
  border: 2px solid transparent;
  background:
    linear-gradient(var(--card), var(--card)) padding-box,
    conic-gradient(from var(--ia-angulo),
      var(--ia-1), var(--ia-2), var(--ia-3), var(--ia-4), var(--ia-1)) border-box;
  animation: ia-girar 6s linear infinite;
}

/* ---------- Filtro de caixas recolhido (zr-filtro-caixas) ----------
   O padrão de TODA faixa de filtro do projeto (item 59; mockup aprovado pelo
   Thiago em 21/10/2026, `docs/specs/filtro-caixas-recolhido-mockup.html`, P1–P8).
   O grupo de caixas de marcar nasce RECOLHIDO num campo da mesma altura dos
   outros (36 px, o rótulo em cima como nos demais filtros) e abre POR CIMA da
   lista, sem empurrar a tela — cinco caixas abertas davam a altura da faixa
   inteira e deixavam a busca e as datas "boiando" embaixo de espaço vazio.

   🔴 A marcação NÃO se escreve à mão: sai de `htmlDoFiltroDeCaixas()` e o
   comportamento (mouse, foco, clique, Esc) mora em `public/js/filtro-caixas.js`,
   como já é com o cartão de indicador. Quem prende o teto por arquivo é
   `public/js/__tests__/filtro-caixas-teto.test.js`; o resumo do campo fechado
   ("Todas", os nomes, "3 de 5: …", "Nenhuma (lista vazia)" em vermelho) é
   testado puro em `public/js/__tests__/filtro-caixas.test.js`.

   ⚠️ A altura é 2,5rem porque é a dos campos da faixa (`.zr-input`/`.zr-select`);
   o mockup mede 36 px porque a faixa DELE usava campos de 36 px. P1 é "a altura
   dos outros", não o número: um campo 4 px mais baixo ao lado da busca é o
   mesmo "boiando" que o item veio corrigir.

   Movimento reduzido: NÃO precisa de regra própria — o bloco global `*` do fim
   deste arquivo zera `transition-duration`, e a seta que gira é só transição. */
.zr-filtro-caixas { position: relative; min-width: 0; }
.zr-filtro-caixas__campo {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  height: 2.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0 0.65rem;
  background: var(--background);
  color: var(--foreground);
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.zr-filtro-caixas__campo:hover { border-color: var(--ring); }
.zr-filtro-caixas__campo:focus-visible {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}
.zr-filtro-caixas__resumo { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* P5: o ÚNICO caso que esvazia a lista — em vermelho para não parecer defeito. */
.zr-filtro-caixas__resumo--vazio { color: var(--error-strong); }
.zr-filtro-caixas__seta { display: inline-flex; color: var(--muted-foreground); transition: transform 0.15s; }
.zr-filtro-caixas--aberto .zr-filtro-caixas__campo { border-color: var(--ring); }
.zr-filtro-caixas--aberto .zr-filtro-caixas__seta { transform: rotate(180deg); }

.zr-filtro-caixas__painel {
  position: absolute;
  left: 0;
  top: calc(100% + 0.25rem);
  z-index: 30;
  min-width: 100%;
  width: max-content;
  /* O teto de 24rem com a folga da janela: o painel é `max-content`, e sem o
     segundo limite um rótulo comprido (o Portador tem nomes de carteira) faria
     a página rolar de lado a 390 px. */
  max-width: min(24rem, calc(100vw - 2rem));
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
  padding: 0.55rem 0.7rem 0.6rem;
  text-align: left;
  /* Mesma armadilha do `.zr-alert[hidden]`/`.zr-btn[hidden]` acima (e a 6ª vez):
     o painel é escondido por `painel.hidden = true` no JS, e um `display` de
     autor venceria o `[hidden]` do navegador — aqui não declara `display`, mas o
     override fica para o primeiro que acrescentar um. */
}
.zr-filtro-caixas__painel[hidden] { display: none; }
.zr-filtro-caixas__painel fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
/* O `legend` fica só para o leitor de tela: o nome do grupo já está no rótulo em
   cima do campo, e repeti-lo dentro do painel seria texto duplicado na tela. */
.zr-filtro-caixas__painel legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
/* A ponte invisível entre o campo e o painel: sem ela o ponteiro "sai" do grupo
   no vão de 0,25rem e o painel fecha sozinho no meio do caminho. */
.zr-filtro-caixas__painel::before { content: ""; position: absolute; left: 0; right: 0; top: -0.35rem; height: 0.35rem; }

/* P4: "todas · nenhuma" moram dentro do painel, no topo. */
.zr-filtro-caixas__topo {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.78rem;
  color: var(--muted-foreground);
  margin-bottom: 0.35rem;
}
.zr-filtro-caixas__atalho {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 0.78rem;
  color: var(--muted-foreground);
  text-decoration: underline;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.zr-filtro-caixas__atalho:hover { color: var(--foreground); }
.zr-filtro-caixas__atalho:focus-visible {
  outline: none;
  color: var(--foreground);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}
/* Uma coluna de propósito: as opções do Portador são NOMES de carteira ("2 ·
   AppsToB — Viacredi · Boleto Viacredi — API da Ailos") e em duas colunas de
   24rem cada uma caberia em ~11rem, cortando justamente o que a pessoa precisa
   ler para escolher. A dica (`title`) guarda o texto inteiro. */
.zr-filtro-caixas__opcoes { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.15rem; }
.zr-filtro-caixas__opcao { display: flex; align-items: center; gap: 0.45rem; padding: 0.2rem 0; min-width: 0; cursor: pointer; }
.zr-filtro-caixas__texto { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* ---------- Caixa com busca (zr-combo) ----------
   A lista que aceita digitar e vai sugerindo (CO7 do mockup aprovado pelo Eder em
   24/10/2026, `docs/specs/copia-titulo-mockup.html` §4; Z1 de
   `FINANCEIRO_68_73_LOOPS.md`). Serve à cópia de título, à renegociação, ao
   rateio e ao Novo lançamento: conta financeira, centro de custo, forma de
   pagamento, conta corrente — listas longas demais para um `<select>`.

   🔴 O comportamento (filtro sem acento e sem maiúscula, setas/Enter/Esc,
   `role="combobox"`/`listbox`, "Nenhum resultado" anunciado) mora em
   `public/js/combo.js` (`montarCombo(raiz, { buscar })`); o filtro e a marcação
   da lista são puros e testados em `public/js/__tests__/combo.test.js`.

   A lista é `position: fixed`, posicionada pelo JS ao abrir (como no mockup): a
   caixa vive dentro de tabela e de modal com rolagem, e uma lista `absolute`
   seria cortada pela borda deles. Por isso ela FECHA ao rolar a página. */
.zr-combo { position: relative; min-width: 0; }
.zr-combo .zr-input { width: 100%; padding-right: 2rem; }
.zr-combo__seta {
  position: absolute;
  right: 0.6rem;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  color: var(--muted-foreground);
  pointer-events: none;
}
.zr-combo__lista {
  position: fixed;
  z-index: 60;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  max-height: 15rem;
  overflow: auto;
  /* O teto com a folga da janela: a 390 px uma lista de 16rem passaria da borda. */
  max-width: calc(100vw - 2rem);
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
}
/* A armadilha do `[hidden]` (ver `.zr-btn[hidden]` acima): a lista é escondida
   por `lista.hidden = true` no JS; o override fica para o primeiro `display`. */
.zr-combo__lista[hidden] { display: none; }
.zr-combo__opcao {
  padding: 0.4rem 0.55rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  cursor: pointer;
  overflow-wrap: anywhere;
}
.zr-combo__opcao[aria-selected="true"], .zr-combo__opcao:hover { background: var(--muted); }
.zr-combo__opcao mark { background: var(--warning-bg-soft); color: inherit; padding: 0; }
.zr-combo__opcao--vazia { color: var(--muted-foreground); cursor: default; }
.zr-combo__opcao--vazia:hover { background: none; }
/* O aviso para o leitor de tela ("Nenhum resultado", "12 resultados"): fora da tela. */
.zr-combo__aviso { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ---------- Cabeçalho de detalhe (zr-cab) e de página (zr-pag) ----------
   O cabeçalho ÚNICO do projeto (Eder, 17/10/2026; mockup aprovado
   `docs/specs/padrao-cabecalho-e-acoes-mockup.html`, D-P1 do
   `PADRAO_CABECALHO_BOTOES_LOOP.md`). Substitui os quatro esqueletos antigos —
   `.os-cab-detalhe`, `.fat-cab`/`.fat-cab-acoes`, `.cab-detalhe` e o
   `.logs-panel-header` das páginas — que saem de `public/styles.css` quando a
   última tela migrar. A contagem é do portão `botoes-padrao.test.js`: só desce.

   🔴 A ALTURA DO BOTÃO É DA CLASSE, NUNCA DE `style`: `.zr-btn` = 36 px no
   cabeçalho, na barra e no modal; `.zr-btn--sm` = 32 px na linha de tabela e no
   cartão — nunca misturados no mesmo lugar. O teste de tela
   `testes-tela/padrao-cabecalho.tela.mjs` mede as duas a 1280 e a 390 px.

   Estrutura do detalhe, sempre nesta ordem: código (mono, apagado) → título (o
   maior texto da tela) → linha de situação (selo + 2 ou 3 fatos curtos); ações
   à direita, no topo; fio embaixo. O "Voltar" fica ACIMA do título, sozinho, e
   é saída (ghost pequeno, 32 px) — não é ação. O irreversível vem por último,
   depois do fio `.zr-cab-fio`. Botão travado por algo temporário fica
   desabilitado COM o motivo em `.zr-cab-dica` (`aria-describedby`). */
.zr-cab {
  display: grid;
  gap: 0.6rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--border);
}
/* Oitava ocorrência da armadilha do `[hidden]` (ver `.zr-btn[hidden]` acima):
   `display: grid` é regra de AUTOR e vence o `[hidden]` do navegador. */
.zr-cab[hidden] { display: none; }
/* As partes que também fixam `display` nascem com o override — o portão
   `hidden-vence-display.test.js` só enxerga o atributo no HTML; `el.hidden = true`
   em JavaScript depende desta linha existir. */
.zr-cab-voltar[hidden],
.zr-cab-corpo[hidden],
.zr-cab-id[hidden],
.zr-cab-meta[hidden],
.zr-cab-acoes[hidden],
.zr-pag-acoes[hidden] { display: none; }

.zr-cab-voltar { display: flex; }
.zr-cab-corpo {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.75rem 1.5rem;
  flex-wrap: wrap;
}
/* `min-width: 0` porque item de flex nasce com `min-width: auto` (= o tamanho
   do CONTEÚDO): sem ele, um título longo empurra as ações e a página passa a
   rolar de lado — o mesmo mecanismo do `.zr-kpi` acima. */
.zr-cab-id {
  display: grid;
  gap: 0.2rem;
  min-width: 0;
  flex: 1 1 22rem;
}
.zr-cab-codigo {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 0.8rem;
  color: var(--muted-foreground);
}
.zr-cab-titulo {
  margin: 0;
  font-size: 1.35rem;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.zr-cab-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1rem;
  margin-top: 0.15rem;
  color: var(--muted-foreground);
  font-size: 0.85rem;
}
.zr-cab-meta b { color: var(--foreground); font-weight: 500; }

.zr-cab-acoes {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
}
/* O fio vertical que separa o irreversível (vermelho), sempre por último. */
.zr-cab-fio {
  width: 1px;
  align-self: stretch;
  background: var(--border);
  margin-inline: 0.25rem;
}
/* O motivo do botão travado. Ocupa a linha inteira abaixo das ações. */
.zr-cab-dica {
  font-size: 0.78rem;
  color: var(--muted-foreground);
  margin: 0.35rem 0 0;
  text-align: right;
  flex-basis: 100%;
}

@media (max-width: 640px) {
  /* No celular as ações descem para baixo do bloco, em duas colunas iguais. */
  .zr-cab-acoes {
    justify-content: stretch;
    width: 100%;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .zr-cab-acoes .zr-btn { width: 100%; }
  .zr-cab-fio { display: none; }
  .zr-cab-dica { text-align: left; grid-column: 1 / -1; }
}

/* Cabeçalho de PÁGINA: título à esquerda, ações à direita. `.zr-pag-acoes` leva
   `margin-left: auto` para a MESMA classe servir à barra de busca da lista (a
   ação executa e fica à direita do campo, nunca numa linha solta abaixo). */
.zr-pag {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--border);
}
.zr-pag[hidden] { display: none; }
.zr-pag-titulo { margin: 0; font-size: 1.5rem; }
.zr-pag-acoes {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
  margin-left: auto;
}

/* ---------- Skeleton (zr-skeleton) ----------
 * Placeholder com a FORMA do conteúdo que está chegando. Não é spinner: o
 * spinner diz "espere" e nada mais; o skeleton diz o que vem, quanto vem, e
 * ocupa o espaço final — que é o que impede o salto de layout quando o dado
 * chega. Tamanho é do call site (`style="height: …; width: …"`), porque quem
 * sabe a forma da linha é a tela, não o primitivo.
 *
 * Sincronização: os variants.ts de skeleton do b2b-v2-site NÃO foram
 * conferidos (o repo não está disponível aqui) — ver DESIGN.md, "Os dois
 * últimos (sessão 117)".
 */
.zr-skeleton {
  background: var(--accent);
  border-radius: var(--radius-md);
  animation: zr-pulsar 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes zr-pulsar {
  50% { opacity: 0.5; }
}

/* ---------- Empty state (zr-empty) ----------
 * Tela vazia com as TRÊS partes que o DESIGN.md ("Texto de interface" §4) exige:
 * o que é, por que está vazia, e a ação que a preenche. A ação é o ponto —
 * `Nenhum registro encontrado.` sozinho é um beco sem saída, e era o padrão do
 * portal até a sessão 117.
 *
 * A ação NÃO ganha estilo próprio aqui: ponha um `.zr-btn` + variante dentro.
 * Duplicar o botão seria criar um segundo botão que diverge do primeiro.
 */
.zr-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 2.5rem 1.5rem;
  text-align: center;
}
/* 🔴 Mesma armadilha do `.zr-alert[hidden]` acima, achada de novo em
   verificação na tela (T-B1, "Configurações do funil"): `display:flex` é
   regra de AUTOR e vence o `[hidden] { display:none }` do navegador, então
   `elemento.hidden = true` sozinho não escondia o estado vazio — ele
   continuava ocupando a tela, com o ícone/título/descrição por cima do
   conteúdo real. Sem este override, TODO `.zr-empty` escondido por JS no
   projeto tem o mesmo defeito, não só este. */
.zr-empty[hidden] { display: none; }
.zr-empty-icone {
  font-size: 1.75rem;
  line-height: 1;
  margin-bottom: 0.25rem;
  opacity: 0.6;
}
.zr-empty-titulo {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--foreground);
}
/* `--muted-foreground` sobre `--background` mede 4.74:1 no navegador — passa AA
   (4.5:1) para os 14px. É o mesmo par do texto secundário do shell, já medido
   na sessão 104; não reduza a fonte daqui sem remedir. */
.zr-empty-descricao {
  margin: 0;
  font-size: 0.875rem;
  color: var(--muted-foreground);
  max-width: 42ch;
}
/* Só respira acima do botão quando ele existe — estado vazio sem ação (uma
   busca sem resultado, por exemplo) não fica com folga sobrando embaixo. */
.zr-empty > .zr-btn {
  margin-top: 0.5rem;
}

/* ---------- Sino de notificação (Help-Desk) ----------
 * Compartilhado entre suporte-painel.html e o topbar do produto inteiro em
 * index.html — é o mesmo widget nos dois cabeçalhos, ver
 * public/js/notificacoes-bell.js. Ícone em `currentColor` (preto no tema
 * claro) porque emoji não obedece `color`; badge é só uma bolinha (sem
 * número) — ver `renderizarBadge()`.
 */
.sino {
  position: relative;
  display: inline-flex;
}
.sino-icone {
  /* Tamanho vem do token (.zr-icone). Aqui fica só a cor, que é o que distingue
     este uso — ver docs/specs/ICONES.md §6. */
  color: var(--foreground);
}
/* Bolinha de "tem não lida", sem número — só liga/desliga (ver renderizarBadge). */
.sino-badge {
  position: absolute;
  top: 0.15rem;
  right: 0.15rem;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: var(--destructive);
}
.sino-dropdown {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  width: 22rem;
  max-height: 26rem;
  overflow-y: auto;
  background: var(--popover);
  color: var(--popover-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 8px 24px color-mix(in oklch, black 20%, transparent);
  z-index: 50;
}
.sino-dropdown-cabecalho {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.6rem 0.8rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.8rem;
  font-weight: 600;
}
.sino-lista { display: flex; flex-direction: column; }
.sino-item {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  width: 100%;
  text-align: left;
  padding: 0.6rem 0.8rem;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  font-size: 0.8rem;
  color: inherit;
}
.sino-item:last-child { border-bottom: none; }
.sino-item:hover { background: color-mix(in oklch, var(--muted) 50%, transparent); }
.sino-item--nao-lido { background: color-mix(in oklch, var(--primary) 6%, transparent); }
.sino-item-linha1 { display: flex; gap: 0.4rem; font-weight: 600; }
.sino-item-assunto { font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sino-item-linha2 { color: var(--muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sino-item-quando { color: var(--muted-foreground); font-size: 0.7rem; }
.sino-vazio { padding: 1.2rem 0.8rem; text-align: center; color: var(--muted-foreground); font-size: 0.8rem; }

/* =========================================================================
   PISO DE QUALIDADE — movimento reduzido
   =========================================================================
   Preferência do SISTEMA OPERACIONAL, não do produto: não existe tela onde
   ligá-la, e quem tem sensibilidade vestibular não tem outro escape. Vem por
   último no arquivo de propósito — precisa vencer as regras acima, e a
   especificidade dentro de `@media` é a mesma do seletor sem ele.

   `!important` aqui é deliberado: o alvo inclui `style` inline e animação
   declarada em qualquer folha, que a cascata normal não alcança.

   Não neutraliza `opacity`/`color` — só a PASSAGEM entre estados. O botão
   continua mudando de cor no hover; ele só para de levar 150ms para isso.

   Guardado por public/js/__tests__/piso-qualidade.test.js.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ─────────────────────────────────────────────────────────────────────────────
   Ícone — `.zr-icone`
   Especificação: docs/specs/ICONES.md §6. O markup NUNCA é escrito à mão: sai de
   `icone()` (public/js/icone.js), que é quem decide o prefixo, o caminho do sprite
   e entre `aria-hidden` e `role="img"`.

   🔴 A COR não é declarada aqui, e isso NÃO é omissão: os símbolos trazem
   `fill="currentColor"` como atributo de apresentação, herdado pela árvore-sombra
   do `<use>`. O ícone segue a `color` do elemento que o usa — o token do botão, do
   badge, do que for — no claro e no escuro, sem regra extra. Declarar `color` ou
   `fill` em `.zr-icone` QUEBRARIA isso: o ícone passaria a ter cor própria e
   deixaria de acompanhar o estado (hover, desabilitado, tema).
   Verificado em navegador (B0, 19/09/2026): a cor computada acompanhou o token nos
   dois esquemas.

   ⚠️ O atributo era `stroke="currentColor"` enquanto a biblioteca era Tabler, que
   desenha por TRAÇO; o Phosphor (21/09/2026) desenha por PREENCHIMENTO. Quem gera o
   sprite carimba o atributo certo (`scripts/gerar-sprite-icones.mjs`) — aqui não há
   nada a mudar, mas a nota fica porque "não declarar cor" só funciona enquanto o
   símbolo trouxer o SEU atributo.
   ───────────────────────────────────────────────────────────────────────────── */
.zr-icone {
  width: var(--icon-size-default, 16px);
  height: var(--icon-size-default, 16px);
  flex-shrink: 0;           /* não encolhe dentro de botão em flex */
  vertical-align: -0.125em; /* alinha com a linha de base do texto */
}

/* 🔴 Estado vazio pede tamanho de ILUSTRAÇÃO, não de interface (16px). A tela vazia
   existe para CONVIDAR a agir (DESIGN.md, "Texto de interface"), e um traço de 16px
   ali fica frio — foi o argumento que segurou esta conversão até 2026-09-20. O tom
   suave continua vindo do `opacity` de `.zr-empty-icone`, que envolve o ícone. */
.zr-icone--vazio {
  width: var(--icon-size-vazio, 1.75rem);
  height: var(--icon-size-vazio, 1.75rem);
}

.zr-icone--badge {
  width: var(--icon-size-badge, 12px);
  height: var(--icon-size-badge, 12px);
}

/* 🔴 Mesma armadilha do `.zr-btn[hidden]`/`.zr-badge[hidden]`/`.zr-empty[hidden]`
   acima — esta é a QUINTA vez. Um primitivo que fixa `display` como regra de autor
   VENCE o `[hidden]` do navegador por empate de especificidade + ordem no arquivo,
   e o elemento "escondido" continua ocupando espaço e clicável. O `.zr-icone` nasce
   com o override para não repetir. */
.zr-icone[hidden] { display: none; }
