/* ============================================================
   HARMIS — Design System
   Linguagem do aplicativo Notion, paleta terracota/papel quente.
   Fontes: system-ui (layout) · JetBrains Mono (tabelas/números)
   Ver DESIGN-notion.md para o porquê de cada decisão.
   ============================================================ */

/* JetBrains Mono é a ÚNICA família servida self-hosted — o layout inteiro usa
   a fonte nativa da plataforma (`system-ui`), que não precisa de arquivo nem
   de rede. O CSP do app (app.py @after_request) só libera 'self': Google
   Fonts fica bloqueado, então um `url()` sem arquivo NÃO cai para a CDN — cai
   para a fonte do sistema, em silêncio. É o que já aconteceu aqui uma vez:
   até esta revisão havia um segundo @font-face, para 'Inter', que baixava
   48KB em toda página e não renderizava em regra nenhuma (nenhum token
   `--font-*` a referenciava) — peso morto removido, junto com
   static/fonts/inter.woff2 e OFL-inter.txt.
   UM arquivo por família, VARIÁVEL, com a faixa de peso do eixo `wght`
   declarada — pedir vários pesos separados baixaria uma cópia por peso.
   Licença em static/fonts/OFL-jetbrainsmono.txt — a OFL exige que siga junto. */
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:100 800;font-display:swap;src:url('../fonts/jetbrainsmono.woff2') format('woff2');}

/* ── Tokens ────────────────────────────────────────────────── */
:root {
  /* ── Superfícies — papel quente com card branco por cima ────
     Canvas (--bg) e sidebar (--nav-bg) compartilham o MESMO papel quente —
     conferido nos dois temas, claro e escuro. Quem se destaca do papel é o
     CARD (--bg2, branco puro), não a sidebar: é ele que carrega o hairline e
     concentra o conteúdo. Ver DESIGN-notion.md ("As cinco inversões", linha
     "Canvas") para o histórico — esta relação já foi documentada ao
     contrário (sidebar diferente do conteúdo) e o CSS nunca bateu com aquele
     texto. */
  --bg:          #faf9f7;
  --bg2:         #ffffff;
  --bg3:         #f3efec;
  --bg4:         #eae5e0;
  --border:      #e4dfd9;
  --border2:     #d1ccc6;
  --txt1:        #2d2926;
  --txt2:        #5c5754;
  /* --txt3 e --txt4 foram ESCURECIDOS nesta revisão. Os valores anteriores
     (#8b8682 e #b5b0ac) rendiam 3,60:1 e 2,15:1 contra o card branco — abaixo
     do piso de 4,5:1 que o CLAUDE.md §3 manda CONFERIR, não presumir. E não é
     texto decorativo: --txt3 pinta todo rótulo de formulário, toda dica, todo
     cabeçalho de tabela e todo `.kpi-label`; --txt4 pintava a coluna de horas
     da agenda. Medido por luminância relativa (WCAG), nos dois fundos:
       --txt3 #706b67 → 5,27:1 no card branco · 5,01:1 no papel (--bg)
       --txt4 #8f8a86 → 3,42:1 / 3,25:1 — a faixa "fantasma" (placeholder, dia
                        fora do mês), que continua acima de 3:1.
     Guia de Design — Acessibilidade: "Strive to meet color contrast minimum
     standards … ensure your UI meets acceptable levels." */
  --txt3:        #706b67;
  --txt4:        #8f8a86;
  --accent:      #b58375;
  --accent-h:    #a37164;
  --accent-deep: #8f5d50;
  --accent-deep-h:#7a4b40;
  /* Um degrau a mais, só para :active do botão primário — ver comentário
     junto de --btn-primary-bg abaixo. */
  --accent-deepest:#684036;
  --accent-glow: #fdf6f5;
  --accent-border:#e8cfc9;
  --on-accent:   #ffffff;

  --nav-bg:      #faf9f7;
  --nav-hover:   #f3efec;

  --zebra:       #faf9f7;

  --warm:        #a39e98;   /* 2ª série de gráfico — lido por static/js/charts.js */

  /* Rampa semântica no estilo TAG do produto: fundo pastel + texto escuro da
     MESMA matiz, sem borda. É o oposto do badge saturado de antes — no Notion
     a etiqueta é um fundo suave que não compete com o texto da linha.
     `--*-border` fica igual ao fundo: as regras de badge ainda pedem a borda,
     e assim ela existe sem desenhar nada. */
  --green:       #1c3829;  --green-bg: #dbeddb;  --green-border: #dbeddb;
  --red:         #5d1715;  --red-bg:   #ffe2dd;  --red-border:   #ffe2dd;
  --amber:       #402c1b;  --amber-bg: #fdecc8;  --amber-border: #fdecc8;

  /* Verde/vermelho de VALOR (val-pos / val-neg) continuam saturados: ali a cor
     é o dado, não o fundo, e um verde escuro de etiqueta some na coluna. */
  --val-pos:     #0f7b3d;
  --val-neg:     #c4372c;

  /* Paleta de STICKERS — decorativa, para ilustração. Nunca pinta CTA,
     badge, fundo de estado ou qualquer estrutura: é regra explícita do
     spec e tests/test_design_contrato.py reprova o uso. */
  --sticker-sky:    #62aef0;
  --sticker-purple: #d6b6f6;
  --sticker-pink:   #ff64c8;
  --sticker-orange: #dd5b00;
  --sticker-teal:   #2a9d99;
  --sticker-green:  #1aae39;

  /* --accent puro (usado em link, anel de foco, linha selecionada) só chega a
     3,25:1 contra texto branco — abaixo do piso de 4,5:1 do CLAUDE.md §3, e é
     o botão mais clicado do app ("Entrar", "Salvar", "Novo atendimento"). O
     botão usa dois degraus mais escuros da MESMA rampa (accent-deep/-h, já
     existiam só para o :active) em vez de --accent: com texto claro fixo, um
     degrau mais escuro no hover reduziria o contraste em vez de aumentar —
     verificado por luminância relativa (WCAG), não só por olho. Rest 5,46:1 ·
     hover 7,22:1 · active 8,9:1 (approx.), os três acima do piso. */
  --btn-primary-bg:  var(--accent-deep);  --btn-primary-bg-h: var(--accent-deep-h);  --btn-primary-fg: var(--on-accent);

  /* O mesmo raciocínio do botão, generalizado — porque ele estava resolvido em
     UM lugar e o resto do sistema continuava usando --accent puro:
       --accent-txt   : acento como TEXTO sobre fundo claro (link, `.badge-blue`,
                        `.chip.accent`, dia de hoje do mini-mês, cabeçalho
                        ordenado). --accent puro dá 3,25:1 no branco e 3,04:1
                        no --accent-glow: reprova para 12–14px.
                        #8f5d50 → 5,46:1 no branco · 5,11:1 no --accent-glow.
       --accent-fill  : acento como SUPERFÍCIE preenchida carregando
                        --on-accent (pill ativo, número do dia de hoje, ícone
                        do logo, trilho do interruptor ligado). Branco sobre
                        --accent é 3,25:1 — reprova nos 13px do `.cal-num`.
     No tema escuro --accent-txt vira o degrau CLARO da rampa: o mesmo #8f5d50
     que resolve no papel some no carbono. --accent-fill não muda, porque o que
     ele carrega é branco nos dois temas. */
  --accent-txt:   var(--accent-deep);
  --accent-txt-h: var(--accent-deep-h);
  --accent-fill:  var(--accent-deep);

  /* Cursor, seleção e controle nativo (checkbox, radio, range, progress)
     pintados pela rampa da marca em vez do azul do sistema operacional —
     uma linha substitui o controle customizado que o CLAUDE.md §3 não quer. */
  accent-color: var(--accent-fill);

  /* ── Movimento ───────────────────────────────────────────────
     Guia de Design — Movimento: "Aim for brevity and precision in feedback
     animations … generally avoid adding motion to UI interactions that occur
     frequently." Daí a escala curta: 120ms é resposta de botão, 200ms é
     mudança de estado, 320ms é a única faixa longa e só a folha/modal usa.

     As curvas são as do sistema, não `ease` do navegador. `--ease-spring`
     (.32,.72,0,1) é a desaceleração de folha do iOS: sai rápido e assenta sem
     ricochete — ricochete é o que a configuração "Reduzir movimento" existe
     para eliminar, então não se coloca de propósito. */
  --ease-standard: cubic-bezier(.4, 0, .2, 1);
  --ease-out:      cubic-bezier(0, .55, .45, 1);
  --ease-spring:   cubic-bezier(.32, .72, 0, 1);
  --dur-1: .12s;   --dur-2: .2s;   --dur-3: .32s;

  /* Altura da CAIXA da `.topbar`, hairline inclusa: 12 + 28 (o botão de ícone)
     + 8 de padding = 48, mais 1px de `border-bottom`. Medido no navegador, não
     estimado — com 48 sobrava uma fresta de 1px por onde o conteúdo aparecia
     por baixo da barra translúcida.
     Existe como token porque o `.view-header` GRUDA abaixo dela: os dois eram
     `sticky top: 0`, e o cabeçalho da view — nome, busca, filtro e "Novo" —
     escorregava para trás da trilha ao rolar. */
  --topbar-h: 49px;

  --sidebar-w:   250px;
  --sidebar-wc:  66px;

  /* Raios do PRODUTO, bem menores que os do site de marketing. Lá o CTA é um
     pill de 9999px; aqui nada é pill exceto o chip de status com bolinha. */
  --radius-sm:   4px;
  --radius:      8px;
  --radius-lg:   12px;
  --radius-card: 12px;
  /* Pill = marcador ou contador, nunca ação nem etiqueta. A lista inteira:
     avatar, número do dia de hoje, contador de equipe, cabeçalho de coluna
     do kanban. Em badge, botão ou campo é o CTA da landing page. */
  --radius-pill: 9999px;

  /* Elevação: muitas camadas quase transparentes, nunca uma sombra dura.
     Card padrão NÃO tem sombra — no produto ele se apoia no hairline.
     Sombra é para o que flutua: modal, popover, tooltip. */
  --shadow-sm: 0 .175px 1.041px rgba(0,0,0,.01), 0 .8px 2.925px rgba(0,0,0,.02),
               0 2.025px 7.847px rgba(0,0,0,.027), 0 4px 18px rgba(0,0,0,.04);
  --shadow-md: 0 .6px 1.4px rgba(0,0,0,.012), 0 1.8px 4.2px rgba(0,0,0,.02),
               0 4.4px 10px rgba(0,0,0,.028), 0 10px 23px rgba(0,0,0,.038),
               0 23px 52px rgba(0,0,0,.05);
  --shadow:      var(--shadow-sm);   /* alias legado — não usar em regra nova */
  --shadow-card: none;

  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-7: 28px; --sp-8: 32px;

  /* Escala tipográfica do spec. O tracking negativo é EXPLÍCITO: a system-ui
     no tracking padrão lê mais solta do que o título calibrado pede, e sem
     isto o título perde o ar "assentado" que define a marca. */
  --t-h1:      700 40px/1.1  var(--font-head);   --t-h1-ls:      -1px;
  --t-h2:      700 26px/1.23 var(--font-head);   --t-h2-ls:      -.625px;
  --t-h3:      700 22px/1.27 var(--font-head);   --t-h3-ls:      -.25px;
  --t-title:   600 20px/1.4  var(--font-head);   --t-title-ls:   -.125px;
  --t-body:    400 16px/1.5  var(--font-body);
  --t-body-sm: 400 15px/1.33 var(--font-body);
  --t-table:   400 14px/1.4  var(--font-body);
  --t-caption: 400 14px/1.43 var(--font-body);
  --t-eyebrow: 600 12px/1.33 var(--font-body);   --t-eyebrow-ls: .125px;

  /* `-apple-system` ANTES de `system-ui`: os dois resolvem para a São
     Francisco no Mac, mas só o primeiro entrega os cortes ópticos que o
     sistema usa (SF Text abaixo de 20px, SF Display acima) — é o handle que a
     Apple documenta para web. `system-ui` sozinho pega a família genérica e
     perde a troca. Em Windows e Linux nada muda: o nome não casa e a lista
     segue para o próximo. */
  --font-body:    -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
  --font-head:    -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
  --font-display: -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
  --font-mono:    'JetBrains Mono', monospace;   /* exceção 1: valor monetário */

  color-scheme: light;
}

/* ── Reset ─────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* `[hidden]` some SÓ até o elemento ganhar uma classe com `display` próprio
   (`.form-group`, `.card`, `.help-box`...) — autor sempre vence agente do
   usuário em empate de especificidade, então `display:flex` da classe fica
   por cima do `display:none` nativo do atributo, e o elemento "escondido"
   continua na tela. Um `hidden` que às vezes esconde e às vezes não é pior
   que nenhum: `!important` aqui é a correção padrão do problema, não gambiarra. */
[hidden] { display: none !important; }
/* `font-size: 16px` CRAVAVA a raiz e anulava o tamanho de texto escolhido no
   navegador ou no sistema — quem aumenta a fonte para enxergar não via
   diferença nenhuma, e o `rem` do sistema inteiro ficava preso.
   `100%` devolve a escala ao usuário sem mudar nada no padrão (16px é o
   default de todo navegador). Guia de Design — Acessibilidade: "Support
   larger text sizes … give people the option to enlarge text by at least
   200 percent." */
html { font-size: 100%; scroll-behavior: smooth; }
body { background: var(--bg); color: var(--txt1); font: var(--t-body);
       min-height: 100vh; -webkit-font-smoothing: antialiased;
       /* A São Francisco tem eixo óptico: `auto` deixa o corte trocar sozinho
          entre Text e Display conforme o tamanho, que é o que os aplicativos
          do sistema fazem. Em fonte sem o eixo, não faz nada. */
       font-optical-sizing: auto;
       /* Sem negrito nem itálico SINTÉTICO: o navegador inclina e engorda o
          desenho à força quando falta o corte. A família do sistema tem todos
          os pesos — o falso só aparece onde não deveria haver nenhum. */
       font-synthesis: none; }

/* Seleção de texto na cor da marca, como um aplicativo do sistema pinta com a
   cor de destaque escolhida nas Configurações. O azul padrão do navegador é a
   única coisa da tela que não é do produto. */
::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); color: var(--txt1); }
/* Link em --accent puro dava 3,25:1 no branco: reprovava em TODA tela.
   --accent-txt é o mesmo tom, dois degraus abaixo (5,46:1). */
a { color: var(--accent-txt); text-decoration: none; }
a:hover { color: var(--accent-txt-h); }
input, select, textarea, button { font-family: inherit; font-size: inherit; }
/* O retângulo cinza que o WebKit pinta por 300ms em todo toque, e o atraso de
   clique que o navegador guarda para ver se vem um duplo-toque. Os dois fazem
   o app parecer lento no telefone sem aparecer em teste nenhum. */
a, button, summary, [role="button"], .nav-item, .cal-ev, .ag-ev, .mini-dia {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
/* Rótulo de CONTROLE não se seleciona — em aplicativo de mesa, arrastar sobre
   um botão não pinta o texto dele de azul. O conteúdo (tabela, prontuário,
   documento) continua selecionável e copiável, que é o ponto. */
   A ETIQUETA fica de fora de propósito: "Vencido", "Assinado" e o nome da
   situação são dado da linha, e copiar uma linha inteira da tabela tem de
   trazê-los junto. */
.btn, .nav-item, .topbar, .view-header, .kanban-cab, .cal-dow, .mini-dow,
.ag-cabeca, .ag-hora, .data-table th, .form-label, .kpi-label, .modal-header,
.sidebar-footer, .config-nav, .chip {
  user-select: none; -webkit-user-select: none;
}
table { border-collapse: collapse; width: 100%; }

/* ── Foco visível ──────────────────────────────────────────────
   O sistema NÃO tinha indicador de foco. `outline` aparecia três vezes no
   arquivo inteiro e duas delas eram `outline: none` — o campo trocava a cor da
   borda (pista de COR sozinha, que o §3 proíbe) e botão, link, item de menu,
   interruptor, linha de tabela, evento da agenda e `<summary>` não tinham
   marca nenhuma. Quem navega por teclado ficava sem saber onde está, em toda
   tela do produto. Não estoura: a página responde 200.

   Guia de Design — Foco e seleção: "Every onscreen component should be
   reachable via focus navigation, and focus should move intuitively through
   your interface."

   `:focus-visible` e não `:focus`: o navegador só o aciona quando a marca
   ajuda (teclado, navegação por acessibilidade), então o clique de mouse
   continua limpo. Uma regra cobre o arquivo inteiro. */
/* Desenho do anel: o do sistema é GROSSO e MACIO, colado no controle — não uma
   hairline flutuando a 2px de distância. 3px, o acento a 80%, deslocamento de
   1px. Os 80% não são estética: a 70% o anel mede 2,97:1 contra branco e
   reprova o piso de 3:1 de indicador de foco; a 80% mede 3,63:1 (claro) e
   4,77:1 (escuro). Conferido no navegador. */
:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--accent-txt) 80%, transparent);
  outline-offset: 1px;
}
/* Quem vive dentro de contêiner com `overflow: hidden` (célula do calendário,
   grade da agenda, coluna do kanban) perde o anel para fora da caixa — o
   deslocamento negativo o desenha por dentro, onde não é recortado. */
.cal-ev:focus-visible, .ag-ev:focus-visible, .cal-dia:focus-visible,
.mini-dia:focus-visible, .lead-card:focus-visible, .movel:focus-visible,
.nav-item:focus-visible, .config-nav a:focus-visible,
.data-table a.registro:focus-visible {
  outline-offset: -3px;   /* = espessura do anel: cabe inteiro dentro da caixa */
}

/* ── Pular para o conteúdo ─────────────────────────────────────
   Sem isto, quem usa teclado ou leitor de tela atravessa a sidebar inteira —
   dezenas de itens — a cada página do sistema, antes de chegar na tabela que
   veio ver. Fica escondido até receber foco. */
.skip-link {
  position: fixed; top: var(--sp-2); left: -200vw; z-index: 999;
  padding: var(--sp-2) var(--sp-4); border-radius: var(--radius);
  background: var(--bg2); border: 1px solid var(--border2);
  color: var(--txt1); font: var(--t-body-sm); font-weight: 600;
  box-shadow: var(--shadow-md);
}
.skip-link:focus { left: var(--sp-2); color: var(--txt1); }

/* Títulos: peso 700 + tracking NEGATIVO explícito. É o gesto que define a
   marca — a system-ui no tracking padrão lê solta e o título perde firmeza. */
h1 { font: var(--t-h1); letter-spacing: var(--t-h1-ls); }
h2 { font: var(--t-h2); letter-spacing: var(--t-h2-ls); }
h3 { font: var(--t-h3); letter-spacing: var(--t-h3-ls); }
h4 { font: var(--t-title); letter-spacing: var(--t-title-ls); }

/* ── Layout ────────────────────────────────────────────────── */
.app-wrapper { display: flex; min-height: 100vh; }

/* Sidebar */
.sidebar {
  width: var(--sidebar-w);
  background: var(--nav-bg);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  position: fixed; top: 0; left: 0; height: 100vh;
  overflow-y: auto; overscroll-behavior: contain; z-index: 100;
  transition: transform var(--dur-3) var(--ease-spring);
}
.sidebar-logo {
  padding: 20px 20px 16px;
}
.sidebar-logo .logo-mark {
  display: flex; align-items: center; gap: 10px;
}
.sidebar-logo .logo-icon {
  width: 32px; height: 32px;
  background: var(--accent-fill);
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 700; font-family: var(--font-head);
  color: var(--on-accent);
}
.sidebar-logo .logo-text {
  font-family: var(--font-head);
  font-size: 1.05rem; font-weight: 800;
  color: var(--txt1); line-height: 1.2;
}
.sidebar-logo .logo-text span { display: block; font-size: .72rem; font-weight: 400; color: var(--txt3); }
/* Logo SVG da empresa. Tema escuro (default): exibida em branco; tema claro: cores
   originais. A versão completa some quando a sidebar está recolhida; a reduzida aparece. */
.sidebar-logo .logo-img { display: block; max-width: 100%; max-height: 42px; height: auto; filter: none; }
.sidebar-logo .logo-mini { display: none; }
html[data-theme="dark"] .sidebar-logo .logo-img { filter: brightness(0) invert(1); }

.sidebar-nav { padding: 12px 0; flex: 1; }
.nav-group { margin-bottom: 4px; }
/* Rótulo de grupo em CAIXA NORMAL, não versalete: no produto é
   "Reuniões", "Recentes", "Particular" — texto pequeno e cinza, discreto. */
.nav-group-label {
  padding: var(--sp-3) var(--sp-3) var(--sp-1);
  font-size: 12px; font-weight: 500; color: var(--txt3);
}
/* Item da sidebar: linha compacta, arredondada dos QUATRO lados e recuada
   das bordas — no produto ela flutua dentro da coluna, não encosta nela. */
.nav-item {
  display: flex; align-items: center; gap: var(--sp-2);
  margin: 1px var(--sp-2);
  padding: 4px var(--sp-2);
  color: var(--txt2); font-size: 14px; line-height: 1.5;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background var(--dur-1) var(--ease-standard),
              transform var(--dur-1) var(--ease-standard);
}
/* Resposta ao toque: a linha recua sob o dedo e volta. É o feedback breve e
   preciso que o guia de movimento pede — 120ms, sem ricochete. */
.nav-item:active { transform: scale(.98); }
/* Ativo é CINZA, não azul, e sem barra lateral. O azul do produto é do botão
   primário; gastá-lo na navegação tira dele a função de indicar ação. */
.nav-item:hover  { background: var(--nav-hover); color: var(--txt1); }
.nav-item.active { background: var(--nav-hover); color: var(--txt1); font-weight: 600; }
.nav-item .icon  { width: 16px; height: 16px; opacity: .55; flex-shrink: 0; }
.nav-item.active .icon { opacity: .85; }

/* Só existe no celular (media query lá embaixo liga). Sem isto ela é uma
   <div> vazia a mais dentro do .app-wrapper flex — inofensiva, mas gratuita. */
.sidebar-backdrop { display: none; }

.sidebar-footer {
  padding: 14px 20px;
  border-top: 1px solid var(--border);
  font-size: .78rem; color: var(--txt3);
}
.sidebar-footer .user-info { font-weight: 500; color: var(--txt2); margin-bottom: 2px; }
.sidebar-footer a { color: var(--txt3); }
.sidebar-footer a:hover { color: var(--red); }

/* Main */
.main-content {
  margin-left: var(--sidebar-w);
  flex: 1;
  min-width: 0;           /* permite que tabelas largas rolem internamente em vez de estourar a página */
  display: flex; flex-direction: column;
  min-height: 100vh;
  transition: margin-left .2s ease;
}
/* Topo = TRILHA, não título. O título grande mora no `.view-header`, uma vez
   só — antes o mesmo texto aparecia duas vezes ("Pacientes" em 30px aqui e
   "Pacientes" logo abaixo), e as duas linhas comiam 76px antes do conteúdo.
   No Notion o topo é uma trilha de 14px em cinza; o nome grande é da página. */
/* MATERIAL, não cor chapada. A barra é uma camada que flutua sobre o conteúdo
   e o conteúdo passa POR BAIXO dela ao rolar — com fundo opaco ela era um
   retângulo colado; com o material, a cor do que está embaixo atravessa e a
   posição na página continua legível.
   Guia de Design — Materiais: "Use materials to create visual separation and
   hierarchy between UI layers … allowing color to pass through from background
   to foreground."
   `color-mix` e não rgba com hexadecimal: literal fora do :root reprova o
   test_cor_literal_so_no_bloco_de_tokens, e o token continua trocando com o
   tema. A hairline só aparece quando há conteúdo por baixo (`.scrolled`, posta
   pelo main.js) — é o "scroll edge effect" do sistema, e sem ele a barra
   desenha uma linha permanente no topo de página que nem rola. */
.topbar {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  padding: var(--sp-3) max(28px, env(safe-area-inset-right))
           var(--sp-2) max(28px, env(safe-area-inset-left));
  min-height: 44px;
  display: flex; align-items: center; justify-content: space-between;
  position: sticky; top: 0; z-index: 50;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-2) var(--ease-standard);
}
.topbar.scrolled { border-bottom-color: var(--border); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar { background: var(--bg); }
}
.topbar-trilha { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.topbar-title { font: var(--t-body-sm); font-weight: 500; color: var(--txt2); }
.topbar-sub   { font: var(--t-body-sm); color: var(--txt3); }
/* Separador da trilha, no CSS e não no HTML: some sozinho quando a tela não
   declara `page_sub`, sem `{% if %}` em todo template. */
.topbar-sub:not(:empty)::before { content: "/"; color: var(--txt3); margin-right: var(--sp-2); }
/* ☰ é recurso de celular: no desktop a sidebar não sai da tela. A media query
   lá embaixo o traz de volta. */
#menu-toggle { display: none; }
/* `env(safe-area-inset-*)` nas laterais também, não só embaixo: no iPhone
   deitado o entalhe come a primeira coluna da tabela, e a `.action-bar` já
   tratava só a faixa inferior. */
.page-body    { flex: 1;
                padding: 24px max(28px, env(safe-area-inset-right))
                         24px max(28px, env(safe-area-inset-left)); }

/* ── Cards ─────────────────────────────────────────────────── */
.card {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: var(--sp-4);
  box-shadow: var(--shadow-card);   /* = none: o card se apoia na hairline */
}
.card-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 16px;
}
.card-title { font-family: var(--font-head); font-size: .92rem; font-weight: 600; color: var(--txt1); }
.card-sub   { font-size: .75rem; color: var(--txt3); margin-top: 2px; }

/* KPI cards */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-4); margin-bottom: 20px; }
.kpi-card {
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius-card); padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow-card);
}
.kpi-label { font-size: 13px; font-weight: 400; color: var(--txt3); margin-bottom: var(--sp-1); }
.kpi-value { font-size: 28px; font-weight: 600; line-height: 1.2; color: var(--txt1); letter-spacing: -.02em; }
.kpi-value.green { color: var(--val-pos); }
.kpi-value.red   { color: var(--val-neg); }
.kpi-value.amber { color: var(--amber); }
.kpi-sub   { font-size: .72rem; color: var(--txt3); margin-top: 4px; }

/* ── Tabelas ────────────────────────────────────────────────── */
.table-wrap { overflow-x: auto; overscroll-behavior-x: contain; border-radius: var(--radius); }
/* Tabela longa: cabeçalho fixo ao rolar (livro, plano de contas) */
.table-scroll { overflow: auto; overscroll-behavior: contain; max-height: 64vh; border-radius: var(--radius); }
.table-scroll thead th { position: sticky; top: 0; z-index: 2; }
/* A base de dados do produto: SEM fundo de cabeçalho, SEM zebra. O que
   separa uma linha da outra é uma hairline e nada mais — é isso que faz a
   tabela parecer documento em vez de planilha. */
.data-table { font: var(--t-table); }
.data-table thead tr { background: transparent; }
.data-table th {
  padding: var(--sp-2) var(--sp-3); text-align: left;
  font: var(--t-caption); font-weight: 400;
  color: var(--txt3); background: transparent;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
.data-table td {
  background: var(--bg2);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  color: var(--txt1);
  vertical-align: middle;
}
@media (hover: hover) {
  .data-table tbody tr:hover td { background: var(--bg3); }
}
.data-table tbody tr:active td   { background: var(--bg4); }
.data-table tbody tr.selected td { background: var(--accent-glow); }
.data-table tbody tr:last-child td { border-bottom: none; }
/* EXCEÇÃO deliberada à família única: valor monetário e coluna numérica
   continuam monoespaçados — dígito de largura fixa alinha a coluna. `.mono`
   fica para dinheiro e CÓDIGO (número de lote, número de série, #id), que é
   onde a família de código informa alguma coisa. */
.data-table .mono { font-family: var(--font-mono); font-size: 13px; font-variant-numeric: tabular-nums;
                    white-space: nowrap; }
/* Data, hora, telefone e e-mail NÃO são código: o Notion escreve os quatro na
   fonte do texto, e em monoespaçada a lista de pacientes virava saída de
   terminal — três colunas de dígitos travados. `tabular-nums` alinha a coluna
   sozinho, sem trocar a família. */
.data-table .data { font-size: 13px; font-variant-numeric: tabular-nums;
                    white-space: nowrap; }
.data-table .num  { text-align: right; font-family: var(--font-mono); font-size: 13px;
                    white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Hairline vertical entre colunas — é o que a visão de tabela do Notion tem
   (print desktop 04) e o que separa "tabela" de "lista de texto solto" quando
   a tela é larga. Sem fundo de cabeçalho e sem zebra continuam valendo. */
.data-table td + td, .data-table th + th { border-left: 1px solid var(--border); }
.data-table .val-pos { color: var(--val-pos); }
.data-table .val-neg { color: var(--val-neg); }
.data-table .val-zero { color: var(--txt3); }
/* Coluna secundária esmaecida (códigos, datas) sem perder leitura */
.data-table td.dim { color: var(--txt3); }
.cal-grade {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--border);
  gap: 1px;   /* a hairline é o próprio fundo aparecendo entre as células */
}
.cal-dow {
  background: var(--bg2); padding: var(--sp-2) var(--sp-3);
  font-size: 12px; font-weight: 500; color: var(--txt3); text-align: center;
}
.cal-dia {
  background: var(--bg2); min-height: 104px;
  padding: var(--sp-1) var(--sp-2) var(--sp-2);
  display: flex; flex-direction: column; gap: 2px;
}
.cal-dia.fora { background: var(--bg3); }
.cal-dia.fora .cal-num { color: var(--txt4); }
.cal-num { font-size: 13px; color: var(--txt3); align-self: flex-end; }
.cal-dia.hoje .cal-num {
  background: var(--accent-fill); color: var(--on-accent);
  border-radius: var(--radius-pill); min-width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 600;
}
/* Evento: barra de cor à esquerda + rótulo. A cor identifica a natureza sem
   gastar o azul, que é do botão primário. */
.cal-ev {
  font-size: 12px; line-height: 1.4;
  padding: 1px var(--sp-1) 1px 6px;
  border: 0; border-left: 3px solid var(--txt4);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--bg3); color: var(--txt2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* O evento marcado é um <button> (abre o modal). `border: 0` acima zera a
   moldura que o browser dá ao botão; o resto alinha ao texto da célula. */
button.cal-ev {
  display: block; width: 100%; text-align: left; cursor: pointer;
  font-family: inherit;
}
button.cal-ev:hover { background: var(--nav-hover); color: var(--txt1); }
.cal-hora { color: var(--txt3); font-variant-numeric: tabular-nums; }
.cal-ev.tipo-aniversario  { border-left-color: var(--sticker-pink); }
.cal-ev.tipo-empresa      { border-left-color: var(--sticker-purple); }
.cal-ev.tipo-ferias       { border-left-color: var(--sticker-teal); }
.cal-ev.tipo-ausencia     { border-left-color: var(--amber); }
.cal-ev.tipo-desligamento { border-left-color: var(--red); }
.cal-ev.tipo-admissao     { border-left-color: var(--sticker-green); }
.cal-ev.tipo-feriado      { border-left-color: var(--sticker-orange); }
.cal-ev.tipo-treinamento  { border-left-color: var(--sticker-sky); }
.cal-ev.tipo-reuniao      { border-left-color: var(--sticker-sky); }
.cal-legenda {
  display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3);
}
.cal-legenda .cal-ev { white-space: nowrap; }
.org-wrap.arrastando { cursor: grabbing; }

/* ── Kanban ─────────────────────────────────────────────────
   O quadro do produto: coluna com FUNDO cinza arredondado, cabeçalho como
   chip de status com bolinha, e cartões brancos flutuando dentro dela. Antes
   isto era só `style=` inline nos templates, e por isso não parecia com nada. */
.kanban {
  display: flex; gap: var(--sp-3);
  overflow-x: auto; padding-bottom: var(--sp-2);
  align-items: flex-start;
}
.kanban-col {
  min-width: 260px; flex: 1;
  background: var(--bg3);
  border-radius: var(--radius-lg);
  padding: var(--sp-2);
}
.kanban-head .contagem { color: var(--txt3); font-variant-numeric: tabular-nums; }
.kanban-card .titulo { font-size: 14px; font-weight: 500; color: var(--txt1); }
.kanban-card .meta   { font-size: 12px; color: var(--txt3); margin-top: 2px; }
.kanban-card .acoes  { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
.kanban-col:empty, .kanban-col .vazio {
  color: var(--txt4); font-size: 13px; padding: var(--sp-2);
}

/* ── Badges ────────────────────────────────────────────────── */
/* Etiqueta do produto: retângulo de canto pequeno, NÃO pill. O pill é do CTA
   do site de marketing; aqui ele faria toda tabela parecer um painel de
   status. O seletor de atributo existe porque metade dos templates usa
   `class="badge-amber"` sem a classe base `badge`. */
.badge, [class^="badge-"], [class*=" badge-"] {
  display: inline-flex; align-items: center;
  padding: 2px var(--sp-2); border-radius: var(--radius-sm);
  font-size: 13px; font-weight: 400; line-height: 1.5;
  white-space: nowrap;
}
.badge-green  { background: var(--green-bg);  color: var(--green);  border: 1px solid var(--green-border); }
.badge-amber  { background: var(--amber-bg);  color: var(--amber);  border: 1px solid var(--amber-border); }
.badge-red    { background: var(--red-bg);    color: var(--red);    border: 1px solid var(--red-border); }
.badge-blue   { background: var(--accent-glow);color: var(--accent-txt);border: 1px solid var(--accent-border); }

/* Marcador discreto: lançamento importado que foi editado na plataforma. */
.edited-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--amber); margin-right: 6px; vertical-align: middle;
  outline: 2px solid var(--amber-bg); cursor: help;
}

/* ── Botões ────────────────────────────────────────────────── */
/* Botão do produto: 6px de canto, altura contida, peso 500. Nada de pill —
   aquilo é o CTA da landing page. */
.btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: 5px var(--sp-3); border-radius: var(--radius);
  font-size: 14px; font-weight: 500; line-height: 1.5; cursor: pointer;
  border: 1px solid transparent;
  transition: background var(--dur-1) var(--ease-standard),
              border-color var(--dur-1) var(--ease-standard),
              transform var(--dur-1) var(--ease-standard);
  white-space: nowrap;
}
/* Pressionar recua o botão 3%. Curto e preciso — o guia de movimento é
   explícito em não coreografar o que se toca cem vezes por dia. */
.btn:active { transform: scale(.97); }
.btn:disabled, .btn[disabled] { opacity: .5; cursor: not-allowed; transform: none; }
.btn-primary   { background: var(--btn-primary-bg); color: var(--btn-primary-fg); border-color: var(--btn-primary-bg); }
.btn-primary:hover  { background: var(--btn-primary-bg-h); border-color: var(--btn-primary-bg-h); }
.btn-primary:active { background: var(--accent-deepest); border-color: var(--accent-deepest); }
/* O botão comum do produto é BRANCO com hairline, não transparente: é ele
   que aparece em "Editar filtros", "Hoje", "Mês". */
.btn-ghost     { background: var(--bg2); color: var(--txt1); border-color: var(--border2); }
.btn-ghost:hover { background: var(--bg3); }
.btn-danger    { background: var(--red-bg);   color: var(--red);   border-color: var(--red-border); }
.btn-danger:hover  { background: var(--red);   color: var(--on-accent); border-color: var(--red); }
.btn-success   { background: var(--green-bg); color: var(--green); border-color: var(--green-border); }
.btn-success:hover { background: var(--green); color: var(--on-accent); border-color: var(--green); }
.btn-sm { padding: 3px var(--sp-2); font-size: 13px; }

/* ── Controle segmentado ───────────────────────────────────────
   O controle do sistema para "escolha UMA leitura entre estas": trilho
   rebaixado, e o segmento escolhido é uma lâmina branca que SOBE dentro dele.
   É o que o Calendário do Mac usa para `‹ Hoje ›` e para `Dia Semana Mês Ano` —
   e é exatamente a forma das abas deste sistema.

   Substitui o que estava lá: uma fileira de botões soltos com o corrente em
   `.btn-primary`. Aquilo pintava a aba atual com a cor de AÇÃO — o mesmo
   "acento em item de navegação" que o CLAUDE.md §3 lista entre os nuncas e que
   o `test_sidebar_ativa_nao_usa_o_azul` já cobra na sidebar. A aba corrente é
   estado, não ação: ela não faz nada quando clicada.

   Não é pill: o trilho usa --radius e o segmento --radius-sm. Pill aqui seria
   o CTA da página de vendas, que é a regra que separa este produto daquilo. */
.segmented {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 2px; background: var(--bg3);
  border: 1px solid var(--border); border-radius: var(--radius);
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  user-select: none;
}
.segmented::-webkit-scrollbar { display: none; }
.segmented > a, .segmented > button {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 4px var(--sp-3); border: none; background: none;
  border-radius: var(--radius-sm);
  font-size: 13px; font-weight: 500; line-height: 1.5; white-space: nowrap;
  color: var(--txt2); cursor: pointer; text-decoration: none;
  transition: background var(--dur-1) var(--ease-standard),
              color var(--dur-1) var(--ease-standard),
              transform var(--dur-1) var(--ease-standard);
}
.segmented > a:hover, .segmented > button:hover { color: var(--txt1); background: var(--bg4); }
.segmented > a:active, .segmented > button:active { transform: scale(.97); }
/* A lâmina que sobe. Sombra do TOKEN, e legítima: aqui ela é o que separa o
   segmento escolhido do trilho — é o caso "só no que flutua" do §3. */
.segmented > [aria-current] {
  background: var(--bg2); color: var(--txt1); font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.segmented > [aria-current]:hover { background: var(--bg2); }
/* Separador entre segmentos vizinhos não escolhidos — a hairline fininha que o
   controle do sistema desenha. Some ao lado da lâmina, dos dois lados. */
.segmented > * + *::before {
  content: ""; position: absolute; left: -1px; top: 20%; bottom: 20%;
  width: 1px; background: var(--border2);
}
.segmented > * { position: relative; }
.segmented > [aria-current]::before,
.segmented > [aria-current] + *::before { display: none; }
/* Alvo de toque: o controle do sistema tem 32px no telefone, mas aqui ele é a
   navegação principal de seis telas — checkbox errado numa evolução é dado
   clínico errado, e aba errada é a mesma classe de erro (§3). */
@media (pointer: coarse) {
  .segmented > a, .segmented > button { min-height: 44px; padding-inline: var(--sp-4); }
}
.btn-icon {
  padding: var(--sp-1); border-radius: var(--radius-sm);
  min-width: 28px; min-height: 28px; justify-content: center;
}
/* 44×44 é requisito de ALVO DE TOQUE (§3), então vale onde há dedo — que é o
   que `pointer: coarse` pergunta, sem chutar pela largura da tela. No desktop
   com mouse, 44px desalinhava o ícone de exportar dos `.btn-sm` de 28px ao
   lado dele no cabeçalho da view. */
@media (pointer: coarse) {
  .btn-icon { min-width: 44px; min-height: 44px; }
}
/* SVG cru sem width/height/classe não tem tamanho intrínseco nenhum — o
   viewBox sozinho não basta, e o botão inteiro encolhe pro tamanho do padding
   (10×10px, quase invisível). É o caso do #menu-toggle desde a Fase 0: o
   ícone "existia" no DOM e nunca apareceu. */
.btn-icon svg { width: 18px; height: 18px; }
/* Garantir que ícones soltos em botões não explodam para o tamanho da tela */
.btn .icon { width: 16px; height: 16px; flex-shrink: 0; }
svg.icon { width: 16px; height: 16px; flex-shrink: 0; display: inline-block; }

.form-group { display: flex; flex-direction: column; gap: var(--sp-1); }
/* Botão dentro de form-group NÃO estica. O grupo é uma coluna flex, então
   qualquer filho ocupa a largura toda — e um "Atribuir" de 400px atravessando
   a tela é o resultado. Vale para todo formulário, não só o de benefícios. */
.form-group > .btn { align-self: flex-start; }
/* Rótulo em caixa normal, não versalete: o produto usa texto pequeno e
   cinza, e o versalete é assinatura de painel administrativo. */
/* O `<label>` cru dentro de um grupo recebe o mesmo tratamento: metade dos
   templates nunca ganhou a classe, e sem isto eles ficam com a tipografia
   herdada do documento no meio de um formulário. */
.form-label, .form-group > label {
  font-size: 13px; font-weight: 500; color: var(--txt3);
}
.form-label.req::after { content: ' *'; color: var(--txt4); }
/* Dica abaixo do campo: explica a REGRA, não repete o rótulo. Fica no fluxo
   do .form-group (que é flex em coluna), então não precisa de margem própria. */
.form-hint {
  font-size: 12px; line-height: 1.35; color: var(--txt3);
}
.form-control {
  background: var(--bg3); border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 5px var(--sp-2);
  color: var(--txt1); font-size: 14px; line-height: 1.5;
  transition: border-color .12s, background .12s;
  width: 100%;
}
/* Campo do produto: fundo cinza sem borda em repouso; a borda azul só
   aparece no foco. Borda permanente em toda tela faz o formulário virar
   grade. */
.form-control:focus { outline: none; background: var(--bg2);
                      border-color: var(--accent); box-shadow: none; }
/* A borda sozinha é pista de COR, e o §3 proíbe cor sozinha informando. Quem
   chega por teclado ganha o anel; quem clicou com o mouse continua vendo só a
   borda, que é o desenho do produto. */
.form-control:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--accent-txt) 80%, transparent);
  outline-offset: 1px;
}
.form-control::placeholder { color: var(--txt4); }
select.form-control { cursor: pointer; }
textarea.form-control { resize: vertical; min-height: 72px; }
/* Campo derivado (só visualização): precisa PARECER não-editável, senão o
   usuário tenta digitar e acha que quebrou. */
.form-control[readonly] {
  background: var(--bg4);
  background-image: repeating-linear-gradient(
    45deg,
    transparent,
    transparent 10px,
    var(--bg3) 10px,
    var(--bg3) 20px
  );
  color: var(--txt3); 
  cursor: not-allowed;
  opacity: 0.85;
}
.form-control[readonly]:focus { border-color: transparent; }

/* Linha de campos. `.form-row` é usado em 20 templates e NÃO tinha regra
   nenhuma: sem ela o div é bloco comum, cada grupo empilha e cada campo herda
   o `width: 100%` acima — é daí que vem o campo de data ocupando a largura
   inteira do card em holerites, benefícios, ausências e férias. */
.form-row {
  display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: flex-start;
}
.form-row > .form-group { flex: 1 1 180px; min-width: 0; }
/* Campo de largura intrínseca não estica: um seletor de mês tem dez
   caracteres, e 100% dele é um alvo de 900px para acertar um dropdown. */
.form-control[type="date"], .form-control[type="month"],
.form-control[type="time"], .form-control[type="number"],
.form-control[type="file"] { width: auto; max-width: 100%; }

.btn-stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.btn-stack .btn { width: 100%; justify-content: center; }

@media (min-width: 600px) {
  .btn-stack {
    flex-direction: row;
    flex-wrap: wrap;
  }
  .btn-stack > * {
    flex: 1 1 200px;
    margin: 0; /* Reset margins if any */
  }
}
/* Bloco de conteúdo curto (formulário de três campos, comparação de duas
   colunas). Vale por bloco e não pela página: a tabela de colaboradores precisa
   da tela toda, e um teto global tiraria dela. */
.narrow { max-width: 760px; }
/* Dentro do modal de rota o `.narrow` volta a caber sozinho: o modal já é a
   largura contida (`.modal-lg`, 900px), então os 760px do `.narrow` sobram
   como uma faixa de conteúdo mais estreita que o próprio modal — e o rodapé
   (que NÃO é `.narrow`, é `.modal-footer` cheio) alinha o Salvar pela borda
   do modal, não pela borda dos cards. O botão parece flutuar 80px à direita
   do conteúdo. Sem teto aqui, cards e rodapé terminam na mesma borda. */
#route-modal-body .narrow { max-width: none; }
.filter-bar .form-control { width: auto; min-width: 130px; }

/* ── Modal ─────────────────────────────────────────────────── */
.modal-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.45);   /* scrim funcional do diálogo (única exceção ao "sem alpha") */
  z-index: 200; display: flex; justify-content: center;
  /* FOLHA DO SISTEMA: nasce presa ao alto da janela, não centralizada na
     vertical. É a diferença mais reconhecível entre um diálogo de aplicativo
     de mesa e um `<dialog>` de página web — e é útil, porque o formulário
     cresce para baixo em vez de escorregar para os dois lados quando ganha
     um campo. O `clamp` mantém a folga proporcional sem colar no topo em tela
     baixa. */
  align-items: flex-start;
  padding: clamp(20px, 7vh, 64px) var(--sp-4) var(--sp-4);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-standard);
}
.modal-overlay.open { opacity: 1; pointer-events: all; }
.modal {
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); width: 100%; max-width: 620px;
  /* 100% e não 90vh: o overlay agora tem preenchimento próprio, e a soma de
     90vh + a folga do topo estourava a tela. A porcentagem resolve contra a
     caixa de conteúdo do overlay, que já desconta o preenchimento. */
  max-height: 100%; overflow: hidden; display: flex; flex-direction: column;
  box-shadow: var(--shadow-md);   /* o que flutua ganha elevação, o card não */
  /* Entrada de folha: DESCE 14px e cresce de 98% — a folha do sistema sai de
     debaixo da barra de título, então o movimento é para baixo, não para cima.
     O par translação+escala é o que faz a camada parecer vir de trás do
     conteúdo em vez de deslizar por cima. Curva do sistema, sem ricochete. */
  transform: translateY(-14px) scale(.98);
  transition: transform var(--dur-3) var(--ease-spring);
}
.modal-overlay.open .modal { transform: translateY(0) scale(1); }
/* Registro completo (atendimento, paciente...) precisa de mais que 620px —
   é a página inteira do celular, só que centralizada. */
.modal-lg { max-width: 900px; }
/* O modal já tem título e X no cabeçalho — o .view-header da própria página
   (título de novo + "Voltar") ficaria repetido e o "Voltar" sem função
   nenhuma aqui dentro. */
#route-modal-body > .view-header { display: none; }
.modal-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 22px; border-bottom: 1px solid var(--border);
  position: sticky; top: 0; background: var(--bg2); z-index: 1;
}
.modal-title  { font-family: var(--font-head); font-size: 1rem; font-weight: 600; }
.modal-close  { background: none; border: none; color: var(--txt3); cursor: pointer;
                font-size: 1.3rem; line-height: 1; padding: 2px 6px; }
.modal-close:hover { color: var(--txt1); }

/* --- SPINNER DE CARREGAMENTO --- */
.loading-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: gf-spin 0.75s linear infinite;
  vertical-align: text-bottom;
  margin-right: 6px;
}
@keyframes gf-spin { to { transform: rotate(360deg); } }
/* `overscroll-behavior: contain` corta o encadeamento de rolagem: sem ele, ao
   chegar no fim do corpo do modal o gesto continua e passa a rolar a PÁGINA
   atrás — no telefone o diálogo parece estar deslizando junto com o fundo. */
.modal-body   { padding: 22px; overflow-y: auto; overscroll-behavior: contain; flex: 1; }
.modal-footer { padding: 14px 22px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 10px; }

/* ── Alertas / Flash ────────────────────────────────────────── */
.flash-container {
  position: fixed; z-index: 300;
  top: calc(var(--topbar-h) + var(--sp-3));
  right: max(20px, env(safe-area-inset-right));
  left: max(20px, env(safe-area-inset-left));
  display: flex; flex-direction: column; align-items: flex-end;
  gap: var(--sp-2); pointer-events: none;
}
.flash {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 16px; border-radius: var(--radius);
  font-size: .82rem; font-weight: 500;
  animation: slideIn var(--dur-3) var(--ease-spring);
  pointer-events: auto; max-width: 360px;
  box-shadow: var(--shadow-md);   /* flutua sobre o conteúdo → leva elevação */
}
/* O aviso era um `<div onclick="this.remove()">`: sem foco, sem papel e sem
   como fechar pelo teclado. Agora o botão é o alvo — e o erro deixou de sumir
   sozinho em 5s (main.js), porque erro que se apaga antes de ser lido é o
   mesmo que não ter avisado.
   Guia de Design — Retorno: "Show people when a command can't be carried out
   and help them understand why." */
.flash-close {
  flex: none; margin: -4px -6px -4px auto; padding: 4px 6px;
  background: none; border: none; color: inherit; opacity: .6;
  font-size: 1.1rem; line-height: 1; cursor: pointer; border-radius: var(--radius-sm);
}
.flash-close:hover { opacity: 1; }
@media (pointer: coarse) { .flash-close { min-width: 44px; min-height: 44px; } }
/* A categoria vem do `flash(msg, cat)` do Python e o template monta
   `flash-{{ cat }}` — a classe NÃO aparece literal em lugar nenhum. Um scan de
   "classe sem uso" a marca como morta e o aviso sai sem cor.
   Produzidas hoje: success (46 chamadas), error (45), warning (1); mais
   `danger`, que só o auth/_publico.html gera, mapeando error. */
.flash-success { background: var(--green-bg); border: 1px solid var(--green-border); color: var(--green); }
.flash-danger,
.flash-error   { background: var(--red-bg);   border: 1px solid var(--red-border);   color: var(--red); }
.flash-warning { background: var(--amber-bg); border: 1px solid var(--amber-border); color: var(--amber); }
@keyframes slideIn { from { opacity:0; transform: translateX(20px); } to { opacity:1; transform: translateX(0); } }

/* ── Barra do botão de exportar (injetado por main.js) ────────
   Sem PDF na tela pra ancorar ao lado, o botão nasce numa div solta ANTES da
   tabela — sem esta regra ela é bloco padrão, o botão cola na esquerda e some
   no meio da página, longe de qualquer outra ação. */
.gf-export-bar { display: flex; justify-content: flex-end; margin-bottom: var(--sp-2); }

/* ── Login ──────────────────────────────────────────────────── */
.login-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: var(--bg); padding: 20px; }
.login-box {
  width: 100%; max-width: 400px;
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 40px;
  box-shadow: var(--shadow);
}
.login-logo { text-align: center; margin-bottom: 28px; }
.login-logo .icon-wrap {
  width: 52px; height: 52px; background: var(--accent-fill); border-radius: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 800; font-family: var(--font-head); color: var(--on-accent);
  margin-bottom: 12px;
}
.login-logo h1 {
  font-family: var(--font-head); font-size: 1.3rem; font-weight: 700;
  line-height: 1.4; letter-spacing: var(--t-title-ls);
}
.login-logo p  { font-size: .80rem; color: var(--txt3); margin-top: 4px; }
.login-footer  { text-align: center; margin-top: 20px; font-size: .75rem; color: var(--txt3); }
.login-footer code { background: var(--bg3); padding: 1px 5px; border-radius: 4px; font-family: var(--font-mono); }
@keyframes gfFade { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
.muted      { color: var(--txt3); }
.data-table tr.row-pendente td { background: var(--red-bg); }
.dre-matriz thead .col-fixa { color: var(--txt2); font-style: italic; }
.font-mono  { font-family: var(--font-mono); }
.page-header .lead { font-size: .84rem; color: var(--txt2); margin-top: 3px; max-width: 70ch; }

/* Caixa de ajuda / instruções */
.help-box {
  background: var(--bg3); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px 16px;
  font-size: .82rem; color: var(--txt2); line-height: 1.6;
}
.help-box strong { color: var(--txt1); }
.help-box code {
  font-family: var(--font-mono); font-size: .78rem;
  background: var(--bg); padding: 1px 6px; border-radius: 4px; color: var(--txt1);
}
/* Sem VERSALETE. O CLAUDE.md §3 lista "versalete em rótulo" entre os nuncas, e
   o guia de escrita da HIG diz a mesma coisa: rótulo de interface é caixa de
   sentença, não caixa alta — ALLCAPS derruba a velocidade de leitura porque
   apaga a silhueta da palavra. Era a única regra do arquivo que ainda fazia
   isso. */
.stat .k { font: var(--t-caption); font-weight: 500; color: var(--txt3); }
.stat .v { font-family: var(--font-mono); font-size: 1.15rem; font-weight: 600; color: var(--txt1); margin-top: 4px; font-variant-numeric: tabular-nums; }
.stat .v.pos { color: var(--green); } .stat .v.neg { color: var(--red); }

/* Evolução acumulada — acordeão por dia, compartilhado entre a ficha da
   paciente e a tela do atendimento (templates/_evolucao_acumulada.html). */
.evolucao-lista {
  max-height: 600px; overflow-y: auto; overscroll-behavior: contain; background: var(--bg3);
  padding: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2);
  border-radius: var(--radius-sm);
}
.evolucao-item {
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-3);
}
.evolucao-item > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; justify-content: space-between;
  border-radius: var(--radius-sm);
}
.evolucao-item > summary::-webkit-details-marker { display: none; }
.evolucao-item[open] .evolucao-icon { transform: rotate(180deg); }
.evolucao-icon { transition: transform .2s ease; display: inline-block; }
.evolucao-resumo {
  display: flex; flex-direction: column; flex: 1; overflow: hidden;
  white-space: nowrap; padding-right: var(--sp-3);
}
.evolucao-preview { overflow: hidden; text-overflow: ellipsis; font-size: .9em; margin-top: 4px; }
.evolucao-meta { display: flex; gap: var(--sp-3); align-items: center; color: var(--txt3); }
.evolucao-corpo { margin-top: var(--sp-3); border-top: 1px dashed var(--border); padding-top: var(--sp-3); }
.evolucao-anexos { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.evolucao-texto { white-space: pre-wrap; font-size: .95em; color: var(--txt1); }
@media (max-width: 840px) {
  /* A lista solta a rolagem interna e passa a fluir com a página: rolagem
     dentro de rolagem é onde o polegar distraído erra repetidas vezes
     (CLAUDE.md §3), bem no ponto de maior risco clínico do produto. */
  .evolucao-lista { max-height: none; overflow-y: visible; }
}

/* Chips de informação (regime, apuração, etc.) */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 20px;
  background: var(--bg3); border: 1px solid var(--border);
  font-size: .74rem; font-weight: 500; color: var(--txt2);
}
.chip.accent { background: var(--accent-glow); border-color: var(--accent-border); color: var(--accent-txt); }
.chip:hover { border-color: var(--txt3); }
.chip.accent:hover { border-color: var(--accent-txt); }

/* Preferências — categorias à esquerda, formulário à direita, como o painel
   de Configurações do Notion (print desktop 05/06). Um `.card` só, dividido
   por hairline interna — duas colunas seriam dois cards, e o contrato de
   card não tem "meio card". */
.config-layout {
  display: grid; grid-template-columns: 220px 1fr;
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); overflow: hidden; align-items: start;
}
.config-nav { border-right: 1px solid var(--border); padding: var(--sp-3) 0; }
.config-nav a {
  display: block; padding: 10px var(--sp-4); font: var(--t-body-sm);
  color: var(--txt2); border-left: 3px solid transparent;
  transition: background var(--dur-1) var(--ease-standard);
}
.config-nav a:hover { background: var(--bg3); }
/* Ativo é FUNDO CINZA, como no painel de configurações do Notion (print
   desktop 05) — não azul. Azul é ação; item de navegação selecionado é o
   mesmo "Nunca" que a fileira de atalhos da Início quebrava. */
.config-nav a.active { color: var(--txt1); background: var(--bg3); font-weight: 500; }
.config-content { padding: var(--sp-5); min-width: 0; }
.config-content h2 { font: var(--t-h3); margin-bottom: 4px; }
.config-content .sub { color: var(--txt3); font: var(--t-body-sm); margin-bottom: var(--sp-5); }
@media (max-width: 840px) {
  .config-layout { grid-template-columns: 1fr; }
  .config-nav { display: flex; border-right: none; border-bottom: 1px solid var(--border);
                overflow-x: auto; padding: var(--sp-2); gap: var(--sp-1); }
  .config-nav a { border-left: none; border-radius: var(--radius); flex: none; }
  .config-nav a.active { border-left: none; }
}

/* Layout de duas colunas que colapsa no mobile.
   minmax(0,...) e não só fr: track de grid sem isso não encolhe além do
   min-content do que tem dentro (uma tabela larga), e a página inteira
   ganha rolagem horizontal numa janela estreita — exatamente o que o
   .table-wrap existe para evitar. */
.split-2 { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 16px; align-items: start; }
.pill { padding: 5px 14px; border-radius: 18px; font-size: .78rem; color: var(--txt2); cursor: pointer; white-space: nowrap;
        transition: background var(--dur-1) var(--ease-standard), color var(--dur-1) var(--ease-standard); }
.pill:hover { color: var(--txt1); }
/* Branco sobre --accent puro é 3,25:1 — reprova. --accent-fill é o mesmo tom
   dois degraus abaixo, 5,46:1 com o mesmo branco. */
.pill.active { background: var(--accent-fill); color: var(--on-accent); font-weight: 600; }
.issue.erro  { background: var(--red-bg);   border: 1px solid var(--red-border); }
.issue.aviso { background: var(--amber-bg); border: 1px solid var(--amber-border); }

/* ── Tema escuro (slider) — chapado, mesmo espírito Ventriloc ─────
   Carbon escuro, sem navy, sem sombra, sem alpha decorativo. */
html[data-theme="dark"] {
  --bg: #1c1a19;
  --bg2: #232120;
  --bg3: #2d2b29;
  --bg4: #383533;
  --border: #363331;
  --border2: #4a4643;
  --txt1: #ededed;
  --txt2: #b3b3b3;
  --txt3: #8a8a8a;
  /* #666666 dava 2,79:1 contra o card escuro. #7a7a7a sobe para 3,74:1 — a
     mesma faixa "fantasma" do tema claro, agora acima de 3:1. */
  --txt4: #7a7a7a;
  --accent: #b58375;
  --accent-h: #d19a8a;
  /* No carbono o acento como TEXTO inverte de direção: o degrau escuro
     (#8f5d50, que resolve no papel) some no fundo. #d19a8a → 6,64:1 no card
     escuro; o hover sobe mais um degrau. --accent-fill NÃO inverte: o que ele
     carrega é --on-accent (branco) nos dois temas, e o par branco/#8f5d50 já
     é 5,46:1. */
  --accent-txt:   #d19a8a;
  --accent-txt-h: #e5b5a6;
  --accent-deep: #8f5d50;
  --accent-deep-h: #7a4b40;
  --accent-glow: #382d2a;
  --accent-border: #4a3834;
  --on-accent: #ffffff;
  --nav-bg: #1c1a19;
  --nav-hover: #232120;
  --zebra: #211f1e;
  --warm: #7d7d7d;
  --green: #a8d5b5; --green-bg: #2b3a30; --green-border: #2b3a30;
  --red: #f0b4ad; --red-bg: #3f2a28; --red-border: #3f2a28;
  --amber: #f0d5a8; --amber-bg: #3b3121; --amber-border: #3b3121;
  --val-pos: #6cc48c;
  --val-neg: #e88b80;
  color-scheme: dark;
}
.icon-tema.moon { display: none; }
html[data-theme="dark"] .icon-tema.sun  { display: none; }
html[data-theme="dark"] .icon-tema.moon { display: block; }

/* ── Cabeçalho de coluna ordenável (Livro) ──────────────────── */
.data-table th.sortable { padding: 0; }
.data-table th.sortable a {
  display: flex; align-items: center; gap: 5px;
  padding: 11px 14px; color: inherit;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
}
.data-table th.sortable.text-right a { justify-content: flex-end; }
.data-table th.sortable a:hover { color: var(--accent-txt); background: var(--bg4); }
.data-table th.sortable .sort-ind { font-size: .9em; opacity: .35; }
.data-table th.sortable[aria-sort] .sort-ind { opacity: 1; color: var(--accent-txt); }
html.sidebar-collapsed .sidebar-collapse svg { transform: rotate(180deg); }

html.sidebar-collapsed .sidebar { width: var(--sidebar-wc); }
html.sidebar-collapsed .main-content { margin-left: var(--sidebar-wc); }
html.sidebar-collapsed .sidebar-logo { padding: 18px 0; text-align: center; }
html.sidebar-collapsed .sidebar-logo .logo-mark { justify-content: center; }
html.sidebar-collapsed .sidebar-logo .logo-full { display: none; }
html.sidebar-collapsed .sidebar-logo .logo-mini { display: block; max-height: 34px; margin: 0 auto; }
html.sidebar-collapsed .logo-text,
html.sidebar-collapsed .nav-group-label,
html.sidebar-collapsed .sidebar-footer .user-info,
html.sidebar-collapsed .sidebar-footer .user-email { display: none; }
html.sidebar-collapsed .nav-item {
  justify-content: center; gap: 0; padding: 10px 0; font-size: 0;
  border-left-width: 3px;
}
html.sidebar-collapsed .nav-item .icon { width: 19px; opacity: .9; }
html.sidebar-collapsed .sidebar-footer { padding: 12px 0; text-align: center; }
html.sidebar-collapsed .sidebar-footer a { font-size: 0; }
html.sidebar-collapsed .sidebar-footer a::after { content: '↩'; font-size: .95rem; }

/* ── Responsive ─────────────────────────────────────────────── */
@media (max-width: 840px) {   /* no mobile a sidebar é overlay (☰) */
  #menu-toggle { display: inline-flex; } /* e aqui o ☰ é a única forma de abrir */
  /* Nada de `html.sidebar-collapsed .sidebar` aqui: a regra tinha
     especificidade maior que a `.sidebar` da folha abaixo e vencia por isso,
     não por ordem — quem tivesse colapsado a sidebar no desktop via a folha
     inferior sair com 250px, encolhida no canto. O bloco abaixo já redefine a
     `.sidebar` inteira, e `.main-content { margin-left: 0 }` mais adiante
     cobre os dois casos. */

  /* Menu vem de BAIXO pra cima — folha inferior, não painel da esquerda.
     Mesmo mecanismo do .modal-overlay.sheet: fechado e aberto declarados, a
     `transition: transform` que .sidebar já tem faz a animação sozinha. */
  .sidebar {
    top: auto; bottom: 0; left: 0; right: 0;
    width: 100%; height: auto; max-height: 80vh;
    border-right: none; border-top: 1px solid var(--border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    transform: translateY(100%);
  }
  .sidebar.open { transform: translateY(0); }
  /* Alça de arrastar — o que diz "isto é uma folha", sem SVG nem imagem. */
  .sidebar::before {
    content: ""; order: -1; flex-shrink: 0;
    width: 36px; height: 4px; margin: 10px auto 2px;
    border-radius: var(--radius-pill); background: var(--border2);
  }
  .sidebar-logo { text-align: center; }
  .sidebar-logo .logo-mark { justify-content: center; }

  .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 99;
    background: rgba(0,0,0,.45);
    opacity: 0; pointer-events: none; transition: opacity .25s ease;
  }
  .sidebar-backdrop.open { opacity: 1; pointer-events: all; }

  .main-content { margin-left: 0; }
  .kpi-grid { grid-template-columns: 1fr 1fr; }
  .page-body { padding: 16px; }
  .split-2 { grid-template-columns: 1fr; }
}

/* Valores em Space Grotesk (geométrico, tracking apertado) — coeso com os KPIs. */
.stat .v { font-family: var(--font-display); letter-spacing: -.02em; }
/* A regra `.topbar { background: var(--bg) }` que morava aqui foi REMOVIDA:
   por vir depois, ela vencia por ordem e desfazia o material declarado lá em
   cima — a barra voltava a ser um retângulo opaco e o `backdrop-filter`
   ficava aplicado a um fundo sem transparência, isto é, sem efeito nenhum.
   O `@supports` junto da regra original já cobre o navegador que não tem
   `backdrop-filter`. */

/* Overlay anti-glitch do PDF (cor RESOLVIDA inline — não muda com o tema) */
#gfPdfOverlay {
  position: fixed; inset: 0; z-index: 99999; display: flex;
  align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 1.05rem;
}
.tema-fonte-op.sel { border-color: var(--accent); background: var(--accent-glow); }

/* O bloco do "Estúdio" (editor de apresentação) do Sistema RH foi REMOVIDO:
   o Harmis não tem essa tela, e CSS de tela inexistente é peso morto que a
   próxima pessoa tenta entender. Uma das regras dele (.est-pres-canvas com
   width: 1080px) reprovava test_nenhuma_largura_fixa_maior_que_a_tela_pequena.
   Registrado em SINCRONIZAR.md como divergência deliberada. */


/* Impressão / "Imprimir / PDF" (Salvar como PDF do navegador) — some o cromo,
   achata a grade em pilha imprimível. */
@media print {
  @page { size: A4 portrait; margin: 12mm; }

  /* ── Papel é sempre claro ────────────────────────────────────────────────
     A paleta clara é imposta AQUI, e não trocando `data-theme` no JS: mexer no
     atributo repintava a página inteira atrás do diálogo de impressão, e quem
     estava no modo escuro via a tela "estourar" em branco. O @media print só
     vale para a mídia impressa, então a tela fica intacta. */
  html[data-theme="dark"] {
    --bg: #ECEEF1; --bg2: #FFFFFF; --bg3: #EFF2F4; --bg4: #E0E5EA;
    --border: #DAE0E6; --border2: #C6CED5;
    --txt1: #1C1C1C; --txt2: #4D4D4D; --txt3: #828282;
    --accent: #0C5D7D; --accent-h: #2E94B5;
    --green: #0E9E66; --green-bg: #E4F4EC; --green-border: #C9E7D6;
    --red: #D23B3B; --red-bg: #F8E7E7; --red-border: #EFCFCF;
    --amber: #B5790F; --amber-bg: #F6EEDD; --amber-border: #E7D6AE;
    color-scheme: light;
  }
  html[data-theme="dark"] .data-table tbody tr:nth-child(even) td { background: #F7F8F9; }
  .sidebar, .topbar, .action-bar, .flash-container { display: none !important; }
  .main-content { margin-left: 0 !important; }
  .page-body { padding: 0 !important; }
  body, .app-wrapper, .main-content { background: #fff !important; }

  /* ── Relatório impresso: só os dados ─────────────────────────────────────
     A altura limitada que existe na TELA não pode cortar o DOCUMENTO. Esta é a
     regra que conserta o PDF cortado — o resto é limpeza de interface. */
  .table-wrap, .table-scroll {
    max-height: none !important;
    overflow: visible !important;
    height: auto !important;
  }
  thead { display: table-header-group; }   /* cabeçalho repete em toda página */
  tfoot { display: table-footer-group; }
  tr { break-inside: avoid; }
  .data-table { font-size: 9pt; }
  .data-table th, .data-table td { padding: 3px 6px; }

  /* Interface fora do documento. */
  .gf-pdf-skip, .btn, .kpi-card, .kpi-grid,
  .sidebar-footer, .modal-overlay { display: none !important; }

  .print-header { display: block !important; margin-bottom: 10px; }
  .print-header h1 { font-size: 14pt; margin: 0 0 2px; line-height: 1.4; letter-spacing: normal; }
  .print-header .meta { font-size: 9pt; color: #444; }
}

/* Item de menu bloqueado por licença (upsell) */
.nav-item.nav-locked { opacity: .55; }
.nav-item.nav-locked .lock { margin-left: auto; font-size: .85em; }

/* ═══════════════════════════════════════════════════════════════
   CELULAR — primitivos do Harmis
   ═══════════════════════════════════════════════════════════════
   O Sistema RH é desktop-first: tinha UMA media query, que só virava a sidebar
   em overlay e colapsava as grades. Isto aqui é o que faltava, e existe porque
   o profissional atende de pé, com o telefone na mão — se a ficha não abrir
   bem, ele volta para o papel (CLAUDE.md §3, spec §4.10).

   Nenhuma cor literal abaixo: só token. É o que
   test_cor_literal_so_no_bloco_de_tokens cobra, e é o que faz o tema escuro
   valer aqui de graça.
   ─────────────────────────────────────────────────────────────── */

/* Alvo de toque. 44×44 é requisito FUNCIONAL, não estético: checkbox errado
   numa evolução é dado clínico errado. Vale no ponteiro grosseiro (dedo), não
   na largura da tela — tablet com mouse não precisa engordar tudo. */
@media (pointer: coarse) {
  .btn, .nav-item, .prop-row,
  .form-control, select, textarea { min-height: 44px; }
  /* O campo de texto ficou de fora na primeira versão desta regra e mediu 33px
     no aparelho — o botão passava, o campo não. Alvo pequeno num campo de
     evolução clínica é dado errado, não incômodo. */
  /* O × do modal também ficou de fora, e mediu 26×25px em toda folha inferior
     do sistema desde a Fase 0 — inclusive na de remover foto do prontuário,
     onde errar o toque é o pior lugar para errar. Não é `.btn`, então nenhuma
     linha acima o alcançava. Só apareceu ao medir no aparelho: no HTML ele
     está lá, e a tela responde 200. */
  .modal-close { min-height: 44px; min-width: 44px; }
  .btn + .btn { margin-left: var(--sp-2); }   /* 8px entre cliques */
}

/* ── Barra de ação inferior ────────────────────────────────────
   A ação da tela ao alcance do polegar. NÃO é navegação, e NÃO recebe ação
   destrutiva: excluir e cancelar moram atrás de um toque a mais, porque o
   polegar erra. env(safe-area-inset-bottom) é a faixa do iPhone — sem ela o
   botão fica embaixo da barra do sistema e não recebe o toque. */
/* Par do `.action-bar`: a barra inferior é só do celular, então a tela de
   página inteira (não a que abre em modal) precisa da MESMA ação visível no
   desktop, ou o formulário fica sem botão de gravar. Os dois nunca aparecem
   juntos — um é o negativo do outro. */
.so-desktop { display: inline-flex; }
@media (max-width: 840px) { .so-desktop { display: none; } }

.action-bar { display: none; }
@media (max-width: 840px) {
  .action-bar {
    display: flex; gap: var(--sp-2); align-items: center;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    padding: var(--sp-2) max(var(--sp-4), env(safe-area-inset-right))
             var(--sp-2) max(var(--sp-4), env(safe-area-inset-left));
    padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom, 0px));
    /* Mesmo material da topbar: a lista continua visível por baixo da barra,
       então dá para ver que ela está rolando e não travada. */
    background: color-mix(in srgb, var(--bg2) 82%, transparent);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    border-top: 1px solid var(--border2);
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .action-bar { background: var(--bg2); }
  }
  .action-bar .btn { flex: 1; justify-content: center; }
  /* Espaço para a barra não cobrir o fim do conteúdo. */
  .has-action-bar .page-body { padding-bottom: 96px; }
}

/* ── Folha inferior (bottom sheet) ─────────────────────────────
   Filtro, ordenação e configuração. O .modal centralizado de 620px não serve no
   telefone: nasce no meio da tela, longe do polegar, e o teclado virtual o
   empurra para fora. Mesmo markup do modal — só muda como entra. */
@media (max-width: 840px) {
  /* `padding: 0` obrigatório: o preenchimento que ancora a folha no alto no
     computador viraria uma faixa vazia ENTRE a folha e o rodapé do telefone —
     e folha inferior descolada da borda não é folha inferior. */
  .modal-overlay.sheet { align-items: flex-end; padding: 0; }
  /* A entrada de baixo usa a `transition: transform` que o .modal já tem — a
     primeira versão daqui trouxe um @keyframes, e ele ficava aplicando
     translateY(100%) fora de hora: a folha abria 156px abaixo do rodapé.
     Estado fechado e estado aberto declarados, e o resto é o que já existia. */
  .modal-overlay.sheet .modal {
    width: 100%; max-width: 100%; max-height: 88vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    transform: translateY(100%);
  }
  /* O modal comum no telefone volta a caber inteiro: sem o `.sheet` ele herda
     o `max-height: 100%` do computador, que aqui já desconta o preenchimento
     do overlay. */
  .modal-overlay { padding: var(--sp-3); }
  .modal-overlay.sheet.open .modal { transform: none; }
  .modal-overlay.sheet .modal-body { overflow-y: auto; }
  .modal-overlay.sheet .modal-footer {
    padding-bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  }
}
.sheet-row .valor { margin-left: auto; color: var(--txt3); }
.sheet-row .chevron { color: var(--txt4); }

/* ── Tabela: rola na horizontal, coluna-âncora fixa ────────────
   A tabela NÃO vira pilha de cards. Pilha de card destrói a comparação entre
   linhas, que é para o que a tabela existe — a escolha é a do Notion.
   .table-wrap já existe e faz o overflow; o que falta é congelar a âncora.

   Toda .data-table precisa estar dentro de .table-wrap. Sem isso a tabela
   estoura a viewport e a PÁGINA inteira rola de lado — não dá erro em lugar
   nenhum, só fica quebrada no telefone de quem usa. test_design_contrato cobra. */
.data-table td.ancora, .data-table th.ancora {
  position: sticky; left: 0; z-index: 3;
  background: var(--bg2); border-right: 1px solid var(--border2);
}
.data-table tbody tr:hover td.ancora { background: var(--bg3); }
/* O NOME abre o registro, e a coluna de botão "Abrir" deixa de existir. Texto
   escuro normal, não azul: uma tabela de 40 linhas com todos os nomes em azul
   vira uma parede de acento e tira do azul a função de indicar ação (§3). É o
   que o Notion faz — o nome da linha é texto, e o sublinhado aparece no hover.
   O alvo de toque é a célula inteira, não só as letras. */
.data-table a.registro {
  display: block; color: var(--txt1); font-weight: 500;
  margin: -6px -10px; padding: 6px 10px;
}
.data-table a.registro:hover { color: var(--txt1); text-decoration: underline; }
@media (max-width: 840px) {
  .data-table td.ancora, .data-table th.ancora { max-width: 55vw; }
  .data-table td.ancora .nome {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
    line-height: 1.3;
  }
}

/* ── Registro como página, não modal ───────────────────────────
   Título grande no topo, propriedades em lista rótulo→valor com ícone à
   esquerda. Formulário de duas colunas em 390px vira duas colunas de nada. */
/* AGRUPADA, como a lista de Ajustes do sistema: uma caixa arredondada com as
   linhas separadas por hairline interna, e não linhas soltas flutuando no
   branco. É a mesma forma do `.config-layout` (que já era esse padrão) e é o
   que dá à ficha do registro a leitura de "propriedades deste item", em vez de
   parágrafos empilhados.
   A hairline entre linhas substitui o `gap: 2px` — no controle do sistema as
   linhas se tocam. */
.prop-list {
  display: flex; flex-direction: column; margin: var(--sp-4) 0;
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); overflow: hidden;
}
.prop-row {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  transition: background var(--dur-1) var(--ease-standard);
}
.prop-row + .prop-row { border-top: 1px solid var(--border); }
.prop-row:hover { background: var(--bg3); }
.prop-row .rotulo {
  display: flex; align-items: center; gap: var(--sp-2);
  flex: 0 0 40%; max-width: 200px;
  font: var(--t-body-sm); color: var(--txt3);
}
.prop-row .rotulo .icon { width: 16px; height: 16px; flex: none; }
.prop-row .valor { flex: 1; font: var(--t-body-sm); color: var(--txt1); min-width: 0; }
.prop-row .valor.vazio { color: var(--txt4); }

/* ── Corpo de documento emitido (achado no review final, Finding 2) ─────
   `documentos/ver.html` joga `doc.corpo_final | safe` dentro de
   `.documento-corpo` sem regra nenhuma pra ele. O reset global (`*, *::before,
   *::after { margin:0; padding:0 }`, linha ~162) cola parágrafo em parágrafo,
   tira o recuo da lista e a tabela some sem borda; o h1/h2/h3 do APP
   (700/40px, --t-h1 etc.) também vaza pro texto do paciente, que não é título
   de página. Medidas equivalentes às de templates/documentos/pdf.html — a
   mesma marcação, gerada pelo mesmo editor — com cor em TOKEN: hex literal
   aqui reprova test_cor_literal_so_no_bloco_de_tokens. */
.documento-corpo { font: var(--t-body); color: var(--txt1); }
.documento-corpo p          { margin: 0 0 var(--sp-2) 0; }
.documento-corpo h1         { font-size: 1.7em;  font-weight: 700; margin: 0 0 var(--sp-3) 0; }
.documento-corpo h2         { font-size: 1.4em;  font-weight: 700; margin: 0 0 var(--sp-3) 0; }
.documento-corpo h3         { font-size: 1.15em; font-weight: 700; margin: 0 0 var(--sp-2) 0; }
.documento-corpo blockquote { margin: 0 0 var(--sp-2) var(--sp-6); padding-left: var(--sp-2); border-left: 3px solid var(--border2); }
.documento-corpo ul         { list-style: disc outside;    margin: 0 0 var(--sp-2) 0; padding-left: 26px; }
.documento-corpo ol         { list-style: decimal outside; margin: 0 0 var(--sp-2) 0; padding-left: 26px; }
.documento-corpo li         { margin-bottom: 3px; }
.documento-corpo table      { border-collapse: collapse; margin: 0 0 var(--sp-2) 0; table-layout: fixed; width: 100%; }
.documento-corpo table.sem-borda td, .documento-corpo table.sem-borda th { border-color: transparent; }
.documento-corpo td, .documento-corpo th { padding: 4px 6px; border: 1px solid var(--border2); }
.documento-corpo th         { background: var(--bg3); font-weight: 700; text-align: left; }
.documento-corpo img        { max-width: 100%; height: auto; }
.documento-corpo img.img-30  { width: 30%; }
.documento-corpo img.img-60  { width: 60%; }
.documento-corpo img.img-100 { width: 100%; }

/* ── Cabeçalho da view, grudado no topo ────────────────────────
   Nome, busca, filtro e o botão de criar acompanham a rolagem. Numa lista de
   200 linhas, filtro que sobe junto com o conteúdo é filtro que ninguém acha. */
.view-header {
  /* `top: 0` grudava o cabeçalho da view no MESMO ponto da `.topbar`, que
     também é sticky e tem z-index maior: ao rolar, nome, busca, filtro e
     "Novo" escorregavam para trás da trilha e sumiam — exatamente o que a
     regra existe para impedir. Agora ele para logo abaixo dela. */
  position: sticky; top: var(--topbar-h); z-index: 40;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) 0;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--border);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .view-header { background: var(--bg); }
}
.view-header .titulo { font: var(--t-title); letter-spacing: var(--t-title-ls); }
.view-header .acoes { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; }

/* ── Toggle de configuração ────────────────────────────────────
   Booleano de configuração é interruptor grande com descrição, não checkbox de
   16px com rótulo curto. */
.toggle-row {
  display: flex; align-items: flex-start; gap: var(--sp-4);
  padding: var(--sp-4) 0; border-bottom: 1px solid var(--border);
}
.toggle-row .texto { flex: 1; }
.toggle-row .texto strong { display: block; font: var(--t-body-sm); font-weight: 600; }
.toggle-row .texto span { font: var(--t-caption); color: var(--txt3); }
.toggle {
  position: relative; flex: none; width: 46px; height: 28px;
  background: var(--bg4); border-radius: var(--radius-pill);
  border: none; cursor: pointer;
  transition: background var(--dur-2) var(--ease-standard);
}
/* O interruptor tem 46×28 por desenho; o alvo de toque precisa de 44px de
   altura (§3). `inset` negativo estende a área clicável sem mexer no visual
   nem no posicionamento do botão dentro da linha. */
.toggle::before { content: ""; position: absolute; inset: -8px; }
.toggle::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 22px; height: 22px; border-radius: var(--radius-pill);
  background: var(--bg2);
  transition: transform var(--dur-2) var(--ease-spring);
  box-shadow: var(--shadow-sm);   /* o botão do interruptor flutua no trilho */
}
.toggle[aria-checked="true"] { background: var(--accent-fill); }
.toggle[aria-checked="true"]::after { transform: translateX(18px); }

/* ── Grade de dias (agenda) ────────────────────────────────────
   Sete colunas não cabem em 390px. Três dias visíveis, rolando na horizontal,
   com a coluna das horas congelada à esquerda — é a saída do Notion Calendar, e
   é a única que mantém a leitura de horário enquanto se navega.
   A Fase 1 monta a tela; o primitivo nasce aqui, junto com o resto do sistema. */
.dias-grade {
  display: grid; grid-auto-flow: column;
  /* 110px e não 220px: com 220 a semana inteira pede 1540px, e um laptop de
     1280 (menos 250 de sidebar e a calha das horas) sobra 918 — a semana
     rolava de lado no DESKTOP, que é justamente onde ela deveria caber
     inteira. Com 110 de piso, o 1fr divide e os sete dias entram; a rolagem
     volta a ser o que ela é no celular, um recurso e não um sintoma. */
  grid-auto-columns: minmax(var(--dia-col, 110px), 1fr);
  overflow-x: auto; scroll-snap-type: x mandatory;
  background: var(--border); gap: 1px;   /* a hairline é o fundo aparecendo */
}
.dias-grade > .dia { background: var(--bg); scroll-snap-align: start; min-width: 0; }
.dias-horas {
  position: sticky; left: 0; z-index: 2;
  background: var(--bg); border-right: 1px solid var(--border2);
}
/* O "agora" é uma linha atravessando as colunas — a única marca que responde
   "onde estou" sem ler número nenhum. */
.linha-agora { position: relative; height: 0; border-top: 1px solid var(--val-neg); }
.linha-agora::before {
  content: ""; position: absolute; left: -4px; top: -4px;
  width: 8px; height: 8px; border-radius: var(--radius-pill);
  background: var(--val-neg);
}
@media (max-width: 840px) {
  /* PORCENTAGEM, não vw. A porcentagem resolve contra o próprio scroller;
     `33vw` resolve contra a viewport e ignora os 56px da calha de horas — em
     390px isso dá 2,6 colunas visíveis, e a terceira nasce cortada ao meio.
     Com 33.33% são exatamente três dias, em qualquer largura de aparelho. */
  .dias-grade { --dia-col: 33.33%; }
}

/* ── Agenda: a grade de horas ──────────────────────────────────
   A coluna das horas fica FORA do scroller: dentro dele ela seria uma coluna
   como as outras e herdaria a largura mínima de 220px — um vão de horário
   ocupando um terço do telefone. Fora, ela nem precisa de sticky.

   A posição do evento vem em `--topo` e `--dur`, em MINUTOS, pelo style inline
   do template. Isso não é CSS escrito à mão: o dado é o horário do
   atendimento, e a regra (quantos pixels vale um minuto) continua aqui. */
.agenda-grade {
  --altura-hora: 65px;
  display: flex; align-items: stretch;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  overflow: hidden;
}
.agenda-grade > .dias-horas { width: 56px; flex: none; }
.agenda-grade > .dias-grade { flex: 1; min-width: 0; }

.ag-cabeca {
  height: 48px; flex: none;
  display: flex; flex-direction: column; justify-content: center;
  padding: 0 var(--sp-2);
  background: var(--bg2); border-bottom: 1px solid var(--border2);
}
.ag-cabeca .dow {
  font: var(--t-eyebrow); letter-spacing: var(--t-eyebrow-ls);
  color: var(--txt3); text-transform: uppercase;
}
.ag-cabeca .num { font-size: 15px; font-weight: 600; color: var(--txt1); }
/* Hoje marcado no NÚMERO, como na grade do mês: pintar a coluna inteira
   competiria com os eventos que estão dentro dela. */
.ag-cabeca.hoje .num {
  background: var(--accent-fill); color: var(--on-accent);
  border-radius: var(--radius-pill); min-width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  align-self: flex-start; padding: 0 6px;
}

.ag-hora { height: var(--altura-hora); text-align: right;
           padding: 2px var(--sp-1) 0 0; }
/* --txt4 aqui era 2,15:1. A hora é o eixo de leitura da agenda — a informação
   que resolve "onde estou" — e estava na faixa mais fraca da rampa, em 11px. */
.ag-hora span { font-size: 11px; color: var(--txt3);
                font-variant-numeric: tabular-nums; }

/* `overflow: hidden` é a SEGUNDA camada da mesma trava que mora em
   `modules/agenda._bloco`. Um atendimento das 23:22 com hora de fim igual à de
   início chegou aqui com `--dur: 1440` e virou um retângulo de 1560px pintado
   por cima da grade do dia inteiro — com a página respondendo 200 e o `style`
   inline contendo um número plausível. O dado está consertado lá; aqui fica o
   limite geométrico, para a próxima aritmética de horário que escapar.
   O bloco curto continua avançando alguns pixels sobre o slot seguinte (é o
   que todo calendário faz): o recorte é na borda do DIA, não na do slot. */
.ag-corpo { position: relative; overflow: hidden;
            height: calc(var(--horas) * var(--altura-hora)); }
.ag-linha { height: var(--altura-hora); border-bottom: 1px solid var(--border); }

.ag-ev {
  position: absolute; left: 3px; right: 3px;
  top:    calc(var(--topo) * var(--altura-hora) / 60);
  min-height: max(36px, calc(var(--dur)  * var(--altura-hora) / 60));
  height: auto;
  /* Atendimento de 30min dá 26px de caixa para 36px de conteúdo: o nome da
     paciente saía cortado ao meio, e é a informação da tela. O piso é a altura
     de hora + nome; bloco curto passa a avançar alguns pixels sobre o slot
     seguinte, que é o que todo calendário faz. */
  display: flex; flex-direction: column; overflow: hidden;
  padding: 2px var(--sp-2) 2px 6px;
  border-left: 3px solid var(--txt3);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--bg3); color: var(--txt2);
  font-size: 12px; line-height: 1.35; text-decoration: none;
}
.ag-ev:hover { background: var(--nav-hover); color: var(--txt1); z-index: 10; }
.ag-ev .hora { color: var(--txt3); font-variant-numeric: tabular-nums; }
.ag-ev .nome {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-weight: 500; color: var(--txt1);
}
.ag-ev .situacao { display: block; color: var(--txt3); }

/* Situação do atendimento. A cor NUNCA informa sozinha: o rótulo textual
   ('Falta', 'Cancelado') acompanha o evento — é requisito de acessibilidade,
   e também o que salva a leitura de quem imprime a agenda em preto e branco.
   Verde/vermelho de VALOR, não a paleta de stickers: sticker é decoração e o
   test_design_contrato reprova o uso fora do marcador de categoria. */
.ag-ev.st-realizado, .cal-ev.st-realizado { border-left-color: var(--val-pos); }
.ag-ev.st-falta,     .cal-ev.st-falta     { border-left-color: var(--val-neg); }
.ag-ev.st-cancelado, .cal-ev.st-cancelado { border-left-color: var(--txt4); }
.ag-ev.st-remarcado, .cal-ev.st-remarcado { border-left-color: var(--txt4); }
.ag-ev.st-cancelado .nome, .cal-ev.st-cancelado { text-decoration: line-through; }

/* O "agora" atravessando as colunas, posicionado pelo mesmo eixo dos eventos. */
.ag-corpo > .linha-agora {
  position: absolute; left: 0; right: 0; z-index: 2;
  top: calc(var(--topo) * var(--altura-hora) / 60);
}

/* ── Mini-mês colapsável ───────────────────────────────────────
   `<details>` NATIVO: abre com toque e com teclado, guarda o próprio estado e
   não precisa de uma linha de JS. Um acordeão escrito à mão aqui seria a
   terceira versão de "abre e fecha" no sistema. */
.mini-mes { margin-bottom: var(--sp-3); }
.mini-mes > summary {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) 0; cursor: pointer; list-style: none;
  font: var(--t-body-sm); color: var(--txt2);
}
.mini-mes > summary::-webkit-details-marker { display: none; }
.mini-mes > summary::after { content: "\25B8"; color: var(--txt4); transition: transform .15s; }
.mini-mes[open] > summary::after { transform: rotate(90deg); }
.mini-grade {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px; max-width: 340px;
}
.mini-dow { text-align: center; font-size: 11px; color: var(--txt4); padding: 2px 0; }
.mini-dia {
  display: flex; align-items: center; justify-content: center; height: 32px;
  border-radius: var(--radius-sm); font-size: 12px; color: var(--txt2);
  text-decoration: none; font-variant-numeric: tabular-nums;
}
.mini-dia:hover { background: var(--bg3); color: var(--txt1); }
.mini-dia.fora { color: var(--txt4); }
/* A faixa que a grade está mostrando. Realce de SELEÇÃO, que é o único uso do
   azul fora do botão primário. */
.mini-dia.na-faixa { background: var(--accent-glow); color: var(--txt1); }
.mini-dia.hoje { font-weight: 700; color: var(--accent-txt); }

/* Barra de navegação da agenda (‹ Hoje ›  |  Semana Dia Mês Profissionais). */
.ag-nav { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
          margin: var(--sp-3) 0; }
.ag-nav .sep { flex: 1; }
/* Barra de ferramentas do sistema: todos os controles da fileira com a MESMA
   altura. O `.segmented` mede 34px (trilho + preenchimento + hairline) e o
   `.btn-sm` ao lado media 26 — os dois centralizados, com dois degraus de
   altura visíveis na mesma linha.
   `pointer: fine` obrigatório: sem isso este seletor (0,2,0) venceria o
   `.btn { min-height: 44px }` (0,1,0) da regra de alvo de toque, e a barra
   voltaria a 34px no telefone — silenciosamente abaixo do mínimo. */
@media (pointer: fine) {
  .ag-nav .btn { min-height: 34px; }
}
/* No celular o `.sep` de flex:1 sobra sozinho numa linha e empurra
   "Profissionais" pra uma TERCEIRA — três linhas de filtro antes da agenda
   aparecer. `flex-basis:100%` faz o span quebrar a linha de propósito, sem
   ocupar espaço: duas linhas sempre (‹ Hoje › / Semana Dia Mês Profissionais),
   nunca três. */
@media (max-width: 840px) {
  .ag-nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: 4px;
    scrollbar-width: none;
  }
  .ag-nav::-webkit-scrollbar { display: none; }
  .ag-nav .btn { flex: 0 0 auto; }
  /* UM scroller, não dois. O `.ag-nav` já rola de lado no telefone; deixar o
     `.segmented` rolando por dentro dele é a rolagem-dentro-de-rolagem que o
     §3 aponta como o lugar onde o polegar distraído erra. O controle cresce
     à vontade e quem rola é a barra. */
  .ag-nav .segmented { flex: 0 0 auto; overflow: visible; }
  .ag-nav .sep { flex: 0 0 12px; height: 1px; }
}

/* ── Kanban do funil ───────────────────────────────────────────
   Uma coluna por etapa, cartão arrastável entre elas. Rola na horizontal — no
   celular cinco colunas não cabem, e virar pilha destruiria a comparação entre
   etapas, que é para o que o quadro existe. */
.kanban {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  overflow-x: auto; overscroll-behavior-x: contain; padding-bottom: var(--sp-2);
  scrollbar-width: none;
  scroll-snap-type: x mandatory;
}
.kanban::-webkit-scrollbar { display: none; }
.kanban-col {
  flex: 0 0 min(85vw, 280px);
  background: var(--bg3); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--sp-2);
  scroll-snap-align: center;
}
/* Alvo do arrasto: o anel de foco marca onde o cartão vai cair. */
.kanban-col.recebendo { border-color: var(--accent); background: var(--accent-glow); }
.kanban-cab {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2); padding: var(--sp-1) var(--sp-2) var(--sp-2);
  font: var(--t-body-sm); font-weight: 600; color: var(--txt2);
}
.kanban-lista { display: flex; flex-direction: column; gap: var(--sp-2); min-height: 60px; }
.kanban-vazio { text-align: center; padding: var(--sp-3) 0; }
.lead-card {
  display: flex; flex-direction: column; gap: 2px;
  background: var(--bg2); border: 1px solid var(--border2);
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-3);
  color: var(--txt1); font: var(--t-body-sm); cursor: grab;
  transition: border-color var(--dur-1) var(--ease-standard),
              transform var(--dur-1) var(--ease-standard);
}
.lead-card:active { transform: scale(.98); }
.lead-card:hover { border-color: var(--txt3); color: var(--txt1); }
.lead-card .nome { font-weight: 500; }
.lead-card .rodape { display: flex; align-items: center; gap: var(--sp-2);
                     flex-wrap: wrap; margin-top: 4px; }
.lead-card.arrastando { opacity: .45; cursor: grabbing; }

/* ── Planta do estoque ─────────────────────────────────────────
   Os móveis ficam ONDE ficam na sala. A área é proporcional e a posição de
   cada um é gravada em porcentagem, então a mesma planta cai de pé no monitor
   da recepção e no telefone de quem está na sala de procedimento. */
.planta {
  position: relative; width: 100%; aspect-ratio: 16 / 9; min-height: 320px;
  background: var(--bg3); border: 1px solid var(--border);
  border-radius: var(--radius-lg); overflow: hidden;
  /* Malha discreta: dá a noção de "planta" e ajuda a alinhar os móveis, sem
     imagem nenhuma para carregar. */
  background-image:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 40px 40px;
  touch-action: none;   /* sem isto o arrasto vira rolagem da página no celular */
}
.planta-vazia {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
}
.movel {
  position: absolute; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 92px; min-height: 76px; padding: var(--sp-2);
  background: var(--bg2); border: 1px solid var(--border2);
  border-radius: var(--radius); cursor: grab;
  font: var(--t-body-sm); color: var(--txt1); text-align: center;
  /* O móvel FLUTUA sobre a planta e é arrastável — é o caso em que a sombra do
     token vale (§3: só no que flutua). */
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-1) var(--ease-standard),
              box-shadow var(--dur-1) var(--ease-standard);
}
.movel .icone { font-size: 22px; line-height: 1; }
.movel .nome  { font-weight: 500; max-width: 120px; overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; }
.movel:hover  { border-color: var(--txt3); }
.movel.aberto { border-color: var(--accent); }
.movel.arrastando { cursor: grabbing; opacity: .85; }
/* Ainda não posicionado: fica marcado até alguém arrastar para o lugar certo. */
.movel.sem-posicao { border-style: dashed; }

/* ── Foto de evolução e câmera ─────────────────────────────────
   Câmera é recurso de CELULAR (CLAUDE.md §3): o profissional atende de pé, com
   o telefone na mão. No desktop a mesma tela oferece o envio de arquivo, e é
   por isso que os dois caminhos postam no MESMO formulário — um `<input
   type=file>` e um Blob do canvas chegam iguais no servidor.

   ⚠️ `getUserMedia` só existe em HTTPS ou em localhost. No telefone, acessando
   o servidor por IP da rede local em http://, o navegador NÃO pede permissão:
   ele simplesmente não expõe a câmera, e a tela parece quebrada sem erro. Ver
   README. */
.foto-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--sp-2);
}
.foto-item {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; background: var(--bg3);
}
.foto-item img { display: block; width: 100%; height: 140px; object-fit: cover; }
.foto-item .meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-1); padding: var(--sp-1) var(--sp-2);
  font: var(--t-caption); color: var(--txt3);
}

.camera-palco {
  position: relative; overflow: hidden; border-radius: var(--radius-lg);
  background: var(--bg4); aspect-ratio: 3 / 4; max-width: 100%;
}
.camera-palco video, .camera-palco canvas {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
/* O "ghosting": a foto anterior por cima da imagem ao vivo, translúcida, para
   o enquadramento bater. É a diferença entre um par antes/depois comparável e
   duas fotos de ângulos diferentes que não provam nada. */
.camera-fantasma {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: .4; pointer-events: none;
}
.camera-controles {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-3); flex-wrap: wrap;
}

/* Grade do MÊS no celular: sete colunas em 375px dão ~48px de célula, e o nome
   da paciente vira três letras e reticências — informação nenhuma ocupando a
   linha toda. Sobra a hora, que é o que se lê de relance; o nome e a situação
   continuam no `aria-label` e a um toque de distância, no registro.
   A alternativa seria rolar o mês na horizontal, e aí ele deixa de ser a
   visão-de-conjunto que justifica existir. */
@media (max-width: 840px) {
  .cal-grade .cal-nome { display: none; }
  .cal-dia { min-height: 76px; }
}
.ag-nav .btn[aria-current="page"] { background: var(--bg4); color: var(--txt1); }

/* ── Cantos contínuos (squircle) ───────────────────────────────────────
   O canto do sistema NÃO é um arco de círculo: a curvatura entra e sai de
   forma contínua, sem a quebra que o `border-radius` clássico deixa onde o
   arco encontra a reta. É o detalhe que faz um ícone, um cartão ou um botão
   "parecerem" do sistema mesmo com o mesmo raio em pixels.

   `corner-shape` faz isso nativamente. Fica atrás de `@supports` porque é
   recente: onde não existe, o raio continua sendo o arco de sempre e nada
   quebra — é progressivo por construção, não um polyfill.

   O que NÃO entra: nada com `--radius-pill`. Squircle num pill deforma a
   ponta, e o pill do sistema é um pill de verdade (avatar, contador, dia de
   hoje, alça da folha). */
@supports (corner-shape: squircle) {
  .card, .kpi-card, .modal, .btn, .btn-icon, .form-control, .badge,
  [class^="badge-"], [class*=" badge-"], .segmented, .segmented > a,
  .segmented > button, .nav-item, .config-nav a, .kanban-col, .lead-card,
  .movel, .foto-item, .login-box, .login-logo .icon-wrap, .sidebar-logo .logo-icon,
  .config-layout, .cal-grade, .agenda-grade, .help-box, .flash, .skip-link,
  .table-wrap, .table-scroll, .planta, .camera-palco, .mini-dia, .prop-list,
  .evolucao-item, .evolucao-lista, .filtro-card {
    corner-shape: squircle;
  }
}

/* ── Animações e Acessibilidade ─────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  /* Transição de tela nativa do navegador (Continuity) */
  @view-transition { navigation: auto; }
}

@media (prefers-reduced-motion: reduce) {
  /* Remove deslocamentos espaciais, preservando estado e cor para acessibilidade */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .modal-overlay.sheet .modal { transform: none !important; }
  /* O modal continua APARECENDO, só que sem percorrer distância: o guia manda
     "replacing transitions in x-, y-, and z-axes with fades". Sem esta linha a
     regra acima zerava a duração e a camada surgia 16px deslocada e a 97%. */
  .modal-overlay .modal { transform: none !important; }
  /* Recuo de pressão é deslocamento — some junto. */
  .btn:active, .nav-item:active { transform: none !important; }

  /* ⚠️ EXCEÇÃO deliberada, e a única. A regra `*` acima também congelava o
     `.loading-spinner` — que é o indicador de progresso INDETERMINADO do
     sistema, o único sinal de "estou salvando" que o modal de rota dá. Parado,
     ele vira um arco cinza sem significado, e quem pediu menos movimento
     passa a não ter retorno nenhum.
     Guia de Design — Carregamento: "Clearly communicate that content is
     loading." Movimento essencial se REDUZ (giro lento e contínuo), não se
     elimina. */
  .loading-spinner {
    animation-duration: 1.6s !important;
    animation-iteration-count: infinite !important;
  }
}

/* ── Mais contraste (ajuste do sistema) ────────────────────────
   Guia de Design — Acessibilidade: "If your app doesn't provide this minimum
   contrast by default, ensure it at least provides a higher contrast color
   scheme when the system setting Increase Contrast is turned on."
   Aqui o padrão JÁ passa em 4,5:1 — este bloco é o degrau acima, para quem
   pediu: a hairline vira a borda forte, a rampa de texto sobe um degrau e o
   campo ganha borda em repouso (que o desenho normal esconde de propósito). */
@media (prefers-contrast: more) {
  :root, html[data-theme="dark"] {
    --border: var(--border2);
    --txt3:   var(--txt2);
    --txt4:   var(--txt3);
  }
  .form-control { border-color: var(--border2); }
  /* Com mais contraste pedido, o anel deixa de ser macio: opaco e mais grosso. */
  :focus-visible { outline: 4px solid var(--accent-txt); }
}

/* ── Menos transparência (ajuste do sistema) ───────────────────
   Material é bonito e é ruído para quem tem baixa visão. Quando o sistema
   pede opacidade, as três camadas viram cor chapada — sem perder a hairline,
   que é o que separa a barra do conteúdo. */
@media (prefers-reduced-transparency: reduce) {
  .topbar, .view-header { background: var(--bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .action-bar           { background: var(--bg2); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .topbar               { border-bottom-color: var(--border); }
}


@media (pointer: coarse) {
  input[type="checkbox"], input[type="radio"] { width: 22px; height: 22px; margin-right: 8px; }
}


/* Filtro colapsável (Financeiro e Relatórios) */
details.filtro-card > summary.filtro-summary::-webkit-details-marker {
  display: none;
}
details.filtro-card[open] > summary.filtro-summary .filtro-toggle {
  transform: rotate(180deg);
}
