/* ============================================================
   Tivolli Admin — design system
   Tokens extraídos da identidade da marca (tivolli.com.br):
   laranja #FF6B19, navy #284A65, fonte Source Sans Pro.
   ============================================================ */

/* ⚠️ [hidden] TEM de vencer o CSS de autor (Leva C, 2026-08-09). A regra [hidden] do
   user-agent perde por ORIGEM (não especificidade) para qualquer display de autor —
   .btn{display:inline-flex}, .gravador video{display:block} etc. Foi ESTA perda que
   fez o gravador aparecer sempre e o "📸 Capturar" gerar JPEGs pretos no modo áudio
   (defeito D5 de docs/portavel-gravacao-ao-vivo.md). Todo hidden do painel é
   semântico (show/hide por JS); não existe hidden "decorativo" que dependa de ser
   anulado. Não remover; não criar regra que dispute com ela. Fica ANTES do :root de
   propósito: o canário da barra lateral proíbe !important no trecho das variáveis até
   o .content, e a posição não muda nada aqui (o !important vence em qualquer ordem). */
[hidden] { display: none !important; }

:root {
  --brand-orange: #FF6B19;
  --brand-orange-hover: #EB5100;
  --brand-navy: #284A65;
  --brand-navy-deep: #282D65;
  --bg: #FDFDFD;
  --bg-soft: #FAFAFA;
  --surface: #FFFFFF;
  --surface-alt: #EBEBEB;
  --border: #E2E5E9;
  --text: #2B3035;
  --text-mute: #6B7682;
  --danger: #DB011D;
  --success: #4A9B00;
  --warning: #C97A00;
  --radius: 8px;
  --shadow: 0 1px 3px rgba(40, 74, 101, 0.08), 0 1px 2px rgba(40, 74, 101, 0.06);
  --sidebar-w: 232px;
  /* Padding horizontal da .content. É token porque a `.acoes-form` sangra até as
     bordas com margem negativa do MESMO valor — casados à mão, divergiriam. */
  --content-pad: 26px;
}

* { box-sizing: border-box; }

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

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

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

.sidebar {
  width: var(--sidebar-w);
  background: var(--brand-navy);
  color: #fff;
  position: fixed;
  inset: 0 auto 0 0;
  display: flex;
  flex-direction: column;
  padding: 0;
  /* Acima da .topbar (z-index 10) e do véu (30), abaixo do modal (50). Só importa
     no modo gaveta, mas fixar aqui evita que o empilhamento dependa do modo. */
  z-index: 40;
}
.sidebar .logo {
  padding: 20px 22px;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: .5px;
  border-bottom: 1px solid rgba(255,255,255,.12);
  white-space: nowrap; overflow: hidden;
}
.sidebar .logo b { color: var(--brand-orange); }
.sidebar .logo .logo-sub { font-weight: 400; font-size: 13px; opacity: .7; }
/* ⚠️ `overflow-y:auto` implica `overflow-x:auto` (a especificação não deixa combinar
   `visible` com `auto`): sem a linha abaixo, o rótulo de 232px dentro de uma barra de
   64px produziria barra de rolagem HORIZONTAL no modo ícones. */
.sidebar nav { padding: 12px 0; overflow-y: auto; overflow-x: hidden; flex: 1; }
.sidebar nav a,
.sidebar nav .item-off {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 22px; font-size: 14.5px;
  border-left: 3px solid transparent;
  position: relative;      /* âncora do .nav-badge em modo ícones */
  white-space: nowrap;
}
.sidebar nav a { color: rgba(255,255,255,.82); }
.sidebar nav a:hover { background: rgba(255,255,255,.06); color: #fff; }
.sidebar nav a.active {
  background: rgba(255,107,25,.14);
  border-left-color: var(--brand-orange);
  color: #fff; font-weight: 600;
}
.sidebar nav .grupo {
  padding: 14px 22px 6px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 1px; color: rgba(255,255,255,.45);
}
/* Tela sem permissão: aparece esmaecida e não-clicável. */
.sidebar nav .item-off {
  color: rgba(255,255,255,.32);
  cursor: default; user-select: none;
}
/* Ícone do menu. `currentColor` é o ponto do desenho: o SVG herda os quatro estados
   do link (normal, hover, ativo, sem permissão) sem uma regra por estado — por isso
   os <symbol> de _icones.html não trazem atributo de apresentação nenhum. */
.sidebar .ic {
  width: 20px; height: 20px; flex: 0 0 20px;
  fill: none; stroke: currentColor;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round;
}

/* ---------- Barra lateral colapsável ----------
   O modo é uma CLASSE no <html>, escrita pelo sidebar.js (e pelo snippet inline do
   <head>, antes da primeira pintura). ⚠️ Nenhuma media query mexe em `--sidebar-w`:
   `@media{:root{...}}` tem a MESMA especificidade de `:root` e venceria por ordem de
   fonte, deixando o botão sem como reabrir a barra numa janela estreita. Quem lê a
   largura da janela é o JS (matchMedia), que traduz para a classe.
     (nenhuma classe)                      → aberta, 232px (padrão)
     .sidebar-icones                       → só ícones, 64px
     .sidebar-oculta                       → off-canvas, gaveta fechada
     .sidebar-oculta .sidebar-gaveta-aberta → gaveta aberta sobre o conteúdo
   ------------------------------------------------------------------ */

/* Rótulo em modo ícones: sai da tela, NÃO do DOM. `display:none` deixaria o <a> sem
   texto acessível — leitor de tela anunciaria "link" e nada mais. */
html.sidebar-icones .sidebar .rotulo {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
html.sidebar-icones { --sidebar-w: 64px; }
html.sidebar-icones .sidebar .logo { padding: 20px 0; text-align: center; }
html.sidebar-icones .sidebar .logo .logo-resto { display: none; }
html.sidebar-icones .sidebar nav a,
html.sidebar-icones .sidebar nav .item-off {
  justify-content: center; padding: 11px 0; gap: 0;
}
/* Cabeçalho de grupo vira filete: o texto some junto com os demais rótulos, e sem
   isto sobraria um espaço morto de 20px entre blocos. */
html.sidebar-icones .sidebar nav .grupo {
  padding: 0; margin: 8px 12px 0;
  border-top: 1px solid rgba(255,255,255,.12);
}
/* Badge de hand-off pendente: em modo ícones não há linha para ele ocupar, então vira
   pílula no canto do ícone. */
html.sidebar-icones .sidebar .nav-badge {
  position: absolute; top: 4px; right: 8px; margin: 0;
  min-width: 16px; padding: 0 4px; font-size: 10px; line-height: 16px;
}

/* Modo gaveta (janela estreita): a barra sai do fluxo (--sidebar-w: 0) e desliza
   sobre o conteúdo. ⚠️ `visibility:hidden` é obrigatório — só `transform` mantém os
   20 links TABULÁVEIS fora da tela, e o Tab levaria o foco para o nada. */
html.sidebar-oculta { --sidebar-w: 0px; }
html.sidebar-oculta .sidebar {
  width: 260px;
  transform: translateX(-100%);
  visibility: hidden;
  box-shadow: 0 0 30px rgba(0,0,0,.35);
}
html.sidebar-gaveta-aberta .sidebar { transform: none; visibility: visible; }
.sidebar-overlay {
  display: none; position: fixed; inset: 0;
  background: rgba(0,0,0,.45); z-index: 30;
}
html.sidebar-gaveta-aberta .sidebar-overlay { display: block; }
html.sidebar-gaveta-aberta body { overflow: hidden; }

/* Anima só `transform` (composição pura). Largura e margem trocam de uma vez DE
   PROPÓSITO: animá-las faz relayout da página inteira a cada frame — pior justamente
   na tela de conversa, a maior do painel e com polling ativo. */
/* `visibility` entra na transição só para ADIAR o `hidden` até o fim do deslize (ela
   é discreta: vira `visible` de imediato na ida, e só no fim na volta). Sem isso a
   gaveta sumiria de estalo em vez de sair deslizando. */
.sidebar { transition: transform .18s ease, visibility .18s; }
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

.main { margin-left: var(--sidebar-w); flex: 1; display: flex; flex-direction: column; }

.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 10px 26px;
  display: flex; align-items: center; justify-content: space-between;
  position: sticky; top: 0; z-index: 10;
}
.topbar .topbar-esq { display: flex; align-items: center; gap: 14px; min-width: 0; }
.topbar .loja-switch { display: flex; align-items: center; gap: 8px; }
.topbar .loja-switch label { font-size: 13px; color: var(--text-mute); }
/* Aviso do modo de encaminhamento (emergência): mora ao lado do seletor de loja porque
   é estado do TENANT selecionado — precisa ser visto em qualquer tela do painel. */
/* Uma linha só: `nowrap` + ellipsis. Sem isso a frase quebra em duas e a faixa estica
   por metade da topbar (a explicação inteira está no `title`). */
.topbar .aviso-encaminhamento {
  display: inline-flex; align-items: center; gap: 5px;
  background: #FBE0E2; color: var(--danger);
  border: 1px solid rgba(219, 1, 29, .25); border-left: 3px solid var(--danger);
  border-radius: var(--radius);
  padding: 3px 10px; font-size: 13px; font-weight: 600; line-height: 1.4;
  /* o `max-width` protege só do destino patológico (link longo demais): o rótulo
     normal cabe inteiro — cortar o número com "…" seria pior que faixa nenhuma. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 58ch;
}
.topbar a.aviso-encaminhamento:hover { background: #F8CFD3; color: var(--danger); }
.topbar .aviso-encaminhamento .mono {
  font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;
  font-weight: 400; font-size: 12.5px;
}
.topbar .user { display: flex; align-items: center; gap: 14px; font-size: 14px; }
.topbar .user .nome { color: var(--brand-navy); font-weight: 600; }

.content { padding: var(--content-pad); max-width: 1180px; }
/* Opt-in de largura total: a tela declara `{% set conteudo_largo = true %}` logo
   após o `{% extends %}`. Recolher a barra sozinho não devolveria área nenhuma em
   monitor largo — os pixels liberados viravam margem branca por causa do teto acima.
   O padding NÃO muda, então a `.acoes-form` continua sangrando certo. */
.content.conteudo-largo { max-width: none; }
h1 { font-size: 22px; color: var(--brand-navy); margin: 0 0 4px; }
.subtitle { color: var(--text-mute); margin: 0 0 22px; font-size: 14px; }
h2 { font-size: 17px; color: var(--brand-navy); margin: 26px 0 12px; }

/* ---------- Cards / KPIs ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 20px;
  margin-bottom: 18px;
}
/* Seção colapsável (<details class="card secao">) — macro _secoes.html.
   Sem JS: o conteúdo fechado continua no DOM e é submetido com o form. */
.card.secao > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
.card.secao > summary::-webkit-details-marker { display: none; }
.card.secao > summary::before {
  content: "▸"; color: var(--text-mute); font-size: 13px; line-height: 1;
}
.card.secao[open] > summary::before { content: "▾"; }
.card.secao > summary h2 { margin: 0; }
.card.secao > summary .subtitle { margin: 0; }
.card.secao[open] > summary { margin-bottom: 14px; }
.card.secao > summary:hover h2 { color: var(--brand-orange); }

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin-bottom: 22px; }
.kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px 20px;
}
.kpi .valor { font-size: 30px; font-weight: 700; color: var(--brand-navy); line-height: 1.1; }
.kpi .rotulo { font-size: 13px; color: var(--text-mute); margin-top: 4px; }
.kpi.destaque .valor { color: var(--brand-orange); }

/* ---------- Tabelas ---------- */
table { width: 100%; border-collapse: collapse; background: var(--surface); }
.table-wrap { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
th, td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--border); font-size: 14px; }
th { background: var(--bg-soft); color: var(--text-mute); font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: .4px; }
/* Botão de ação dentro do cabeçalho da tabela (ex.: "Novo job", 2026-08-12). O `.btn`
   não reseta nem `text-transform` nem `letter-spacing`, então sem isto o rótulo sai
   "NOVO JOB" e com o tracking do cabeçalho. */
th .btn { text-transform: none; letter-spacing: normal; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: #fcfdff; }

/* ---------- Botões ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--brand-orange); color: #fff; border: none;
  padding: 9px 16px; border-radius: var(--radius); font-size: 14px;
  font-weight: 600; cursor: pointer; font-family: inherit; line-height: 1.2;
}
.btn:hover { background: var(--brand-orange-hover); color: #fff; }
.btn.secondary { background: var(--brand-navy); }
.btn.secondary:hover { background: var(--brand-navy-deep); }
.btn.ghost { background: transparent; color: var(--brand-navy); border: 1px solid var(--border); }
.btn.ghost:hover { background: var(--bg-soft); color: var(--brand-navy); }
.btn.danger { background: var(--danger); }
.btn.danger:hover { background: #b00018; color: #fff; }
.btn.sm { padding: 5px 11px; font-size: 13px; }
/* Botão só-ícone (hoje: o de recolher o menu, na topbar). `line-height:0` tira a
   folga que a fonte reservaria abaixo do SVG e deixa o botão quadrado. */
.btn.icone { padding: 6px; line-height: 0; }
.btn.icone .ic {
  width: 18px; height: 18px; display: block;
  fill: none; stroke: currentColor;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round;
}

/* Barra de ações do formulário (macro _acoes.html) — Salvar sempre visível.
   `sticky` (não `fixed`) para respeitar o max-width:1180px da .content; as
   margens laterais negativas sangram até as bordas (--content-pad da .content). */
.acoes-form {
  position: sticky; bottom: 0; z-index: 5;
  display: flex; align-items: center; gap: 10px;
  margin: 18px calc(-1 * var(--content-pad)) 0; padding: 14px var(--content-pad);
  background: var(--bg); border-top: 1px solid var(--border);
}

/* ---------- Forms ---------- */
label.field { display: block; margin-bottom: 14px; }
label.field > span { display: block; font-size: 13px; color: var(--text-mute); margin-bottom: 5px; font-weight: 600; }
label.chk { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; margin: 6px 14px 6px 0; cursor: pointer; }
label.chk input { width: auto; }
input[type=text], input[type=password], input[type=number], input[type=url],
input[type=email], input[type=tel], input[type=time], input[type=date], select, textarea {
  width: 100%; padding: 9px 12px; border: 1px solid var(--border);
  border-radius: var(--radius); font-size: 14px; font-family: inherit;
  background: #fff; color: var(--text);
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand-orange);
  box-shadow: 0 0 0 3px rgba(255,107,25,.12);
}
textarea { resize: vertical; min-height: 120px; }
textarea.mono { font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace; font-size: 13px; }
.form-row { display: flex; gap: 14px; flex-wrap: wrap; }
.form-row > * { flex: 1; min-width: 180px; }
.toolbar { display: flex; gap: 10px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }

/* ---------- Badges / chips ---------- */
.badge {
  display: inline-block; padding: 2px 9px; border-radius: 20px;
  font-size: 12px; font-weight: 600; background: var(--surface-alt); color: var(--text-mute);
}
/* Status do funil — uma cor por estado */
.badge.novo { background: #E8F0F7; color: var(--brand-navy); }                 /* azul */
.badge.em_atendimento { background: #FFF0E5; color: var(--brand-orange-hover); } /* laranja */
.badge.cotacao_enviada { background: #EDE7F6; color: #6A3FB5; }                /* roxo */
/* ⚠️ .aguardando_retorno também é reusada como classe utilitária "âmbar" fora do funil
   (ex.: hand-off 'encaminhado' em handoffs.html) — não repintar nem remover. */
.badge.aguardando_retorno { background: #FFF6E0; color: #C97A00; }            /* âmbar */
.badge.aguardando_emissao { background: #F3E8D6; color: #8A5A00; }            /* âmbar terroso — pedido fechado, a emitir no ERP */
.badge.aguardando_pagamento { background: #DDEEF9; color: #0B6BA8; }          /* ciano — emitida, pagamento a cair */
.badge.cliente { background: #E0F2F1; color: #00796B; }                       /* teal */
.badge.convertido { background: #E7F3D9; color: var(--success); }             /* verde */
.badge.finalizado { background: #ECEFF3; color: var(--text-mute); }             /* cinza neutro — atendido, sem venda (sem conotação de perda) */
/* .perdido não é mais estado de funil (virou 'finalizado' na V023), mas segue como
   classe utilitária "vermelha" reusada por vários badges (inativo/erro/override/0). */
.badge.perdido, .badge.bloqueado { background: #FBE0E2; color: var(--danger); } /* vermelho */
.badge.urgente { background: #FBE0E2; color: var(--danger); }
.badge.excecao { background: #FDEFD0; color: #9A6700; }                        /* âmbar claro */
/* Status da conversa */
.badge.ativo { background: #E8F0F7; color: var(--brand-navy); }
.badge.aguardando_humano { background: #FFF0E5; color: var(--brand-orange-hover); font-weight: 700; } /* laranja destaque */
/* Status de execução de job */
.badge.concluido { background: #E7F3D9; color: var(--success); }
.badge.erro { background: #FBE0E2; color: var(--danger); }
.badge.em_andamento { background: #FFF6E0; color: #C97A00; }
/* 'aviso' (V049) é AZUL, entre o verde e o amarelo na escala concluido<aviso<parcial<erro:
   rodou até o fim e sem falha, mas há o que verificar (ERP calado há dias, loja sem
   credencial, fase pulada). Azul de propósito — amarelo/vermelho já significam "algo
   quebrou", e isto aqui não quebrou. */
.badge.aviso { background: #DDEEF9; color: #0B6BA8; font-weight: 700; }
/* 'parcial' é amarelo, não verde: rodou até um teto e deixou fila para a próxima rodada */
.badge.parcial { background: #FFF0C2; color: #8A6100; font-weight: 700; }
.badge.pendente { background: #ECEFF1; color: #546E7A; }
/* Segmento RFM do CRM (crm_mapeamento.classificar_rfm).
   ⚠️ O prefixo `rfm-` é OBRIGATÓRIO, e não é estética: `.badge.novo` já existe e
   significa "funil: contato novo", então `class="badge {{ segmento }}"` faria o
   segmento 'novo' herdar a cor do funil. Com o prefixo a colisão é inexprimível, e
   segmento futuro sem classe degrada para o cinza padrão do `.badge`.
   Os 7 pares de hex são reusados da paleta existente — nomes novos, zero cor nova. */
.badge.rfm-campeao    { background: #FFF0C2; color: #8A6100; font-weight: 700; } /* ouro */
.badge.rfm-fiel       { background: #E7F3D9; color: var(--success); }            /* verde */
.badge.rfm-novo       { background: #E8F0F7; color: var(--brand-navy); }         /* azul */
.badge.rfm-ocasional  { background: #E0F2F1; color: #00796B; }                   /* teal */
.badge.rfm-em_risco   { background: #FFF0E5; color: var(--brand-orange-hover); } /* laranja */
.badge.rfm-inativo    { background: #FBE0E2; color: var(--danger); }             /* vermelho */
.badge.rfm-sem_compra { background: #ECEFF3; color: var(--text-mute); }          /* cinza */

/* Loja do job, na lista de /admin/jobs (2026-08-12).
   ⚠️ Prefixo `loja-` OBRIGATÓRIO, pela mesma razão do `rfm-` acima: o valor pintado é o
   CÓDIGO da loja (SPFC/NRN) e `class="badge {{ j.loja_codigo }}"` faria uma loja futura
   de código `NOVO`/`ATIVO`/`CLIENTE` herdar a cor do funil. Com o prefixo a colisão é
   inexprimível. Zero cor nova — os dois pares de hex já estão na paleta.
   Global é VERMELHO de propósito: job com `loja_id IS NULL` roda para TODAS as lojas, e
   o custo de confundi-lo com um job de tenant é alto (alerta em massa, sync duplo). */
.badge.loja-global { background: #FBE0E2; color: var(--danger); font-weight: 700; }
.badge.loja-tenant { background: #E8F0F7; color: var(--brand-navy); }

/* Código curto do tipo de job (jobs/tipos.py::CODIGOS) — etiqueta técnica, discreta e
   monoespaçada para o olho bater os sufixos (_INAT / _PEND / _OCIO) numa coluna só. */
.badge.tipo-job {
  background: var(--surface-alt); color: var(--text-mute); font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px;
}

/* Nome vindo do PERFIL do WhatsApp, ainda não confirmado (V070). Prefixo próprio
   (`nome-`) pela regra acima, e ZERO cor nova: reusa o cinza do texto secundário.
   Itálico é o sinal — o nome continua legível e clicável, só deixa de parecer um
   fato. ⚠️ A mesma classe serve ao badge "perfil" do H1, e lá o itálico é
   desfeito: badge em itálico lê como ênfase, não como etiqueta. */
.nome-perfil { font-style: italic; color: var(--text-mute); }
.badge.nome-perfil { font-style: normal; background: var(--surface-alt); color: var(--text-mute); }

/* ---------- Badge de contagem na navegação (hand-offs pendentes) ---------- */
.nav-badge {
  display: inline-block; min-width: 18px; padding: 0 6px; margin-left: 4px;
  background: var(--danger); color: #fff; border-radius: 10px;
  font-size: 11px; font-weight: 700; text-align: center; line-height: 18px;
}

/* ---------- Banner de hand-off (estado da conversa) ---------- */
.handoff-banner {
  padding: 12px 15px; border-radius: var(--radius); margin: 0 0 14px;
  font-size: 14px; line-height: 1.4; border: 1px solid transparent;
}
.handoff-banner.danger { background: #FBE0E2; color: var(--danger); border-color: #F3C0C4; }
.handoff-banner.warn   { background: #FFF6E0; color: #9A6700; border-color: #F0DCA8; }
.handoff-banner.ok     { background: #EAF4F3; color: #00695C; border-color: #C7E6E1; }
.handoff-banner.neutral{ background: #F1F2F4; color: #4A5158; border-color: #DCDFE3; }
.handoff-banner .muted { color: inherit; opacity: .75; font-size: 13px; }

/* ---------- Sidebar de conversas do detalhe (Leva D) ----------
   Grid estilo WhatsApp Web dentro do conteudo-largo: coluna fixa de conversas +
   conversa aberta. `minmax(0, 1fr)` é essencial — sem o piso 0 o conteúdo do chat
   (imagens/tabelas) alargaria a coluna e empurraria o grid p/ fora da viewport. */
/* A largura da 1ª coluna é VARIÁVEL desde 2026-08-10: o vendedor arrasta a divisória e a
   preferência fica no `localStorage` (por navegador, como o modo da barra lateral). O
   padrão de 300px vive AQUI — o JS só sobrescreve quando há preferência salva, então com
   `localStorage` bloqueado a tela continua correta. `gap: 5px` × 2 + os 8px da alça dão
   os mesmos 18px de respiro de antes. */
.conversa-grid {
  display: grid; grid-template-columns: var(--conv-w, 300px) 8px minmax(0, 1fr);
  gap: 5px; align-items: start;
}
.conv-resize {
  position: sticky; top: 12px; height: calc(100vh - 24px); cursor: col-resize;
  border-radius: 4px; background: transparent; transition: background .12s;
}
.conv-resize:hover, .conv-resize:focus-visible, .conv-resize.arrastando { background: var(--border); }
/* Enquanto arrasta: sem seleção de texto (o gesto marcaria a conversa inteira) e o cursor
   de redimensionar vale na página toda, não só sobre a alça de 8px. */
body.conv-arrastando { user-select: none; cursor: col-resize; }
.conv-list {
  position: sticky; top: 12px; max-height: calc(100vh - 24px); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.conv-item { display: block; padding: 10px 12px; border-bottom: 1px solid var(--border); color: var(--text); }
.conv-item:last-child { border-bottom: none; }
.conv-item:hover { background: var(--bg-soft); color: var(--text); }
.conv-item.ativo { background: #E8F0F7; border-left: 3px solid var(--brand-orange); padding-left: 9px; }
.conv-topo { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.conv-nome { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-hora { font-size: 11px; color: var(--text-mute); flex-shrink: 0; }
.conv-prev { font-size: 13px; color: var(--text-mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
/* Busca + chips (Leva E). `sticky` dentro do aside, que é quem rola: o filtro tem de
   continuar visível depois de 200 conversas roladas — do contrário, para trocar de chip
   o vendedor teria de voltar ao topo do trilho. O fundo opaco é obrigatório (sem ele os
   itens passariam por baixo do cabeçalho translúcido). */
.conv-topo-fixo {
  position: sticky; top: 0; z-index: 1; background: var(--surface);
  border-bottom: 1px solid var(--border); padding: 8px; border-radius: var(--radius) var(--radius) 0 0;
}
.conv-busca input { width: 100%; box-sizing: border-box; font-size: 13px; padding: 6px 8px; }
.conv-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
/* ⚠️ `.chip` NÃO existia neste CSS — a Leva E escreveu a marcação supondo que sim, e os
   filtros saíram como links laranja soltos (herdando o `a{color}` da linha 52), sem
   nenhuma cara de pill. Classe que a marcação inventa precisa nascer aqui junto.
   `.chips` (2026-08-16) é o contêiner genérico, para chip fora da sidebar de conversas
   — as regras abaixo passaram a valer nos dois sem mudar um pixel do que já existia.
   ⚠️ Nada de `.pill`: essa classe NUNCA existiu aqui (só `.pill-nav a`), e é por isso
   que os filtros de /picos e /avise-me ainda saem como links soltos. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chips .chip, .conv-chips .chip {
  font-size: 11px; padding: 3px 9px; border-radius: 20px; white-space: nowrap;
  border: 1px solid var(--border); background: var(--surface); color: var(--text-mute);
}
.chips .chip:hover, .conv-chips .chip:hover { background: var(--bg-soft); color: var(--text); }
.chips .chip.ativo, .conv-chips .chip.ativo {
  background: var(--brand-navy); border-color: var(--brand-navy); color: #fff; font-weight: 600;
}
.conv-vazio { padding: 14px 12px; font-size: 13px; }
.conv-mais {
  display: block; width: 100%; padding: 10px; border: none; border-top: 1px solid var(--border);
  background: var(--surface); color: var(--brand-blue); font-size: 13px; cursor: pointer;
}
.conv-mais:hover { background: var(--bg-soft); }
/* Quem falou, na mesma paleta em toda a tela: BADGE na lista de contatos (mesmo formato
   do funil e do status) e SETA na sidebar — 300px não comportam três badges disputando
   com o nome e o preview.
   ⚠️ Prefixo próprio `.fonte-*` — não reusar `.perdido`/`.aguardando_retorno` como cor
   utilitária (invariante de badge). O nome da classe é o RÓTULO ("agente"), não o valor
   do banco ("bot"): a tradução vive uma vez só, em admin/deps.py::ROTULO_FONTE.
   ⚠️ Duas formas, um par de cores: a regra SEM `.badge` pinta só o texto (é o que a seta
   usa) e a com `.badge` acrescenta o fundo do pill. Mexer numa exige mexer na outra. */
.fonte-cliente  { color: #A05E00; }
.fonte-agente   { color: #276749; }
.fonte-vendedor { color: #2358A0; }
.badge.fonte-cliente,  .conv-fonte.fonte-cliente  { background: #FDF1DE; }
.badge.fonte-agente,   .conv-fonte.fonte-agente   { background: #E4F1E7; }
.badge.fonte-vendedor, .conv-fonte.fonte-vendedor { background: #E2EDF9; }
/* A SETA MORREU, em duas tentativas (2026-08-10). Primeiro recebeu cor e não se lia;
   depois ganhou corpo e peso e continuou não se lendo. O motivo não era a cor nem o
   tamanho: **a base é homogênea** — 22 das 25 conversas do trilho têm a mesma origem, e
   código de cor sobre um glifo só, com ~90% das linhas na mesma cor, não tem contra o
   que contrastar. Sinal que depende de comparação não funciona sem variedade.
   Entra a MESMA badge da lista, em miniatura: a palavra é legível linha a linha, sem
   depender de comparar com a vizinha nem de decorar legenda.
   ⚠️ Emoji foi considerado e recusado: 🤖 contradiz a decisão de tirar "bot" da tela; a
   linha de preview JÁ carrega emoji de mídia (📷🎙️🎥📄) e viraria sopa; e o desenho do
   glifo muda por sistema, então a 11px nem sempre se distingue. */
.conv-fonte {
  display: inline-block; font-size: 10px; font-weight: 700; padding: 1px 6px;
  border-radius: 10px; line-height: 1.5; vertical-align: 1px;
}
/* Alinhado ao breakpoint 1100 da navegação colapsável: em tela estreita a sidebar
   some e o grid vira uma coluna — a conversa aberta é o que importa. */
@media (max-width: 1099px) {
  .conversa-grid { display: block; }
  .conv-list { display: none; }
  /* a alça vai junto: sem trilho não há o que redimensionar, e ela viraria uma faixa
     cinza clicável no meio do nada */
  .conv-resize { display: none; }
}

/* ---------- Chat / histórico ---------- */
.chat { display: flex; flex-direction: column; gap: 8px; max-height: 540px; overflow-y: auto; padding: 6px; }
.msg { max-width: 74%; padding: 9px 13px; border-radius: 12px; font-size: 14px; }
.msg .meta { display: block; font-size: 11px; color: var(--text-mute); margin-top: 4px; }
/* Ticks de entrega (V058): ✓ enviado · ✓✓ entregue · ✓✓ azul lido · ✗ falhou.
   Span presente (vazio) em toda mensagem de saída — o poll o preenche por data-id. */
.msg .entrega { font-weight: 600; }
.msg .entrega-lido { color: #34B7F1; }
.msg .entrega-falhou { color: #C0392B; }
.msg .entrega-motivo { font-size: 11px; color: #C0392B; margin-top: 2px; }
.msg.cliente { align-self: flex-start; background: var(--surface-alt); border-bottom-left-radius: 3px; }
.msg.bot { align-self: flex-end; background: #E8F0F7; border-bottom-right-radius: 3px; }
.msg.humano { align-self: flex-end; background: #FFF0E5; border-bottom-right-radius: 3px; border: 1px solid #FFD9BF; }
/* Menu de anexo 📎 (Leva B): <details> com painel absoluto abrindo PARA CIMA
   (a toolbar fica no rodapé do card — abrir para baixo colidiria com o gravador). */
.menu-anexo { position: relative; display: inline-block; }
.menu-anexo > summary { list-style: none; }
.menu-anexo > summary::-webkit-details-marker { display: none; }
.menu-anexo-itens {
  position: absolute; bottom: calc(100% + 6px); left: 0; z-index: 30;
  min-width: 190px; padding: 6px; display: flex; flex-direction: column; gap: 2px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: var(--shadow);
}
.menu-anexo-itens button {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 8px 10px; border-radius: 6px; font: inherit; font-size: 14px;
  color: var(--text); cursor: pointer; white-space: nowrap;
}
.menu-anexo-itens button:hover { background: var(--surface-alt); }
/* A guarda local de [hidden] que vivia aqui (Leva B) virou a regra GLOBAL
   `[hidden]{display:none !important}` no topo do arquivo (Leva C). */

/* Texto formatado da mensagem (filtro wa_texto). O pre-wrap fica no .txt, NUNCA
   no .msg: a bolha contém a indentação do próprio template Jinja, que viraria
   espaçamento visível. Quebras de linha do WhatsApp aparecem sem <br>. */
.msg .txt { white-space: pre-wrap; overflow-wrap: anywhere; }
.msg code { background: rgba(0,0,0,.07); padding: 1px 5px; border-radius: 4px;
            font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;
            font-size: 13px; white-space: pre-wrap; overflow-wrap: anywhere; }
.msg img { display: block; max-width: 100%; max-height: 320px; border-radius: 8px; cursor: pointer; }
/* Figurinha: menor que foto, sem moldura e SEM cursor de lightbox (não abre em
   tamanho grande — é um emoji grande, não uma imagem para inspecionar). O xadrez
   discreto existe porque figurinha tem fundo transparente: arte clara sobre a
   bolha branca ficaria invisível. */
.msg img.sticker { max-height: 150px; width: auto; border-radius: 0; cursor: default;
                   background: repeating-conic-gradient(var(--surface-alt, #f2f2f2) 0% 25%, transparent 0% 50%) 0 0 / 16px 16px; }
.midia-sticker { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;
                 background: repeating-conic-gradient(var(--surface-alt, #f2f2f2) 0% 25%, transparent 0% 50%) 0 0 / 16px 16px; }
.midia-sticker img { object-fit: contain !important; padding: 8px; }
.msg audio { display: block; width: 240px; max-width: 100%; margin: 2px 0; }
.msg video { display: block; max-width: 100%; max-height: 320px; border-radius: 8px; }
.doc-link { display: inline-block; padding: 6px 10px; background: #fff; border: 1px solid var(--border, #ddd); border-radius: 8px; text-decoration: none; }

/* ---------- Biblioteca de mídia ---------- */
/* Frame superior recolhível: só para ADICIONAR mídia. */
.add-midia { margin-bottom: 18px; }
.add-midia > summary { cursor: pointer; font-weight: 600; color: var(--brand-navy); list-style: none; font-size: 15px; }
.add-midia > summary::-webkit-details-marker { display: none; }
.add-midia[open] > summary { margin-bottom: 12px; }
.origem-toggle { display: flex; gap: 18px; margin: 6px 0 10px; font-size: 14px; }
.origem-toggle label { display: flex; gap: 6px; align-items: center; cursor: pointer; }

/* Barra do botão "Adicionar" (topo da biblioteca / do produto). */
.add-midia-barra { margin-bottom: 16px; }

/* Picker de produto (busca com dropdown de resultados) no form de mídia. */
.picker-resultados { position: relative; }
.picker-resultados { border: 1px solid var(--border); border-radius: var(--radius); margin-top: 4px; max-height: 240px; overflow-y: auto; background: #fff; box-shadow: 0 6px 20px rgba(0,0,0,.08); }
.picker-item { padding: 8px 12px; cursor: pointer; font-size: 14px; border-bottom: 1px solid var(--surface-alt); }
.picker-item:last-child { border-bottom: none; }
.picker-item:hover { background: var(--surface-alt); }

/* Preview do arquivo atual na tela de editar. */
.midia-atual { margin-top: 4px; }
.midia-atual img, .midia-atual video { max-width: 220px; max-height: 180px; border-radius: 8px; display: block; }
.midia-atual audio { width: 260px; max-width: 100%; }

/* Filtros num frame, campos e botão lado a lado (sobrepõe o width:100% global). */
.filtros-card { margin-bottom: 18px; }
.filtros { margin-bottom: 0; align-items: center; }
.filtros select, .filtros input[type=text] { width: auto; }
.filtros select { min-width: 150px; }
.filtros input[type=text] { flex: 1 1 220px; min-width: 200px; max-width: 320px; }

/* Resumo + seletor de densidade acima da grade. */
.galeria-resumo { margin: 4px 0 12px; }
.galeria-resumo a { color: var(--brand-navy); }

/* Grade densa de miniaturas quadradas (estilo DAM). */
.midia-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }
.midia-card { padding: 8px; }
.midia-card.inativo { opacity: .55; }
.midia-preview { position: relative; aspect-ratio: 1 / 1; display: flex; align-items: center; justify-content: center; background: var(--surface-alt); border-radius: 8px; overflow: hidden; margin-bottom: 8px; }
.midia-preview img, .midia-preview video { width: 100%; height: 100%; object-fit: cover; }
.midia-preview .midia-icone { font-size: 46px; text-decoration: none; }
.midia-badges { position: absolute; top: 6px; left: 6px; display: flex; gap: 4px; z-index: 1; }
.midia-badges .badge { font-size: 10.5px; padding: 1px 6px; }
.midia-info { display: flex; flex-direction: column; gap: 5px; }
.midia-info > strong,
.midia-info > .midia-titulo { font-size: 13.5px; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* Título do cartão como gatilho do modal de edição — o "nome clicável" da lista
   (docs/guia-botoes-lista.md). É <button> e não <a> porque o detalhe da mídia é um
   MODAL, não uma rota; e é justamente por ser <button> que precisa de regra própria:
   `a { color: var(--brand-orange) }` não o alcança. Desfaz o cromo do botão e assume
   a MESMA cor do nome-link das outras listas — uma só convenção de "isto abre".
   ⚠️ Não reusar `.col-todos`: 11px + navy sublinhado, desenhado para um "ver todos"
   discreto; num título de 13,5px em duas linhas fica errado. */
.midia-info > .midia-titulo { background: none; border: 0; padding: 0; font-family: inherit;
  font-weight: 700; text-align: left; color: var(--brand-orange); cursor: pointer; }
.midia-info > .midia-titulo:hover { color: var(--brand-orange-hover); }
.midia-sub { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.midia-acoes { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.midia-acoes form { margin: 0; }
.midia-acoes .btn.sm { padding: 3px 8px; font-size: 12px; }

/* ---------- Ações de LINHA de lista (macro _acoes_lista.html, 2026-08-14) ----------
   Uma ação primária visível + menu "⋮" com o resto. Guia: docs/guia-botoes-lista.md.

   Substituiu a `.acoes-grade`, que arranjava os QUATRO botões de /admin/jobs numa grade
   2×2 porque em linha só eles empurravam a tabela para o scroll horizontal mesmo com
   `conteudo_largo` (2026-08-12). O kebab resolve na raiz: sobra um botão. */
.acoes-lista { display: flex; gap: 6px; align-items: center; }
.acoes-lista form { margin: 0; }
.acoes-kebab { padding: 3px 9px; font-size: 16px; line-height: 1.2; }

/* ⭐ O painel é POPOVER NATIVO — pintado na TOP LAYER, fora do fluxo. É o que o torna
   viável dentro de uma tabela: `.table-wrap { overflow:hidden }` (~24 telas) recortaria
   qualquer `position:absolute`, que é o caso do `.menu-anexo` da tela de conversa.
   Por isso NÃO há z-index aqui: não existe empilhamento a disputar. */
.menu-acoes {
  min-width: 200px; padding: 6px; border: 1px solid var(--border);
  border-radius: 10px; background: var(--surface); color: var(--text);
  box-shadow: 0 12px 34px rgba(0,0,0,.22);
}
.menu-acoes form { margin: 0; }
.menu-acoes .menu-item {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 8px 10px; border-radius: 6px; font: inherit; font-size: 14px;
  color: var(--text); cursor: pointer; white-space: nowrap; text-decoration: none;
}
.menu-acoes .menu-item:hover { background: var(--surface-alt); }
.menu-acoes .menu-item.perigo { color: var(--danger); }
.menu-acoes .menu-item:disabled { opacity: .45; cursor: not-allowed; }
.menu-acoes .menu-item:disabled:hover { background: none; }
/* Sem ancoragem o popover cai no default do UA (`inset:0; margin:auto`) e vira uma
   FOLHA centralizada — degradação usável, a mesma que a `.ajuda-pop` já usa em cheio.
   Aí o título da linha é a única coisa que diz de qual linha é o menu, e o backdrop
   escuro faz a folha parecer intencional. */
.menu-acoes::backdrop { background: rgba(0,0,0,.35); }
.menu-acoes-titulo { margin: 2px 8px 8px; font-size: 12px; color: var(--text-mute); font-weight: 600; }

/* Ancorado ao "⋮" que o abriu: o menu desce colado ao botão, alinhado pela DIREITA dele
   (`span-inline-start` = ocupa a coluna do meio + a do inline-start), que é o que cabe
   numa coluna de ações encostada na borda direita da tabela.

   ⚠️ Os DOIS defeitos abaixo estiveram vivos de 2026-08-14 a 2026-08-15, e o sintoma de
   cada um é o MESMO: o menu abre centralizado no meio da tela (o default do UA), longe
   da linha que o abriu. Um esconde o outro, então corrigir só um não move nada.

   1. `position-area: bottom span-inline-start` era INVÁLIDO — a gramática tem ramos
      separados por sistema de coordenadas e proíbe MISTURAR físico (`bottom`) com lógico
      (`span-inline-start`). Valor inválido ⇒ `@supports` dá FALSE em todo navegador ⇒ o
      bloco inteiro era descartado. Nunca funcionou em lugar nenhum, e o `@supports`
      transformou o erro de digitação numa degradação silenciosa e plausível.
      ⇒ Par LÓGICO nos dois lados: `block-end span-inline-start`.

   2. A âncora IMPLÍCITA do `popovertarget` não basta na prática. O HTML a define ("set
      element's implicit anchor element to source"), mas a própria documentação do Chrome
      posiciona popover com `anchor-name`/`position-anchor` EXPLÍCITOS. Como o custo real
      é um `style=` curto por linha — emitido pelo macro, sem tocar em nenhuma das 8
      telas —, o padrão passa a declarar a âncora em vez de deduzi-la.
      ⇒ `anchor-name: --acoes-<chave>` no "⋮" e `position-anchor` igual no painel.

   O nome da âncora deriva da MESMA chave do `id` (`id_linha`), então já é único na
   página pela regra que o macro tem desde sempre — e é por isso que ela não pode virar
   `loop.index`: nome repetido faria o menu ancorar no "⋮" de outra linha. */
@supports (position-area: block-end span-inline-start) {
  .menu-acoes {
    position-area: block-end span-inline-start;
    /* Perto do rodapé o menu sobe; na borda inline, espelha. Sem isto, a última linha
       de uma tabela longa abriria o menu para fora da viewport. */
    position-try-fallbacks: flip-block, flip-inline;
    /* Zera o `inset:0; margin:auto` do UA — é esse par que centraliza. O 4px vira a
       folga entre o botão e o menu. */
    inset: auto; margin: 4px 0 0;
  }
  .menu-acoes::backdrop { background: transparent; }
  .menu-acoes-titulo { display: none; }
}

/* Barra acima de uma lista: filtro à esquerda, ação primária à direita. */
.lista-barra { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }

/* Paginação numerada. */
.paginacao { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: center; margin: 22px 0 8px; }
.paginacao a, .paginacao .atual, .paginacao .desabilitado { padding: 6px 11px; border-radius: 8px; border: 1px solid var(--border); font-size: 13.5px; text-decoration: none; color: var(--brand-navy); background: var(--surface); }
.paginacao .atual { background: var(--brand-navy); color: #fff; border-color: var(--brand-navy); font-weight: 600; }
.paginacao .desabilitado { opacity: .4; }
.paginacao .reticencias { padding: 6px 4px; color: var(--text-mute); }

/* Ícone de ajuda "?" — o texto fica no `title` NATIVO (ver o comentário do macro
   `ajuda()` em _midia_modal.html: balão de CSS seria recortado pelo overflow). Existe
   para tirar do fluxo textos longos de auxílio, que empurravam o rodapé do modal. */
.ajuda { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; margin-left: 4px; border: 1px solid var(--border); border-radius: 50%; background: transparent; color: var(--text-mute); font-size: 11px; font-weight: 700; line-height: 1; cursor: help; vertical-align: middle; }
.ajuda:hover, .ajuda:focus-visible { border-color: var(--brand-orange); color: var(--brand-orange); }

/* Variante FORTE do "?" — fundo azul da marca, para o auxílio que a pessoa precisa
   ler (não apenas pode). Vem com painel clicável: ver `_ajuda.html`. `cursor:pointer`
   e não `help` porque aqui clicar FAZ algo — `help` prometeria só a dica ao passar. */
.ajuda-forte { width: 17px; height: 17px; border-color: var(--brand-navy); background: var(--brand-navy); color: #fff; cursor: pointer; }
.ajuda-forte:hover, .ajuda-forte:focus-visible { border-color: var(--brand-navy-deep); background: var(--brand-navy-deep); color: #fff; }

/* O painel do `?` forte. `popover` é pintado na TOP LAYER — não precisa de z-index e
   nenhum `overflow` de ancestral o recorta; é o que torna o balão viável dentro de um
   modal, coisa que o `title` existia para contornar. A centralização é o default do
   popover (`inset:0; margin:auto`), e ela serve tanto ao desktop quanto ao toque. */
.ajuda-pop { max-width: 560px; width: calc(100% - 32px); padding: 20px 22px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface); color: var(--text); box-shadow: 0 16px 50px rgba(0,0,0,.3); }
.ajuda-pop::backdrop { background: rgba(0,0,0,.45); }
.ajuda-pop h3 { margin: 0 0 12px; font-size: 16px; color: var(--brand-navy); }
.ajuda-pop p, .ajuda-pop ul { margin: 0 0 10px; font-size: 13.5px; line-height: 1.55; }
.ajuda-pop ul { padding-left: 20px; }
.ajuda-pop li { margin-bottom: 5px; }
.ajuda-pop .exemplo { padding: 10px 12px; border-left: 3px solid var(--brand-navy); border-radius: 0 6px 6px 0; background: var(--bg-soft); color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.5; white-space: pre-wrap; }
.ajuda-pop .btn { margin-top: 6px; }

/* ---------- Modal ---------- */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.45); display: flex; align-items: center; justify-content: center; z-index: 50; padding: 16px; }
.modal { background: #fff; border-radius: 14px; width: min(720px, 96vw); max-height: 88vh; display: flex; flex-direction: column; overflow: hidden; padding: 18px; box-shadow: 0 16px 50px rgba(0,0,0,.3); }
.modal-head { display: flex; justify-content: space-between; align-items: center; }
.modal-body { overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; padding: 6px 2px; }
.modal-foot { display: flex; justify-content: space-between; align-items: center; padding-top: 10px; border-top: 1px solid var(--border, #eee); margin-top: 8px; }
/* Modal de formulário (padrão _modal.html) — sobrepõe o layout de galeria acima.
   O desligamento sob [hidden] vem da regra global no topo do arquivo. */
.modal-body.modal-form { display: block; }
.modal-foot.modal-acoes { justify-content: flex-start; gap: 10px; }
/* ⚠️ Modal cujo conteúdo é um <form> REAL (upload multipart: _midia_modal.html,
   jobs.html). Todo o mecanismo de altura acima depende de `.modal-body` ser FILHO
   DIRETO do flex column `.modal`: sendo área de rolagem, seu `min-height:auto`
   resolve para 0, ele encolhe, absorve a sobra dos 88vh e ancora head/foot.
   Com um <form> no meio, o único item flex passa a ser o form — que NÃO é área de
   rolagem, tem `min-height:auto` = altura do conteúdo inteiro e por isso não encolhe;
   o `overflow-y:auto` do corpo nunca dispara e o excedente era pintado FORA da caixa
   branca (botões Adicionar/Cancelar abaixo da borda, rodapé cortado — 2026-08-13).
   Estas duas linhas põem o form de volta no layout; o `overflow:hidden` da `.modal`
   é a rede: nada é pintado fora, aconteça o que acontecer. */
.modal > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.biblio-item { display: flex; flex-direction: column; gap: 4px; border: 1px solid var(--border, #ddd); border-radius: 10px; padding: 6px; cursor: pointer; font-size: 13px; }
.biblio-item img, .biblio-item video { width: 100%; height: 90px; object-fit: cover; border-radius: 6px; }
.biblio-item .ph { height: 90px; display: flex; align-items: center; justify-content: center; font-size: 30px; background: var(--surface-alt); border-radius: 6px; }
.biblio-item input { align-self: flex-start; }
.biblio-tit { line-height: 1.2; }

/* ---------- Galeria de imagens ---------- */
.galeria { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
.galeria .item { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: #fff; }
.galeria .item img { width: 100%; height: 150px; object-fit: cover; display: block; background: var(--surface-alt); }
/* Vídeo: cabe inteiro (contain) na mesma caixa de 150px, sem cortar o quadro. */
.galeria .item video { width: 100%; height: 150px; object-fit: contain; display: block; background: #000; }
.galeria .item .midia-audio { height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; background: var(--surface-alt); padding: 10px; }
.galeria .item .midia-audio .rotulo { font-size: 13px; color: var(--text-mute); text-align: center; word-break: break-word; }
.galeria .item audio { width: 100%; display: block; }
.galeria .item .midia-doc { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; height: 150px; background: var(--surface-alt); color: var(--brand-navy); text-decoration: none; font-size: 30px; padding: 8px; text-align: center; }
.galeria .item .midia-doc span { font-size: 12px; color: var(--text-mute); word-break: break-word; }
.galeria .item .acoes { padding: 8px; display: flex; gap: 6px; align-items: center; justify-content: space-between; }

/* ---------- Diversos ---------- */
.flash { padding: 12px 16px; border-radius: var(--radius); margin-bottom: 18px; font-size: 14px; }
.flash.ok { background: #E7F3D9; color: var(--success); border: 1px solid #C9E5A8; }
.flash.err { background: #FBE0E2; color: var(--danger); border: 1px solid #F3C0C4; }
.empty { text-align: center; color: var(--text-mute); padding: 40px 20px; }
.muted { color: var(--text-mute); font-size: 13px; }
.pill-nav { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.pill-nav a { padding: 7px 14px; border-radius: 20px; background: var(--surface); border: 1px solid var(--border); font-size: 13.5px; color: var(--brand-navy); }
.pill-nav a.active { background: var(--brand-navy); color: #fff; border-color: var(--brand-navy); }
hr { border: none; border-top: 1px solid var(--border); margin: 22px 0; }
code.k { background: var(--surface-alt); padding: 1px 6px; border-radius: 4px; font-size: 13px; }

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

/* ---------- Gravador ao vivo (áudio / foto / vídeo na conversa) ---------- */
.gravador { margin-top: 12px; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-soft); }
.gravador.rec { border-color: var(--danger); }
.gravador video, .gravador img, .gravador audio { max-width: 100%; border-radius: 6px; display: block; }
.gravador video, .gravador img { max-height: 320px; background: #000; }
.gravador audio { width: 100%; }
.gravador-barra { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.gravador .timer { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--brand-navy); min-width: 52px; }
.gravador .timer.aviso { color: var(--warning); }
.gravador.rec .timer::before { content: '⏺'; color: var(--danger); margin-right: 6px; animation: grav-pulso 1.2s ease-in-out infinite; }
.gravador-erro { padding: 8px 10px; border-radius: 6px; background: #FBE0E2; color: var(--danger); font-size: 13.5px; }
@keyframes grav-pulso { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .gravador.rec .timer::before { animation: none; } }

/* ---------- Estados desabilitados ----------
   Faltavam no design system: `<button class="btn" disabled>` renderizava idêntico
   a um habilitado (laranja, cara de clicável) — ver jobs.html / jobs_detalhe.html. */
.btn:disabled, .btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn:disabled:hover { background: var(--brand-orange); }
.btn.secondary:disabled:hover { background: var(--brand-navy); }
.btn.ghost:disabled:hover { background: transparent; color: var(--brand-navy); }
.btn.danger:disabled:hover { background: var(--danger); }
input:disabled, select:disabled, textarea:disabled {
  background: var(--bg-soft); color: var(--text-mute); cursor: not-allowed;
}
input[type=radio]:disabled, input[type=checkbox]:disabled { opacity: .4; cursor: not-allowed; }
label.chk:has(input:disabled) { color: var(--text-mute); cursor: not-allowed; }

/* ---------- Modo somente-leitura (regra central do base.html) ----------
   Aplicado quando `pode_editar` é falso: o usuário tem leitura na tela, então os
   forms de ESCRITA ficam inertes em vez de aceitarem trabalho que o backend vai
   recusar com 403. Deliberadamente NÃO usa `disabled`: três templates
   (usuario_form, config_atribuicao, contato_detalhe) mexem em .disabled por conta
   própria e reabilitariam os controles. Aqui usamos pointer-events (+ tabIndex e
   captura do submit, no JS) — domínios disjuntos, então não há briga.
   Filtros (method=get), paginação (<a>) e o seletor de loja (.topbar) não casam. */
body.somente-leitura .content form[method="post"] { cursor: not-allowed; }
body.somente-leitura .content form[method="post"] :is(input, select, textarea, button) {
  pointer-events: none; opacity: .5; filter: grayscale(.75);
}
.aviso-leitura {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 16px; border-radius: var(--radius); margin-bottom: 18px; font-size: 14px;
  background: #FDF3E3; color: var(--warning); border: 1px solid #F0DBB4;
}

/* Controle "marcar coluna" no cabeçalho da matriz de permissões (usuario_form.html).
   É <button type=button> de propósito: não casa o seletor 'input' do sync() e não
   submete o form por acidente. */
.col-todos {
  background: none; border: none; padding: 2px 0; margin-top: 2px;
  font: inherit; font-size: 11px; font-weight: 400; text-transform: none;
  color: var(--brand-navy); text-decoration: underline; cursor: pointer;
}
.col-todos:hover { color: var(--brand-orange); }
.col-todos:disabled { opacity: .4; cursor: not-allowed; text-decoration: none; }

/* ---------- Diff unificado (plataforma_instrucoes_revisar.html, V044) ----------
   As classes vêm PRONTAS do router (`_diff()` classifica cada linha em
   meta/hunk/add/del) — o template não faz lógica de string. `white-space: pre`
   + overflow-x no contêiner: linha longa de prompt rola dentro do bloco, nunca
   estica a página. */
.diff { margin: 0; padding: 0; overflow-x: auto; border-radius: var(--radius);
        border: 1px solid var(--border); background: var(--surface); }
.diff .ln {
  display: block; white-space: pre; padding: 1px 12px; min-width: max-content;
  font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace; font-size: 12.5px;
  line-height: 1.55;
}
.diff .ln.meta { color: var(--text-mute); background: var(--bg-soft); }
.diff .ln.hunk { color: var(--brand-navy); background: var(--surface-alt); font-weight: 600; }
.diff .ln.add  { color: #1F6100; background: #EAF6E1; }
.diff .ln.del  { color: #8C0113; background: #FBE7E9; }

/* ---------- Campo fora da política de frete selecionada (config_frete.html) ----------
   Sinaliza "este campo não se aplica à política escolhida" sem tirá-lo do form.
   ⚠️ Deliberadamente NÃO usa `disabled`: o POST de /config/frete reconstrói o JSON
   `frete` INTEIRO, e campo desabilitado não é submetido — acinzentar com `disabled`
   apagaria em silêncio o valor gravado (e ainda ficaria fora do snapshot do
   guard-form.js, que também ignora `disabled`). Aqui é só aparência: o campo segue
   editável, focável e submetido. Mesma escolha do modo somente-leitura acima. */
.campo-inerte { opacity: .6; }
.campo-inerte > summary h2 { color: var(--text-mute); }
.campo-inerte :is(input, select, textarea) {
  background: var(--bg-soft); color: var(--text-mute); border-color: var(--border);
}

/* ---------- Cards das regras de valor fixo (config_frete.html) ----------
   A regra é DADO em hiddens + resumo em texto; editar/remover/desativar são botões,
   nunca campos na página. `.regra-inativa` = guardada, não aplicada pelo motor. */
.regra-fixa {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.regra-fixa .regra-corpo { flex: 1 1 320px; display: flex; align-items: center; gap: 8px; }
.regra-fixa .regra-resumo { font-size: 13.5px; line-height: 1.5; }
.regra-fixa .regra-acoes { display: flex; gap: 6px; flex: 0 0 auto; }
.regra-fixa.regra-inativa { background: var(--bg-soft); }
.regra-fixa.regra-inativa .regra-resumo { color: var(--text-mute); text-decoration: line-through; }
