/*
 * CestaHub — custom.css
 * Estilos globais (extraidos dos inline da Fase 2 CSP). Carregado via <link>
 * nas bases para permitir remover 'unsafe-inline' de style-src.
 */

/* ===========================================================================
 * TEMA — tokens semanticos do modo "liquid glass". A troca vive so aqui
 * (`html.dark` reescreve os tokens); o HTML usa utilities semanticas, sem `dark:`.
 *
 * CRITICO: tokens sao TRIPLETS DE CANAIS "R G B" (nunca HEX) — o tailwind-config
 * os consome como `rgb(var(--c-card) / <alpha>)`; um HEX aqui mata a classe em
 * silencio. Paleta dark = "deep slate"; brand indigo preservado.
 * =========================================================================== */
:root {
  /* Superficies */
  --c-canvas:          248 250 252; /* slate-50  — fundo da pagina (claro)     */
  --c-card:            255 255 255; /* white     — cartoes/superficies         */
  --c-card-muted:      248 250 252; /* slate-50  — superficie rebaixada        */
  /* Texto (forte -> sutil) */
  --c-content-strong:   15  23  42; /* slate-900 — titulos                     */
  --c-content:          51  65  85; /* slate-700 — corpo                       */
  --c-content-muted:   100 116 139; /* slate-500 — secundario                  */
  --c-content-subtle:  148 163 184; /* slate-400 — placeholder/legenda         */
  /* Linhas */
  --c-hairline:        226 232 240; /* slate-200 — bordas/divisores            */
  color-scheme: light;
}

html.dark {
  /* Superficies — deep slate */
  --c-canvas:           10  14  26; /* navy-slate profundo (mais escuro que slate-950) */
  --c-card:             15  23  42; /* slate-900 — cartoes/superficies         */
  --c-card-muted:        2   6  23; /* slate-950 — superficie rebaixada        */
  /* Texto */
  --c-content-strong:  241 245 249; /* slate-100 — titulos                     */
  --c-content:         203 213 225; /* slate-300 — corpo                       */
  --c-content-muted:   148 163 184; /* slate-400 — secundario                  */
  --c-content-subtle:  100 116 139; /* slate-500 — placeholder/legenda         */
  /* Linhas */
  --c-hairline:         51  65  85; /* slate-700 — bordas/divisores            */
  color-scheme: dark;
}

/*
 * ANTI-FLASH (lado CSS): pinta o fundo com a cor de canvas do tema antes de o
 * Tailwind CDN processar as utilities. Cobre claro e escuro porque --c-canvas
 * troca em `html.dark` (aplicado sincrono pelo theme-init.js) — sem flash branco.
 */
html {
  background-color: rgb(var(--c-canvas));
}

/*
 * Transicao suave ao trocar de tema. Anima APENAS color/background/border
 * (baratas); NUNCA blur/backdrop-filter (caros, travariam). 200ms.
 */
body,
.glass,
.glass-elevated,
.glass-hairline {
  transition-property: color, background-color, border-color;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 200ms;
}

/* Acessibilidade: quem pede menos movimento nao recebe a animacao de tema. */
@media (prefers-reduced-motion: reduce) {
  body,
  .glass,
  .glass-elevated,
  .glass-hairline {
    transition: none;
  }
}

body {
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* HTMX indicator helpers */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline-flex; }
.htmx-request.htmx-indicator { display: inline-flex; }

/* Scrollbar hidden for sidebar */
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
}

/* Rolagem horizontal com momentum no iOS antigo (a faixa de abas do modal
 * social nunca quebra: rola na horizontal em telas estreitas). Navegadores
 * atuais já fazem momentum por padrão; mantido para WebKit legado. */
.touch-scroll-x {
  -webkit-overflow-scrolling: touch;
}

/* Smooth transitions for all interactive elements */
a, button, input, select, textarea {
  transition-property: color, background-color, border-color, box-shadow, opacity, transform;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

/* Form field error states — applied via .field-error wrapper in _form_field.html */
.field-error input,
.field-error select,
.field-error textarea {
  border-color: #fca5a5 !important; /* error-300 */
  background-color: rgba(254, 242, 242, 0.5) !important; /* error-50/50 */
}
.field-error input:focus,
.field-error select:focus,
.field-error textarea:focus {
  border-color: #f87171 !important; /* error-400 */
  --tw-ring-color: rgb(239 68 68 / 0.15) !important; /* error-500/15 */
}

/*
 * input[type=date] no iOS Safari ignora width/max-width com appearance nativo
 * e estoura containers estreitos (campos "enormes" no PWA). appearance:none faz
 * o input respeitar o box; o seletor nativo ainda abre ao toque.
 */
input[type="date"] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  min-width: 0;
  max-width: 100%;
}
input[type="date"]::-webkit-date-and-time-value {
  text-align: left;
  margin: 0;
}
/* Mantem o icone do calendario clicavel no desktop (Chrome/Edge), que
   appearance:none poderia ofuscar. */
input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: 1;
  cursor: pointer;
}

/* ===========================================================================
 * MODAL DINAMICO (#modal-host carregado via HTMX) — scroll-lock + entrada.
 *
 * .modal-scroll-locked: trava o overflow do elemento que de fato rola o fundo
 * (o container interno .overflow-y-auto, NAO o body) enquanto o modal-host esta
 * aberto. O modal.js compensa a largura da scrollbar via padding-right inline
 * para evitar "layout shift" ao sumir a barra.
 * =========================================================================== */
.modal-scroll-locked {
  overflow: hidden !important;
  overscroll-behavior: contain;
}

/*
 * ENTRADA do painel: bottom-sheet sobe de baixo no mobile (slide-up) e vira um
 * card que faz scale+fade no desktop (sm+). Uma classe so (.modal-panel-enter)
 * troca de keyframe pelo breakpoint, evitando conflito de variantes animate-*
 * do Tailwind no mesmo elemento. O backdrop entra com fade suave.
 */
@keyframes cestahubSheetUp {
  from { opacity: 0; transform: translateY(100%); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes cestahubModalIn {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes cestahubBackdropIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.modal-backdrop-enter {
  animation: cestahubBackdropIn 0.2s ease-out;
}
.modal-panel-enter {
  animation: cestahubSheetUp 0.32s cubic-bezier(0.32, 0.72, 0, 1);
  will-change: transform;
}
@media (min-width: 640px) {
  .modal-panel-enter {
    animation: cestahubModalIn 0.2s ease-out;
  }
}

/*
 * "Grabber" do bottom-sheet (a barrinha de arraste no topo): so faz sentido no
 * mobile, onde o painel cola na base. Escondido a partir de sm (vira card).
 */
.modal-grabber {
  width: 2.25rem;
  height: 0.3125rem;
  border-radius: 9999px;
  background-color: rgb(var(--c-hairline));
}
@media (min-width: 640px) {
  .modal-grabber { display: none; }
}

/*
 * Acessibilidade: quem pede menos movimento NAO recebe transform/opacity
 * animados — o painel e o backdrop aparecem direto, sem slide nem scale.
 */
@media (prefers-reduced-motion: reduce) {
  .modal-panel-enter,
  .modal-backdrop-enter {
    animation: none;
  }
}

/* ===========================================================================
 * REVELACAO do card "Responsavel pela Coleta" (family_form.html — S3b).
 *
 * Tecnica grid-template-rows 0fr->1fr: e o unico jeito CSS de animar "altura
 * automatica" suavemente (height:auto nao e animavel). O wrapper
 * [data-collector-collapsible] e um grid de UMA linha cuja fracao vai de 0fr
 * (fechado) a 1fr (aberto); o filho direto precisa de min-height:0 +
 * overflow:hidden para a linha poder encolher e o conteudo nao vazar.
 *
 * O estado vem do atributo ``data-open`` (servidor no 1o paint; depois o
 * collector-reveal.js alterna no change do checkbox is_incapable). Anima
 * tambem opacity para um fade leve junto da altura. A margem (mt-6) fica no
 * proprio wrapper e colapsa com a do card seguinte quando fechado (sem gap
 * fantasma).
 * =========================================================================== */
[data-collector-collapsible] {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 280ms ease, opacity 280ms ease;
}
[data-collector-collapsible][data-open="true"] {
  grid-template-rows: 1fr;
  opacity: 1;
}
/* Filho direto: deixa a linha do grid encolher a 0 sem cortar/vazar conteudo. */
[data-collector-collapsible] > * {
  min-height: 0;
  overflow: hidden;
}
/* Acessibilidade: quem pede menos movimento troca o estado instantaneamente. */
@media (prefers-reduced-motion: reduce) {
  [data-collector-collapsible] {
    transition: none;
  }
}

/* ===========================================================================
 * REVELACAO em 2 NIVEIS da deficiencia POR LINHA de membro (_member_row.html —
 * S3c). CSP-safe: CSS puro, sem JS, sem inline. Cada [data-member-row] e um
 * escopo independente — o `:has()` so reage aos campos DAQUELA linha, entao
 * varias linhas nao se interferem.
 *
 * MECANISMO display:none/revert (e nao grid-rows 0fr->1fr). Motivo: o nivel 1
 * ([data-disability-types]) e um <fieldset> com <legend>, e o <legend> e
 * renderizado FORA do grid track — com grid-rows:0fr o conteudo encolhia mas a
 * legend + a borda/padding do fieldset deixavam um "fantasma" visivel quando
 * is_pcd != "sim". display:none esconde o bloco INTEIRO (legend incluso) de
 * forma confiavel; o foco aqui e confiabilidade (nao aparecer sem "Sim"), nao
 * animacao. O fade entra so no conteudo revelado, sem mexer na altura.
 *
 *   Nivel 1: revela [data-disability-types] quando o radio is_pcd="sim" desta
 *            linha esta marcado.
 *   Nivel 2: revela [data-disability-other] quando o checkbox "outra" de
 *            disability_types desta linha esta marcado.
 * =========================================================================== */
[data-member-row] [data-disability-types],
[data-member-row] [data-disability-other] {
  display: none;
}
[data-member-row]:has(input[name$="-is_pcd"][value="sim"]:checked) [data-disability-types] {
  display: block;
  animation: cestahubRevealIn 0.2s ease-out;
}
[data-member-row]:has(input[name$="-disability_types"][value="outra"]:checked) [data-disability-other] {
  display: block;
  animation: cestahubRevealIn 0.2s ease-out;
}
@keyframes cestahubRevealIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* Acessibilidade: quem pede menos movimento troca o estado sem o fade. */
@media (prefers-reduced-motion: reduce) {
  [data-member-row]:has(input[name$="-is_pcd"][value="sim"]:checked) [data-disability-types],
  [data-member-row]:has(input[name$="-disability_types"][value="outra"]:checked) [data-disability-other] {
    animation: none;
  }
}

/* ===========================================================================
 * REVELACAO em 2 NIVEIS da deficiencia do RESPONSAVEL (aba responsavel do
 * Formulario Social — _family_social_fields.html). Espelha a regra por linha de
 * membro acima, porem o escopo e [data-responsavel-pcd] e os names sao EXATOS
 * (is_pcd/disability_types, sem prefixo de formset), entao nao colidem com os
 * do membro (members-N-...). CSP-safe: CSS puro, sem JS. Reusa o keyframe
 * cestahubRevealIn definido acima.
 *
 *   Nivel 1: revela [data-disability-types] quando o radio is_pcd="sim" marcado.
 *   Nivel 2: revela [data-disability-other] quando o tipo "outra" marcado.
 * =========================================================================== */
[data-responsavel-pcd] [data-disability-types],
[data-responsavel-pcd] [data-disability-other] {
  display: none;
}
[data-responsavel-pcd]:has(input[name="is_pcd"][value="sim"]:checked) [data-disability-types] {
  display: block;
  animation: cestahubRevealIn 0.2s ease-out;
}
[data-responsavel-pcd]:has(input[name="disability_types"][value="outra"]:checked) [data-disability-other] {
  display: block;
  animation: cestahubRevealIn 0.2s ease-out;
}
/* Acessibilidade: quem pede menos movimento troca o estado sem o fade. */
@media (prefers-reduced-motion: reduce) {
  [data-responsavel-pcd]:has(input[name="is_pcd"][value="sim"]:checked) [data-disability-types],
  [data-responsavel-pcd]:has(input[name="disability_types"][value="outra"]:checked) [data-disability-other] {
    animation: none;
  }
}

/* ===========================================================================
 * FLUXO "SALVAR MEMBRO" — editor x resumo POR LINHA (_member_row.html — S3d).
 * CSP-safe: estado pelo atributo [data-collapsed] da linha (servidor no 1o paint
 * para membros existentes; o member-formset.js alterna depois). Os campos ficam
 * SEMPRE no DOM — display:none apenas os ESCONDE, eles seguem submetendo.
 *
 *   expandida (data-collapsed="false"): mostra o EDITOR, esconde o RESUMO.
 *   colapsada (data-collapsed="true"):  esconde o EDITOR, mostra o RESUMO.
 * =========================================================================== */
[data-member-row] > [data-member-summary] {
  display: none;
}
[data-member-row][data-collapsed="true"] > [data-member-editor] {
  display: none;
}
[data-member-row][data-collapsed="true"] > [data-member-summary] {
  display: flex;
  animation: cestahubSummaryIn 0.18s ease-out; /* fade leve ao colapsar */
}
@keyframes cestahubSummaryIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* "Adicionar membro" some enquanto há um editor aberto (so 1 por vez): o JS
   marca data-editing="true" no container. Escopo pela aba de composicao com
   :has() — robusto, NAO depende da ordem do botao em relacao ao container
   (o combinador de irmao ~ era fragil). O botao vive na MESMA aba. */
[data-tab-panel="composicao"]:has([data-member-container][data-editing="true"]) [data-member-add] {
  display: none;
}

/* Acessibilidade: quem pede menos movimento troca o estado sem o fade. */
@media (prefers-reduced-motion: reduce) {
  [data-member-row][data-collapsed="true"] > [data-member-summary] {
    animation: none;
  }
}

/* Global HTMX loading bar — keyframes + shimmer */
@keyframes cestahubLoadingBar {
  0%   { width: 0;    opacity: 1; }
  50%  { width: 70%;  opacity: 1; }
  100% { width: 100%; opacity: 0; }
}
@keyframes cestahubShimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.cestahub-loading-bar {
  background: linear-gradient(90deg, #6366f1, #818cf8, #a78bfa, #6366f1);
  background-size: 200% 100%;
  animation: cestahubLoadingBar 2s cubic-bezier(0.4, 0, 0.2, 1) infinite,
             cestahubShimmer 1.5s ease-in-out infinite;
}

/* Auth layout (base_auth.html) — floating decorative shapes */
@keyframes cestahubFloat1 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(30px, -30px) scale(1.05); }
  66%      { transform: translate(-20px, 20px) scale(0.95); }
}
@keyframes cestahubFloat2 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(-25px, 25px) scale(1.08); }
  66%      { transform: translate(35px, -15px) scale(0.92); }
}
@keyframes cestahubFloat3 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%      { transform: translate(20px, -40px) scale(1.1); }
}
.float-1 { animation: cestahubFloat1 12s ease-in-out infinite; }
.float-2 { animation: cestahubFloat2 15s ease-in-out infinite; }
.float-3 { animation: cestahubFloat3 10s ease-in-out infinite; }

/* Padrao radial da sidebar decorativa (base_auth.html) */
.cestahub-auth-grid-pattern {
  opacity: 0.03;
  background-image: radial-gradient(circle, white 1px, transparent 1px);
  background-size: 32px 32px;
}

/*
 * Pizza "Distribuicao por bairro" — POP-OUT no hover de cada fatia (.pie-wedge):
 * leve zoom (scale 1.08) + drop-shadow, via CSS puro (o JS so faz o tooltip).
 *
 * CRITICO: `transform-box: fill-box` + `transform-origin: center` ancoram o
 * scale no centro da fatia. Versao anterior usava `view-box` + origem `70px 70px`;
 * quando o navegador ignorava `view-box`, a origem caia FORA da bbox e o scale
 * arremessava a fatia para longe. O <svg> usa overflow-visible para nao recortar.
 */
.pie-wedge {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1),
              filter 150ms cubic-bezier(0.4, 0, 0.2, 1);
  cursor: pointer;
}
.pie-wedge:hover {
  transform: scale(1.08);
  filter: drop-shadow(0 4px 8px rgba(15, 23, 42, 0.35));
}
/* Respeita usuarios que pedem menos movimento (acessibilidade). */
@media (prefers-reduced-motion: reduce) {
  .pie-wedge {
    transition: none;
  }
}

/* ===========================================================================
 * LIQUID GLASS — superficies translucidas das POUCAS superficies flutuantes
 * (header, sidebar, dropdowns, modais). Cada uma compoe fundo translucido +
 * blur + borda specular (linha de luz no topo) + sombra.
 *
 * ORCAMENTO DE PERFORMANCE (NAO-NEGOCIAVEL):
 *   - Blur MODERADO (12-20px) — e a operacao mais cara. Header sticky e o PIOR
 *     caso (rola atras em tempo real): `.glass-elevated` usa blur MENOR + fundo
 *     mais OPACO, efeito vindo da translucidez, nao do blur pesado.
 *   - SEMPRE `-webkit-backdrop-filter` junto (iOS Safari).
 *   - NUNCA animar backdrop-filter/blur.
 *   - Nunca usar em item de lista repetido (cada blur multiplica o custo).
 * =========================================================================== */

/* Superficie padrao de vidro (dropdowns, popovers, cartoes flutuantes).
   ALPHA ALTO de proposito: e um MENU DE ACOES (dropdown de perfil), nao um
   cartao decorativo — precisa ser legivel mesmo onde o WebView/celular nao
   aplica backdrop-filter (sem o blur que mascararia o fundo). O toque de vidro
   vem do leve residual de translucidez + saturate, nao do desfoque. */
.glass {
  background-color: rgb(var(--c-card) / 0.96);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  backdrop-filter: blur(16px) saturate(180%);
  border: 1px solid rgb(var(--c-hairline) / 0.7);
  box-shadow:
    0 1px 0 0 rgb(255 255 255 / 0.5) inset,           /* borda specular (topo) */
    0 10px 30px -12px rgb(15 23 42 / 0.18);            /* profundidade          */
}

/* Variante "elevada" calibrada para o HEADER STICKY (pior caso de blur):
   blur MENOR + opacidade ALTA, para o efeito nao depender de desfoque pesado
   enquanto o conteudo rola atras. */
.glass-elevated {
  background-color: rgb(var(--c-card) / 0.85);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
  backdrop-filter: blur(12px) saturate(160%);
  box-shadow:
    0 1px 0 0 rgb(255 255 255 / 0.45) inset,
    0 1px 24px -8px rgb(15 23 42 / 0.12);
}

/* Apenas o "fio" specular + leve translucidez, sem profundidade pesada —
   para divisores/superficies sutis que ainda querem o toque de vidro. */
.glass-hairline {
  background-color: rgb(var(--c-card) / 0.6);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
  border: 1px solid rgb(var(--c-hairline) / 0.6);
}

/*
 * Calibragem DARK: vidro mais OPACO (texto claro precisa de contraste sobre o
 * navy), borda specular vira linha branca de alpha baixissima, sombra mais densa.
 */
html.dark .glass {
  background-color: rgb(var(--c-card) / 0.94);
  border-color: rgb(255 255 255 / 0.08);
  box-shadow:
    0 1px 0 0 rgb(255 255 255 / 0.06) inset,
    0 12px 36px -12px rgb(0 0 0 / 0.6);
}
html.dark .glass-elevated {
  background-color: rgb(var(--c-card) / 0.8);
  box-shadow:
    0 1px 0 0 rgb(255 255 255 / 0.06) inset,
    0 1px 28px -8px rgb(0 0 0 / 0.55);
}
html.dark .glass-hairline {
  background-color: rgb(var(--c-card) / 0.5);
  border-color: rgb(255 255 255 / 0.07);
}

/*
 * SIDEBAR do app: sempre navy nos dois temas. No dark o canvas (~#0a0e1a) fica
 * mais escuro que a sidebar e inverteria a profundidade; entao levantamos o
 * fundo dela (slate-900) + um "fio" specular na borda direita. Troca vive aqui.
 */
.app-sidebar {
  border-right: 1px solid rgb(255 255 255 / 0.04);
}
html.dark .app-sidebar {
  /* slate-900: distinto do canvas navy ~#0a0e1a, mantendo o texto claro legivel. */
  background-color: rgb(15 23 42);
  border-right-color: rgb(255 255 255 / 0.07);
}

/*
 * FALLBACK OPACO automatico (performance/acessibilidade): com menos
 * transparencia pedida OU dispositivo lento a repintar (`update: slow`),
 * desliga o blur e cai para fundo SOLIDO. Mata o custo do backdrop-filter.
 */
@media (prefers-reduced-transparency: reduce), (update: slow) {
  .glass,
  .glass-elevated,
  .glass-hairline {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background-color: rgb(var(--c-card));
  }
}

/* ===========================================================================
 * CAMADA DE COMPATIBILIDADE TRANSITORIA (DARK) — PONTE DE LEGIBILIDADE.
 * Remapeia classes de cor LEGADAS (text-surface-900, bg-white, badges/KPI...)
 * para os tokens do tema SO no dark, enquanto a migracao por tokens nao chega
 * aos templates. Objetivo: legibilidade do conteudo nao migrado, nao acabamento.
 * REMOVER quando todos os templates usarem tokens semanticos.
 *
 * POR QUE FUNCIONA: `html.dark .classe` (0,2,0) vence a utility do Tailwind
 * (0,1,0) por especificidade, independente da ordem de injecao. Variantes de
 * opacidade tem a barra escapada no nome da classe (`.bg-white\/80` -> `\/`).
 *
 * REGRA DE OURO: nunca clarear texto sem escurecer o fundo sob ele (senao vira
 * claro-sobre-claro) — cada bloco trata texto + fundo + borda em conjunto.
 *
 * NAO mexer: shell ja migrado (header/sidebar), superficies escuras intencionais
 * (scrims de modal, botoes/badges surface-700..950 com text-white, scanner QR) e
 * solidos vivos (bg-brand/success/error-500). Performance: cor pura, zero blur.
 * =========================================================================== */

/* --- A) NEUTROS: texto (forte -> sutil) ---------------------------------- */
html.dark .text-surface-900,
html.dark .text-surface-800 { color: rgb(var(--c-content-strong)); }
html.dark .text-surface-700,
html.dark .text-surface-600 { color: rgb(var(--c-content)); }
html.dark .text-surface-500 { color: rgb(var(--c-content-muted)); }
html.dark .text-surface-400,
html.dark .text-surface-300,
html.dark .text-surface-200 { color: rgb(var(--c-content-subtle)); }

/* --- A) NEUTROS: fundos --------------------------------------------------- */
/* bg-white -> cartao (a superficie elevada do tema). */
html.dark .bg-white { background-color: rgb(var(--c-card)); }
/* Variantes de opacidade de bg-white (barra ESCAPADA no nome da classe).
   Preservamos o alpha original para nao destruir o efeito de translucidez. */
html.dark .bg-white\/95 { background-color: rgb(var(--c-card) / 0.95); }
html.dark .bg-white\/80 { background-color: rgb(var(--c-card) / 0.8); }
html.dark .bg-white\/70 { background-color: rgb(var(--c-card) / 0.7); }
/* bg-surface-50 -> canvas (sub-fundos: zebra de tabela, secoes rebaixadas);
   le melhor que card-muted por ser um degrau abaixo do card no escuro. */
html.dark .bg-surface-50 { background-color: rgb(var(--c-canvas)); }
html.dark .bg-surface-50\/60 { background-color: rgb(var(--c-canvas) / 0.6); }
html.dark .bg-surface-50\/50 { background-color: rgb(var(--c-canvas) / 0.5); }
html.dark .bg-surface-50\/40 { background-color: rgb(var(--c-canvas) / 0.4); }
html.dark .bg-surface-50\/30 { background-color: rgb(var(--c-canvas) / 0.3); }
/* bg-surface-100/200 -> superficie rebaixada (chips, trilhos, thumbs). */
html.dark .bg-surface-100 { background-color: rgb(var(--c-card-muted)); }
html.dark .bg-surface-200 { background-color: rgb(var(--c-card-muted)); }

/* Hover de linha/lista (DIFERENTE de bg-surface-50: e outra classe). No claro
   o hover clareia para surface-50/100; no escuro precisa ESCURECER/realcar,
   nunca clarear para branco. Usamos um veu sutil de hairline. */
html.dark .hover\:bg-surface-50:hover,
html.dark .hover\:bg-surface-100:hover { background-color: rgb(var(--c-hairline) / 0.35); }
html.dark .hover\:bg-surface-50\/60:hover,
html.dark .hover\:bg-surface-50\/50:hover { background-color: rgb(var(--c-hairline) / 0.28); }

/* --- A) NEUTROS: bordas, rings e divisores -> hairline ------------------- */
html.dark .border-surface-100,
html.dark .border-surface-200,
html.dark .border-surface-300 { border-color: rgb(var(--c-hairline)); }
html.dark .border-surface-200\/80 { border-color: rgb(var(--c-hairline) / 0.8); }
html.dark .border-surface-200\/60 { border-color: rgb(var(--c-hairline) / 0.6); }
html.dark .border-surface-200\/50 { border-color: rgb(var(--c-hairline) / 0.5); }
/* Rings neutros (cards/inputs com ring no lugar de border). */
html.dark .ring-surface-200 { --tw-ring-color: rgb(var(--c-hairline)); }
html.dark .ring-surface-200\/70 { --tw-ring-color: rgb(var(--c-hairline) / 0.7); }
html.dark .ring-surface-200\/60 { --tw-ring-color: rgb(var(--c-hairline) / 0.6); }
/* Divisores de tabela. */
html.dark .divide-surface-100 > :not([hidden]) ~ :not([hidden]) { border-color: rgb(var(--c-hairline)); }
html.dark .divide-surface-100\/80 > :not([hidden]) ~ :not([hidden]) { border-color: rgb(var(--c-hairline) / 0.8); }

/* --- B) SEMANTICOS TONAIS: fundos -50/-100 -> veu ESCURO translucido -----
 * Fundos claros de badge/alert viram realce escuro da mesma matiz em alpha baixo
 * (texto clareia no bloco abaixo). Matizes: success 34 197 94 | error 239 68 68
 * | warning 245 158 11 | brand 99 102 241. */
html.dark .bg-success-50 { background-color: rgb(34 197 94 / 0.12); }
html.dark .bg-success-50\/40 { background-color: rgb(34 197 94 / 0.08); }
html.dark .bg-error-50 { background-color: rgb(239 68 68 / 0.12); }
html.dark .bg-error-50\/50 { background-color: rgb(239 68 68 / 0.09); }
html.dark .bg-error-50\/40 { background-color: rgb(239 68 68 / 0.08); }
html.dark .bg-warning-50 { background-color: rgb(245 158 11 / 0.12); }
html.dark .bg-warning-50\/70 { background-color: rgb(245 158 11 / 0.10); }
html.dark .bg-warning-50\/50 { background-color: rgb(245 158 11 / 0.08); }
html.dark .bg-warning-50\/40 { background-color: rgb(245 158 11 / 0.07); }
html.dark .bg-brand-50 { background-color: rgb(99 102 241 / 0.14); }
html.dark .bg-brand-50\/60 { background-color: rgb(99 102 241 / 0.10); }
html.dark .bg-brand-50\/40 { background-color: rgb(99 102 241 / 0.08); }
html.dark .bg-brand-25 { background-color: rgb(99 102 241 / 0.08); }
html.dark .bg-brand-100 { background-color: rgb(99 102 241 / 0.18); }
html.dark .bg-warning-100 { background-color: rgb(245 158 11 / 0.16); }

/* --- B) SEMANTICOS TONAIS: KPI cards com GRADIENTE claro -----------------
 * KPIs usam `from-{matiz}-50 to-{matiz}-100` (gradiente claro) que ficaria
 * ilegivel com o texto clareado. Neutralizamos as paradas para tons escuros
 * translucidos via as custom props do Tailwind (--tw-gradient-from/to/stops). */
html.dark .from-brand-50 {
  --tw-gradient-from: rgb(99 102 241 / 0.14) var(--tw-gradient-from-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(99 102 241 / 0));
}
html.dark .to-brand-100 { --tw-gradient-to: rgb(99 102 241 / 0.20) var(--tw-gradient-to-position); }
html.dark .to-brand-200 { --tw-gradient-to: rgb(99 102 241 / 0.24) var(--tw-gradient-to-position); }
html.dark .from-brand-100 {
  --tw-gradient-from: rgb(99 102 241 / 0.20) var(--tw-gradient-from-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(99 102 241 / 0));
}
html.dark .from-success-50 {
  --tw-gradient-from: rgb(34 197 94 / 0.12) var(--tw-gradient-from-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(34 197 94 / 0));
}
html.dark .to-success-100 { --tw-gradient-to: rgb(34 197 94 / 0.18) var(--tw-gradient-to-position); }
html.dark .from-warning-50 {
  --tw-gradient-from: rgb(245 158 11 / 0.12) var(--tw-gradient-from-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(245 158 11 / 0));
}
html.dark .to-warning-100 { --tw-gradient-to: rgb(245 158 11 / 0.18) var(--tw-gradient-to-position); }
/* Acentos de KPI do relatorio de periodo — teal (Atendimentos) e violet
 * (Familias atendidas). Mesmo tratamento dos demais KPIs: o gradiente claro
 * (from-{matiz}-50 to-{matiz}-100) vira veu escuro translucido e o icone
 * -500/-600 clareia para a matiz ~-300, legivel sobre o veu. teal espelha o
 * success (acento frio/positivo); violet espelha o brand (mesma familia roxa),
 * com a matiz mais purpura p/ NAO confundir com o indigo do "Recebido".
 * teal-500 20 184 166 -> teal-300 #5eead4 | violet-500 139 92 246 -> violet-300 #c4b5fd. */
html.dark .from-teal-50 {
  --tw-gradient-from: rgb(20 184 166 / 0.12) var(--tw-gradient-from-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(20 184 166 / 0));
}
html.dark .to-teal-100 { --tw-gradient-to: rgb(20 184 166 / 0.18) var(--tw-gradient-to-position); }
html.dark .text-teal-600,
html.dark .text-teal-500 { color: #5eead4; }
html.dark .from-violet-50 {
  --tw-gradient-from: rgb(139 92 246 / 0.14) var(--tw-gradient-from-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(139 92 246 / 0));
}
html.dark .to-violet-100 { --tw-gradient-to: rgb(139 92 246 / 0.20) var(--tw-gradient-to-position); }
html.dark .text-violet-600,
html.dark .text-violet-500 { color: #c4b5fd; }
/* Gradientes neutros claros (from-surface-100 to-surface-200) -> veu de card. */
html.dark .from-surface-100 {
  --tw-gradient-from: rgb(var(--c-card-muted)) var(--tw-gradient-from-position);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(var(--c-card-muted) / 0));
}
html.dark .to-surface-200 { --tw-gradient-to: rgb(var(--c-card-muted)) var(--tw-gradient-to-position); }
html.dark .to-surface-50 { --tw-gradient-to: rgb(var(--c-canvas)) var(--tw-gradient-to-position); }
html.dark .to-surface-50\/30 { --tw-gradient-to: rgb(var(--c-canvas) / 0.3) var(--tw-gradient-to-position); }

/* --- B) SEMANTICOS TONAIS: texto sobre os fundos escurecidos -------------
 * Textos -700/-600 (escuros) clareiam para a matiz ~-300 sobre o veu escurecido
 * (regra de ouro fechada). success-300 #86efac | error-300 #fca5a5 |
 * warning-300 #fcd34d | brand-300 #a5b4fc. */
html.dark .text-success-700,
html.dark .text-success-600 { color: #86efac; }
html.dark .text-error-700,
html.dark .text-error-600 { color: #fca5a5; }
html.dark .text-warning-800,
html.dark .text-warning-700,
html.dark .text-warning-600 { color: #fcd34d; }
html.dark .text-brand-700,
html.dark .text-brand-600 { color: #a5b4fc; }
/* Textos/icones -500/-400 (acentos tonais soltos, ex.: icone do KPI) tambem
   clareiam um degrau para nao sumir no escuro. */
html.dark .text-brand-500,
html.dark .text-brand-400 { color: #a5b4fc; }
html.dark .text-success-500 { color: #86efac; }
html.dark .text-error-500,
html.dark .text-error-400 { color: #fca5a5; }
html.dark .text-warning-500 { color: #fcd34d; }

/* --- B) SEMANTICOS TONAIS: rings/bordas tonais -100/-200/-300 ------------
 * Rings/bordas claros de badge/alert (ring-{matiz}-100/-200) viram um anel da
 * mesma matiz em alpha baixo — visivel no escuro sem virar linha gritante. */
html.dark .ring-brand-100 { --tw-ring-color: rgb(99 102 241 / 0.30); }
html.dark .ring-brand-200,
html.dark .ring-brand-200\/60 { --tw-ring-color: rgb(99 102 241 / 0.35); }
html.dark .ring-brand-300\/50 { --tw-ring-color: rgb(99 102 241 / 0.40); }
html.dark .ring-success-100 { --tw-ring-color: rgb(34 197 94 / 0.30); }
html.dark .ring-success-200\/60 { --tw-ring-color: rgb(34 197 94 / 0.35); }
html.dark .ring-error-200,
html.dark .ring-error-200\/60 { --tw-ring-color: rgb(239 68 68 / 0.35); }
html.dark .ring-warning-100 { --tw-ring-color: rgb(245 158 11 / 0.30); }
html.dark .ring-warning-200\/60,
html.dark .ring-warning-200\/70 { --tw-ring-color: rgb(245 158 11 / 0.35); }
html.dark .border-error-300 { border-color: rgb(239 68 68 / 0.45); }
html.dark .border-brand-100 { border-color: rgb(99 102 241 / 0.30); }
html.dark .border-brand-200,
html.dark .border-brand-300 { border-color: rgb(99 102 241 / 0.40); }
html.dark .border-warning-200,
html.dark .border-warning-200\/50 { border-color: rgb(245 158 11 / 0.35); }
html.dark .border-success-200\/50 { border-color: rgb(34 197 94 / 0.35); }
