/* ==========================================================================
   static/css/tema_componentes.css — FASE 4b do tema da escola

   Camada de tema para componentes COMPARTILHADOS (Bootstrap 5.3 + padrões
   globais de UI). Remapeia as variáveis de componente do Bootstrap para as
   variáveis do tema (--cor-*) publicadas pelo base_dashboard.html.

   ORDEM DE CARGA (obrigatória): DEPOIS do bootstrap.min.css e ANTES dos
   CSS de app — assim os apps ainda podem sobrescrever pontualmente.

   Fallbacks = cores atuais do Bootstrap/design → zero regressão visual
   em qualquer contexto sem as variáveis do tema.
   ========================================================================== */

/* ── BOTÕES ─────────────────────────────────────────────────────────── */
.btn-primary {
    --bs-btn-bg: var(--cor-secundaria, #0d6efd);
    --bs-btn-border-color: var(--cor-secundaria, #0d6efd);
    --bs-btn-color: var(--cor-secundaria-texto, #fff);
    --bs-btn-hover-bg: color-mix(in srgb, var(--cor-secundaria, #0d6efd) 85%, black);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--cor-secundaria, #0d6efd) 80%, black);
    --bs-btn-hover-color: var(--cor-secundaria-texto, #fff);
    --bs-btn-active-bg: color-mix(in srgb, var(--cor-secundaria, #0d6efd) 78%, black);
    --bs-btn-active-border-color: color-mix(in srgb, var(--cor-secundaria, #0d6efd) 75%, black);
    --bs-btn-active-color: var(--cor-secundaria-texto, #fff);
    --bs-btn-disabled-bg: var(--cor-secundaria, #0d6efd);
    --bs-btn-disabled-border-color: var(--cor-secundaria, #0d6efd);
    --bs-btn-disabled-color: var(--cor-secundaria-texto, #fff);
}

/* ⚠️ TEXTO E CONTORNO USAM A TINTA — é a mesma regra declarada logo abaixo
   para `.text-primary`, e este bloco era o único que não a seguia.
   `--bs-btn-color` é a cor do TEXTO do botão: com a marca crua, uma escola de
   logomarca clara ficava com o rótulo a 1,36:1 sobre branco. Medido em
   02/09/2026 no `satreinamentos` (#73F277), no botão "Importar via CSV" — e
   esta camada alcança as ~160 telas do shell, então valia em todas.
   O contorno acompanha porque WCAG 1.4.11 pede 3:1 para o limite de um
   componente, e 1,36:1 não chega perto: um botão sem texto e sem borda
   visíveis não é um botão.
   O estado PREENCHIDO (hover/active) segue com a cor pura — ali o par
   `--cor-secundaria`/`--cor-secundaria-texto` já resolve, e é o que mantém a
   identidade da escola visível na interação.
   Travado por `configuracoes/test_botao_outline_contraste.py`. */
.btn-outline-primary {
    --bs-btn-color: var(--cor-secundaria-tinta, #0d6efd);
    --bs-btn-border-color: var(--cor-secundaria-tinta, #0d6efd);
    --bs-btn-hover-bg: var(--cor-secundaria, #0d6efd);
    --bs-btn-hover-border-color: var(--cor-secundaria, #0d6efd);
    --bs-btn-hover-color: var(--cor-secundaria-texto, #fff);
    --bs-btn-active-bg: var(--cor-secundaria, #0d6efd);
    --bs-btn-active-border-color: var(--cor-secundaria, #0d6efd);
    --bs-btn-active-color: var(--cor-secundaria-texto, #fff);
    --bs-btn-disabled-color: var(--cor-secundaria-tinta, #0d6efd);
    --bs-btn-disabled-border-color: var(--cor-secundaria-tinta, #0d6efd);
}

/* ── UTILITÁRIOS DE COR (ícones de título, badges, fundos) ──────────────
   TEXTO usa a TINTA; FUNDO usa a cor pura. A --cor-secundaria é a cor
   crua da logomarca: no azul padrão ela mede 3,68:1 sobre branco e
   reprova o WCAG AA (4,5:1) para texto normal. A --cor-secundaria-tinta
   é a mesma cor escurecida no servidor até ficar legível sobre fundo
   claro — é o que .pg-footer .page-link já usa. Quando a cor é FUNDO
   (.bg-primary, .btn-primary), o contraste já vem resolvido pelo par
   --cor-secundaria/--cor-secundaria-texto e NADA muda aqui. */
.text-primary {
    color: var(--cor-secundaria-tinta, #2563eb) !important;
}

.bg-primary {
    background-color: var(--cor-secundaria, #0d6efd) !important;
    color: var(--cor-secundaria-texto, #fff);
}

.link-primary {
    color: var(--cor-secundaria-tinta, #2563eb) !important;
}

/* ── UTILITÁRIOS DE COR DO BOOTSTRAP QUE REPROVAM EM FUNDO CLARO ────────
   Medidos no navegador em 02/09/2026, com o fundo efetivo:
     .text-warning    #ffc107 sobre branco = 1,63:1
     .text-secondary  #6c757d sobre #f1f5f9 = 4,36:1
     .text-muted      idem
   São classes do Bootstrap, não do projeto — por isso a correção mora aqui,
   na camada que alcança as ~160 telas do shell, e não em cada folha de app.
   Os tons novos mantêm a MESMA leitura (âmbar continua âmbar, cinza continua
   cinza) e passam sobre branco E sobre o #f1f5f9 do `.main-content`. */
.text-warning {
    color: #8a6100 !important;   /* 5,00:1 sobre branco · 4,56:1 sobre #f1f5f9 */
}

.text-secondary,
.text-muted {
    color: var(--txt-3, #5b6777) !important;   /* 5,75:1 · 5,25:1 */
}

/* O `.btn-outline-secondary` do Bootstrap usa `--bs-btn-color: #6c757d`, que
   mede 4,28:1 sobre o #f1f5f9 do `.main-content`. Mesmo tom do piso de texto:
   continua um cinza secundário, e passa nas duas superfícies. */
/* Título da PÁGINA, na faixa do shell.
 *
 * ⚠️ É um <h1> com tamanho de corpo, e isso é deliberado: o elemento é o
 * título da página (semântica), e a faixa do cabeçalho é densa demais para o
 * 2,5rem que o Bootstrap dá ao h1 (visual). Era um <h4> com `font-size`
 * inline — o que deixava 160 telas do shell SEM nenhum <h1> e quem navega por
 * leitor de tela sem para onde saltar.
 *
 * Os valores reproduzem exatamente o que o <h4> produzia, medido no navegador
 * antes da troca: 16px, peso 700, line-height 19,2px.
 */
.top-header__titulo {
    font-size: var(--fs-5, 1rem);
    line-height: 1.2;
}

/* ══════════════════════════════════════════════════════════════════════
   A CATEGORIA ANTES DO NOME DA TELA — 03/09/2026, pedido do dono

   "ícone, nome da categoria e nome da tela": ⚙ Configurações - Dados da
   Escola. Nasceu com as dezoito seções de Configurações virando páginas
   próprias — o cabeçalho dizia só "Dados da Escola", e o ícone de engrenagem
   sozinho não nomeia o lugar.

   ⚠️ A categoria é MENOS pesada que o nome da tela, e a diferença não é
   enfeite: o que a pessoa procura ao voltar para a aba é ONDE ELA ESTÁ dentro
   da categoria. Dois textos do mesmo peso na mesma linha se leem como um
   título só, e o olho não sabe onde começa o que interessa.

   ⚠️ `--txt-3` é o PISO de texto em fundo claro (4,76:1 sobre branco, 5,25:1
   sobre o cinza do shell) — não um cinza escolhido a olho. Abaixo dele o
   projeto não pinta texto.
   ══════════════════════════════════════════════════════════════════════ */
.top-header__categoria {
    color: var(--txt-3, #5b6777);
    font-weight: 500;
}

/* Alvo de toque das caixas de seleção — WCAG 2.5.8 (AA), 24x24 CSS px.
 *
 * ⚠️ O critério AA é 24px, NÃO 44px. Os 44px são o 2.5.5, que é AAA. A
 * diferença não é acadêmica: medido em 02/09/2026 no celular de 390px, o
 * produto tem **867 alvos abaixo de 44px** e **5 abaixo de 24px**. Aumentar
 * 867 controles para atender um critério AAA que ninguém pediu inflaria a
 * interface inteira; é o precedente dos "403 botões corretos" do CLAUDE.md.
 *
 * ⚠️ E o 2.5.8 tem DUAS isenções que reduzem o número de novo: alvo em linha
 * de texto (link dentro de um parágrafo) e alvo cujo vizinho mais próximo está
 * a 24px. Aplicadas, sobram 5 — todos caixa de seleção de 13 a 16px com o
 * vizinho a 5–10px, empilhadas em lista.
 *
 * `pointer: coarse` e não uma largura: o que decide é o dedo, não a janela.
 * Um navegador estreito no monitor continua tendo mouse, e engordar a caixa
 * ali seria mudar o desenho por um problema que não existe naquele contexto.
 */
/* ══════════════════════════════════════════════════════════════════════
   CAIXAS DE SELEÇÃO EMPILHADAS TÊM DE SE SEPARAR — 04/09/2026

   ⚠️ O `.form-check` do Bootstrap tem `min-height: 1.5rem` e nenhuma margem
   entre irmãos: duas seguidas ficam com passo de 26px e a caixa mede 16, o que
   deixa **10px de folga**. O WCAG 2.5.8 (AA) aceita alvo menor que 24px quando
   há espaço em volta, e o espaço exigido é `24 − metade do alvo` = 16px.
   Medido em 04/09/2026: quatro telas reprovando (área nova, prova nova,
   calendário novo, comunicados), até 15 alvos numa só.

   ⚠️ A saída NÃO é crescer a caixa no ponteiro fino. Ela é do Bootstrap e
   aparece em todo formulário do produto; mudar o tamanho mexeria no desenho de
   160 telas para resolver uma distância. O que falta é distância — e 8px entre
   irmãos levam o passo a 34 e a folga a 18.

   No TOQUE o problema não existe: a regra abaixo já cresce a caixa para
   1.5rem, e aí ela passa dos 24px por tamanho próprio.
   ══════════════════════════════════════════════════════════════════════ */
.form-check + .form-check {
    margin-top: var(--esp-2, 8px);
}

@media (pointer: coarse) {
    .form-check-input,
    input[type="checkbox"],
    input[type="radio"] {
        width: 1.5rem;
        height: 1.5rem;
        /* Bootstrap alinha a caixa ao topo da linha com um margin-top de
           .25em, calculado para 1em de altura. Crescendo a caixa, o mesmo
           valor a desalinha do rótulo. */
        margin-top: var(--esp-0, 2px);
    }
}

/* Botão de confirmação do SweetAlert2 pintado com a MARCA DA ESCOLA.
 *
 * ⚠️ O `confirmButtonColor` da biblioteca só define o FUNDO; o texto continua
 * no `--swal2-confirm-button-color: #fff` padrão. Como a marca sai da
 * logomarca da escola, o par pode ser qualquer um: no `satreinamentos`
 * (#73F277) o branco media 1,43:1 — rótulo ilegível no botão que confirma uma
 * ação. Aqui o texto vem de `texto_contraste`, o mesmo motor que resolve o
 * sidebar e os botões primários.
 *
 * Só alcança quem pede a classe `swal-btn-marca`. Os diálogos com cor FIXA
 * (vermelho de excluir, âmbar de aviso) continuam como estão — vários deles
 * também reprovam, e trocá-los muda a aparência de ~150 confirmações do
 * produto: está medido e registrado no relatório da auditoria, para decisão.
 */
.swal2-confirm.swal-btn-marca {
    --swal2-confirm-button-color: var(--cor-secundaria-texto, #0f172a);
    color: var(--cor-secundaria-texto, #0f172a);
}

.btn-outline-secondary {
    --bs-btn-color: var(--txt-3, #5b6777);
    --bs-btn-border-color: var(--txt-3, #5b6777);
    --bs-btn-hover-bg: var(--txt-3, #5b6777);
    --bs-btn-hover-border-color: var(--txt-3, #5b6777);
    --bs-btn-active-bg: var(--txt-3, #5b6777);
    --bs-btn-active-border-color: var(--txt-3, #5b6777);
    --bs-btn-disabled-color: var(--txt-3, #5b6777);
    --bs-btn-disabled-border-color: var(--txt-3, #5b6777);
}

/* ── PAGINAÇÃO ──────────────────────────────────────────────────────── */
.pagination {
    --bs-pagination-color: var(--cor-secundaria-tinta, #0d6efd);
    --bs-pagination-hover-color: color-mix(in srgb, var(--cor-secundaria, #0d6efd) 80%, black);
    --bs-pagination-focus-color: color-mix(in srgb, var(--cor-secundaria, #0d6efd) 80%, black);
    --bs-pagination-active-bg: var(--cor-secundaria, #0d6efd);
    --bs-pagination-active-border-color: var(--cor-secundaria, #0d6efd);
    --bs-pagination-active-color: var(--cor-secundaria-texto, #fff);
}

/* ── FORMULÁRIOS: checks, switches e radios marcados ────────────────── */
.form-check-input:checked {
    background-color: var(--cor-secundaria, #0d6efd);
    border-color: var(--cor-secundaria, #0d6efd);
}

/* ── NAVEGAÇÃO: pills e dropdowns ativos ────────────────────────────── */
.nav-pills {
    --bs-nav-pills-link-active-bg: var(--cor-secundaria, #0d6efd);
    --bs-nav-pills-link-active-color: var(--cor-secundaria-texto, #fff);
}

.dropdown-menu {
    --bs-dropdown-link-active-bg: var(--cor-secundaria, #0d6efd);
    --bs-dropdown-link-active-color: var(--cor-secundaria-texto, #fff);
}

/* ── PROGRESS ───────────────────────────────────────────────────────── */
.progress-bar {
    background-color: var(--cor-secundaria, #0d6efd);
}
/* Barras de status coloridas (bg-success/warning/danger) mantêm a cor
   semântica: a regra acima só vale para a cor padrão, e o .bg-* delas
   tem a mesma especificidade declarada DEPOIS no bootstrap? Não — por
   segurança, re-afirmamos as semânticas: */
.progress-bar.bg-success { background-color: var(--bs-success) !important; }
.progress-bar.bg-warning { background-color: var(--bs-warning) !important; }
.progress-bar.bg-danger  { background-color: var(--bs-danger) !important; }
.progress-bar.bg-info    { background-color: var(--bs-info) !important; }

/* ── ABAS DE CONFIGURAÇÕES (padrão .config-tabs) ────────────────────────
   Decisão de arquitetura: o marcador de aba ativa é identidade de marca,
   então a camada de tema centraliza o override em vez de editar o CSS
   do app configuracoes novamente. Mesmo padrão vale para futuras abas. */
.config-tabs .nav-link:hover {
    color: var(--cor-secundaria-tinta, #3b82f6);
}
.config-tabs .nav-link.active {
    color: var(--cor-secundaria-tinta, #3b82f6);
    border-bottom-color: var(--cor-secundaria-tinta, #3b82f6);
}

/* ── SPINNERS de carregamento padrão ────────────────────────────────── */
.spinner-border.text-primary,
.spinner-grow.text-primary {
    color: var(--cor-secundaria, #0d6efd) !important;
}
/* ── BADGES coloridos: contraste garantido sobre a secundária ─────────
   !important vence .text-white/text-bg-* do Bootstrap nos pills de
   turma e afins — texto escuro sobre secundária clara, branco sobre escura. */
.badge.bg-primary,
.badge.text-bg-primary {
    background-color: var(--cor-secundaria, #0d6efd) !important;
    color: var(--cor-secundaria-texto, #fff) !important;
}

/* ── SIDEBAR: item ativo — regra de autoridade do tema ────────────────
   .dashboard-wrapper .sidebar (0-3-1) vence as duplicatas 0-3-0 dos CSS de página,
   independente da ordem de carga. Protege contra futuras cópias. */
.dashboard-wrapper .sidebar .nav-link.active {
    background-color: var(--cor-secundaria, #3b82f6);
    color: var(--cor-secundaria-texto, #fff);
}

/* ── SIDEBAR: fundo — regra de autoridade do tema ─────────────────────
   .dashboard-wrapper .sidebar (0-1-1) vence duplicatas .sidebar (0-1-0) em CSS de
   página, independente da ordem de carga. */
.dashboard-wrapper .sidebar {
    background-color: var(--cor-primaria, #0f172a);
}

/* ── CABEÇALHO: a assinatura da escola ────────────────────────────────
   Uma faixa de 3px na cor da marca, na base do `.top-header`. Vale para as
   160 telas que estendem o shell (medido em 21/08/2026) e para as standalone
   que incluem `base_visual.html`.

   ⚠️ POR QUE FAIXA, E NÃO UM WASH DA COR NO CABEÇALHO INTEIRO (21/08/2026).
   O wash foi comparado lado a lado com esta opção, nas quatro marcas, e perde
   por três motivos:

     1. O topo é a faixa de MAIOR densidade funcional do produto — título,
        nome da escola, ações primárias, sino, relógio, avatar. O botão sólido
        de ação usa a MESMA cor da marca: pousado sobre um fundo tingido dela,
        perde a separação figura-fundo justamente onde o clique precisa ser
        mais óbvio.
     2. O sidebar já é a primária sólida e ENCOSTA no cabeçalho. Como primária
        e secundária saem da mesma logomarca, costumam ser parentes — o wash
        criaria uma terceira variação da mesma família em dois centímetros.
     3. O degradê do `.cfg-hero` (aba Fiscal) funciona por ser EXCEÇÃO: ele
        abre um assunto num fundo branco. Tingir também o topo do sistema faria
        aquela peça deixar de se destacar de alguma coisa.

   A borda é a única propriedade de borda que alguém declara neste elemento —
   medido com tinycss2 nos 9 CSS que tocam `.top-header`, incluindo os blocos
   dentro de `@media`. Seletor de 0-1-0 de propósito: a camada tem de PERDER
   para qualquer CSS de app que precise de tratamento próprio. */
.top-header {
    /* ⚠️ 2px NA TINTA, e não 3px na cor pura (ajustado em 22/08/2026 a pedido
       do dono, que reclamou que a linha "incomodava"). Três razões somadas:

       1. COR SATURADA EM ELEMENTO FINO VIBRA contra o branco — é efeito óptico,
          e a mesma cor num bloco grande (o sidebar) não produz isso;
       2. a marca de muitas escolas é vermelha, e vermelho carrega significado
          de ALERTA: a faixa competia com os botões de ação destrutiva da
          própria tela, e o olho não distinguia marca de aviso;
       3. em tela cujo cabeçalho só tem o título — sem ícone, sem botão — a
          faixa sublinha um vazio, e ali ela é o único elemento gráfico.

       A TINTA é a versão escurecida que `_tinta` já calcula para 4,5:1 medidos.
       Não vibra, continua sendo a marca, e o fallback cobre o painel central,
       que não tem tema de escola. */
    border-bottom: 2px solid var(--cor-secundaria-tinta, var(--cor-secundaria, #3b82f6));
    /* ⚠️ O RESPIRO NÃO PODE DEPENDER DE HAVER BOTÃO NO CABEÇALHO. Telas com
       ações ("Importar via CSV", "Matricular Aluno") ganhavam altura pelos
       botões, e o título ficava naturalmente afastado da faixa; telas sem
       ação nenhuma — importação, exportação, comprovantes — tinham a altura
       do próprio texto, e o título encostava na linha. Duas telas do mesmo
       produto pareciam desenhadas por pessoas diferentes, e a diferença era
       um acidente do conteúdo. */
    padding-bottom: var(--esp-2, 8px);
}

/* ── WIZARD: etapa ativa — regra de autoridade do tema ────────────────
   Cobre as cópias do padrão de steps nos apps (0-4-0 vence 0-3-0).
   Etapas concluídas ficam VERDES por semântica — não temacizar. */
.main-content .wizard-step.active .step-circle {
    background: var(--cor-secundaria, #3b82f6);
    color: var(--cor-secundaria-texto, #fff);
}

/* ══════════════════════════════════════════════════════════════════════════
   Paginação unificada — footer do partial partials/paginacao.html
   ══════════════════════════════════════════════════════════════════════════ */
.pg-footer {
    display: flex; flex-wrap: wrap; gap: var(--esp-3, 12px);
    align-items: center; justify-content: space-between;
    /* fica colado no rodapé da área de rolagem: a paginação nunca "sai da tela" */
    position: sticky; bottom: 0; z-index: 5;
    margin-top: var(--esp-2, 8px); padding: var(--esp-3, 12px) var(--esp-4, 16px);
    background: rgba(255, 255, 255, .96);
    backdrop-filter: blur(4px);
    border-top: 1px solid #e2e8f0;
    border-radius: 0 0 12px 12px;
}
.pg-info { font-size: var(--fs-3, .82rem); color: #5b6777; }
.pg-info strong { color: #334155; }
.pg-footer .pagination { flex-wrap: wrap; }
.pg-footer .page-link {
    min-width: 34px; text-align: center; border-radius: 8px !important;
    color: var(--cor-secundaria-tinta, #2563eb);
}
.pg-footer .page-item.active .page-link {
    background: var(--cor-secundaria, #3b82f6);
    border-color: var(--cor-secundaria, #3b82f6);
    color: var(--cor-secundaria-texto, #fff);
}

/* ══════════════════════════════════════════════════════════════════════════
   Estado vazio unificado — card com ícone + mensagem (partial estado_vazio.html)
   ══════════════════════════════════════════════════════════════════════════ */
.estado-vazio {
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: var(--raio-3, 14px);
    box-shadow: 0 2px 10px rgba(15, 23, 42, .05);
    margin: var(--esp-5, 20px) 0;
    padding: var(--esp-8, 48px) var(--esp-6, 24px);
    text-align: center;
}
/* Dentro de um card/tabela da listagem, o estado vazio vira só conteúdo
   centralizado ocupando a largura toda (modelo da listagem de alunos,
   colspan) — sem card-dentro-de-card.

   ⚠️ ESTA É UMA LISTA, E LISTA ESQUECE. A agenda apareceu com cartão dentro de
   cartão (22/08/2026) porque `.agenda-painel` não estava aqui: o painel branco
   da tela, com borda e sombra, ganhou outro painel branco dentro, esticado até
   o pé da página pelo `alto`. Nada denunciava — as duas peças estão certas
   isoladamente; erra o ENCAIXE.
   Travado por `configuracoes/test_camada_visual.EstadoVazioDentroDeCartaoTests`,
   que varre os templates e cobra que todo cartão que contenha o partial esteja
   nesta régua. Cartão novo com estado vazio dentro → acrescente a classe aqui. */
.card-body .estado-vazio,
.imp-card .estado-vazio,
.com-card .estado-vazio,
.rh-card .estado-vazio,
.agenda-painel .estado-vazio,
.oc-card .estado-vazio,
.mat-card .estado-vazio,
.fiscal-bloco .estado-vazio,
td .estado-vazio {
    margin: 0;
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
    padding: var(--esp-8, 48px) var(--esp-5, 20px);
}
/* ══════════════════════════════════════════════════════════════════════
   VARIANTE "OCUPA A TELA" (21/08/2026)

   Tela cujo conteúdo INTEIRO é a lista — boletim, aprovação, redações — não
   deve mostrar uma tarja de 7rem no topo com o resto em branco: o cartão vazio
   ocupa o que sobra, e o conteúdo fica no MEIO. Cartão alto com o texto grudado
   no topo parece uma tela que carregou pela metade.

   ⚠️ `min-height` em unidade de VIEWPORT, e NUNCA `flex: 1` com `height: 100%`
   herdado. A segunda forma escondeu a listagem de alunos inteira em 21/08/2026:
   a faixa de conteúdo do shell se resolve PELO conteúdo, então `height: 100%`
   vira `auto`, o flex não tem o que dividir e a área colapsa para zero.
   `calc(100vh - …)` sempre resolve, e o `justify-content: center` centraliza
   dentro do que existir — inclusive se a conta errar por alguns pixels.

   O desconto de 16rem cobre cabeçalho do shell, filtros e respiros. É
   estimativa, mas erra para o lado seguro: sobrando, aparece uma faixa fina;
   faltando, a página rola, como sempre fez.
   ══════════════════════════════════════════════════════════════════════ */
.estado-vazio--tela {
    min-height: calc(100vh - 16rem);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
@media (max-width: 767.98px) {
    /* No celular a tela é curta e a página inteira rola: um cartão de altura de
       viewport empurraria o resto para fora sem nada ganhar. */
    .estado-vazio--tela { min-height: 0; }
}

.estado-vazio__ic {
    width: 76px; height: 76px; border-radius: 50%;
    margin: 0 auto var(--esp-4, 16px);
    display: flex; align-items: center; justify-content: center;
    font-size: var(--fs-9, 2rem);
    color: var(--cor-secundaria, #3b82f6);
    background: var(--cor-secundaria-suave, #eff6ff);
}
.estado-vazio__titulo { font-size: var(--fs-5, 1rem); font-weight: 700; color: #334155; margin: 0 0 var(--esp-1, 4px); }
.estado-vazio__texto  { font-size: var(--fs-4, .9rem); color: var(--txt-3, #5b6777); margin: 0 0 var(--esp-4, 16px); line-height: 1.45; }
.estado-vazio__acao   { border-radius: var(--raio-2, 10px); font-weight: 600; }
/* Duas ações lado a lado (criar × importar). Quebram para baixo no celular em
   vez de encolher: botão apertado numa tela vazia é o pior lugar para exigir
   pontaria, porque é o único alvo da página. */
.estado-vazio__acoes {
    display: flex; flex-wrap: wrap;
    gap: var(--esp-2, 8px); justify-content: center;
}

/* ══════════════════════════════════════════════════════════════════════════
   Combobox de filtro (páginas de análise) — visual mais cuidado
   ══════════════════════════════════════════════════════════════════════════ */
.sit-seletor .form-select,
.diag-seletor .form-select,
.cmp-seletor .form-select {
    border-radius: var(--raio-2, 10px);
    border: 1.5px solid #e2e8f0;
    padding: var(--esp-2, 8px) var(--esp-7, 32px) var(--esp-2, 8px) var(--esp-4, 16px);
    height: auto;
    font-weight: 500;
    color: #1e293b;
    background-color: #fff;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
    transition: border-color .15s, box-shadow .15s;
}
.sit-seletor .form-select:hover,
.diag-seletor .form-select:hover,
.cmp-seletor .form-select:hover { border-color: #cbd5e1; }
.sit-seletor .form-select:focus,
.diag-seletor .form-select:focus,
.cmp-seletor .form-select:focus {
    border-color: var(--cor-secundaria, #3b82f6);
    box-shadow: 0 0 0 3px var(--cor-secundaria-suave, #eff6ff);
}

/* ══════════════════════════════════════════════════════════════════════════
   Páginas de análise zeram o padding do main-content (voltam a fluxo rolável);
   o top-header (título) precisa do recuo horizontal p/ não colar no sidebar.
   ══════════════════════════════════════════════════════════════════════════ */
.main-content.sit-page  > .top-header,
.main-content.diag-page > .top-header,
.main-content.cmp-page  > .top-header {
    padding-left: var(--esp-6, 24px);
    padding-right: var(--esp-6, 24px);
}

/* ══════════════════════════════════════════════════════════════════════════
   FOCO DE TECLADO VISÍVEL — regra de autoridade do tema
   ──────────────────────────────────────────────────────────────────────────
   16 CSS de app zeram o outline (`outline: none` em campos, botões e cards
   clicáveis) e só 2 devolvem algum :focus-visible. Resultado: quem navega
   por Tab não enxerga onde está. Aqui devolvemos o anel, uma vez só, para
   toda a superfície do dashboard.

   COR: usa a TINTA, nunca a --cor-secundaria pura. A tinta é a única
   variante garantida legível sobre fundo claro qualquer que seja a
   logomarca da escola — o anel de foco não pode sumir na escola de cor
   clara. Mesmo critério de .text-primary/.link-primary lá em cima.

   ESPECIFICIDADE (o porquê do :focus-visible repetido): este arquivo carrega
   ANTES dos CSS de app, então empate de especificidade perde por ordem de
   carga. Os `outline: none` de app chegam no máximo a 0-2-0 (`.classe:focus`,
   ex.: .bq-busca-input:focus, .cur-input:focus). Repetir o :focus-visible
   sobe estas regras para 0-2-1 (seletor de elemento) e 0-3-0 ([tabindex]),
   vencendo por especificidade — sem !important, que travaria o app que
   PRECISE de um foco próprio. Repetir a pseudo-classe é inócuo na
   correspondência: só conta na especificidade.

   :focus-visible (e não :focus) preserva o comportamento atual do mouse —
   o anel aparece na navegação por teclado, não no clique.
   ══════════════════════════════════════════════════════════════════════════ */
a:focus-visible:focus-visible,
button:focus-visible:focus-visible,
input:focus-visible:focus-visible,
select:focus-visible:focus-visible,
textarea:focus-visible:focus-visible,
[tabindex]:focus-visible:focus-visible {
    outline: 2px solid var(--cor-secundaria-tinta, #2563eb);
    outline-offset: 2px;
}

/* ══════════════════════════════════════════════════════════════════════════
   ALVO DE TOQUE NO MOBILE — .btn-sm
   ──────────────────────────────────────────────────────────────────────────
   .btn-sm aparece 489 vezes nos templates e no desktop é a escolha certa;
   no celular ele fecha em ~31px de altura, contra os 44px recomendados para
   alvo de toque. Só no mobile, portanto, garantimos a altura mínima.

   BREAKPOINT 768px: é o dominante do projeto (38 das ~120 media queries) e o
   MESMO em que o base_dashboard troca o sidebar pelo drawer — manter um só
   ponto de virada evita estados intermediários incoerentes.

   POR QUE PADDING E NÃO `display: inline-flex`:
   trocar o display quebra o espaçamento de ícone+rótulo. Em `inline-block` o
   nó de texto que segue `</i>` preserva o espaço inicial; virando container
   flex, esse texto vira um item anônimo e o whitespace da borda COLAPSA —
   o ícone gruda na palavra em todo botão cujo <i> não tenha `me-*`
   (19 casos reais, ex.: rh/dashboard.html:118 e rh/folha_detalhe.html:39).
   Declarar `gap` também não serve: dobraria o respiro nos botões que JÁ têm
   `me-1`. Crescer pelo padding mantém o modelo de layout intacto e centra o
   rótulo naturalmente, porque o padding é simétrico.

   A CONTA: .btn-sm do Bootstrap 5.3 fecha em ~31px (21px de linha + 2×4px de
   padding + 2×1px de borda). Para 44px o padding vertical precisa ir a
   ~10,5px = .66rem. O `min-height` fica como piso, cobrindo botão com
   line-height customizado por CSS de app.

   Especificidade 0-2-0 (.btn.btn-sm): `.btn-sm` nunca anda sem `.btn`, e o
   par protege a regra de um CSS de app que venha depois com `.btn-sm` 0-1-0.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
    .btn.btn-sm {
        padding-block: var(--esp-3, 12px);
        min-height: 44px;
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   AVATAR DE INICIAIS (.avatar-iniciais)
   ──────────────────────────────────────────────────────────────────────────
   Substitui a chamada externa ao ui-avatars.com: hoje o avatar do usuário
   depende de um terceiro para renderizar (latência, offline, e o nome do
   usuário viajando na querystring de outro domínio). As iniciais são
   montadas no template e pintadas aqui.

   CONTRATO DE COR: fundo --cor-secundaria e texto --cor-secundaria-texto —
   esse par já vem com contraste calculado no servidor (base_dashboard),
   então é o único combo seguro para texto sobre a cor da marca. NÃO trocar
   por --cor-secundaria-tinta: aqui a secundária é FUNDO, não texto.

   flex-shrink: 0 porque o avatar quase sempre mora num flex ao lado do nome;
   sem isso ele vira elipse quando o nome é longo.

   Dois tamanhos, os mesmos já usados hoje: 28px (padrão, topbar) e o
   modificador --34 (listas de pessoas do dashboard do funcionário).
   ══════════════════════════════════════════════════════════════════════════ */
.avatar-iniciais {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: var(--cor-secundaria, #3b82f6);
    color: var(--cor-secundaria-texto, #fff);
    font-size: .7rem;
    font-weight: 600;
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: .02em;
    overflow: hidden;
    user-select: none;
}
.avatar-iniciais--34 {
    width: 34px;
    height: 34px;
    font-size: .8rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   ██ CAMADA DE ACABAMENTO — 19/08/2026
   ──────────────────────────────────────────────────────────────────────────
   Tudo daqui para baixo é ADITIVO e vale para as 137 telas que estendem o
   base_dashboard, sem tocar em nenhum dos 125 CSS de app. O motivo de estar
   AQUI e não espalhado: este arquivo carrega depois do bootstrap e antes do
   CSS de app, então refina o padrão e continua perdendo para quem precisar
   de um tratamento próprio. Nenhuma regra abaixo muda estrutura, grid ou
   espaçamento de tela existente — só o acabamento.

   O que a auditoria mediu e motivou cada bloco está anotado em cada seção.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── TIPOGRAFIA: o acabamento da Inter ──────────────────────────────────────
   A Inter é hospedada no projeto desde 16/08 e até 19/08 NUNCA renderizou:
   um comentário fechado duas vezes no tokens.css descartava a declaração
   `--fonte` inteira (medido com tinycss2 — as outras 38 passavam, só ela
   caía), e como ninguém declara 'Inter' fora do @font-face, o produto todo
   caía no system-ui. Corrigido o token, estas três linhas são o que separa
   "a fonte carregou" de "a fonte está bem ajustada":

   - o antialiasing subpixel do WebKit engorda a Inter em fundo claro;
   - `cv11` troca o "a" de dois andares por um de um andar nos tamanhos
     pequenos, que é onde a interface vive;
   - `tabular-nums` é o detalhe que designer de dashboard percebe primeiro:
     sem ele, uma coluna de valores dança horizontalmente a cada dígito,
     porque o "1" é mais estreito que o "8". */
body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-feature-settings: 'cv11' 1, 'ss01' 1;
    text-rendering: optimizeLegibility;
}

/* Onde o número é dado, e não texto: tabela, métrica, badge de contagem.
   Alinhamento vertical de coluna numérica é requisito de leitura, não
   enfeite — é o que permite comparar dois valores sem lê-los. */
.table td,
.table th,
.metric-card .valor,
.metric-value,
.d360-kpi-valor,
.pg-info,
time,
.badge {
    font-variant-numeric: tabular-nums;
}

/* ── CAMPOS: o foco passa a ser da ESCOLA ───────────────────────────────────
   Medido no próprio arquivo do CDN (bootstrap@5.3.0):

       .form-control:focus { border-color:#86b7fe;
                             box-shadow:0 0 0 .25rem rgba(13,110,253,.25) }

   Os dois valores são LITERAIS — ao contrário de `.btn`, o campo não foi
   convertido para custom properties no 5.3, então não há variável para
   remapear e o override precisa ser direto. Consequência até aqui: o produto
   inteiro calcula o contraste da marca no servidor, pinta sidebar, botões e
   badges com a cor da escola — e todo campo de formulário piscava AZUL
   BOOTSTRAP. Numa escola de marca verde ou laranja, era o elemento mais
   tocado da interface contradizendo a identidade.

   Anel em duas camadas de propósito: o `outline` da seção de foco de teclado
   (acima) é o que sobrevive ao modo de alto contraste do Windows, onde
   `box-shadow` é simplesmente descartado; o halo abaixo é o que dá o
   acabamento no clique de mouse. Na mesma família de cor os dois leem como
   um anel só.

   A cor do halo é a --cor-secundaria PURA, não a tinta: aqui ela é
   preenchimento translúcido, não texto, então o critério de 4,5:1 não se
   aplica — é o mesmo raciocínio de .bg-primary contra .text-primary. */
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    border-color: var(--cor-secundaria, #3b82f6);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--cor-secundaria, #3b82f6) 20%, transparent);
}

/* Hover de campo: sinal de que o alvo é interativo antes do clique. O
   Bootstrap não dá nenhum, e sem ele um formulário longo parece impresso. */
.form-control:hover:not(:focus):not(:disabled):not([readonly]),
.form-select:hover:not(:focus):not(:disabled) {
    border-color: var(--borda-forte, #cbd5e1);
}
.form-control,
.form-select {
    transition: border-color var(--transicao, .15s ease),
                box-shadow var(--transicao, .15s ease);
}

/* Campo inválido mantém a semântica vermelha — marca não sequestra erro. */
.form-control.is-invalid:focus,
.form-select.is-invalid:focus {
    border-color: var(--erro, #dc2626);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--erro, #dc2626) 20%, transparent);
}

/* ── BOTÕES: o feedback de PRESSIONAR ───────────────────────────────────────
   A varredura achou 339 regras de :hover contra 15 de :active em todo o
   projeto. Ou seja: o botão reage ao ponteiro chegar e não reage ao clique.
   Em telas de ação demorada (gerar prova, importar CSV) isso lê como "não
   funcionou" e produz o clique repetido.

   1px é deslocamento deliberado: o suficiente para o olho registrar, pouco
   o bastante para não reflowar nada — `transform` não participa do layout. */
/* ══════════════════════════════════════════════════════════════════════
   O BOOTSTRAP PASSA A USAR A ESCALA DO PROJETO — 03/09/2026

   ⚠️ ELE TINHA A PRÓPRIA ESCALA, E ELA VAZAVA PARA TODA TELA. O Bootstrap 5.3
   vendorizado traz 4px (`-sm`), 6px (padrão), 8px (`-lg`), 16px (`-xl`) — e o
   projeto pratica 6 / 10 / 14. Medido no navegador: `.btn` e `.btn-sm` em 4px,
   `.page-link` em 8px, `.form-select` em 4px. Como botão, paginação e campo
   aparecem em quase toda página, eram três arredondamentos alheios somados a
   cada tela, e nenhum deles decidido por ninguém daqui.

   ⚠️ A TRADUÇÃO É NAS VARIÁVEIS DELE, não em regra por componente. O Bootstrap
   deriva `.card`, `.alert`, `.dropdown-menu`, `.modal-content`, `.badge`,
   `.form-control`, `.page-link` e o resto de `--bs-border-radius*`; reescrever
   componente a componente seria uma lista para envelhecer, e a próxima classe
   usada ficaria de fora sem nada denunciar.

   ⚠️ Os degraus dele são MAIS finos que os nossos, então há convergência: `-sm`
   e o padrão caem os dois em `--raio-1`. É de propósito — a escala do projeto
   tem três degraus porque três papéis foram identificados (input/badge, botão
   e card pequeno, card e modal), não porque seis valores fossem necessários.
   ══════════════════════════════════════════════════════════════════════ */
:root {
    --bs-border-radius:      var(--raio-1, 6px);    /* input, badge, botão   */
    --bs-border-radius-sm:   var(--raio-1, 6px);
    --bs-border-radius-lg:   var(--raio-2, 10px);   /* card pequeno          */
    --bs-border-radius-xl:   var(--raio-3, 14px);   /* card, modal           */
    --bs-border-radius-xxl:  var(--raio-3, 14px);
    --bs-border-radius-pill: var(--raio-pill, 999px);
}

.btn {
    transition: background-color var(--transicao, .15s ease),
                border-color var(--transicao, .15s ease),
                color var(--transicao, .15s ease),
                box-shadow var(--transicao, .15s ease),
                transform .06s ease;
}
.btn:active:not(:disabled):not(.disabled) {
    transform: translateY(1px);
}

/* Botão cheio ganha elevação de repouso e a perde ao ser pressionado — é o
   que dá a leitura física de "afundou". Só nos preenchidos: em .btn-outline
   e .btn-link a sombra sujaria a borda fina. */
.btn-primary,
.btn-success,
.btn-danger,
.btn-warning,
.btn-info,
.btn-dark,
.btn-secondary {
    box-shadow: var(--sombra-1, 0 1px 3px rgba(15, 23, 42, .06));
}
.btn-primary:hover,
.btn-success:hover,
.btn-danger:hover,
.btn-warning:hover,
.btn-info:hover,
.btn-dark:hover,
.btn-secondary:hover {
    box-shadow: var(--sombra-2, 0 2px 8px rgba(15, 23, 42, .08));
}
.btn-primary:active,
.btn-success:active,
.btn-danger:active,
.btn-warning:active,
.btn-info:active,
.btn-dark:active,
.btn-secondary:active {
    box-shadow: inset 0 1px 3px rgba(15, 23, 42, .18);
}

/* Botão desabilitado: o Bootstrap só baixa a opacidade, o que mantém o
   cursor de mão e sugere que o clique vale. */
.btn:disabled,
.btn.disabled {
    cursor: not-allowed;
    box-shadow: none;
}

/* ── TABELAS (.table) — 71 das 96 do projeto ────────────────────────────────
   As 25 restantes têm classe própria (.grade-table de lançar notas,
   .sit-tabela, .bol-resumo-tabela…) e ficam intocadas de propósito: o
   cabeçalho `sticky` da grade de notas depende do fundo declarado no CSS
   daquele app, e é a regressão que o CLAUDE.md já registra. Escopar em
   `.table` foi conferido tabela a tabela, não presumido.

   `table-striped` aparece ZERO vez nos templates e `table-hover` em 27 de
   96 — as listagens do produto são grades chapadas, em que o olho perde a
   linha ao atravessar da primeira à última coluna. O hover abaixo vale para
   toda `.table`, mesmo sem a classe: seguir a linha com o ponteiro é
   comportamento esperado de tabela de dados, não decoração opcional. */
.table {
    --bs-table-hover-bg: color-mix(in srgb, var(--cor-secundaria, #3b82f6) 6%, transparent);
    --bs-table-striped-bg: var(--sup-1, #f8fafc);
    --bs-table-border-color: var(--borda, #e2e8f0);
}
.table > tbody > tr {
    transition: background-color var(--transicao, .15s ease);
}
.table:not(.table-hover) > tbody > tr:hover > * {
    background-color: color-mix(in srgb, var(--cor-secundaria, #3b82f6) 6%, transparent);
}

/* Cabeçalho: o papel dele é rotular a coluna, não competir com o dado.
   Caixa alta pequena com espacejamento é o padrão que separa rótulo de
   conteúdo sem precisar de régua nem de fundo forte.
   `text-transform`, e nunca a classe `text-uppercase` escrita como
   declaração — o CLAUDE.md registra os 2 arquivos em que isso já falhou
   em silêncio. */
.table > thead > tr > th {
    background-color: var(--sup-1, #f8fafc);
    color: var(--txt-3, #5b6777);
    font-size: var(--fs-2, .75rem);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    border-bottom: 1px solid var(--borda, #e2e8f0);
    white-space: nowrap;
}

/* ── ELEVAÇÃO COERENTE ──────────────────────────────────────────────────────
   A varredura contou 147 sombras distintas em 125 arquivos — o produto tem
   hoje quase uma sombra por lugar onde precisou de uma. Os quatro degraus do
   tokens.css existem para isso; aqui eles passam a valer nas superfícies
   flutuantes do Bootstrap, que são as que aparecem em toda tela. */
.dropdown-menu {
    box-shadow: var(--sombra-3, 0 6px 20px rgba(15, 23, 42, .10));
    border-color: var(--borda, #e2e8f0);
    border-radius: var(--raio-2, 10px);
}
.modal-content,
.offcanvas {
    box-shadow: var(--sombra-4, 0 12px 32px rgba(15, 23, 42, .14));
    border-color: var(--borda, #e2e8f0);
}
.modal-content {
    border-radius: var(--raio-3, 14px);
}
.toast {
    box-shadow: var(--sombra-3, 0 6px 20px rgba(15, 23, 42, .10));
}

/* O véu do modal do Bootstrap é preto a 50%, que achata a cor por baixo.
   Navy translúcido é a mesma tinta das sombras — o fundo escurece sem
   acinzentar a marca da escola. */
.modal-backdrop.show {
    background-color: rgba(15, 23, 42, .55);
    opacity: 1;
}

/* Item de dropdown: alvo inteiro reagindo, não só o texto. */
.dropdown-item {
    border-radius: var(--raio-1, 6px);
    transition: background-color var(--transicao, .15s ease),
                color var(--transicao, .15s ease);
}
.dropdown-menu {
    padding: var(--esp-1, 4px);
}

/* ── ABAS ───────────────────────────────────────────────────────────────────
   .nav-tabs não tinha nenhuma linha de tema: a aba ativa do Bootstrap é
   marcada por borda cinza, então a tela não diz de que escola é. */
.nav-tabs {
    --bs-nav-tabs-link-active-color: var(--cor-secundaria-tinta, #2563eb);
    --bs-nav-tabs-link-active-border-color: var(--borda, #e2e8f0) var(--borda, #e2e8f0) #fff;
    --bs-nav-tabs-link-hover-border-color: var(--sup-2, #f1f5f9) var(--sup-2, #f1f5f9) var(--borda, #e2e8f0);
    --bs-nav-tabs-border-radius: var(--raio-1, 6px);
}

/* ── ALERTAS ────────────────────────────────────────────────────────────────
   Barra lateral no lugar da borda em volta: o olho encontra a faixa de cor
   antes de ler, e a mensagem ganha o alinhamento de texto que a caixa
   fechada tirava. Semântica preservada — alerta não usa cor de marca. */
.alert {
    border: 1px solid var(--borda, #e2e8f0);
    border-left-width: 4px;
    border-radius: var(--raio-2, 10px);
    box-shadow: var(--sombra-1, 0 1px 3px rgba(15, 23, 42, .06));
}
.alert-success { border-left-color: var(--ok, #16a34a); }
.alert-danger  { border-left-color: var(--erro, #dc2626); }
.alert-warning { border-left-color: var(--alerta, #f59e0b); }
.alert-info,
.alert-primary { border-left-color: var(--cor-secundaria, #3b82f6); }

/* ── BADGES ─────────────────────────────────────────────────────────────────
   Tipografia de rótulo: o badge é lido de relance, e peso alto em corpo
   minúsculo é o que o torna legível sem aumentar de tamanho. */
.badge {
    font-weight: 600;
    letter-spacing: .01em;
    border-radius: var(--raio-1, 6px);
}
.badge.rounded-pill {
    border-radius: var(--raio-pill, 999px);
}

/* ── BARRA DE ROLAGEM ───────────────────────────────────────────────────────
   O sidebar já refina a sua (tenant_dashboard.css). O conteúdo — que é onde
   a rolagem realmente acontece — ficava com a barra padrão do sistema, larga
   e cinza. Firefox usa `scrollbar-width`; WebKit usa o pseudo-elemento; os
   dois estão aqui porque nenhum dos dois cobre o outro. */
.main-content,
.table-responsive,
.modal-body,
.offcanvas-body {
    scrollbar-width: thin;
    scrollbar-color: var(--borda-forte, #cbd5e1) transparent;
}
.main-content::-webkit-scrollbar,
.table-responsive::-webkit-scrollbar,
.modal-body::-webkit-scrollbar,
.offcanvas-body::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}
.main-content::-webkit-scrollbar-track,
.table-responsive::-webkit-scrollbar-track,
.modal-body::-webkit-scrollbar-track,
.offcanvas-body::-webkit-scrollbar-track {
    background: transparent;
}
.main-content::-webkit-scrollbar-thumb,
.table-responsive::-webkit-scrollbar-thumb,
.modal-body::-webkit-scrollbar-thumb,
.offcanvas-body::-webkit-scrollbar-thumb {
    background: var(--borda-forte, #cbd5e1);
    border-radius: var(--raio-pill, 999px);
    /* a borda transparente afina o polegar sem estreitar a área de arraste:
       o alvo continua com 10px, o desenho fica com 6px */
    border: 2px solid transparent;
    background-clip: content-box;
}
.main-content::-webkit-scrollbar-thumb:hover,
.table-responsive::-webkit-scrollbar-thumb:hover,
.modal-body::-webkit-scrollbar-thumb:hover,
.offcanvas-body::-webkit-scrollbar-thumb:hover {
    background: var(--txt-3, #5b6777);
    background-clip: content-box;
}

/* ── SELEÇÃO DE TEXTO ───────────────────────────────────────────────────────
   Detalhe pequeno e presente em toda tela: o azul de seleção do sistema é o
   último lugar em que a marca da escola ainda não chegava. */
::selection {
    background: color-mix(in srgb, var(--cor-secundaria, #3b82f6) 26%, transparent);
    color: inherit;
}

/* ── MOVIMENTO: quem pediu para desligar ────────────────────────────────────
   Só 7 dos 125 CSS respeitam a preferência do sistema. Como a camada acima
   ACRESCENTA transição a botão, campo e linha de tabela, ela também precisa
   saber recuar — senão o refinamento vira exatamente o problema que a
   preferência existe para evitar (enjoo de movimento, distração).

   Não é `animation: none`: zerar animação apaga spinner de carregamento e a
   tela passa a mentir sobre estar trabalhando. A duração quase nula preserva
   o estado final de cada transição. */
@media (prefers-reduced-motion: reduce) {
    .btn,
    .form-control,
    .form-select,
    .table > tbody > tr,
    .dropdown-item {
        transition-duration: .01ms;
    }
    .btn:active:not(:disabled):not(.disabled) {
        transform: none;
    }
}

/* ── LINKS DE TEXTO: a marca chega ao último lugar que faltava ──────────────
   Medido no arquivo do CDN:

       a { color: rgba(var(--bs-link-color-rgb), …) }
       --bs-link-color-rgb: 13,110,253

   É o mesmo caso do campo de formulário: azul do Bootstrap, em qualquer
   escola. São 130 links de texto nos templates fora de botão, menu e
   paginação — todos azuis, ao lado de botões pintados com a cor da escola.

   A variável do Bootstrap pede RGB decomposto e a marca chega como hex, então
   não há como remapeá-la; o caminho é declarar a cor.

   A TINTA e não a --cor-secundaria pura: link é TEXTO sobre fundo claro, e a
   tinta é a única variante escurecida até 4,5:1 medidos, qualquer que seja a
   logomarca. Mesmo critério de .text-primary e .link-primary acima.

   Escopado em .main-content: é a área de conteúdo, sempre clara. O sidebar
   tem fundo da marca e cor própria, e um link temático ali poderia sumir.
   As exclusões são os papéis que já trazem cor própria (botão, menu,
   paginação).

   ⚠️ `:where()` NOS DOIS LADOS, e é o detalhe que decide se esta regra é
   segura. `:not()` herda a especificidade do que está dentro dele: a primeira
   versão desta regra usava `:not(.btn):not(.nav-link)…` com seis classes e
   fechava em 0-7-1 — mais específica que praticamente todo CSS de app, ou
   seja, passaria por cima da cor de link de qualquer tela que já tivesse
   escolhido a sua. Era o oposto do contrato deste arquivo, que perde de
   propósito para o app. `:where()` tem especificidade ZERO, então o seletor
   inteiro fecha em 0-0-1: empata com o `a` do Bootstrap e vence por ordem de
   carga, que é exatamente o alcance pretendido, e perde para qualquer
   `.classe` de app.

   O sublinhado do Bootstrap FICA. Cor sozinha não distingue link para quem
   não diferencia matiz, e os 93 `text-decoration: none` dos CSS de app já
   tiram o traço onde o link não é texto corrido. */
:where(.main-content) a:not(:where(.btn, .nav-link, .dropdown-item, .page-link, .badge, .list-group-item)) {
    color: var(--cor-secundaria-tinta, #2563eb);
    text-underline-offset: .16em;
    text-decoration-thickness: from-font;
    transition: color var(--transicao, .15s ease);
}
:where(.main-content) a:not(:where(.btn, .nav-link, .dropdown-item, .page-link, .badge, .list-group-item)):hover {
    color: color-mix(in srgb, var(--cor-secundaria-tinta, #2563eb) 80%, black);
}

/* ── CARTÕES: o arredondamento para de destoar ──────────────────────────────
   O `.card` do Bootstrap arredonda em .375rem (6px). Os cartões escritos à
   mão no projeto usam 10, 12 ou 14px — 12px sozinho aparece 247 vezes. O
   resultado é um cartão do Bootstrap ao lado de um cartão de app com cantos
   visivelmente diferentes, na mesma tela.

   São variáveis de componente do próprio Bootstrap, então isto é remapeamento
   e não override: qualquer CSS de app que já defina o seu continua vencendo. */
.card {
    --bs-card-border-radius: var(--raio-3, 14px);
    --bs-card-inner-border-radius: calc(var(--raio-3, 14px) - 1px);
    --bs-card-border-color: var(--borda, #e2e8f0);
    --bs-card-cap-bg: var(--sup-1, #f8fafc);
}

/* ══════════════════════════════════════════════════════════════════════
   CABEÇALHO DE TELA DE LISTAGEM — o "hero compacto" (22/08/2026)

   Aprovado por comparativo em escala real contra as outras duas opções: sem
   cabeçalho (o que havia) e o `cfg-hero` cheio das Configurações. O critério
   não foi estética — foi ALTURA: numa listagem, cabeçalho é linha de dado que
   deixa de aparecer, e com a paginação medindo a tela isso deixou de ser espaço
   em branco e virou registro que o servidor não traz.

   Medido: ~1,5 linha de tabela, contra ~3 do hero cheio.

   Mora aqui e não num CSS de app porque alcança as 160 telas do shell de uma
   vez — mesma razão da faixa da cor da escola no `.top-header`.
   ══════════════════════════════════════════════════════════════════════ */
.tela-hero {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--esp-4, 16px);
    flex-wrap: wrap;
    padding: var(--esp-2, 8px) var(--esp-3, 12px);
    margin-bottom: var(--esp-3, 12px);
    /* O mesmo degradê de 12% da marca sobre branco do `.cfg-hero`. Uma segunda
       receita de degradê divergiria da primeira na primeira correção feita numa
       delas. */
    background: linear-gradient(180deg,
                var(--cor-secundaria-suave, #eff6ff), var(--sup-0, #ffffff) 78%);
    border: 1px solid var(--borda, #e2e8f0);
    border-radius: var(--raio-3, 14px);
}

.tela-hero__id { display: flex; align-items: center; gap: var(--esp-3, 12px); min-width: 0; }

.tela-hero__ic {
    width: 2rem; height: 2rem;
    border-radius: var(--raio-2, 10px);
    flex: none;
    display: grid; place-items: center;
    font-size: var(--fs-4, .9rem);
    /* O par cor/texto vem resolvido do servidor por `texto_contraste`, que mede
       contraste WCAG em vez de decidir por limiar de luminância. Vale para
       qualquer logomarca, inclusive as claras. */
    background: var(--cor-secundaria, #3b82f6);
    color: var(--cor-secundaria-texto, #fff);
}

.tela-hero__tt {
    font-size: var(--fs-5, 1rem);
    font-weight: 700;
    line-height: 1.3;
    margin: 0;
    min-width: 0;
    color: var(--txt-1, #1e293b);
}

/* O recorte ativo é o que este cabeçalho acrescenta de fato — o nome da tela já
   está na faixa do shell. Peso menor porque ele COMPLEMENTA o título, não
   compete com ele. */
.tela-hero__recorte {
    font-weight: 500;
    color: var(--txt-2, #475569);
}
.tela-hero__recorte::before {
    content: "—";
    margin: 0 var(--esp-2, 8px);
    color: var(--txt-3, #5b6777);
    font-weight: 400;
}

.tela-hero__contagem {
    font-size: var(--fs-2, .75rem);
    font-weight: 600;
    color: var(--txt-3, #5b6777);
    background: var(--sup-0, #ffffff);
    border: 1px solid var(--borda, #e2e8f0);
    border-radius: var(--raio-3, 14px);
    padding: var(--esp-0, 2px) var(--esp-2, 8px);
    flex: none;
    font-variant-numeric: tabular-nums;
}

.tela-hero__acao { flex: none; }

@media (max-width: 640px) {
    /* No celular a ação desce e ocupa a largura: alvo de toque grande é mais
       importante que a linha única. */
    .tela-hero { align-items: flex-start; }
    .tela-hero__acao { width: 100%; }
}

/* ══════════════════════════════════════════════════════════════════════
   ÍCONE DA TELA NO CABEÇALHO (22/08/2026)

   Pedido do dono depois de ver o "hero" dentro do conteúdo e recusá-lo: duas
   faixas horizontais empilhadas diziam a mesma palavra, e a de baixo custava
   altura numa tela onde altura é linha de dado. A identidade veio para a faixa
   que JÁ existe — custo zero, e vale para as 160 telas do shell.

   ⚠️ O DEGRADÊ FOI TENTADO E REMOVIDO no mesmo dia, a pedido do dono. É a
   TERCEIRA vez que tingir esta faixa é recusado (o wash em 21/08, o hero e o
   degradê horizontal em 22/08), e o motivo é sempre o mesmo: o cabeçalho é a
   faixa de maior densidade funcional do produto, e a cor ali disputa com o que
   precisa ser visto — o botão de ação primária, que usa a MESMA cor da marca.

   O ícone sozinho resolve o que a cor tentava resolver: dá a identidade da tela
   e traz a marca, num quadrado de 1,9rem que não compete com nada. A borda de
   3px logo abaixo continua sendo a assinatura da escola.
   ══════════════════════════════════════════════════════════════════════ */
.top-header__ic {
    width: 1.9rem;
    height: 1.9rem;
    border-radius: var(--raio-2, 10px);
    flex: none;
    display: grid;
    place-items: center;
    font-size: var(--fs-4, .9rem);
    /* O par cor/texto vem resolvido do servidor por `texto_contraste`, que mede
       contraste WCAG em vez de decidir por limiar. Vale para qualquer marca. */
    background: var(--cor-secundaria, #3b82f6);
    color: var(--cor-secundaria-texto, #fff);
}

/* Tela que não declara `page_icon` deixa o `<i>` sem classe — o quadrado some
   em vez de aparecer vazio. É o que permite o gancho existir nas 160 telas e
   custar nada em 157 delas. */
.top-header__ic:has(> i[class=""]) { display: none; }


/* ── MODAL COM A ASSINATURA DA ESCOLA (.modal--marca) ───────────────────────
   Uma faixa de 4px no topo, da primária à secundária.

   ⚠️ POR QUE A FAIXA, E NÃO O TÍTULO EM DEGRADÊ (22/08/2026).
   A proposta original era pintar o TÍTULO do modal com o degradê. Foi medida
   e descartada, e o motivo está nas cores reais das escolas:

       colegiosantaafra  primária #37367C (10,60:1)  secundária #EE374A (3,98:1)
       satreinamentos    primária #37367C (10,60:1)  secundária #73F277 (1,43:1)

   Texto em degradê tem contraste VARIÁVEL ao longo da palavra: no Santa Afra
   "Documentos do aluno" iria de 10,60:1 a 3,98:1 — a última sílaba abaixo do
   piso AA. No SA Treinamentos o verde-limão fecha em **1,43:1**, praticamente
   invisível sobre o branco do modal, e nem o meio do degradê (3,56:1) passa.

   O produto inteiro calcula contraste no servidor — `texto_contraste` mede
   contra branco e navy, `_tinta` escurece até 4,5:1 — justamente porque a cor
   é escolhida pela ESCOLA e não se pode confiar na sorte. Um degradê de texto
   é o único elemento em que essa garantia não existe: não há "o contraste de
   um degradê", há infinitos ao longo do traço.

   A faixa usa as mesmas duas cores, não encosta em texto nenhum, e repete a
   linguagem que o produto já estabeleceu na assinatura de 3px do cabeçalho.

   ADOÇÃO INCREMENTAL: é uma classe, não uma regra em `.modal-content`. Modal
   de confirmação e de alerta não deve competir com a marca — quem manda ali é
   a cor semântica.
   ══════════════════════════════════════════════════════════════════════════ */
.modal-content.modal--marca {
    /* o Bootstrap já dá position: relative; declarado aqui para o ::before não
       depender de um detalhe do framework que pode mudar de versão */
    position: relative;
    overflow: hidden;   /* faz a faixa respeitar o arredondamento do modal */
}
.modal-content.modal--marca::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;      /* topo, de ponta a ponta */
    height: 4px;
    background: linear-gradient(
        90deg,
        var(--cor-primaria, #0f172a) 0%,
        var(--cor-secundaria, #3b82f6) 100%);
    /* acima do conteúdo do cabeçalho, abaixo de qualquer dropdown do corpo */
    z-index: 1;
    pointer-events: none;
}

/* ─────────────────────────────────────────────────────────────────────────
   MOVIMENTO REDUZIDO — respeita a preferência declarada no sistema
   (22/08/2026, auditoria de CSS)

   Medido antes: 9 folhas do projeto já traziam a guarda `prefers-reduced-
   motion` e 6 NÃO — entre elas o "ao vivo" pulsante do painel de presença, os
   4 indicadores animados do chat e os spinners do dashboard do professor. Não
   era desconhecimento, era inconsistência: quem escrevia a folha lembrava ou
   não. Resolver folha a folha deixaria a próxima animação de fora pelo mesmo
   motivo, então a regra vive aqui, na camada que alcança todas as telas — como
   já acontece com o foco visível.

   A preferência é EXPLÍCITA: só vale para quem a ligou no próprio sistema
   operacional, e é aí que animação infinita deixa de ser enfeite e passa a
   causar enjoo, tontura ou crise em quem tem distúrbio vestibular ou
   fotossensibilidade (WCAG 2.1, 2.3.3).

   ⚠️ Os valores são 0.01ms e não `none`: com `animation: none` o elemento pode
   parar num quadro intermediário — um spinner congelado no meio da volta, uma
   barra de progresso parada em 40% — e `animationend` nunca dispara, o que
   trava JS que espera o fim da transição. Com duração quase zero a animação
   COMPLETA imediatamente, o evento dispara e o estado final é o correto.
   `iteration-count: 1` é o que impede o infinito de continuar piscando.

   `scroll-behavior: auto` desfaz a rolagem suave — inclusive a do índice das
   telas de configuração —, que é movimento não solicitado como qualquer outro.

   `!important` é necessário: a regra precisa vencer a folha de cada app, que
   carrega DEPOIS desta. É a única exceção do arquivo, e existe porque aqui não
   se trata de estilo, e sim de uma preferência de acessibilidade do usuário.
   ───────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── Pular para o conteúdo ──────────────────────────────────────────────────
 * Primeiro elemento focável da página. Sem ele, quem navega por teclado
 * percorre os ~90 links do menu ANTES de chegar ao conteúdo, em TODA tela — e
 * de novo a cada navegação.
 *
 * ⚠️ NÃO usar `display:none` nem `visibility:hidden` para escondê-lo: os dois
 * o tiram da ordem de tabulação e o tornam inalcançável, que é o oposto do
 * objetivo. A técnica é posicioná-lo fora da tela e trazê-lo de volta no foco.
 *
 * Cor da ESCOLA, com o texto resolvido por `texto_contraste` — o mesmo motor
 * que garante contraste em qualquer marca. Com fallback, senão o browser
 * descarta a declaração onde o token não existe.
 */
.pular-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 2000;
    padding: var(--esp-3, 12px) var(--esp-5, 20px);
    background: var(--cor-secundaria, #1e293b);
    color: var(--cor-secundaria-texto, #fff);
    font-weight: 600;
    text-decoration: none;
    border-radius: 0 0 6px 0;
}
.pular-link:focus {
    left: 0;
    outline: 3px solid var(--cor-primaria, #0f172a);
    outline-offset: 2px;
}

/* ── Selo de cadastro incompleto (cabeçalho do shell) ──────────────────────
   Aviso DISCRETO, pedido do dono: lembra sem bloquear e sem gastar a faixa de
   alerta que a inadimplência usa. Âmbar e não vermelho — falta preencher, não
   há nada quebrado; e o vermelho já significa "modo leitura" neste produto.

   Mora aqui, na camada de acabamento, porque o selo vive no shell e vale para
   as 160 telas de uma vez.

   ⚠️ No celular fica só o ícone: o cabeçalho ali divide a linha com o
   hambúrguer, o sino e o avatar, e mais um rótulo de texto empurraria o título
   para fora. O `title` mantém a explicação a um toque longo. */
.cad-selo {
    display: inline-flex;
    align-items: center;
    gap: var(--esp-2, 8px);
    padding: var(--esp-1, 4px) var(--esp-3, 12px);
    border: 1px solid #fde68a;
    border-radius: var(--raio-pill, 999px);
    background: #fffbeb;
    color: #78350f;
    font-size: .78rem;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    white-space: nowrap;
}
.cad-selo:hover { border-color: var(--alerta, #f59e0b); background: #fef3c7; }
.cad-selo i { color: #b45309; }
.cad-selo__n {
    display: inline-grid;
    place-items: center;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 var(--esp-1, 4px);
    border-radius: var(--raio-pill, 999px);
    background: #b45309;
    color: #fff;
    font-size: var(--fs-1, .68rem);
}
/* O "×" que dispensa o aviso (13/09/2026, pedido do dono).
   ⚠️ Botão IRMÃO do selo, não aninhado — botão dentro de botão é HTML inválido
   e o clique cai no elemento errado. O grupo é quem desenha o par; o alvo de
   toque tem 28px, que é o piso confortável num chip desta altura. */
.cad-selo-grupo { display: inline-flex; align-items: center; gap: var(--esp-1, 4px); }
/* ⚠️ Mesmo motivo do `.pp-reabrir[hidden]`: `hidden` sozinho não vence um
   `display` declarado — sem esta linha, dispensar o aviso dependeria de o
   reboot do Bootstrap continuar no ar. */
.cad-selo-grupo[hidden] { display: none !important; }
.cad-selo__x {
    display: inline-grid;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--raio-pill, 999px);
    background: transparent;
    color: #b45309;
    font-size: .8rem;
    line-height: 1;
    cursor: pointer;
}
.cad-selo__x:hover { border-color: #fde68a; background: #fffbeb; }

@media (max-width: 768px) {
    .cad-selo__txt { display: none; }
    .cad-selo { padding: var(--esp-1, 4px) var(--esp-2, 8px); }
}

/* ── Modal do aviso ────────────────────────────────────────────────────── */
.cad-modal__intro { font-size: var(--fs-4, .9rem); color: var(--txt-2, #475569); }
.cad-lista { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--esp-3, 12px); }
.cad-item {
    padding: var(--esp-3, 12px) var(--esp-3, 12px);
    border: 1px solid var(--borda, #e2e8f0);
    border-left: 3px solid var(--alerta, #f59e0b);
    border-radius: 0 var(--raio-2, 10px) var(--raio-2, 10px) 0;
    background: var(--sup-1, #f8fafc);
}
.cad-item__rot {
    display: inline-flex;
    align-items: center;
    gap: var(--esp-2, 8px);
    font-weight: 700;
    color: var(--cor-secundaria-tinta, #2563eb);
    text-decoration: none;
}
.cad-item__rot:hover { text-decoration: underline; }
.cad-item__rot i { font-size: .72em; }
.cad-item__motivo {
    display: block;
    margin-top: var(--esp-1, 4px);
    font-size: var(--fs-3, .82rem);
    color: var(--txt-2, #475569);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * VÉU DE CARREGAMENTO — `partials/carregando.html`
 *
 * Cobre a tela enquanto uma operação demorada corre, e impede o segundo
 * clique. Medido na busca do material: 11,16 s na primeira consulta de cada
 * worker (o modelo de embeddings carregando), 0,07–0,24 s nas seguintes.
 *
 * ⚠️ A COR É A DA ESCOLA, e isso não é preciosismo: a identidade visual deste
 * produto sai da logomarca de cada escola, e nenhuma garantia pode depender de
 * uma cor fixa. Um anel arco-íris traria doze cores que não são de ninguém
 * para dentro de um sistema inteiro construído em cima da marca do cliente.
 * O que se preserva da referência é o MOVIMENTO — pontos girando com tamanhos
 * decrescentes —, que é o que comunica "aguarde".
 * ═══════════════════════════════════════════════════════════════════════════ */

.carregando {
    position: fixed;
    inset: 0;
    z-index: 2000;              /* acima do sidebar e dos modais do Bootstrap */
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, .55);
    backdrop-filter: blur(2px);
}

.carregando__caixa {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    padding: 2rem 2.5rem;
    background: #fff;
    border-radius: var(--raio-2, 10px);
    box-shadow: 0 10px 40px rgba(15, 23, 42, .25);
    max-width: min(90vw, 22rem);
    text-align: center;
}

.carregando__anel {
    position: relative;
    width: 68px;
    height: 68px;
}

.carregando__anel span {
    position: absolute;
    top: 0;
    left: 50%;
    width: 100%;
    height: 100%;
    margin-left: -50%;
    animation: carregando-gira 1.2s linear infinite;
}

.carregando__anel span::before {
    content: "";
    display: block;
    width: 11px;
    height: 11px;
    margin: 0 auto;
    border-radius: 50%;
    background: var(--cor-secundaria, #4f46e5);
}

/* Os doze pontos: cada um parte de um ângulo e de um tamanho, e a defasagem
   do atraso é o que produz a sensação de rotação com cauda. */
.carregando__anel span:nth-child(1)  { transform: rotate(0deg);   animation-delay: -1.1s; }
.carregando__anel span:nth-child(2)  { transform: rotate(30deg);  animation-delay: -1.0s; }
.carregando__anel span:nth-child(3)  { transform: rotate(60deg);  animation-delay: -.9s; }
.carregando__anel span:nth-child(4)  { transform: rotate(90deg);  animation-delay: -.8s; }
.carregando__anel span:nth-child(5)  { transform: rotate(120deg); animation-delay: -.7s; }
.carregando__anel span:nth-child(6)  { transform: rotate(150deg); animation-delay: -.6s; }
.carregando__anel span:nth-child(7)  { transform: rotate(180deg); animation-delay: -.5s; }
.carregando__anel span:nth-child(8)  { transform: rotate(210deg); animation-delay: -.4s; }
.carregando__anel span:nth-child(9)  { transform: rotate(240deg); animation-delay: -.3s; }
.carregando__anel span:nth-child(10) { transform: rotate(270deg); animation-delay: -.2s; }
.carregando__anel span:nth-child(11) { transform: rotate(300deg); animation-delay: -.1s; }
.carregando__anel span:nth-child(12) { transform: rotate(330deg); animation-delay: 0s; }

@keyframes carregando-gira {
    0%   { opacity: 1;   }
    100% { opacity: .12; }
}

.carregando__texto {
    margin: 0;
    font-size: .92rem;
    font-weight: 600;
    color: var(--txt-1, #1e293b);
}

/* ⚠️ Sob movimento reduzido a regra global deste arquivo zera a animação com
   `!important`, e o anel congela. Um anel parado não diz "aguarde" — por isso
   ele some, e a frase, que sempre esteve lá, passa a ser tudo. */
@media (prefers-reduced-motion: reduce) {
    .carregando__anel { display: none; }
}


/* ═══════════════════════════════════════════════════════════════════════
   AÇÃO DO FORMULÁRIO QUE NÃO SAI DA TELA  (`.acoes-fixas`)
   ═══════════════════════════════════════════════════════════════════════

   ⚠️ NASCEU DE UM DEFEITO RELATADO E MEDIDO (07/09/2026). O dono, num tablet
   de 11", disse que "o botão de salvar some quando escolho a turma" na tela de
   abrir portfólio. Ele não sumia: com o formulário vazio ficava em y=570, e
   assim que as turmas eram carregadas as sete séries o empurravam para **y=984
   numa janela de 900px** — fora da dobra. Só ele viu porque a altura útil do
   tablet é menor que a do monitor.

   A varredura que veio depois mediu 87 telas com dados reais em 1194×834 e
   achou mais SEIS no mesmo estado. Não é caso isolado: é o que acontece com
   todo formulário cuja altura CRESCE com o que o usuário escolhe, ou que
   simplesmente tem muitos campos.

   ⚠️ POR QUE `sticky` E NÃO `fixed`: `sticky` se desliga sozinho quando o
   conteúdo cabe na tela — sem media query, sem barra flutuante sobrando num
   formulário curto. `fixed` ficaria colado o tempo todo, roubando altura de
   quem não precisa.

   ⚠️ ARMADILHA AO APLICAR: `position: sticky` MORRE se qualquer ancestral entre
   o elemento e o contêiner de rolagem tiver `overflow` diferente de `visible`.
   Neste projeto quem rola é o `.main-content`; `.container-fluid` e os cartões
   são `visible`, então funciona. Ao usar numa tela nova, MEDIR — o sintoma de
   falha é silencioso: a barra simplesmente não gruda, e o CSS não avisa.

   Uso: acrescente a classe ao contêiner que já agrupa os botões.
       <div class="cal-form-acoes acoes-fixas"> … </div>                        */
.acoes-fixas {
    position: sticky;
    bottom: 0;
    z-index: 3;
    padding-top: var(--esp-3, 12px);
    padding-bottom: var(--esp-1, 4px);
    /* O degradê evita o corte seco sobre o conteúdo que passa por baixo. A cor
       é a do fundo da área de conteúdo, com fallback porque o token pode não
       existir nas telas que não carregam o `tokens.css` (PDF, e-mail). */
    background: linear-gradient(to top,
        var(--fundo-1, #f8fafc) 62%, transparent);
}

/* Em tela estreita o formulário empilha e a barra costuma ser a última coisa;
   manter o grude ali é ainda mais útil, mas o degradê fica curto demais para
   ler — vira fundo sólido. */
@media (max-width: 768px) {
    .acoes-fixas { background: var(--fundo-1, #f8fafc); }
}
