/* =============================================================================
   AURORA.CSS — Design System V3  ·  ERP Fazenda Camping  ·  2026
   Reset + Layout + Componentes.
   Zero hex neste arquivo — tudo via var(--*).
   Requer que tokens.css seja carregado ANTES via <link> próprio (já é o caso
   em todo template que usa aurora.css). Removido o @import redundante:
   ele não tinha query de versão, então o navegador podia cachear uma cópia
   desatualizada de tokens.css e sobrescrever silenciosamente os tokens
   corretos carregados pelo <link> versionado (cascata CSS = a regra @import,
   processada depois, vencia). Achado em 18/07/2026 verificando a conversão
   px→rem — sem isso, mudanças em tokens.css podiam nunca chegar ao usuário.
   ============================================================================= */

/* ═══════════════════════════════════════════════════════════════
   RESET & BASE
   ═══════════════════════════════════════════════════════════════ */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html { font-size: 100%; -webkit-font-smoothing: antialiased; }

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  background: var(--bg-canvas);
  color: var(--text-primary);
  min-height: 100vh;
  line-height: 1.5;
}

/* Skip link */
.skip-link {
  position: absolute; top: -100px; left: var(--sp-4); z-index: var(--z-skip-link);
  background: var(--accent); color: var(--text-on-accent);
  padding: var(--sp-2) var(--sp-4); border-radius: var(--r-sm);
  font-weight: 600; font-size: var(--text-sm); text-decoration: none;
}
.skip-link:focus { top: var(--sp-4); }

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button, input, select, textarea { font-family: var(--font-sans); }

/* Anel de foco global (F8, §2.9) — só em navegação por teclado
   (:focus-visible ignora o foco de um clique de mouse). Seletores mais
   específicos (.btn, .sidebar-fav-btn...) já tinham a mesma regra antes
   disso existir; ficam como estão, não conflitam. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* Números sempre alinhados em colunas */
.num { font-variant-numeric: tabular-nums; }

/* ═══════════════════════════════════════════════════════════════
   LAYOUT DE PÁGINA (sidebar + conteúdo)
   ═══════════════════════════════════════════════════════════════ */
.app-shell {
  display: flex;
  min-height: 100vh;
}

/* ── Sidebar ── */
.sidebar {
  position: fixed;
  top: 0; left: 0;
  width: var(--sidebar-w);
  height: 100vh;
  background: var(--bg-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-right: 1px solid var(--border-subtle);
  display: flex;
  flex-direction: column;
  z-index: var(--z-sidebar);
  overflow-y: auto;
  overflow-x: hidden;
  transition: transform var(--dur-base) var(--ease);
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.sidebar-logo img {
  height: 36px;
  width: 36px;
  border-radius: var(--r-sm);
  object-fit: contain;
  flex-shrink: 0;
}
.sidebar-brand { min-width: 0; }
.sidebar-brand-name {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar-brand-sub {
  font-size: var(--text-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .07em;
  margin-top: 1px;
}

.sidebar-nav {
  flex: 1;
  padding: var(--sp-3) var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.sidebar-section {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .09em;
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
  display: block;
  margin-top: var(--sp-2);
}
.sidebar-section:first-child { margin-top: 0; }

/* Rótulo de Grupo Master (Fazenda Camping / Eventos / Administrativo) —
   reestruturação Grupos Master 20/07/2026 */
.sidebar-section-master {
  color: var(--accent);
  letter-spacing: .12em;
  margin-top: var(--sp-4);
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--sp-4);
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 10px var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
  cursor: pointer;
  white-space: nowrap;
}
.sidebar-link svg {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sidebar-link:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}
.sidebar-link.active {
  background: var(--accent-subtle);
  color: var(--accent);
  font-weight: 600;
}
.sidebar-link.active svg { stroke: var(--accent); }

.sidebar-badge {
  margin-left: auto;
  background: var(--status-danger-bg);
  color: var(--status-danger);
  font-size: 10px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: var(--r-full);
  min-width: 18px;
  text-align: center;
}

.sidebar-divider {
  height: 1px;
  background: var(--border-subtle);
  margin: var(--sp-2) var(--sp-3);
}

/* ── Grupos colapsáveis (F2) ── */
.sidebar-group { display: flex; flex-direction: column; }
.sidebar-group-header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
  margin-top: var(--sp-2);
  background: none;
  border: none;
  cursor: pointer;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .09em;
  font-family: var(--font-sans);
  transition: color var(--dur-fast) var(--ease);
}
.sidebar-group:first-child .sidebar-group-header { margin-top: 0; }
.sidebar-group-header:hover { color: var(--text-secondary); }
.sidebar-group-header .chevron {
  width: 12px; height: 12px;
  stroke: currentColor; fill: none;
  stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round;
  flex-shrink: 0;
  transition: transform var(--dur-fast) var(--ease);
  transform: rotate(-90deg);
}
.sidebar-group.open .sidebar-group-header .chevron { transform: rotate(0deg); }
.sidebar-group-header .sidebar-badge { margin-left: auto; }
.sidebar-group-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  overflow: hidden;
  max-height: 0;
}
.sidebar-group.open .sidebar-group-body { max-height: none; }

/* ── Recentes ── */
.sidebar-recents-link {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 8px var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--text-sm);
  color: var(--text-muted);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.sidebar-recents-link:hover { background: var(--bg-hover); color: var(--text-primary); }
.sidebar-recents-link svg {
  width: 14px; height: 14px; flex-shrink: 0;
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}

/* ── Favoritar (F2): estrela ao final de cada link de navegação ── */
.sidebar-fav-btn {
  margin-left: auto;
  flex-shrink: 0;
  background: none; border: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border-radius: var(--r-xs);
  color: var(--text-muted);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.sidebar-link:hover .sidebar-fav-btn,
.sidebar-fav-btn:focus-visible,
.sidebar-fav-btn.active { opacity: 1; }
.sidebar-fav-btn:hover { color: var(--text-primary); }
.sidebar-fav-btn svg {
  width: 13px; height: 13px;
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.sidebar-fav-btn.active { color: var(--amber-500); }
.sidebar-fav-btn.active svg { fill: currentColor; }

.sidebar-user {
  padding: var(--sp-4);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-shrink: 0;
}
.sidebar-user-avatar {
  width: 32px; height: 32px;
  border-radius: var(--r-full);
  background: var(--accent-subtle);
  color: var(--accent);
  font-size: var(--text-xs);
  font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  text-transform: uppercase;
}
.sidebar-user-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar-user-role {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.sidebar-user-info { min-width: 0; }

/* ── Overlay Manager (F1) — scrim único reutilizado por sidebar/sheet.
   Modal mantém o próprio scrim (.modal-overlay) até a unificação da F5. ── */
.app-scrim {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  z-index: var(--z-scrim);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease);
}
.app-scrim.open { opacity: 1; pointer-events: auto; }

/* ── Conteúdo principal ── */
.page-content {
  flex: 1;
  /* min-width:0 é obrigatório num flex item: sem ele, o default min-width:auto
     impede o item de encolher abaixo do min-content de algum filho (tabela larga,
     texto sem quebra, grid), fazendo a área de conteúdo estourar a viewport no
     mobile (overflow horizontal do app inteiro). Com min-width:0 o conteúdo
     encolhe até a viewport e cada tabela lida com seu próprio scroll (.table-scroll). */
  min-width: 0;
  margin-left: var(--sidebar-w);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ── Navbar mobile (hambúrguer) ── */
.topbar {
  display: none;
  position: sticky;
  top: 0;
  z-index: var(--z-topbar);
  align-items: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-4);
  height: var(--navbar-h);
  background: var(--bg-glass);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.topbar-logo img { height: 28px; border-radius: var(--r-xs); }
.topbar-brand {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--text-primary);
  flex: 1;
}
.btn-hamburger {
  background: var(--bg-raised);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-sm);
  padding: var(--sp-2) 10px;
  cursor: pointer;
  color: var(--text-primary);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--dur-fast) var(--ease);
  flex-shrink: 0;
}
.btn-hamburger:hover { background: var(--bg-overlay); }
.btn-hamburger svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; }

/* ── Header desktop (F2) — busca/palette, notificações, usuário ── */
.header-desktop {
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: 0 var(--sp-6);
  height: var(--navbar-h);
  background: var(--bg-glass);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.header-search-trigger {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--bg-raised);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3);
  color: var(--text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
  width: 100%;
  max-width: 360px;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.header-search-trigger:hover { background: var(--bg-overlay); border-color: var(--border-strong); }
.header-search-trigger svg {
  width: 16px; height: 16px; flex-shrink: 0;
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.header-search-trigger span {
  flex: 1; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.header-kbd {
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--bg-overlay);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-xs);
  padding: 2px 6px;
  flex-shrink: 0;
}
.header-actions { display: flex; align-items: center; gap: var(--sp-3); flex-shrink: 0; }
/* Slot do shell (ver base.html + base.js::sincronizarSlotsDoShell): nó fixo que
   o htmx re-sincroniza a cada navegação boosted. `display: contents` faz o
   filho — quando existe — ser o item de flex direto do `.header-actions`; um
   slot vazio não gera caixa nenhuma e não consome o `gap`. */
.shell-slot { display: contents; }
.header-icon-btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
  border-radius: var(--r-sm);
  color: var(--text-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.header-icon-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.header-icon-btn svg {
  width: 18px; height: 18px;
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
/* Refinamento Operacional F5: Voltar/Avançar são <button>, não <a> — reset dos
   estilos nativos de botão para caírem no mesmo desenho de `.header-icon-btn`. */
button.header-icon-btn { border: none; background: none; padding: 0; font: inherit; cursor: pointer; }
.header-icon-btn[disabled] { opacity: .35; pointer-events: none; cursor: not-allowed; }
.header-badge {
  position: absolute; top: 2px; right: 2px;
  background: var(--status-danger); color: #fff;
  font-size: 9px; font-weight: 700;
  min-width: 15px; height: 15px;
  border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  padding: 0 3px;
  border: 2px solid var(--bg-canvas);
}

.header-user { position: relative; }
.header-user-btn {
  background: none; border: none; cursor: pointer;
  display: flex; align-items: center; padding: 2px;
  border-radius: var(--r-full);
  transition: box-shadow var(--dur-fast) var(--ease);
}
.header-user-btn:hover, .header-user-btn[aria-expanded="true"] { box-shadow: 0 0 0 2px var(--border-soft); }
.header-user-dropdown {
  position: absolute;
  top: calc(100% + var(--sp-2));
  right: 0;
  min-width: 200px;
  background: var(--bg-overlay);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-3);
  z-index: var(--z-header-dropdown);
  animation: modal-in var(--dur-fast) var(--ease);
}
.header-user-dropdown[hidden] { display: none; }
.header-user-dropdown-name { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); padding: var(--sp-1) var(--sp-2) 0; }
.header-user-dropdown-role { font-size: var(--text-xs); color: var(--text-muted); padding: 0 var(--sp-2) var(--sp-2); }
.header-user-dropdown-link {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2);
  border-radius: var(--r-xs);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-top: var(--sp-1);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.header-user-dropdown-link:hover { background: var(--bg-hover); color: var(--text-primary); }
.header-user-dropdown-link.danger { color: var(--status-danger); }
.header-user-dropdown-link.danger:hover { background: var(--status-danger-bg); }

/* ── Área de conteúdo da página ── */
@keyframes page-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes page-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.page-inner {
  padding: var(--sp-6);
  flex: 1;
  max-width: var(--content-max);
  width: 100%;
  margin: 0 auto;
  /* fill-mode `backwards` (não `both`): cobre o delay sem flash, mas LIBERA o
     stacking context quando a animação termina. Com `both` a animação fica
     "aplicada" para sempre e `.page-inner` vira stacking context permanente —
     o que prendia sheet (z999) e modais (z1000) abaixo do `.app-scrim`
     (filho de body). Ver docs/PLANO_CORRECAO_ESTRUTURAL_UX.md (CR-1/G0). */
  animation: page-fade-in var(--dur-slow) var(--ease-out) backwards;
}

/* KPI stagger (Aurora V4) — `backwards` pelo mesmo motivo do `.page-inner`:
   `transform` com fill both criava containing block permanente em cada card. */
.kpi-grid .kpi-card { animation: page-in var(--dur-slow) var(--ease-out) backwards; }
.kpi-grid .kpi-card:nth-child(1) { animation-delay:   0ms; }
.kpi-grid .kpi-card:nth-child(2) { animation-delay:  60ms; }
.kpi-grid .kpi-card:nth-child(3) { animation-delay: 120ms; }
.kpi-grid .kpi-card:nth-child(4) { animation-delay: 180ms; }
.kpi-grid .kpi-card:nth-child(5) { animation-delay: 240ms; }
.kpi-grid .kpi-card:nth-child(6) { animation-delay: 300ms; }

/* ── Page header (breadcrumbs + título + ações) ── */
.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-6);
  flex-wrap: wrap;
}
.page-header-left { min-width: 0; }
.page-title {
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text-primary);
  margin: 0;
  line-height: 1.2;
}
.page-subtitle {
  font-size: var(--text-sm);
  color: var(--text-muted);
  margin-top: var(--sp-1);
}
.page-header-actions {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  flex-shrink: 0;
  flex-wrap: wrap;
}

/* Breadcrumbs */
.breadcrumbs {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-bottom: var(--sp-2);
  flex-wrap: wrap;
}
.breadcrumbs a {
  color: var(--text-muted);
  transition: color var(--dur-fast);
}
.breadcrumbs a:hover { color: var(--accent); }
.breadcrumbs-sep {
  width: 12px; height: 12px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  flex-shrink: 0;
}
.breadcrumbs-current { color: var(--text-secondary); font-weight: 500; }

/* ═══════════════════════════════════════════════════════════════
   BOTÕES
   ═══════════════════════════════════════════════════════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 8px var(--sp-4);
  border-radius: var(--r-sm);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  border: none;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
  white-space: nowrap;
  font-family: var(--font-sans);
  line-height: 1;
  position: relative;
}
.btn:active { transform: scale(.97); }
.btn:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}
.btn svg {
  width: 14px; height: 14px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  flex-shrink: 0;
}

/* Variantes */
.btn-primary {
  background: var(--accent);
  color: var(--text-on-accent);
}
.btn-primary:hover { background: var(--accent-hover); }

.btn-secondary {
  background: var(--bg-raised);
  color: var(--text-secondary);
  border: 1px solid var(--border-soft);
}
.btn-secondary:hover { background: var(--bg-overlay); color: var(--text-primary); }

.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--border-subtle);
}
.btn-ghost:hover { background: var(--bg-raised); color: var(--text-primary); }

.btn-danger {
  background: var(--red-a15);
  color: var(--status-danger);
  border: 1px solid var(--red-a25);
}
.btn-danger:hover { background: var(--red-500); color: #fff; }

.btn-success {
  background: var(--green-a15);
  color: var(--status-success);
  border: 1px solid var(--green-a25);
}
.btn-success:hover { background: var(--green-500); color: #fff; }

.btn-warning {
  background: var(--amber-a15);
  color: var(--status-warning);
  border: 1px solid var(--amber-500);
}
.btn-warning:hover { background: var(--amber-500); color: #fff; }

.btn-info {
  background: var(--blue-a15);
  color: var(--status-info);
  border: 1px solid var(--blue-500);
}
.btn-info:hover { background: var(--blue-500); color: #fff; }

/* Variantes com gradiente + glow (Aurora V4) */
.btn-primary { background: var(--grad-accent); }
.btn-primary:hover {
  background: var(--grad-accent);
  box-shadow: var(--glow-accent);
}

.btn-glow {
  background: var(--grad-accent);
  color: var(--text-on-accent);
  box-shadow: var(--glow-accent);
}
.btn-glow:hover { box-shadow: 0 0 24px var(--violet-500-a25); }

/* Tamanhos */
.btn-sm { padding: 5px 12px; font-size: var(--text-xs); border-radius: var(--r-xs); }
.btn-sm svg { width: 12px; height: 12px; }
.btn-lg { padding: 11px var(--sp-6); font-size: var(--text-base); }
.btn-block { width: 100%; }

/* Loading state */
.btn[disabled], .btn.loading {
  opacity: .5;
  cursor: not-allowed;
  pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════
   CARDS
   ═══════════════════════════════════════════════════════════════ */
.card {
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-subtle);
}
.card-title {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}
.card-subtitle {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: 2px;
}
.card-actions { display: flex; gap: var(--sp-2); align-items: center; }
.card-body { padding: var(--sp-5); }
.card-footer {
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2);
}

/* ═══════════════════════════════════════════════════════════════
   KPI CARDS
   ═══════════════════════════════════════════════════════════════ */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-5);
}
.kpi-card {
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  position: relative;
  overflow: hidden;
}
.kpi-icon {
  width: 40px; height: 40px;
  border-radius: var(--r-sm);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: var(--sp-3);
  flex-shrink: 0;
}
.kpi-icon svg {
  width: 20px; height: 20px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.kpi-icon.success { background: var(--status-success-bg); color: var(--status-success); }
.kpi-icon.danger  { background: var(--status-danger-bg);  color: var(--status-danger); }
.kpi-icon.warning { background: var(--status-warning-bg); color: var(--status-warning); }
.kpi-icon.info    { background: var(--status-info-bg);    color: var(--status-info); }
.kpi-icon.accent  { background: var(--accent-subtle);     color: var(--accent); }
.kpi-icon.neutral { background: var(--status-neutral-bg); color: var(--status-neutral); }

.kpi-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .07em;
  margin-bottom: var(--sp-1);
}
.kpi-value {
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.kpi-delta {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: var(--text-xs);
  font-weight: 600;
  margin-top: var(--sp-2);
}
.kpi-delta.up   { color: var(--status-success); }
.kpi-delta.down { color: var(--status-danger); }
.kpi-delta svg  { width: 12px; height: 12px; }
.kpi-hint { font-size: var(--text-xs); color: var(--text-muted); margin-top: var(--sp-1); }

/* KPI card — variantes de tipo financeiro (Aurora V4) */
.kpi-card.type-entrada   { border-top: 3px solid var(--type-entrada);   background: color-mix(in srgb, var(--type-entrada)   8%, var(--bg-raised) 92%); }
.kpi-card.type-saida     { border-top: 3px solid var(--type-saida);     background: color-mix(in srgb, var(--type-saida)     8%, var(--bg-raised) 92%); }
.kpi-card.type-resultado { border-top: 3px solid var(--type-resultado); background: color-mix(in srgb, var(--type-resultado) 8%, var(--bg-raised) 92%); }
.kpi-card.type-neutro    { border-top: 3px solid var(--type-neutro);    background: color-mix(in srgb, var(--type-neutro)    8%, var(--bg-raised) 92%); }

/* ═══════════════════════════════════════════════════════════════
   BADGES / STATUS PILLS
   ═══════════════════════════════════════════════════════════════ */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: var(--r-full);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}
.badge-success { background: var(--status-success-bg); color: var(--status-success); }
.badge-danger  { background: var(--status-danger-bg);  color: var(--status-danger); }
.badge-warning { background: var(--status-warning-bg); color: var(--status-warning); }
.badge-info    { background: var(--status-info-bg);    color: var(--status-info); }
.badge-neutral { background: var(--status-neutral-bg); color: var(--status-neutral); }
.badge-accent  { background: var(--status-accent-bg);  color: var(--status-accent); }

/* ═══════════════════════════════════════════════════════════════
   TABELAS
   ═══════════════════════════════════════════════════════════════ */
.table-wrap {
  position: relative;
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
}
/* Sem overflow-x aqui de propósito: qualquer overflow-x != visible força
   overflow-y:auto de graça (regra do CSS) e vira scroll container — o que
   quebra o position:sticky do thead (ele passa a "stickar" relativo a um
   container que nunca rola de verdade, quem rola é a página). Sticky só
   funciona em desktop por isso; o scroll horizontal do mobile continua
   entrando via responsive.css (`@media (max-width:768px) .table-scroll`),
   onde não precisamos do sticky (tela menor, menos linhas visíveis). */
.table-scroll { -webkit-overflow-scrolling: touch; }
/* Tabela mais larga que o container, em desktop: `base.js` mede e marca
   `.table-scroll--overflow` (ver `conterTabelasLargas`) — só nesse caso o
   scroll fica contido (fundo do `.table-wrap` acompanha a tabela inteira em
   vez do conteúdo vazar por cima do fundo da página). Custo aceito: o thead
   sticky (que depende de rolar a PÁGINA, não um contêiner) para de grudar
   só nessa tabela específica — ela já rola na horizontal, UX já é outra. */
.table-scroll--overflow {
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border-subtle) transparent;
}
.table-scroll--overflow::-webkit-scrollbar { height: 8px; }
.table-scroll--overflow::-webkit-scrollbar-track { background: transparent; }
.table-scroll--overflow::-webkit-scrollbar-thumb {
  background: var(--border-subtle);
  border-radius: var(--r-md);
}
/* Achado do Felipe (06/08): tabela contida por `.table-scroll--overflow`
   ficava tecnicamente correta (fundo intacto, dado íntegro), mas SEM
   nenhum aviso visual de que dava pra rolar — no Mac a barra de rolagem
   some sozinha até o gesto. Sem esse aviso, a última coluna "parece"
   cortada, mesmo estando lá. `base.js::vigiarScrollTabelas` liga esta
   classe enquanto sobrar conteúdo à direita e desliga ao chegar no fim —
   nasce ligada (a tabela sempre abre rolada pro início). */
.table-wrap--scroll-hint::after {
  content: "";
  position: absolute;
  top: 1px; right: 1px; bottom: 1px;
  width: 28px;
  background: linear-gradient(to right, transparent, var(--table-scroll-hint));
  border-top-right-radius: var(--r-lg);
  border-bottom-right-radius: var(--r-lg);
  pointer-events: none;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
thead th {
  background: var(--bg-overlay);
  padding: 10px var(--sp-4);
  text-align: left;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
  cursor: default;
}
thead th.sortable { cursor: pointer; user-select: none; padding: 0; }
thead th.sortable:hover { color: var(--text-secondary); }

/* ── Sticky header (F3) — só nas tabelas com .table-wrap (~35 telas).
   .table-wrap não pode ter overflow:hidden (quebraria o sticky pelo mesmo
   motivo do .table-scroll acima), então os cantos arredondados que antes
   vinham do clip do wrapper agora são explícitos nas células de borda. ── */
.table-wrap thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}
.table-wrap thead th:first-child { border-top-left-radius: var(--r-lg); }
.table-wrap thead th:last-child { border-top-right-radius: var(--r-lg); }
.table-wrap tbody tr:last-child td:first-child { border-bottom-left-radius: var(--r-lg); }
.table-wrap tbody tr:last-child td:last-child { border-bottom-right-radius: var(--r-lg); }

/* ── data_table() (F3) — links de ordenação no cabeçalho ── */
.dt-sort-link {
  display: flex; align-items: center; gap: 4px;
  padding: 10px var(--sp-4);
  color: inherit;
}
.dt-sort-link:hover { color: var(--text-secondary); }
.dt-sort-link.active { color: var(--accent); }
.dt-sort-icon {
  width: 11px; height: 11px; flex-shrink: 0;
  stroke: currentColor; fill: none; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round;
  opacity: .35;
}
.dt-sort-link.active .dt-sort-icon { opacity: 1; }

/* ── data_table() — seleção em massa ── */
.dt-check-col { width: 40px; padding-left: var(--sp-4) !important; }
.dt-check-col input, .dt-row-check { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.dt-mass-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  background: var(--accent-subtle);
  border-bottom: 1px solid var(--border-subtle);
  flex-wrap: wrap;
}
.dt-mass-bar[hidden] { display: none; }
.dt-mass-count { font-size: var(--text-sm); font-weight: 600; color: var(--accent); }
.dt-mass-actions { display: flex; align-items: center; gap: var(--sp-2); }

tbody tr {
  border-bottom: 1px solid var(--border-subtle);
  transition: background var(--dur-fast) var(--ease);
}
tbody tr:last-child { border-bottom: none; }
tbody tr:hover { background: var(--bg-hover); }

tbody td {
  padding: 13px var(--sp-4);
  color: var(--text-primary);
  vertical-align: middle;
}
/* Densidade horizontal para tabela de muitas colunas que precisa caber sem
   rolagem em desktop ~1280px (Conciliação, 9 colunas). Só o padding
   HORIZONTAL muda; o vertical fica.
   ⚠️ Especificidade (0,1,1) de propósito: no mobile as regras de card do
   responsive.css (carregado DEPOIS) empatam e vencem pelo cascade — o card
   não muda. NÃO subir para `.table--compact tbody td`. */
.table--compact th,
.table--compact td {
  padding-left: 10px;
  padding-right: 10px;
}
/* Célula que trunca em uma linha no desktop (coluna estreita, texto longo).
   Existe como classe — e não como `style=` inline — porque no mobile a mesma
   célula vira a faixa de título de um card e precisa mostrar o texto INTEIRO;
   `responsive.css` desliga o truncamento lá. Um `white-space:nowrap` inline
   não teria como ser desligado. */
.td-truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.td-muted  { color: var(--text-muted); }
/* Segunda linha discreta dentro de uma célula (fusão de colunas secundárias —
   Conciliação 12→9 colunas). display:block porque no desktop a célula é
   table-cell e um <span> seria inline; no card mobile o span leva
   .col-mobile-hide e simplesmente não existe. */
.td-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.td-num    { font-variant-numeric: tabular-nums; }
.td-right  { text-align: right; }
.td-center { text-align: center; }

/* ── Filter bar acima da tabela ── */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: flex-end;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-subtle);
}
.filter-bar-left  { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: flex-end; flex: 1; min-width: 0; }
.filter-bar-right { display: flex; gap: var(--sp-2); align-items: center; flex-shrink: 0; }

/* ⚠️ `.filter-group` e `.filter-label` FORAM REMOVIDAS em 28/07/2026 e não
   devem voltar. Eram um segundo par de classes para exatamente o que
   `.form-group`/`.form-label` já fazem — e com cinza diferente
   (`--text-muted` × `--text-secondary`), o que deixava dois tons de rótulo em
   barras de filtro do mesmo ERP. Cinco templates usavam o par antigo
   (`dashboard`, `financeiro_dashboard`, `financeiro_dre_anual`,
   `financeiro_relatorio_eventos`, `saida`) e foram migrados.
   docs/PLANO_PADRONIZACAO_RELATORIOS_E_FILTROS.md §3.1 (achado A6) */

/* ── .filter-bar--card — variante "caixa standalone" da filter-bar (fundo,
   borda completa e radius próprios, com margem inferior), para uso FORA de
   um .table-wrap. A base .filter-bar acima é a variante "flush" (só borda
   inferior, para viver dentro do card da tabela). Esta regra centraliza o
   que antes era copiado inline em ~13 templates (contas_pagar, financeiro_*,
   freelancers_*, marketing_*, inbox, contratos, pessoas, relatorios). ── */
.filter-bar--card {
  padding: var(--sp-4);
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  margin-bottom: var(--sp-4);
}
.filter-bar--card .form-group { display: flex; flex-direction: column; gap: 3px; min-width: 140px; }

/* ── Presets de período (F0 do Dashboard Executivo) — promovido do <style>
   local de templates/dashboard.html, que tinha as mesmas regras copiadas.
   Usado pela macro `periodo_filtro` (components/macros.html) e por qualquer
   tela com filtro de datas por atalho. ── */
.period-presets { display: flex; gap: 4px; flex-wrap: wrap; }
.period-preset-btn {
  padding: 4px 12px; border-radius: var(--r-full);
  border: 1px solid var(--border-soft);
  background: transparent; color: var(--text-muted);
  font-size: var(--text-xs); font-weight: 500;
  cursor: pointer; font-family: var(--font-sans);
  transition: all var(--dur-fast) var(--ease);
}
.period-preset-btn.ativo { background: var(--accent); color: var(--text-on-accent); border-color: var(--accent); }
.period-preset-btn:hover:not(.ativo) { background: var(--bg-overlay); color: var(--text-primary); }

/* ── Filtros em bottom-sheet (F4) — opt-in via [data-mobile-sheet] no
   elemento do filtro (form ou div.filter-bar); injeção do botão-gatilho
   e do overlay é feita por base.js. Comportamento fixed/slide-up entra
   só no mobile via responsive.css; aqui só o que é comum aos dois. ── */
.filter-sheet-trigger {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: 600;
  margin-bottom: var(--sp-4);
  cursor: pointer;
}
.filter-sheet-trigger svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; }
/* ── FAB — botão de ação flutuante (F4), só visível em mobile
   (display:flex entra via responsive.css) ── */
.fab {
  display: none;
  position: fixed;
  right: var(--sp-5);
  bottom: calc(var(--sp-5) + env(safe-area-inset-bottom, 0px));
  width: 56px;
  height: 56px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--text-on-accent);
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  box-shadow: var(--shadow-overlay);
  z-index: var(--z-fab);
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.fab:hover { background: var(--accent-hover); transform: scale(1.05); }
.fab svg { width: 24px; height: 24px; stroke: currentColor; fill: none; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }

/* ── Paginação de _paginacao.html.controles() (F3) — é o componente
   REALMENTE usado em produção (10 telas), diferente de .pagination
   abaixo (macros.pagination(), só aparece no styleguide /dev/design).
   Centralizado aqui porque cada template duplicava esse bloco local
   (e produtos_cadastrados.html nunca teve — ficava sem estilo). ── */
.paginacao {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--text-sm); color: var(--text-muted);
  flex-wrap: wrap; gap: var(--sp-2);
}
.paginacao-btns { display: flex; align-items: center; gap: var(--sp-1); }
.paginacao-btns a {
  display: flex; align-items: center; justify-content: center;
  min-width: 30px; padding: 5px 10px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: var(--text-xs); font-weight: 600;
}
.paginacao-btns a:hover { background: var(--bg-raised); }
.paginacao-btns a.active-page { background: var(--accent); color: #fff; border-color: var(--accent); }
.paginacao-ellipsis {
  display: flex; align-items: center; justify-content: center;
  min-width: 30px; color: var(--text-muted); font-size: var(--text-xs);
}

/* ── Paginação ── */
.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border-subtle);
  flex-wrap: wrap;
}
.pagination-info { font-size: var(--text-xs); color: var(--text-muted); }
.pagination-pages { display: flex; gap: 4px; align-items: center; }
.page-btn {
  min-width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-xs);
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--text-secondary);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease);
  text-decoration: none;
}
.page-btn:hover { background: var(--bg-raised); border-color: var(--border-soft); color: var(--text-primary); }
.page-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
.page-btn[disabled] { opacity: .35; pointer-events: none; }
.page-btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* ═══════════════════════════════════════════════════════════════
   FORMULÁRIOS
   ═══════════════════════════════════════════════════════════════ */
.form-group { margin-bottom: var(--sp-4); }
.form-label {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: .06em;
  margin-bottom: var(--sp-2);
}
.form-label .required { color: var(--status-danger); margin-left: 2px; }

.form-control,
input[type=text],
input[type=number],
input[type=password],
input[type=email],
input[type=search],
input[type=date],
input[type=time],
select,
textarea {
  width: 100%;
  padding: 10px var(--sp-3);
  background: var(--bg-canvas);
  border: 1.5px solid var(--border-soft);
  border-radius: var(--r-sm);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1.5;
  outline: none;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
  -webkit-appearance: none;
  appearance: none;
}
.form-control:focus,
input:focus,
select:focus,
textarea:focus {
  border-color: var(--border-focus);
  box-shadow: none;
}
.form-control::placeholder,
input::placeholder,
textarea::placeholder {
  color: var(--text-placeholder);
}
.form-control:disabled,
input:disabled,
select:disabled,
textarea:disabled {
  opacity: .5;
  cursor: not-allowed;
}

.form-control.error,
input.error { border-color: var(--status-danger); }
.form-hint {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: var(--sp-1);
}
.form-error {
  font-size: var(--text-xs);
  color: var(--status-danger);
  margin-top: var(--sp-1);
  font-weight: 500;
}

select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238B93AD' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 32px;
  cursor: pointer;
}

/* Select dark options */
select option {
  background: var(--bg-overlay);
  color: var(--text-primary);
}

textarea { resize: vertical; min-height: 80px; }

/* `.form-grid` NUNCA EXISTIU no Aurora — só `.form-grid-2` e `.form-grid-3`.
   19 templates a usam passando `grid-template-columns` inline, e o navegador
   ignorava tudo: sem `display:grid` o elemento é um bloco comum, então esses
   formulários sempre empilharam em UMA coluna no desktop, por mais larga que
   fosse a tela. Achado em 26/07/2026 ao montar o modal de lançamento da F2
   (o modal tem 720px e mostrava 10 campos em fila única).
   Cada template já declara as próprias colunas; esta regra só faz o `display`
   que faltava — o layout que sai é o que o autor de cada tela escreveu.
   `auto-fit`+`minmax` cai sozinho para 1 coluna no mobile, por isso não há
   media query aqui. */
.form-grid   { display: grid; gap: var(--sp-4); }
.form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.form-grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--sp-4); }
.form-actions {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  justify-content: flex-end;
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border-subtle);
}

/* ── Seção de formulário ── */
.form-section {
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .08em;
  margin: var(--sp-6) 0 var(--sp-4);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border-subtle);
}
.form-section:first-child { border-top: none; padding-top: 0; margin-top: 0; }

/* ═══════════════════════════════════════════════════════════════
   MODAIS
   ═══════════════════════════════════════════════════════════════ */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--scrim);
  z-index: var(--z-modal);
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
}
.modal-overlay.open { display: flex; }

.modal-box {
  background: var(--bg-overlay);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-xl);
  width: 100%;
  max-width: 520px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow-overlay);
  animation: modal-in var(--dur-base) var(--ease);
}
.modal-box.modal-lg { max-width: 720px; }
.modal-box.modal-sm { max-width: 360px; }

/* Modal cujo conteúdo vive dentro de um <form> (metade dos do ERP: produto,
   cadastro, pagamento...). Sem isto, o form é o ÚNICO item flex da box e, sendo
   `display: block`, quebra a cadeia: `flex: 1` do `.modal-body` e
   `flex-shrink: 0` do `.modal-footer` deixam de valer, porque eles não são mais
   itens flex de ninguém. O form cresce além do `max-height: 90vh` da box, que
   tem `overflow: hidden`, e o rodapé — com os botões de salvar — é simplesmente
   cortado: invisível e inalcançável, sem nada rolar.
   Achado em 25/07/2026 ao acrescentar um campo ao modal de edição de produto
   (form de 832px numa caixa de 635px). Valia para todo modal em <form> que
   passasse da altura da tela. */
.modal-box > form {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.modal-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}
.modal-close {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-muted);
  border-radius: var(--r-xs);
  transition: color var(--dur-fast), background var(--dur-fast);
  display: flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
}
.modal-close:hover { color: var(--text-primary); background: var(--bg-raised); }
.modal-close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.modal-body {
  padding: var(--sp-5);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1;
  /* `min-height: 0` é o que faz o `overflow-y: auto` acima realmente funcionar.
     Item flex nasce com `min-height: auto`, ou seja, recusa encolher abaixo do
     próprio conteúdo: sem esta linha, um modal mais alto que `90vh` não rola —
     o body estica e empurra o `.modal-footer` (flex-shrink: 0) para fora da
     caixa, que tem `overflow: hidden`. O rodapé some, e com ele os botões de
     Salvar/Cancelar. Achado em 25/07/2026 ao acrescentar um campo ao modal de
     edição de produto: o botão "Atualizar" foi parar em bottom:870 numa
     viewport de 720. Valia para todos os modais do ERP — só não aparecia
     porque nenhum passava da altura. */
  min-height: 0;
}
.modal-footer {
  display: flex;
  gap: var(--sp-3);
  justify-content: flex-end;
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border-subtle);
  flex-shrink: 0;
}

/* Modal header — variantes de cor (Aurora V4) */
.modal-header.danger  { border-bottom: 2px solid var(--type-saida); }
.modal-header.success { border-bottom: 2px solid var(--type-entrada); }
.modal-header.info    { border-bottom: 2px solid var(--type-neutro); }

/* ── Modal de Relatório Operacional (iframe com o documento) ────────────────
   docs/PLANO_RELATORIOS_OPERACIONAIS.md §3.6

   Este CSS é CROMO DO ERP — tema escuro, tokens, `.modal-box`. O documento
   dentro do iframe tem CSS próprio (`static/css/relatorio.css`), que não
   carrega tokens.css nem conhece tema. Duas coisas diferentes, dois arquivos:
   misturar faria o print voltar a ser regra global mal delimitada, que é o
   defeito que o plano veio corrigir. */
.modal-relatorio {
  max-width: 900px;
  width: 100%;
  height: 92vh;
  max-height: 92vh;
}
/* Paisagem precisa de mais largura útil, senão a folha deitada aparece
   miniaturizada e a pré-visualização deixa de provar o que cabe na página. */
.modal-relatorio--paisagem { max-width: 1180px; }

.modal-relatorio-body {
  padding: 0;                 /* a folha já tem a própria margem */
  display: flex;
  flex-direction: column;
  overflow: hidden;           /* quem rola é o iframe, não o body do modal */
}
.rel-iframe {
  flex: 1;
  width: 100%;
  min-height: 0;
  border: 0;
  /* Sem isto, um retângulo escuro pisca antes de o documento carregar. */
  background: var(--paper);
}
.rel-iframe-falha {
  margin: 0;
  padding: var(--sp-5);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* A troca de ação do rodapé (M2). Por padrão o caminho é o do desktop. */
.rel-acao-mobile { display: none; }

@media (max-width: 768px) {
  /* No celular o modal vira uma janela de conferência ("é este documento
     mesmo?"). A folha mantém a geometria A4 e rola na horizontal — quem
     encolhia era o CSS do documento, e era isso que sobrepunha os valores
     (M1, em `relatorio.css`). */
  .modal-relatorio,
  .modal-relatorio--paisagem { max-width: 100%; height: 88vh; max-height: 88vh; }

  /* ⚠️ `iframe.contentWindow.print()` imprime o documento de CIMA no iOS
     Safari — a tela escura do ERP, não o relatório. Abaixo de 768px a ação
     primária passa a ser abrir o documento, onde `window.print()` é de
     primeiro nível e funciona em todo lugar. */
  .rel-acao-desktop { display: none; }
  .rel-acao-mobile  { display: inline-flex; }
}

@media print {
  /* O caso "usuário aperta Ctrl+P com o modal aberto": sem isto, sairia a tela
     escura do ERP com o modal por cima. Imprimir a partir daqui não é o
     caminho recomendado (o botão "Imprimir" imprime o iframe, que tem @page
     próprio), mas também não pode produzir lixo. */
  body:has(.modal-relatorio-host.open) .app-shell { display: none !important; }
  .modal-relatorio-host.open {
    position: static;
    background: none;
    padding: 0;
  }
  .modal-relatorio-host.open .modal-box {
    max-width: none;
    width: 100%;
    height: auto;
    max-height: none;
    border: 0;
    box-shadow: none;
    animation: none;
  }
  .modal-relatorio-host.open .modal-header,
  .modal-relatorio-host.open .modal-footer { display: none !important; }
}

/* ── Command palette Ctrl+K (F2) — usa .modal-overlay/.modal-box do padrão ── */
.cmdk-box { max-width: 560px; align-self: flex-start; margin-top: 12vh; }
.cmdk-input-wrap {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.cmdk-input-wrap svg {
  width: 18px; height: 18px; flex-shrink: 0;
  stroke: var(--text-muted); fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.cmdk-input {
  flex: 1; background: none; border: none; outline: none;
  font-size: var(--text-lg); color: var(--text-primary); font-family: var(--font-sans);
}
.cmdk-input::placeholder { color: var(--text-placeholder); }
.cmdk-results { max-height: 360px; overflow-y: auto; padding: var(--sp-2); }
.cmdk-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); border-radius: var(--r-sm);
  cursor: pointer; color: var(--text-secondary);
}
.cmdk-item svg {
  width: 16px; height: 16px; flex-shrink: 0; color: var(--text-muted);
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.cmdk-item-label { flex: 1; font-size: var(--text-sm); color: var(--text-primary); }
.cmdk-item-group { font-size: var(--text-xs); color: var(--text-muted); flex-shrink: 0; }
.cmdk-item.active { background: var(--accent-subtle); }
.cmdk-item.active .cmdk-item-group { color: var(--accent); }
.cmdk-empty { padding: var(--sp-6); text-align: center; color: var(--text-muted); font-size: var(--text-sm); }

/* ── Ajuda de atalhos de teclado ("?") — F6 ── */
.atalhos-tabela { width: 100%; border-collapse: collapse; }
.atalhos-tabela td { padding: var(--sp-2) 0; font-size: var(--text-sm); color: var(--text-secondary); border-bottom: 1px solid var(--border-subtle); }
.atalhos-tabela tr:last-child td { border-bottom: none; }
.atalhos-tabela td:first-child { white-space: nowrap; padding-right: var(--sp-4); }

/* ═══════════════════════════════════════════════════════════════
   TOASTS
   ═══════════════════════════════════════════════════════════════ */
.toast-container {
  position: fixed;
  top: var(--sp-4);
  right: var(--sp-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  pointer-events: none;
  max-width: 400px;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: var(--bg-glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--border-soft);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-primary);
  pointer-events: all;
  animation: toast-in var(--dur-base) var(--ease);
  min-width: 260px;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: translateX(0); }
}
.toast-icon {
  width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px;
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.toast-success { border-left: 3px solid var(--status-success); }
.toast-success .toast-icon { color: var(--status-success); }
.toast-error   { border-left: 3px solid var(--status-danger); }
.toast-error   .toast-icon { color: var(--status-danger); }
.toast-warning { border-left: 3px solid var(--status-warning); }
.toast-warning .toast-icon { color: var(--status-warning); }
.toast-info    { border-left: 3px solid var(--status-info); }
.toast-info    .toast-icon { color: var(--status-info); }
.toast-msg { flex: 1; }

/* ═══════════════════════════════════════════════════════════════
   EMPTY STATES
   ═══════════════════════════════════════════════════════════════ */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--sp-8) var(--sp-5);
  text-align: center;
  gap: var(--sp-3);
}
.empty-state-icon {
  width: 52px; height: 52px;
  color: var(--text-muted);
  stroke: currentColor; fill: none;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.empty-state-title {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-secondary);
}
.empty-state-hint {
  font-size: var(--text-sm);
  color: var(--text-muted);
  max-width: 320px;
}

/* ⚠️ 04/08/2026. `.td-empty` era usada em 33 lugares, em 21 templates, e NUNCA
   tinha sido definida em CSS nenhum — "Nenhum papel cadastrado." saía como
   texto preto colado à esquerda, enquanto as telas com `empty_state()` mostravam
   o cinza centralizado que a pessoa já tinha aprendido a reconhecer.

   É a variante COMPACTA do `.empty-state` acima: mesma informação, outro
   suporte. Dentro de um `<tbody>` ela é UMA LINHA da tabela, não um bloco de
   80px com ícone — por isso herda as cores e o tamanho do `.empty-state-hint`
   de propósito, e não a caixa inteira. */
.td-empty {
  padding: var(--sp-5) var(--sp-4);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ═══════════════════════════════════════════════════════════════
   AVATARES
   ═══════════════════════════════════════════════════════════════ */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-full);
  font-weight: 700;
  text-transform: uppercase;
  flex-shrink: 0;
  font-size: var(--text-xs);
}
.avatar-sm  { width: 24px; height: 24px; font-size: 9px; }
.avatar-md  { width: 32px; height: 32px; font-size: var(--text-xs); }
.avatar-lg  { width: 40px; height: 40px; font-size: var(--text-sm); }

/* ═══════════════════════════════════════════════════════════════
   PROGRESS BARS
   ═══════════════════════════════════════════════════════════════ */
.progress {
  background: var(--border-subtle);
  border-radius: var(--r-full);
  height: 4px;
  overflow: hidden;
}
.progress-bar {
  height: 100%;
  border-radius: var(--r-full);
  background: var(--accent);
  transition: width var(--dur-base) var(--ease);
}
.progress-bar.success { background: var(--status-success); }
.progress-bar.danger  { background: var(--status-danger); }
.progress-bar.warning { background: var(--status-warning); }

/* ═══════════════════════════════════════════════════════════════
   ALERTAS INLINE (banners de erro/sucesso na página)
   ═══════════════════════════════════════════════════════════════ */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-radius: var(--r-md);
  font-size: var(--text-sm);
  margin-bottom: var(--sp-4);
  border: 1px solid;
}
.alert svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; margin-top: 1px; }
/* O `display:flex` acima existe para o par ícone+texto. O efeito colateral é
   que, num alerta com texto rico, CADA elemento inline (`<strong>`, `<a>`)
   vira um flex item e ganha o `gap` — a frase sai picotada, com buracos de
   12px no meio das palavras. Nunca apareceu porque todo alerta do ERP era
   texto puro; o primeiro com link (Automação, F11) revelou.
   O texto continua sendo UM item: quem quebra a linha é o navegador. */
.alert > p, .alert > span { margin: 0; flex: 1; min-width: 0; }
.alert-success { background: var(--status-success-bg); color: var(--status-success); border-color: var(--green-a25); }
.alert-danger  { background: var(--status-danger-bg);  color: var(--status-danger);  border-color: var(--red-a25); }
.alert-warning { background: var(--status-warning-bg); color: var(--status-warning); border-color: var(--amber-a25); }
.alert-info    { background: var(--status-info-bg);    color: var(--status-info);    border-color: var(--blue-a25); }

/* ═══════════════════════════════════════════════════════════════
   SKELETONS
   ═══════════════════════════════════════════════════════════════ */
@keyframes shimmer {
  0%   { background-position: -400px 0; }
  100% { background-position: 400px 0; }
}
.skeleton {
  background: linear-gradient(90deg, var(--bg-raised) 25%, var(--bg-overlay) 50%, var(--bg-raised) 75%);
  background-size: 800px 100%;
  animation: shimmer 1.5s infinite linear;
  border-radius: var(--r-xs);
}
.skeleton-text  { height: 14px; }
.skeleton-title { height: 20px; width: 60%; }
.skeleton-row   { height: 48px; border-radius: 0; }

/* ═══════════════════════════════════════════════════════════════
   TABS
   ═══════════════════════════════════════════════════════════════ */
.tabs {
  display: flex;
  gap: 2px;
  background: var(--bg-raised);
  padding: 4px;
  border-radius: var(--r-md);
  border: 1px solid var(--border-subtle);
  width: fit-content;
}
.tab-btn {
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-sm);
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  font-family: var(--font-sans);
  transition: all var(--dur-fast) var(--ease);
}
.tab-btn:hover  { color: var(--text-secondary); }
.tab-btn.active { background: var(--bg-overlay); color: var(--text-primary); font-weight: 600; }
.tab-content    { display: none; }
.tab-content.active { display: block; }

/* Variante NAVEGACIONAL (F2 da Central Financeira): a aba é um link, não um
   botão de troca local — é assim que "Relatórios" e "Cadastros" reúnem telas
   que continuam sendo rotas próprias (nenhum bookmark morre no reagrupamento
   do menu 13→8). Antes disso cada tela reescrevia `.modo-tabs` no próprio
   <style>; o DRE Anual era a única e virou este componente. */
a.tab-btn {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  white-space: nowrap;
}
/* Num grupo com 4 abas, 390px não comporta a fileira: rola dentro da própria
   faixa em vez de esticar a página. */
.tabs--nav { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.tabs--nav::-webkit-scrollbar { display: none; }

/* ═══════════════════════════════════════════════════════════════
   UTILITÁRIOS
   ═══════════════════════════════════════════════════════════════ */
.flex         { display: flex; }
.flex-col     { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: var(--sp-2); }
.gap-3 { gap: var(--sp-3); }
.gap-4 { gap: var(--sp-4); }
.gap-5 { gap: var(--sp-5); }
.flex-1 { flex: 1; }
.flex-wrap { flex-wrap: wrap; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--sp-4); }
.grid-auto { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--sp-4); }

.text-xs   { font-size: var(--text-xs); }
.text-sm   { font-size: var(--text-sm); }
.text-base { font-size: var(--text-base); }
.text-lg   { font-size: var(--text-lg); }
.text-xl   { font-size: var(--text-xl); }

.text-primary   { color: var(--text-primary); }
.text-secondary { color: var(--text-secondary); }
.text-muted     { color: var(--text-muted); }
.text-success   { color: var(--status-success); }
.text-danger    { color: var(--status-danger); }
.text-warning   { color: var(--status-warning); }
.text-info      { color: var(--status-info); }
.text-accent    { color: var(--accent); }

.font-500 { font-weight: 500; }
.font-600 { font-weight: 600; }
.font-700 { font-weight: 700; }

.text-right  { text-align: right; }
.text-center { text-align: center; }
.text-left   { text-align: left; }

.mt-0 { margin-top: 0; }
.mt-2 { margin-top: var(--sp-2); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mb-0 { margin-bottom: 0; }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-4 { margin-bottom: var(--sp-4); }
.mb-5 { margin-bottom: var(--sp-5); }

.w-full  { width: 100%; }
.min-w-0 { min-width: 0; }
.overflow-x-auto { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.overflow-hidden { overflow: hidden; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.divider {
  height: 1px;
  background: var(--border-subtle);
  margin: var(--sp-5) 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Ocultar valores — docs/PLANO_OCULTAR_VALORES.md
   O ERP nasce OCULTO; revelar é ato deliberado. `static/js/valores.js` marca
   os elementos, este bloco é quem os borra.

   ⚠️ INVARIANTE: `.valor-sensivel` só é aplicada a elemento FOLHA (Regra A =
   classe no pai de um nó de texto único; Regra B = <span> em volta do trecho).
   Isso não é estética — `filter` cria stacking context E containing block para
   descendentes posicionados, que é exatamente o bug documentado em
   base.js:199-216. Enquanto o elemento borrado for folha, nenhum modal,
   dropdown ou <thead> sticky pode ser descendente dele. Se algum dia alguém
   puser `data-valor-sensivel` num container, o defeito volta por essa porta —
   há lint em tests/test_ocultar_valores.py travando isso.
   ═══════════════════════════════════════════════════════════════════════════ */
html.valores-ocultos .valor-sensivel,
html.valores-ocultos [data-valor-sensivel] {
  filter: var(--blur-valor);
  /* O texto continua no DOM (nunca é reescrito — importar.html:612 lê dinheiro
     do textContent para calcular markup). Sem isto, selecionar e copiar a
     tabela devolveria os valores que o desfoque acabou de esconder. */
  user-select: none;
  -webkit-user-select: none;
  transition: filter var(--dur-base) var(--ease);
}

/* Anti-flash. O boot inline do <head> liga `.valores-ocultos` antes do primeiro
   paint, mas nesse instante NADA está marcado ainda: o JS só varre o DOM depois
   do parse. Numa página longa (DRE anual) o navegador pinta os números e só
   então eles borram — justamente o vazamento que a feature existe para evitar,
   e que com o padrão invertido aconteceria em TODA carga, para todo usuário.
   Estas classes já existem e cobrem as colunas monetárias; o desfoque cego vale
   só até `valores.js` terminar o primeiro passe e marcar `.valores-prontos`. */
html.valores-ocultos:not(.valores-prontos) :is(.num, .val-pos, .val-neg, .kpi-value) {
  filter: var(--blur-valor);
}

/* O botão traz os DOIS ícones no HTML e o CSS escolhe qual aparece — assim o JS
   nunca mexe em `<use href>`, que é onde mora a armadilha do sprite em cache.
   Seletor por atributo, não por id: o botão existe DUAS vezes (topbar mobile e
   header desktop), mesmo motivo de `data-shell-nav`. Id repetido reprova em
   tests/test_lint_templates.py::test_nenhum_id_html_repetido_no_mesmo_template. */
[data-ocultar-valores] .icone-olho-fechado { display: none; }
[data-ocultar-valores] .icone-olho-aberto  { display: block; }
html.valores-ocultos [data-ocultar-valores] .icone-olho-fechado { display: block; }
html.valores-ocultos [data-ocultar-valores] .icone-olho-aberto  { display: none; }

/* Impressão nunca borra: número ilegível num documento é dado corrompido — pior
   que o vazamento. Quem imprime já decidiu pôr o valor no papel. */
@media print {
  html.valores-ocultos .valor-sensivel,
  html.valores-ocultos [data-valor-sensivel],
  html.valores-ocultos:not(.valores-prontos) :is(.num, .val-pos, .val-neg, .kpi-value) {
    filter: none;
    user-select: auto;
    -webkit-user-select: auto;
  }
}

/* Ícones SVG genéricos inline */
.icon {
  display: inline-block;
  width: 16px; height: 16px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  vertical-align: middle;
  flex-shrink: 0;
}
.icon-sm { width: 14px; height: 14px; }
.icon-lg { width: 20px; height: 20px; }
.icon-xl { width: 24px; height: 24px; }

/* Drop zone */
.drop-zone {
  border: 2px dashed var(--border-soft);
  border-radius: var(--r-md);
  padding: var(--sp-6);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dur-fast), background var(--dur-fast);
  color: var(--text-muted);
}
.drop-zone:hover { border-color: var(--accent); background: var(--accent-subtle); }
.drop-zone input[type=file] { display: none; }

/* Thumb */
.thumb {
  width: 40px; height: 40px;
  object-fit: cover;
  border-radius: var(--r-sm);
  cursor: pointer;
  border: 1px solid var(--border-subtle);
}

/* Lightbox */
.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--scrim-heavy);
  z-index: var(--z-lightbox);
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
}
.lightbox.open { display: flex; }
.lightbox img { max-width: 90vw; max-height: 85vh; border-radius: var(--r-md); }
.lightbox-close {
  position: absolute; top: var(--sp-5); right: var(--sp-6);
  color: var(--text-secondary); cursor: pointer;
  background: var(--bg-glass); border-radius: var(--r-sm);
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
}

/* ── Alerta de chips (estoque baixo etc.) ── */
.alert-chips-wrap {
  background: var(--status-danger-bg);
  border: 1px solid var(--red-a20);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  margin-bottom: var(--sp-5);
}
.alert-chips-title {
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--status-danger);
  text-transform: uppercase;
  letter-spacing: .07em;
  margin-bottom: var(--sp-2);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.alert-chips-title svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.alert-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.alert-chip {
  background: var(--bg-raised);
  border: 1px solid rgba(242,109,109,.2);
  color: var(--status-danger);
  padding: 3px 10px;
  border-radius: var(--r-full);
  font-size: var(--text-xs);
  font-weight: 600;
}

/* ── Utilitário de valor positivo/negativo (F5) — antes redefinido inline
   em ~8 templates financeiros (P9 do plano de redesign); centralizado
   aqui na primeira tela migrada (financeiro_dashboard.html). As demais
   cópias locais continuam funcionando idênticas (mesma regra), migrar
   o resto fica pra uma sessão de dedup dedicada. ── */
.val-pos { color: var(--status-success); font-weight: 700; font-variant-numeric: tabular-nums; }
.val-neg { color: var(--status-danger); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ── Cards de Dashboard Financeiro (F5) — saldo por conta, DRE por evento,
   barras de fluxo mensal. Antes inline em financeiro_dashboard.html. ── */
.saldo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--sp-3); margin-top: var(--sp-2); }
.saldo-card { padding: var(--sp-4); border-radius: var(--r-md); border: 1px solid var(--border-subtle); background: var(--bg-canvas); }
.saldo-card-nome { font-size: 13px; font-weight: 600; color: var(--text-primary); margin-bottom: 2px; }
.saldo-card-empresa { font-size: 11px; color: var(--text-muted); margin-bottom: var(--sp-3); }
.saldo-card-valor { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
.fluxo-bar-wrap { width: 100px; height: 7px; background: var(--bg-canvas); border-radius: 4px; overflow: hidden; display: inline-block; vertical-align: middle; margin-left: 8px; }
.fluxo-bar-fill { height: 100%; border-radius: 4px; }
.fluxo-bar-entrada { background: var(--status-success); }
.fluxo-bar-saida { background: var(--status-danger); }
.dre-evento { margin-bottom: var(--sp-4); }
.dre-evento-header { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-3) var(--sp-4); background: var(--bg-canvas); border-radius: var(--r-md); margin-bottom: var(--sp-3); flex-wrap: wrap; gap: 8px; }
.dre-evento-nome { font-size: 14px; font-weight: 700; color: var(--text-primary); }
.dre-evento-totals { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.dre-total-card { padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md); text-align: center; }
.dre-total-receita { background: color-mix(in srgb, var(--status-success) 10%, transparent); }
.dre-total-despesa { background: color-mix(in srgb, var(--status-danger) 10%, transparent); }
.dre-total-neutro { background: var(--bg-canvas); }
.dre-total-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); margin-bottom: 4px; }
.dre-total-value { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
@media (max-width: 768px) {
  .dre-evento-totals { grid-template-columns: 1fr; }
  .fluxo-bar-wrap { display: none; }
}

/* ── Alertas acionáveis do dashboard (F5) — cada card já é o link direto
   pra tela onde a pendência se resolve. tone-* reaproveita as cores de
   status já existentes (nunca hex novo). ── */
.alert-actions-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.alert-action-card {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-radius: var(--r-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-raised);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: 600;
  transition: transform var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.alert-action-card:hover { transform: translateY(-1px); border-color: var(--border-focus); }
.alert-action-card svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
.alert-action-card.tone-danger  { border-left: 3px solid var(--status-danger); }
.alert-action-card.tone-danger  svg { color: var(--status-danger); }
.alert-action-card.tone-warning { border-left: 3px solid var(--status-warning); }
.alert-action-card.tone-warning svg { color: var(--status-warning); }
.alert-action-card.tone-info    { border-left: 3px solid var(--status-info); }
.alert-action-card.tone-info    svg { color: var(--status-info); }
.alert-action-card .chevron { margin-left: auto; width: 14px; height: 14px; opacity: .5; }

/* ── Charts wrap ── */
.chart-wrap {
  position: relative;
  height: 240px;
}

/* ── Tom Select dark theme override ── */
/* Tom Select copia as classes do <select> original para o WRAPPER que ele
   cria. Como todo select do projeto é `.form-control`, o wrapper herdava
   borda + fundo + padding do campo — e o `.ts-control` de dentro desenha a
   borda de novo: dois retângulos concêntricos em cada dropdown do ERP
   (visível em qualquer filtro). O wrapper vira contêiner nu; quem é o campo
   é o `.ts-control`. */
.ts-wrapper.form-control {
  padding: 0 !important;
  border: 0 !important;
  background: transparent !important;
  min-height: 0 !important;
  height: auto !important;
  border-radius: 0 !important;
}
.ts-wrapper .ts-control {
  background: var(--bg-canvas) !important;
  border: 1.5px solid var(--border-soft) !important;
  border-radius: var(--r-sm) !important;
  color: var(--text-primary) !important;
  min-height: 44px !important;   /* mesmo alvo de toque dos demais campos */
  box-shadow: none !important;
  /* mesmo padding do .form-control: sem isso o dropdown fica 4px mais baixo
     que o <input> ao lado dele na mesma linha de filtro. */
  padding: 10px var(--sp-3) !important;
}
.ts-wrapper.focus .ts-control {
  border-color: var(--border-focus) !important;
  box-shadow: none !important;
}
.ts-dropdown {
  background: var(--bg-popover) !important;
  border: 1px solid var(--border-elevated) !important;
  border-radius: 10px !important;
  box-shadow: var(--shadow-overlay) !important;
  color: var(--text-primary) !important;
  z-index: var(--z-popover) !important;
}
.ts-dropdown-content {
  max-height: 260px !important;
  scrollbar-width: thin !important;
  scrollbar-color: rgba(124,108,255,.4) transparent !important;
}
.ts-dropdown-content::-webkit-scrollbar { width: 5px; }
.ts-dropdown-content::-webkit-scrollbar-track { background: transparent; }
.ts-dropdown-content::-webkit-scrollbar-thumb { background: rgba(124,108,255,.4); border-radius: 99px; }
.ts-dropdown-content::-webkit-scrollbar-thumb:hover { background: rgba(124,108,255,.7); }
.ts-dropdown .option { padding: 8px 12px !important; font-size: 13px !important; border-radius: 0 !important; }
.ts-dropdown .option:hover,
.ts-dropdown .option.active { background: rgba(255,255,255,.07) !important; color: var(--text-primary) !important; }
.ts-dropdown .create { color: var(--accent) !important; }
/* Chip roxo só faz sentido em múltipla escolha (vários itens no mesmo campo).
   Num select simples o valor É o conteúdo do campo — pintá-lo de chip fazia
   todo filtro do ERP parecer ter uma tag selecionada. */
.ts-wrapper.multi .ts-control .item { background: var(--accent-subtle) !important; color: var(--accent) !important; border-radius: var(--r-xs) !important; }
.ts-wrapper.single .ts-control .item { background: transparent !important; color: var(--text-primary) !important; padding: 0 !important; }
.ts-wrapper .ts-control input { color: var(--text-primary) !important; }
.ts-wrapper .ts-control input::placeholder { color: var(--text-placeholder) !important; }
.plugin-dropdown_input .dropdown-input {
  background: transparent !important;
  border-bottom: 1px solid rgba(124,108,255,.25) !important;
  color: var(--text-primary) !important;
  padding: 8px 12px !important;
}
.plugin-dropdown_input .dropdown-input::placeholder { color: var(--text-placeholder) !important; }

/* ── Chips de filtro de 1 clique (Evolução Inbox E3) — promovido daqui pra
   frente em vez de local por página; qualquer lista que precise de filtros
   rápidos reaproveita. Estado ativo = borda/fundo na cor de accent. ── */
.chip-bar { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.filter-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 14px; border-radius: 999px;
  border: 1px solid var(--border-subtle);
  background: var(--bg-canvas);
  color: var(--text-secondary);
  font-size: var(--text-xs); font-weight: 600;
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.filter-chip:hover { border-color: var(--accent); color: var(--text-primary); }
.filter-chip.active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: var(--accent);
  color: var(--accent);
}

/* ── Grade de quiosques (Reservas) — seleção múltipla sem JavaScript ──
   Uma reserva pode ocupar vários quiosques. O estado marcado sai de
   `:has(input:checked)`, não de classe trocada por script: sem handler inline
   não há atrito com a CSP, e a tela funciona antes de qualquer JS carregar.
   Modelado no `.filter-chip` acima; o que muda é o alvo de toque (44px, dedo
   na recepção) e a grade que se acomoda sozinha a 375px. ── */
.quiosque-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(52px, 1fr));
  gap: var(--sp-2);
}
.quiosque-chip {
  position: relative;  /* prende o input absoluto dentro do próprio chip */
  display: flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 var(--sp-2);
  border-radius: var(--r-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-canvas);
  color: var(--text-secondary);
  font-size: var(--text-sm); font-weight: 600;
  cursor: pointer; user-select: none;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
/* O input segue no fluxo (não é `display:none`) para continuar focável por
   teclado e visível a leitor de tela — só não é desenhado. */
.quiosque-chip input {
  position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%;
  margin: 0; cursor: pointer;
}
.quiosque-chip:hover { border-color: var(--accent); color: var(--text-primary); }
.quiosque-chip:has(input:checked) {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: var(--accent);
  color: var(--accent);
}
.quiosque-chip:has(input:focus-visible) {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* ── Card rico de documento (Evolução Inbox E3) — promovido do padrão
   .notif-card (notificacoes_central.html) pra um componente compartilhado;
   borda esquerda colorida por prioridade, igual ao original. ── */
.doc-card {
  display: flex; gap: var(--sp-4); align-items: flex-start;
  padding: var(--sp-4); margin-bottom: var(--sp-2);
  border: 1px solid var(--border-subtle); border-left: 4px solid var(--border-subtle);
  border-radius: var(--r-md); background: var(--bg-raised);
  transition: border-color var(--dur-fast) var(--ease);
}
.doc-card.nao-lido { border-left-color: var(--accent); background: color-mix(in srgb, var(--accent) 5%, var(--bg-raised)); }
.doc-card.prioridade-alta { border-left-color: var(--status-warning); }
.doc-card.prioridade-critica { border-left-color: var(--status-danger); }
.doc-card-body { flex: 1; min-width: 0; }
.doc-card-title-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); flex-wrap: wrap; }
.doc-card-titulo { font-weight: 600; font-size: var(--text-sm); }
.doc-card-remetente { font-size: var(--text-xs); color: var(--text-muted); }
.doc-card-resumo {
  font-size: var(--text-xs); color: var(--text-secondary); margin-top: 4px;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.doc-card-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--sp-2); align-items: center; }
.doc-card-meta { font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap; }
.doc-card-actions { display: flex; flex-direction: column; gap: 6px; align-items: flex-end; flex-shrink: 0; }
@media (max-width: 640px) {
  .doc-card { flex-direction: column; }
  .doc-card-actions { flex-direction: row; align-self: stretch; justify-content: flex-end; flex-wrap: wrap; }
}
