/*
 * Skin do Cents (F9/§5.18) sobre o Bootstrap 5. REGRA DE OURO: nenhuma cor literal
 * aqui — só var(--keys-*) definidas em tokens.css (o linter de tema fiscaliza).
 */

/* ---------- Tipografia: Sora (títulos/marca/números, self-hosted 700/800) + system-ui
   (corpo/interface, zero download) — CSP 'self'. Famílias em var(--keys-fonte*) para o
   Painel Master poder trocar. ---------- */
@font-face {
    font-family: 'Sora';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('/assets/vendor/sora/sora-700.woff2') format('woff2');
}
@font-face {
    font-family: 'Sora';
    font-style: normal;
    font-weight: 800;
    font-display: swap;
    src: url('/assets/vendor/sora/sora-800.woff2') format('woff2');
}

body {
    font-family: var(--keys-fonte);
    letter-spacing: -0.006em;
    -webkit-font-smoothing: antialiased;
}

/* ============================================================================================
   `hidden` GANHA DE QUALQUER UTILITÁRIA DE DISPLAY — a linha que fecha uma família inteira
   de defeito (23/08/2026, achado da câmera em `/mensagens`).
   ============================================================================================

   O DEFEITO, medido: `<div class="d-flex …" hidden>` fica VISÍVEL. Não é engano de quem
   escreveu a view — é a cascata, e ela é contraintuitiva:

     bootstrap.min.css, byte  10.129 (reboot)     `[hidden]{display:none!important}`
     bootstrap.min.css, byte 163.996 (utilities)  `.d-flex{display:flex!important}`

   Os DOIS são `!important` e os dois têm especificidade (0,1,0) — atributo e classe pesam
   igual. Empate em tudo, então quem decide é a ORDEM: vence o último, e o último é a
   utilitária. O Bootstrap já declara a regra certa e depois se atropela sozinho.

   Por isso o conserto não é escrever a regra — é RE-declará-la depois das utilitárias. Este
   arquivo carrega logo após o `bootstrap.min.css` nos três layouts que o incluem
   (`layout.php`, `layout_app.php`, `layout_master.php`), e são exatamente esses os layouts
   onde as utilitárias existem: `landing`, `portal` e `vitrine` não carregam Bootstrap.

   ⚠️ É A TERCEIRA VEZ, e as duas anteriores foram consertadas UMA DE CADA VEZ:
   `whatsapp.css` (`.keys-wa-doca[hidden]`) e `landing.css` (`.lp-ia-demo-resultado[hidden]`).
   Cada conserto pontual devolvia a tela e deixava a armadilha armada para a próxima view.
   A varredura de 23/08/2026 achou CINCO elementos vivos com `hidden` + utilitária de display,
   em cinco telas de quatro frentes diferentes — o aviso de reimportação de `/extratos` e as
   barras de fila de `/captacao` e `/vistoria` estavam desenhando caixa de alerta VAZIA em
   toda visita, que é o "aviso permanente sobre nada" que o próprio comentário do
   `vistoria/form.php` diz existir para evitar.

   CUSTO DO FALSO POSITIVO, medido antes de escrever (o `!important` global é justamente o
   que costuma quebrar coisas):
     · nenhuma regra de CSS deste projeto mostra um `[hidden]` de propósito — a varredura de
       `public/assets/css/` por `[hidden]` seguido de `display` diferente de `none` volta VAZIA;
     · o Bootstrap declara esta mesma regra, então re-declarar não contraria o vendor: restaura
       o que ele já queria;
     · o único lugar que revela elemento por `style.display` inline é o martelo da passada 2 de
       `trava-duplo-envio.js` (inline sem `!important`, que PERDERIA para esta linha) — e ele
       nunca vê um `[hidden]`, porque a passada 1 do mesmo arquivo faz `no.hidden = false` antes.
       Aliás, é o contrário: hoje aquele `no.hidden = false` é decorativo (o elemento já estava
       visível por causa da utilitária); com esta linha ele volta a fazer efeito.

   Quem escrever `hidden` numa view não precisa mais saber nada disto. É esse o ponto. */
[hidden] { display: none !important; }

/* Nenhum elemento pode gerar rolagem HORIZONTAL na página (bug 14/07: o offcanvas do guia
   "Entenda esta tela", fixo e escondido em translateX(100%) logo à direita da viewport,
   esticava a largura rolável em ~59px — barra horizontal + "+ Novo lançamento" empurrado para
   fora). `clip` (e NÃO `hidden`) porque `clip` não cria contêiner de rolagem e, portanto, não
   quebra o `position: sticky` do navbar; na raiz (`html`) porque o offcanvas é fixo relativo à
   viewport (filho direto do body, sem ancestral transformado) e só a raiz o contém. O offcanvas
   ABERTO fica em right:0 dentro da viewport, então nunca é cortado — só o estado escondido é.

   ⚠️ 04/08/2026 — O CÓDIGO AQUI CONTRADIZ O PRÓPRIO COMENTÁRIO (diz `clip`, escreve `hidden`), e
   a troca foi TENTADA E MEDIDA nesta data antes de ser desfeita: `clip` **não muda nada** no
   defeito de largura. A viewport do celular não se estica pelo documento — ela se estica pela
   CAIXA DE ROLAGEM ANINHADA. Medido por CDP em `/funil` a 576px, já com `clip` na raiz:
   `body.scrollWidth` 576 (a raiz continha), `.keys-fn-quadro` com `scrollWidth` 1124 dentro de
   516 — e `window.innerWidth` **1125**, ou seja, exatamente a largura da caixa aninhada. Nenhum
   truque de raiz conserta isso: quem conserta é não existir caixa aninhada mais larga que a tela
   (item 42/42-B abaixo). A troca foi desfeita porque `clip` ainda deixou a página sem rolar
   verticalmente na sondagem (`scrollTop` preso em 0 com `scrollHeight` 2502) — mexer nela é
   entrega própria, com medição própria. */
html {
    overflow-x: hidden;
}

/* Títulos e marca na fonte de exibição (Sora por padrão). */
h1, h2, h3, h4, h5, .h1, .h2, .h3, .h4, .h5 {
    font-family: var(--keys-fonte-titulo);
    font-weight: 700;
    letter-spacing: -0.022em;
}

/* Números tabulares em valores e tabelas — colunas de dinheiro alinham de verdade. */
.keys-valor, td, .keys-kpi-numero {
    font-variant-numeric: tabular-nums;
}

/* ---------- Marca ---------- */
.keys-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    color: var(--keys-tinta);
    text-decoration: none;
}
/* 29/07/2026: o quadradinho decorativo com o "¢" (símbolo de CENTAVO) saiu — era marca do Cents
   aparecendo ao lado do nome do Keys. Enquanto a identidade não estiver aprovada, a marca é só o
   nome escrito (Marca::logoAppTag devolve o wordmark textual quando não há logo configurado). */
.keys-brand-wordmark {
    font-family: var(--keys-fonte-titulo);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: inherit;
}
.keys-brand:hover { color: var(--keys-primaria-texto); }

/* ---------- Logo (SVG por padrão: símbolo + wordmark "cents" + tagline) ----------
   Duas variantes (clara/escura) trocadas por CSS conforme o data-bs-theme do <html> — que o
   tema-inicial.js fixa ANTES do 1º paint em toda tela — então o logo certo aparece sem "piscar"
   e sem o servidor precisar saber o tema. Tamanho por contexto (navbar 28px, login/hero maiores). */
.keys-brand-logo { height: 28px; width: auto; vertical-align: middle; }
.keys-brand-logo-escuro { display: none; }
[data-bs-theme="dark"] .keys-brand-logo-claro { display: none; }
[data-bs-theme="dark"] .keys-brand-logo-escuro { display: inline-block; }
/* Havendo logo <img> na marca, esconde o "¢" decorativo do ::before (evita símbolo duplicado). */
/* (a regra que escondia o ::before saiu junto com ele) */
.keys-auth-logo { line-height: 0; }
.keys-auth-logo .keys-brand-logo { height: 44px; }
.keys-hero-logo .keys-brand-logo { height: 58px; }

/* ---------- Navbar ---------- */
/* Fica FIXA no topo (sticky) em toda a área: some ao rolar para baixo e reaparece na hora ao
   rolar para cima (classe .keys-nav-oculta ligada pelo nav-scroll.js). z-index abaixo do banner
   de instalação (1050) e dos toasts (1090), acima das colunas sticky das tabelas (z-index 2). */
.navbar.bg-body {
    position: sticky;
    top: 0;
    z-index: 1030;
    transition: transform 0.3s ease;
    background: var(--keys-superficie) !important;
    box-shadow: var(--keys-sombra-1);
    padding-top: 0.65rem;
    padding-bottom: 0.65rem;
}
.navbar.bg-body.keys-nav-oculta { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) {
    .navbar.bg-body { transition: none; }
}
.navbar .nav-link {
    font-weight: 500;
    font-size: 0.925rem;
    color: var(--keys-texto-2);
    border-radius: var(--keys-raio-xs);
    padding: 0.4rem 0.7rem;
}
.navbar .nav-link:hover,
.navbar .nav-link:focus {
    color: var(--keys-primaria-texto);
    background: var(--keys-primaria-suave);
}
.navbar .dropdown-menu { margin-top: 0.4rem; }

/* Hambúrguer no celular/tablet (bug do dono 14/07): `navbar-expand-xxl` colapsa abaixo de 1400px
   (muito item — 5 links + 4 dropdowns + usuário com idioma/tema/sair, layout_app.php:83-86) e o
   `.navbar-collapse` é sticky mas SEM limite de altura — o menu aberto cabia mais que a viewport
   e transbordava por baixo sem rolar; o fim (idioma/tema/sair) ficava inacessível (tampado ainda
   pela tabbar/balão fixos). Trava a altura ao espaço que sobra depois da PRÓPRIA barra fechada e
   deixa ROLAR por dentro — mesmo padrão do .modal-content (linha ~768). 6.5rem cobre o pior caso
   medido no navegador real: a 1ª linha (busca/+lançar/sino/guia) quebra pro hambúrguer descer pra
   uma 2ª linha em telas bem estreitas (ex. 360px, ~98px de altura fechada no total; em 390px+,
   tudo cabe numa linha só, ~66px — 6.5rem sobra à toa mas nunca estoura). Só no estado colapsado
   (abaixo do breakpoint do navbar-expand-xxl); expandido (≥1400px) fica como está, sem scroll. */
@media (max-width: 1399.98px) {
    .navbar-collapse {
        max-height: calc(100vh - 6.5rem - env(safe-area-inset-bottom, 0px));
        max-height: calc(100dvh - 6.5rem - env(safe-area-inset-bottom, 0px));
        overflow-y: auto;
    }
    /* Dropdowns DENTRO do menu colapsado (os 4 do nav + o do usuário com nome/senha/idioma/tema/
       sair) viram bloco ESTÁTICO no fluxo — padrão do Bootstrap para navbar no mobile — em vez de
       menu FLUTUANTE do Popper, que perto da borda abria CORTADO pela esquerda (o dropdown do
       usuário é `dropdown-menu-end` e saía pela borda esquerda; bug flagrado pelo dono 14/07,
       screenshot do celular). Estático = acordeão inline (toca a seção, abre logo abaixo, indentado),
       sem overflow horizontal, rolando junto com o menu. Os ícones SEMPRE-visíveis (busca/sino/guia)
       ficam FORA do `.navbar-collapse`, então seguem menus flutuantes normais. */
    .navbar-collapse .dropdown-menu {
        position: static;
        float: none;
        width: auto;
        margin: 0.1rem 0.6rem 0.4rem 0.75rem; /* inset dos DOIS lados: o realce do item ativo não vaza pela direita */
        border: 0;
        box-shadow: none;
        background: transparent;
    }
    .navbar-collapse .dropdown-menu .dropdown-item {
        border-radius: var(--keys-raio-xs); /* realce do item ativo vira pílula arredondada, não barra até a borda */
        width: auto;          /* o Bootstrap força width:100%, que IGNORA margin-right e faz o realce vazar */
        margin-right: 0.6rem; /* agora o fundo do item ativo respeita a margem e não encosta na borda */
    }
    /* ÁREA DA CONTA no fim do menu colapsado (discrição + usuário). Feedback do dono: no celular
       ficava "estranha" — o olho de discrição e o gatilho do perfil eram dois botõezinhos
       horizontais soltos, o nome sumia e os itens abriam desconectados. Vira uma SEÇÃO vertical
       limpa, separada da navegação por um divisor, com o padrão de app: avatar + nome + caret numa
       linha de menu; ao tocar, senha/idioma/tema/sair abrem indentados (estáticos, v3.84). */
    .keys-menu-conta {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        margin-top: 0.6rem;
        padding-top: 0.4rem;
        border-top: 1px solid var(--keys-borda);
    }
    .keys-menu-conta .dropdown { width: 100%; }
    /* Discrição, LENTE DE HORAS (F150.1 — ficava como pill contornado destoando entre as duas
       linhas) e gatilho da conta: linhas de largura total, estilo item de menu (não "caixinha"). */
    .keys-menu-conta > [data-discricao],
    .keys-menu-conta > [data-lente-horas],
    .keys-menu-conta .dropdown > .dropdown-toggle {
        width: 100%;
        display: flex;
        align-items: center;
        gap: 0.6rem;
        text-align: left;
        border: 0;
        background: transparent;
        color: var(--keys-texto-2);
        font-weight: 500;
        font-size: 0.95rem;
        padding: 0.6rem 0.7rem;
        border-radius: var(--keys-raio-xs);
    }
    .keys-menu-conta > [data-discricao]:hover,
    .keys-menu-conta > [data-discricao]:focus-visible,
    .keys-menu-conta > [data-lente-horas]:hover,
    .keys-menu-conta > [data-lente-horas]:focus-visible,
    .keys-menu-conta .dropdown > .dropdown-toggle:hover,
    .keys-menu-conta .dropdown > .dropdown-toggle:focus-visible {
        background: var(--keys-primaria-suave);
        color: var(--keys-primaria-texto);
    }
    .keys-menu-conta .dropdown > .dropdown-toggle::after { margin-left: auto; } /* caret encostado à direita */
    .keys-menu-conta .dropdown > .dropdown-toggle .bi-person-circle { font-size: 1.5rem; color: var(--keys-primaria-texto); }
    .keys-menu-conta .keys-conta-nome { font-weight: 600; color: var(--keys-tinta); }
    /* "Sair" ganha leve destaque (fim da seção). */
    .keys-menu-conta form[action="/sair"] .dropdown-item { color: var(--keys-despesa); font-weight: 500; }
}
/* Abaixo de 992px a tabbar fixa TAMBÉM come espaço da viewport por baixo — sem descontar ela
   aqui, o fim do menu (Configurações/idioma/tema/sair) ainda entestava nela. 4.5rem (não só os
   56px da barra) porque o "+" central da tabbar fica ELEVADO por cima da própria barra
   (.keys-tabbar-acao-icone, margin-top negativo) — medido no navegador real, o topo do ícone
   fica uns 9px acima do topo da barra; 4.5rem cobre os 56px + a saliência + folga.
   `body:has(.keys-tabbar)` = mesma detecção usada no padding do <main> e no toast (linha ~1171).
   05/08/2026: esta saliência virou token — `--keys-tabbar-saliencia` / `--keys-tabbar-topo`, no
   `:root` dos flutuantes. Este 4.5rem continua cru DE PROPÓSITO: ele é medida de altura de MENU
   ROLÁVEL (com a variante do banner do PWA logo abaixo, medida junto), não de posição de objeto
   colado no rodapé — trocá-lo pelo token encolheria a folga do menu em ~6px sem ninguém ter
   medido isso. Quem posiciona objeto ACIMA da barra usa o token. */
@media (max-width: 991.98px) {
    body:has(.keys-tabbar) .navbar-collapse {
        max-height: calc(100vh - 6.5rem - 4.5rem - env(safe-area-inset-bottom, 0px));
        max-height: calc(100dvh - 6.5rem - 4.5rem - env(safe-area-inset-bottom, 0px));
    }
    /* Convite de instalação do PWA (topo, `.keys-instalar-banner`) ainda visível empurra a barra
       pra baixo, comendo a mesma folga por cima — sem descontar ele também, o fim do menu
       (Configurações) ainda entestava no "+" elevado da tabbar antes mesmo de rolar a página
       (achado medindo no navegador real, banner com os 3 botões empilhados ~326px). */
    body:has(.keys-tabbar):has([data-pwa-banner]:not([hidden])) .navbar-collapse {
        max-height: calc(100vh - 6.5rem - 4.5rem - 21rem - env(safe-area-inset-bottom, 0px));
        max-height: calc(100dvh - 6.5rem - 4.5rem - 21rem - env(safe-area-inset-bottom, 0px));
    }
}

/* ---------- Dropdowns ---------- */
.dropdown-menu {
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio-sm);
    box-shadow: var(--keys-sombra-2);
    background: var(--keys-superficie);
    padding: 0.35rem;
}
.dropdown-item {
    border-radius: var(--keys-raio-xs);
    padding: 0.45rem 0.7rem;
    font-size: 0.925rem;
}
.dropdown-item:hover, .dropdown-item:focus {
    background: var(--keys-primaria-suave);
    color: var(--keys-primaria-texto);
}
.dropdown-item.active { background: var(--keys-primaria); color: var(--keys-sobre-primaria); }

/* ---------- Cards ---------- */
.card {
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio);
    background: var(--keys-superficie);
    box-shadow: var(--keys-sombra-1);
}
.card .card-header {
    background: transparent;
    border-bottom: 1px solid var(--keys-borda);
    font-weight: 600;
}

/* Card de KPI (dashboards) */
.keys-kpi { transition: transform 0.12s ease, box-shadow 0.12s ease; }
.keys-kpi:hover { transform: translateY(-2px); box-shadow: var(--keys-sombra-2); }
.keys-kpi .keys-kpi-icone {
    display: grid;
    place-items: center;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 0.8rem;
    font-size: 1.2rem;
    color: var(--keys-primaria-texto);
    background: var(--keys-primaria-suave);
}
.keys-kpi .keys-kpi-icone.tom-aviso   { color: var(--keys-aviso);   background: var(--keys-aviso-suave); }
.keys-kpi .keys-kpi-icone.tom-despesa { color: var(--keys-despesa); background: var(--keys-despesa-suave); }
.keys-kpi .keys-kpi-icone.tom-info    { color: var(--keys-info);    background: var(--keys-info-suave); }
.keys-kpi-numero { font-family: var(--keys-fonte-titulo); font-size: 1.7rem; font-weight: 700; letter-spacing: -0.02em; }

/* ---------- Tabelas ---------- */
.table {
    --bs-table-bg: transparent;
    margin-bottom: 0;
}
.table thead th {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--keys-texto-3);
    border-bottom: 1px solid var(--keys-borda);
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
    white-space: nowrap;
}
.table td { border-color: var(--keys-borda); padding-top: 0.65rem; padding-bottom: 0.65rem; }
.table-hover tbody tr:hover { background: var(--keys-superficie-2); }
.table code { color: var(--keys-primaria-texto); background: var(--keys-primaria-suave); padding: 0.1rem 0.4rem; border-radius: 0.35rem; font-size: 0.8rem; }

/* Tabela dentro de card/responsive ganha respiro e cantos */
.table-responsive {
    background: var(--keys-superficie);
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio);
    box-shadow: var(--keys-sombra-1);
    padding: 0.4rem 1rem;
}

/* ---------- Destaque de linha por estado (P9/§5.18) ----------
   Substitui table-danger/table-warning/table-success do Bootstrap, que pintam a
   linha com pastéis CLAROS fixos e apagam o texto claro no tema escuro. Aqui o
   fundo é um véu translúcido dos tokens e o texto segue o da tabela — legível
   nos dois temas. Barra de acento à esquerda reforça o estado sem depender só
   do tom do fundo. */
.table tbody tr.keys-linha-estouro { background: var(--keys-linha-estouro-fundo); }
.table tbody tr.keys-linha-alerta  { background: var(--keys-linha-alerta-fundo); }
.table tbody tr.keys-linha-ok      { background: var(--keys-linha-ok-fundo); }
.table-hover tbody tr.keys-linha-estouro:hover { background: var(--keys-linha-estouro-hover); }
.table-hover tbody tr.keys-linha-alerta:hover  { background: var(--keys-linha-alerta-hover); }
.table-hover tbody tr.keys-linha-ok:hover      { background: var(--keys-linha-ok-hover); }
/* A barra soma-se ao inset do Bootstrap (que pinta o estado hover da célula),
   por isso o segundo box-shadow replica a cadeia de fallback original. */
.table tbody tr.keys-linha-estouro > td:first-child,
.table tbody tr.keys-linha-alerta > td:first-child,
.table tbody tr.keys-linha-ok > td:first-child {
    box-shadow: inset 0.18rem 0 0 var(--keys-linha-acento, var(--keys-despesa)),
                inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)));
}
.table tbody tr.keys-linha-estouro { --keys-linha-acento: var(--keys-despesa); }
.table tbody tr.keys-linha-alerta  { --keys-linha-acento: var(--keys-aviso); }
.table tbody tr.keys-linha-ok      { --keys-linha-acento: var(--keys-receita); }
/* Texto semântico (Δ vermelho/verde, botões de ação) dentro da linha destacada:
   os utilitários do Bootstrap ficam abaixo de AA sobre os véus no tema claro —
   troca pelos tons "fortes" dos tokens (≥5,5:1 nos dois temas). */
.keys-linha-estouro .text-danger, .keys-linha-alerta .text-danger, .keys-linha-ok .text-danger { color: var(--keys-despesa-forte) !important; }
.keys-linha-estouro .text-success, .keys-linha-alerta .text-success, .keys-linha-ok .text-success { color: var(--keys-receita-forte) !important; }

/* ---------- Ajuda ao passar o mouse (P9) ----------
   Marca elementos que carregam explicação em title=.

   ## O DEFEITO QUE ESTA REGRA DEIXOU DE TER (achado E7 do módulo 26 — 25/08/2026)

   O convite era escrito por NOME DE ETIQUETA — `th` e `span` ganhavam o pontilhado, e mais
   ninguém. Nome de etiqueta é acidente de marcação, não significado: um `<div class="h5
   keys-ajuda" title="…">487 créditos</div>` e um `<span class="keys-ajuda">` são a MESMA coisa
   para quem usa, e só o segundo se anunciava. Varridas as views (`keys-ajuda` sem sufixo, andando
   para trás até a etiqueta que a abre — `[^>]*` não serve: quase toda etiqueta destas tem `<?= ?>`
   dentro): **542 elementos, TODOS com `title`**, e **330** com o pontilhado. Nos outros **212**
   sobrava `cursor: help`, que só aparece se a pessoa já tiver posto o mouse em cima — ou seja, a
   ajuda existia e não se anunciava, que é o mesmo que não existir.

   Agora o convite é da CLASSE. Fica de fora, por engenharia e não por esquecimento:

     * quem já tem affordance própria — `a` (28: quase todos `btn` ou `badge`), `button` (8),
       `select`/`input`/`textarea` (17) e `summary` (2, que já abre e fecha). Sublinhar um botão o
       faz parecer link, e em controle nativo o navegador nem pinta;
     * `i` (12): já É o ícone de ajuda (`bi-question-circle`). Pontilhado sob um glifo lê como
       defeito de fonte;
     * `td` (20): decisão de produto anterior e mantida — em célula numérica de tabela densa só o
       cursor muda, senão vinte pontilhados brigam com a leitura da coluna;
     * `.card-body` e `.progress` (6 `div`): são CAIXA, não texto. `text-decoration` propaga para
       todos os descendentes e o descendente NÃO consegue desligá-la (CSS Text Decoration §2.4) —
       num `.card-body` o pontilhado sairia sob o cartão inteiro, rótulo e valor.

   `:not()` encadeado, e não `:not(a, button, …)`: argumento inválido invalida o SELETOR TODO, e um
   navegador que não entenda a lista derrubaria em silêncio a ajuda das 542. */
.keys-ajuda { cursor: help; }
.keys-ajuda:not(a):not(button):not(select):not(input):not(textarea):not(i):not(td):not(summary):not(.card-body):not(.progress) {
    text-decoration: underline dotted;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
}

/* ---------- Legenda "como ler" (P9) ---------- */
.keys-legenda {
    font-size: 0.85rem;
    color: var(--keys-texto-2);
    background: var(--keys-superficie);
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio-sm);
    padding: 0.55rem 0.9rem;
}
.keys-legenda summary { cursor: pointer; font-weight: 500; }
.keys-legenda summary:hover { color: var(--keys-tinta); }
.keys-legenda ul { margin: 0.5rem 0 0; padding-left: 1.2rem; }
.keys-legenda li { margin-bottom: 0.3rem; }
.keys-legenda li:last-child { margin-bottom: 0; }

/* ---------- Ajuda de campo recolhível (P9 item 10) ----------
   Substitui o antigo .form-text sempre visível sob cada campo de formulário. Acordeão
   <details>/<summary> nativo — sem JS, sem custo de acessibilidade (o navegador já expõe
   o estado aberto/fechado ao leitor de tela) — sempre FECHADO por padrão. Gatilho minúsculo
   e discreto (seta + rótulo curto) para não voltar a inchar o formulário. */
.keys-ajuda-campo { margin-top: 0.25rem; }
.keys-ajuda-campo summary {
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.78rem;
    color: var(--keys-texto-2);
    user-select: none;
}
.keys-ajuda-campo summary::-webkit-details-marker,
.keys-ajuda-campo summary::marker { display: none; content: ''; }
.keys-ajuda-campo summary:hover { color: var(--keys-tinta); }
.keys-ajuda-campo summary:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: 2px; border-radius: var(--keys-raio-xs); }
.keys-ajuda-campo summary i { font-size: 0.7rem; transition: transform 0.15s ease; }
.keys-ajuda-campo[open] summary i { transform: rotate(90deg); }
.keys-ajuda-campo > .form-text { margin-top: 0.3rem; }

/* Linha clicável (DRE/previsão §5.8.1) — abre o modal de detalhe. Sinaliza a interação sem cor
   literal (só cursor + realce de foco de teclado, herdando os tokens do tema). */
.keys-clicavel { cursor: pointer; }
.keys-clicavel:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: -2px; }

/* ---------- Botões ---------- */
.btn { border-radius: var(--keys-raio-xs); font-weight: 500; }
.btn:focus-visible { box-shadow: 0 0 0 0.25rem var(--keys-anel-foco); }
.btn-primary {
    /* Ouro é a cara da marca no CTA — TEXTO ESCURO por cima (regra: nunca branco sobre ouro). */
    --bs-btn-bg: var(--keys-primaria);
    --bs-btn-border-color: var(--keys-primaria);
    --bs-btn-color: var(--keys-sobre-primaria);
    --bs-btn-hover-bg: var(--keys-primaria-escura);
    --bs-btn-hover-border-color: var(--keys-primaria-escura);
    --bs-btn-hover-color: var(--keys-sobre-primaria);
    --bs-btn-active-bg: var(--keys-primaria-escura);
    --bs-btn-active-border-color: var(--keys-primaria-escura);
    --bs-btn-active-color: var(--keys-sobre-primaria);
    --bs-btn-disabled-bg: var(--keys-primaria);
    --bs-btn-disabled-border-color: var(--keys-primaria);
    --bs-btn-disabled-color: var(--keys-sobre-primaria);
    box-shadow: var(--keys-sombra-1);
}
.btn-outline-primary {
    --bs-btn-color: var(--keys-primaria-texto);
    --bs-btn-border-color: var(--keys-borda-forte);
    --bs-btn-hover-bg: var(--keys-primaria-suave);
    --bs-btn-hover-color: var(--keys-primaria-texto);
    --bs-btn-hover-border-color: var(--keys-primaria);
    --bs-btn-active-bg: var(--keys-primaria);
    --bs-btn-active-color: var(--keys-sobre-primaria);
    --bs-btn-active-border-color: var(--keys-primaria);
}
.btn-outline-secondary {
    --bs-btn-color: var(--keys-texto-2);
    --bs-btn-border-color: var(--keys-borda-forte);
    --bs-btn-hover-color: var(--keys-tinta);
    --bs-btn-hover-bg: var(--keys-superficie-2);
    --bs-btn-hover-border-color: var(--keys-borda-forte);
    --bs-btn-active-bg: var(--keys-superficie-2);
    --bs-btn-active-color: var(--keys-tinta);
    --bs-btn-active-border-color: var(--keys-borda-forte);
}
.btn-link { color: var(--keys-primaria-texto); }

/* ---------- Formulários ---------- */
.form-control, .form-select {
    border-radius: var(--keys-raio-xs);
    border-color: var(--keys-borda-forte);
    background-color: var(--keys-superficie);
    padding: 0.5rem 0.8rem;
}
.form-control:focus, .form-select:focus {
    border-color: var(--keys-primaria);
    box-shadow: 0 0 0 0.25rem var(--keys-anel-foco);
    background-color: var(--keys-superficie);
}
.form-label { font-weight: 500; font-size: 0.9rem; margin-bottom: 0.35rem; }
.form-text { color: var(--keys-texto-3); }

/* ---------- Olhinho de senha (mostrar/ocultar, P9) ----------
   senha-olho.js injeta o botão dentro de um .input-group (existente ou criado na hora) logo
   após o input[type=password]. Cores herdam de .btn-outline-secondary (já tokenizado acima —
   contraste garantido nos 2 temas). Some com o "olho" nativo do Edge/IE p/ não duplicar ícone. */
.keys-senha-olho {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.keys-senha-olho i { pointer-events: none; }
input[type="password"]::-ms-reveal,
input[type="password"]::-ms-clear { display: none; }

/* ---------- Badges e alerts ---------- */
.badge { font-weight: 600; border-radius: 0.5rem; padding: 0.35em 0.6em; }
.badge.text-bg-success { background: var(--keys-receita-suave) !important; color: var(--keys-receita) !important; }
.badge.text-bg-danger  { background: var(--keys-despesa-suave) !important; color: var(--keys-despesa) !important; }
.badge.text-bg-warning { background: var(--keys-aviso-suave) !important;   color: var(--keys-aviso) !important; }
.badge.text-bg-secondary { background: var(--keys-superficie-2) !important; color: var(--keys-texto-2) !important; border: 1px solid var(--keys-borda); }

.alert { border-radius: var(--keys-raio-sm); border: 1px solid var(--keys-borda); box-shadow: var(--keys-sombra-1); }
.alert-success { background: var(--keys-receita-suave); color: var(--keys-receita); border-color: transparent; }
.alert-danger  { background: var(--keys-despesa-suave); color: var(--keys-despesa); border-color: transparent; }
.alert-info    { background: var(--keys-info-suave);    color: var(--keys-info);    border-color: transparent; }

/* ---------- Links ---------- */
a { color: var(--keys-primaria-texto); text-decoration: none; }
a:hover { color: var(--keys-primaria-texto); text-decoration: underline; }

/* ---------- Cabeçalho de página / estado vazio ---------- */
.keys-page-header { margin-bottom: 1.25rem; }
.keys-page-header .keys-page-sub { color: var(--keys-texto-2); font-size: 0.925rem; margin: 0.15rem 0 0; }

.keys-vazio {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--keys-texto-2);
    background: var(--keys-superficie);
    border: 1px dashed var(--keys-borda-forte);
    border-radius: var(--keys-raio);
}
.keys-vazio .bi { font-size: 2rem; color: var(--keys-texto-3); display: block; margin-bottom: 0.5rem; }
/* item 42/§9.1 (empty states com CTA): o botão primário do estado vazio some colado no texto —
   respiro acima, consistente nas telas que ganharam CTA dentro do .keys-vazio. */
.keys-vazio .btn { margin-top: .75rem; }

/* ---------- Autenticação (login/cadastro/reset) ---------- */
.keys-auth {
    min-height: calc(100vh - 160px);
    display: grid;
    place-items: center;
}
.keys-auth-card {
    width: 100%;
    max-width: 430px;
    background: var(--keys-superficie);
    border: 1px solid var(--keys-borda);
    border-radius: calc(var(--keys-raio) + 4px);
    box-shadow: var(--keys-sombra-3);
    padding: 2.25rem 2rem;
}

/* ---------- Listas (list-group) ---------- */
.list-group-item { border-color: var(--keys-borda); background: var(--keys-superficie); }
.list-group-item-primary { background: var(--keys-primaria-suave); color: var(--keys-tinta); }

/* ---------- Toast (desfazer §5.20-6) ---------- */
.toast { border-radius: var(--keys-raio-sm); box-shadow: var(--keys-sombra-2); border: 1px solid var(--keys-borda); }

/* ---------- Rodapé ---------- */
footer.border-top { color: var(--keys-texto-3) !important; border-color: var(--keys-borda) !important; }

/* ---------- Semântica financeira (telas usam nos valores) ---------- */
.keys-receita { color: var(--keys-receita) !important; }
.keys-despesa { color: var(--keys-despesa) !important; }

/* ---------- Modo discrição (§5.20-21) — borra os valores monetários ---------- */
.keys-oculto .keys-valor {
    filter: blur(0.4rem);
    user-select: none;
}

/* ---------- Hero público (landing) ---------- */
.keys-hero {
    min-height: 60vh;
    display: grid;
    place-items: center;
    text-align: center;
}

/* ---------- Tour guiado (F10) ---------- */
/* Holofote: cobre o elemento-alvo e escurece TODO o resto da página com um
   box-shadow gigante — o alvo fica "aceso" e o entorno apagado. */
.keys-tour-foco {
    position: absolute;
    z-index: 1090;
    border-radius: var(--keys-raio-sm);
    box-shadow: 0 0 0 9999px var(--keys-tour-sombra);
    pointer-events: none;
    transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease;
}

/* Camada transparente que bloqueia cliques na página durante o tour (o cartão fica acima). */
.keys-tour-trava {
    position: fixed;
    inset: 0;
    z-index: 1089;
}

.keys-tour-card {
    position: absolute;
    z-index: 1091;
    width: 340px;
    max-width: calc(100vw - 16px);
    background: var(--keys-superficie);
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio);
    box-shadow: var(--keys-sombra-3);
    padding: 1rem 1.1rem;
}
.keys-tour-card h5 {
    font-weight: 700;
    font-size: 1rem;
    color: var(--keys-tinta);
    margin-bottom: 0.35rem;
}
.keys-tour-card p {
    color: var(--keys-texto-2);
    font-size: 0.925rem;
    margin-bottom: 0.85rem;
}
.keys-tour-rodape {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.keys-tour-contador {
    margin-left: auto;
    color: var(--keys-texto-3);
    font-size: 0.8rem;
    white-space: nowrap;
}

/* ---------- Acompanhamento anual (§5.8.7) — grade compacta de 16+ colunas ----------
   Pedido do dono (produção): ganhar espaço, colunas de mês uniformes, letra menor.
   Sem cor nova: tudo via tokens/variáveis já existentes (linter de tema). */
.keys-tabela-anual {
    font-size: 0.8rem;
}
.keys-tabela-anual th,
.keys-tabela-anual td {
    padding: 0.2rem 0.4rem;
    font-variant-numeric: tabular-nums; /* dígitos alinhados coluna a coluna */
}
/* Colunas de mês com largura uniforme (números "secos", sem R$, cabem folgados). */
.keys-tabela-anual .keys-col-mes {
    min-width: 4.6rem;
    width: 4.6rem;
}
/* Primeira coluna (categoria/rótulo) fixa no scroll horizontal, com fundo opaco. */
.keys-tabela-anual th:first-child,
.keys-tabela-anual td:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background-color: var(--bs-body-bg);
}
/* Categoria truncada com reticências (o nome completo fica no title). */
.keys-tabela-anual .keys-col-categoria {
    max-width: 11rem;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Zebra sutil (superfície-2 do tema) — replicada na célula sticky p/ não "rasgar" a linha. */
.keys-tabela-anual tbody tr:nth-child(even) > * {
    background-color: var(--keys-superficie-2);
}
.keys-tabela-anual tbody tr:nth-child(even) > :first-child {
    background-color: var(--keys-superficie-2);
}

/* Valor PROJETADO (meses futuros do acompanhamento anual): itálico e atenuado, para
   nunca se confundir com realizado. Sem cor própria — só opacidade sobre o texto do tema. */
.keys-proj {
    font-style: italic;
    opacity: 0.72;
}

/* ===== P9 Onda 1: utilitárias de explicabilidade (usadas pelas ondas A–F) =====
   Só var(--keys-*) — zero cor literal. Substituem os text-*/table-*/btn-outline-*
   crus do Bootstrap que reprovavam contraste (sobretudo no tema escuro). */

/* Texto de aviso/informação legível nos 2 temas (AA — ver tokens.css). */
.keys-aviso { color: var(--keys-aviso-forte) !important; }
.keys-info  { color: var(--keys-info) !important; }

/* Badges de info/primária com fundo suave + tinta contrastante (o text-bg-* do
   Bootstrap usa fundo saturado que lava a tinta no escuro). */
.badge.text-bg-info    { background-color: var(--keys-info-suave) !important;     color: var(--keys-info) !important; }
.badge.text-bg-primary { background-color: var(--keys-primaria-suave) !important; color: var(--keys-primaria-texto) !important; }

/* Bolinha de cor de categoria: contorno para não sumir sobre fundos próximos. */
.keys-cor-bolinha { border: 1px solid var(--keys-borda-forte); }

/* Barras de progresso das metas (div vazio, sem texto) tokenizadas — não-texto ≥3:1. */
.progress-bar.text-bg-success { background-color: var(--keys-receita) !important; }
.progress-bar.text-bg-warning { background-color: var(--keys-aviso) !important; }
.progress-bar.text-bg-danger  { background-color: var(--keys-despesa) !important; }

/* ---------- Convite de instalação do app (§6.1) — faixa que desce do topo ----------
   Fixa no topo, acima da navbar; entra deslizando (translateY) e sai igual. Só var(--keys-*)
   (linter de tema). O ícone reaproveita o padrão dos KPIs. */
.keys-instalar-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1050; /* acima da navbar (em fluxo) e dos dropdowns (1000); abaixo dos toasts (1090) */
    background: var(--keys-superficie);
    border-bottom: 1px solid var(--keys-borda);
    box-shadow: var(--keys-sombra-2);
    transform: translateY(-100%);
    transition: transform 0.35s ease;
}
.keys-instalar-banner.is-shown { transform: translateY(0); }
.keys-instalar-icone {
    display: grid;
    place-items: center;
    width: 2.6rem;
    height: 2.6rem;
    flex: none;
    border-radius: 0.8rem;
    font-size: 1.3rem;
    color: var(--keys-primaria-texto);
    background: var(--keys-primaria-suave);
}
@media (prefers-reduced-motion: reduce) {
    .keys-instalar-banner { transition: none; }
}

/* ---------- Evolução patrimonial (§5.8.5) — 1ª coluna (Mês) fixa no scroll horizontal ----------
   Mesma técnica sticky do Acompanhamento Anual (keys-tabela-anual), enxuta: aqui a 1ª coluna é
   um link curto (mês) — não precisa do encolhimento de fonte nem das larguras de mês da grade.
   Fundo opaco via token do tema (zero cor literal); overflow-x vem do wrapper .table-responsive. */
.keys-tabela-patrimonio th:first-child,
.keys-tabela-patrimonio td:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background-color: var(--bs-body-bg);
}

/* ---------- Caixa de gráfico responsiva (§5.3) ----------
   Wrapper com altura fixa por breakpoint; o dashboard.js usa maintainAspectRatio:false e o
   Chart.js preenche esta caixa. Garante altura legível no celular (antes o gráfico "achatava"). */
.keys-grafico {
    position: relative;
    width: 100%;
    height: 300px;
}
.keys-grafico-alto { height: 340px; }

/* Ícone de ação (editar/excluir/voltar) — centraliza o glifo; vira alvo de 44px no celular
   (regra no @media abaixo). No desktop fica no tamanho natural, sem inflar linhas de tabela. */
.keys-acao-icone { display: inline-flex; align-items: center; justify-content: center; }

/* ---------- Overlay "processando" (F150.4 — pedido do dono) ----------
   Enquanto a IA lê a foto/arquivo, o app INTEIRO escurece e uma bolinha gira NO MEIO da tela:
   no celular o spinner ao lado do botão passava despercebido e dava sensação de travamento.
   Escurecimento pelo token do tour (já tematizado nos 2 temas); z-index acima dos flutuantes
   (1042) e da tabbar (1030), abaixo do modal do Bootstrap (1055) para nunca cobrir um diálogo. */
.keys-processando {
    position: fixed;
    inset: 0;
    z-index: 1050;
    display: grid;
    place-items: center;
    padding: 1.5rem;
    background: var(--keys-tour-sombra);
    backdrop-filter: blur(2px);
}
/* O conteúdo vive num CARTÃO de superfície: legível nos 2 temas usando só tokens (texto solto
   sobre o escurecimento exigiria uma cor invertida que não existe no catálogo). */
.keys-processando-caixa {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.9rem;
    max-width: 18rem;
    text-align: center;
    padding: 1.5rem 1.75rem;
    border-radius: var(--keys-raio);
    background: var(--keys-superficie);
    box-shadow: var(--keys-sombra-3);
}
.keys-processando-spin {
    width: 3.25rem;
    height: 3.25rem;
    border-width: 0.3rem;
    color: var(--keys-primaria-texto);
}
.keys-processando-texto {
    margin: 0;
    color: var(--keys-tinta);
    font-weight: 600;
}
body.keys-processando-ativo { overflow: hidden; }
@media (prefers-reduced-motion: reduce) {
    .keys-processando-spin { animation-duration: 2s; }
}

/* ---------- Central de notificações (sininho) — F150.3 ----------
   O menu tinha min-width:320px INLINE, que o `min-width:0 !important` do mobile não conseguia
   anular na prática: alinhado à direita (dropdown-menu-end), transbordava pela ESQUERDA em telas
   ≤~390px e cortava os títulos (print do dono). Agora a largura é responsiva por regra, sem inline:
   no desktop 320px; no celular ocupa a tela menos o respiro lateral. */
.keys-notif-menu { min-width: 320px; max-width: calc(100vw - 1.5rem); }
.keys-notif-lista { max-height: 60vh; overflow-y: auto; }
.keys-notif-lista .list-group-item { white-space: normal; word-break: break-word; }
/* Os outros dois dropdowns "largos" caíam na MESMA armadilha (largura fixa inline): busca do topo
   e "Meus filtros". Mesma regra responsiva para os três. */
.keys-busca-menu { min-width: 320px; width: 22rem; max-width: calc(100vw - 1.5rem); }
.keys-filtros-menu { min-width: 260px; max-width: calc(100vw - 1.5rem); }
/* Offcanvas do guia "Entenda esta tela": o Bootstrap fixa 400px (--bs-offcanvas-width) e em
   telas de 360-393px o painel VAZAVA pela esquerda, cortando o início do título e dos itens.
   Mesma armadilha dos dropdowns acima — largura responsiva resolve nos dois temas. */
.keys-guia-offcanvas { width: min(25rem, calc(100vw - 1.5rem)); }
@media (max-width: 575.98px) {
    .keys-notif-menu,
    .keys-busca-menu,
    .keys-filtros-menu {
        min-width: 0;
        width: calc(100vw - 1.5rem);
    }
    /* Alvo de toque nos itens da lista + título/linha que não estouram a largura. */
    .keys-notif-lista .list-group-item { padding-top: 0.7rem; padding-bottom: 0.7rem; }
}

/* ---------- F150.1: utilitárias da auditoria MOBILE (usadas pelas views apontadas) ----------
   .keys-desc-truncada — descrição longa em célula: máx. 2 linhas + reticências (sem estourar o card)
   .keys-tabela-fixa1  — matrizes largas (pessoas/aderência/sazonalidade/comparativo/anual): 1ª
                          coluna fixa no scroll horizontal, fundo de superfície p/ não vazar texto
   .keys-toque         — alvo de toque 44px p/ botões-ícone (a regra mobile vive no @media ≤576) */
.keys-desc-truncada {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}
.keys-tabela-fixa1 th:first-child,
.keys-tabela-fixa1 td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--keys-superficie);
}
/* Descrição longa truncável em tabela (encurta ainda mais no celular — ver @media). */
.keys-col-desc { max-width: 18rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* =========================================================================================
   CAIXA QUE ROLA DE LADO CONTÉM ATÉ O QUE É `position: absolute` (05/08/2026, rodada 4)

   ## O que estava escrito neste projeto, e o que a medição corrigiu
   Três entregas seguidas repetiram a frase "`overflow-x` no filho NÃO segura a viewport". Ela
   descreve o SINTOMA certo e a causa errada, e a causa errada é o motivo de o conserto ter sido
   aplicado ao caso quatro vezes sem fechar a condição.

   Medido por CDP na conta de demonstração, nas 57 rotas do menu (`tools/medir_escala_pagina.ps1`
   + bissecção no DOM vivo): caixa larga que rola de lado NÃO tira a página de escala. O
   `/relatorios/anual` tem uma `.table-responsive` de **1662px numa caixa de 372** e mede
   `innerWidth` 390 numa viewport de 390. `/comissoes/extrato` (617 em 372), `/nfse` (803 em 372),
   `/relatorios/produtividade` (736 em 338): todas em escala. O `overflow-x` contém, sim.

   Quem estica o documento é um DESCENDENTE `position: absolute` cujo bloco contêiner está FORA da
   caixa. Ele não é filho do scroller para efeito de recorte: fica pendurado no bloco contêiner
   inicial, no lugar ESTÁTICO onde nasceu — que dentro de uma tabela larga é muito além da borda
   da tela. Aí `document.documentElement.scrollWidth` cresce, o navegador do celular afasta o zoom
   para caber, e a página inteira sai de escala levando junto a `.keys-tabbar`, que é `fixed`.

   E o fugitivo universal é o nosso próprio padrão de acessibilidade: `.visually-hidden` do
   Bootstrap é `position: absolute !important`. Ou seja, quanto MAIS P9 a tela tem, mais chance de
   quebrar — o rótulo invisível do ícone de ação, dentro da última coluna da tabela mais larga.

   | rota                       | viewport | innerWidth | fugitivo             |
   |----------------------------|----------|------------|----------------------|
   | `/relatorios/aging`        | 390      | **456**    | 1 `.visually-hidden` |
   | `/relatorios/aging`        | 576/844  | **610/850**| idem                 |
   | `/relatorios/aderencia`    | 390      | **526**    | 2 `.visually-hidden` |
   | `/relatorios/sazonalidade` | 390      | **397**    | 1 `.visually-hidden` |
   | `/visitas` (CAMPO)         | 576      | **597**    | 14 `.visually-hidden`|

   A/B na MESMA carga de página, pondo `position: relative` na caixa: `innerWidth` = viewport em
   TODAS as rotas e larguras, sem exceção.

   ## Por que `position: relative` e por que NESTA lista
   `position: relative` faz a caixa virar o bloco contêiner dos abspos de dentro — e aí o
   `overflow` dela passa a recortá-los, que era o que todo mundo já achava que acontecia. Zero
   efeito visual: sem `top/left`, `relative` não move nada e não cria contexto de empilhamento
   (o `z-index` fica `auto`).

   A lista abaixo NÃO é uma lista de telas: é a lista de todo seletor que declara rolagem
   horizontal e é usado por alguma view — INCLUSIVE os do Bootstrap, que é onde o defeito morava.
   Quem inventar a próxima caixa que rola de lado é cobrado pela REGRA 3-C do
   `tests/Unit/LarguraCelularTest.php`, que varre nosso CSS **e o de terceiro** e não tem lista de
   exceção. `.lp-ia-extrato`, `.pt-rolagem` e `.vt-galeria-miniaturas` recebem o mesmo tratamento
   nos arquivos onde nascem.
   ========================================================================================= */
.table-responsive,
pre { position: relative; }

/* =========================================================================================
   A DOCA DOS FLUTUANTES (04/08/2026) — os três botões do canto e o texto que ficava embaixo.

   ## O defeito, medido quadro a quadro
   Os três flutuantes (feedback, consultor de IA e câmera) formavam uma COLUNA VERTICAL no canto
   inferior direito: a 390px de tela, 50px de largura por 168px de altura DENTRO da coluna de
   conteúdo. Isso não come um detalhe — come o FIM de quatro linhas seguidas, e no Keys o fim da
   linha é onde mora o nome. A auditoria das aulas pegou em flagrante ("Retornar a ligação d[o]",
   "Bruno Alv" no cartão das 11:00) e antes disso já tinha comido o fim da frase do selo do DRE.

   Medido por CDP na conta de demonstração (`tools/medir_flutuantes.ps1`, 390x844), em /agenda,
   /agenda/roteiro, /visitas e /relatorios/dre — elementos de `main` debaixo dos botões e, entre
   parênteses, LINHAS DE TEXTO debaixo deles:

   | rolagem   | antes      | depois     |
   |-----------|------------|------------|
   | topo      | 15 (0)     | 11 (0)     |
   | meio      | 21 (7)     | 22 (4)     |
   | **fim**   | **17 (6)** | **0 (0)**  |

   O que sumiu no fim: «Otávio Mendes» e «Camila Prado» no aviso de visita sem ficha, «Desmarcar»,
   «24/07/2026, 16:12» na lista de visitas, e o selo do DRE («É o patrimônio de hoje mais o que
   ainda deve entrar…»). O FIM é a linha que mais importa: no meio da lista o corretor ainda pode
   rolar para tirar a frase de baixo do botão; no fim do documento não existe para onde rolar — o
   que está ali é ilegível PARA SEMPRE, porque `main` reservava a tabbar (56px) e ignorava a
   coluna de 168px que subia por cima dela.

   ## Por que ESTA saída, e não as três óbvias
   * **Esconder ao rolar** (o padrão da indústria) mata o gesto do campo: o corretor rola a lista
     PROCURANDO — é exatamente aí que a câmera precisa estar à mão. Recusado.
   * **Fundir os três num só botão que abre os três** cobra +1 toque no gesto que o P10 quer em
     zero, e esconde duas affordances atrás de uma. Recusado.
   * **Reservar espaço só no fim da lista** conserta o corte do fim e NADA do meio. Entrou como
     PARTE da correção (o `padding-bottom` do main, lá embaixo), nunca como a correção.
   * **Faixa lateral** (`padding-right` no main) provaria zero sobreposição, mas cobra ~14% da
     LARGURA em 100% da altura da página por um objeto que ocupa 20% dela — e no telefone a
     largura é o recurso escasso (foi ela que obrigou a auditoria dos 390px). Recusado.

   ## O que se faz aqui
   No telefone (onde existe a barra de baixo) os três deixam de ser coluna e viram UMA FILEIRA
   ancorada logo acima da tabbar — a faixa que o olho já lê como "controles", não como texto —, e
   `main` passa a reservar a altura dessa fileira do mesmo jeito que já reservava a da tabbar.
   A mordida no meio da rolagem cai de uma coluna que corta até 4 linhas para uma faixa colada na
   barra (7 → 4 linhas de texto somadas nas quatro telas); no fim da rolagem ela some (6 → 0).
   Nenhum botão sumiu, nenhum toque foi acrescentado. A contagem de ELEMENTOS no meio fica igual
   (21 → 22) e isso não é empate escondido: uma fileira horizontal encosta em mais irmãos da MESMA
   linha (o `<td>`, o `<tr>`, o botão ao lado), enquanto a coluna encostava em menos elementos de
   linhas DIFERENTES. Quem lê a tela conta linhas, não nós do DOM — por isso o número que manda
   aqui é o de linhas.

   ## A geometria mora NESTES tokens e em nenhum outro lugar
   Cada flutuante repetia a própria aritmética em quatro pontos de corte — foi assim que a câmera
   ficou parada em cima da estrelinha do chat (v4.18). Agora um passo (`--keys-fab-linha`)
   descreve UM slot e quem chegar depois só escolhe o índice do slot.
   ========================================================================================= */
:root {
    --keys-fab-tam:    3.4rem;   /* diâmetro do lançador */
    --keys-fab-icone:  1.4rem;   /* ícone dentro dele */
    --keys-fab-gap:    0.75rem;  /* respiro entre vizinhos e entre a doca e o resto do chrome */
    --keys-fab-borda:  1.25rem;  /* distância da borda da tela */
    --keys-fab-linha:  calc(var(--keys-fab-tam) + var(--keys-fab-gap));   /* passo de UM slot */
    --keys-tabbar-alt: calc(56px + env(safe-area-inset-bottom, 0px));     /* barra de baixo */
    /* A BARRA NÃO É O TOPO DA BARRA (05/08/2026). O slot central da tabbar é um círculo ELEVADO
       (`.keys-tabbar-acao-icone`, `margin-top: -1.35rem`) que sobe POR CIMA dela: medido no
       navegador a 390x844 e a 360x640, o topo do ícone fica **9px acima** do topo da barra. Este
       arquivo já sabia disso 1.300 linhas acima (o `4.5rem` do `.navbar-collapse`: "56px + a
       saliência + folga") — e a doca, ancorada só em `--keys-tabbar-alt`, esqueceu: a câmera
       ficou a 1px do "+" e 5px sobreposta a ele (20px a 360). Dois círculos de ~50px, MESMO
       gradiente, colados: abrir a câmera e iniciar uma visita — os dois gestos do campo — viravam
       um alvo único para um dedo de 9mm.
       0.6rem = 9,6px cobre os 9px medidos e escala junto com o resto (a saliência nasce de
       medidas em rem: ícone 2,5rem, margem -1,35rem, rótulo 0,66rem).
       Quem posiciona qualquer coisa acima da barra usa `--keys-tabbar-topo`, NUNCA `-alt`. */
    --keys-tabbar-saliencia: 0.6rem;
    --keys-tabbar-topo: calc(var(--keys-tabbar-alt) + var(--keys-tabbar-saliencia)); /* topo REAL do chrome de baixo */
    --keys-doca-alt:   calc(var(--keys-fab-tam) + 2 * var(--keys-fab-gap)); /* faixa da doca */
}
@media (max-width: 575.98px) {
    /* Celular estreito: tudo encolhe JUNTO, num lugar só (antes cada balão encolhia no seu
       bloco, e o que ficasse para trás virava colisão silenciosa). */
    :root {
        --keys-fab-tam:   3.1rem;
        --keys-fab-icone: 1.25rem;
        --keys-fab-gap:   0.6rem;
        --keys-fab-borda: 0.75rem;
    }
}

/* ---------- Balão de feedback flutuante (item 7) ----------
   Canto inferior direito, estilo WhatsApp. z-index abaixo dos toasts/tour (1090/1091) e do
   banner de instalação (1050) para nunca cobrir um alerta importante, mas acima da navbar
   (1030) e dos dropdowns comuns (1000). Só var(--keys-*) — linter de tema.
   É o slot 0 da doca (o mais à direita) por dois motivos: é o único que existe para TODO papel
   (os outros dois têm porteiro de permissão/plano), e o painel dele abre ancorado em `right: 0`
   — em qualquer outro slot ele vazaria pela borda da tela. */
.keys-feedback {
    position: fixed;
    right: var(--keys-fab-borda);
    bottom: var(--keys-fab-borda);
    z-index: 1040;
}
.keys-feedback-lancador {
    width: var(--keys-fab-tam);
    height: var(--keys-fab-tam);
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: var(--keys-fab-icone);
    color: var(--keys-superficie);
    background: linear-gradient(135deg, var(--keys-grad-a), var(--keys-grad-b));
    border: none;
    box-shadow: var(--keys-sombra-3);
    cursor: pointer;
    transition: transform 0.15s ease;
}
.keys-feedback-lancador:hover { transform: scale(1.06); }
.keys-feedback-lancador:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: 3px; }

.keys-feedback-painel {
    position: absolute;
    right: 0;
    bottom: var(--keys-fab-linha);
    width: 340px;
    max-width: calc(100vw - 2.5rem);
    max-height: min(32rem, calc(100vh - 7rem));
    overflow-y: auto;
    background: var(--keys-superficie);
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio);
    box-shadow: var(--keys-sombra-3);
    padding: 1rem 1.1rem 1.1rem;
}
.keys-feedback-cabecalho {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}
.keys-feedback-titulo { font-size: 1rem; font-weight: 700; color: var(--keys-tinta); margin: 0; }
.keys-feedback-sub { font-size: 0.82rem; color: var(--keys-texto-2); margin: 0.2rem 0 0; }

.keys-feedback-tipos {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-bottom: 0.75rem;
}
.keys-feedback-tipo {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    font-weight: 500;
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio-xs);
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.keys-feedback-tipo:hover { background: var(--keys-superficie-2); }
.keys-feedback-tipo input[type="radio"] { margin: 0; }
.keys-feedback-tipo:has(input:checked) {
    border-color: var(--keys-primaria);
    background: var(--keys-primaria-suave);
    color: var(--keys-primaria-texto);
}
.keys-feedback-rodape { display: flex; justify-content: flex-end; margin-top: 0.6rem; }
.keys-feedback-msg {
    font-size: 0.82rem;
    color: var(--keys-despesa-forte);
    margin-top: 0.5rem;
}
.keys-feedback-sucesso { text-align: center; padding: 0.75rem 0.25rem; }
.keys-feedback-sucesso .bi { font-size: 1.9rem; color: var(--keys-receita); display: block; margin-bottom: 0.4rem; }
.keys-feedback-sucesso-titulo { font-weight: 700; color: var(--keys-tinta); margin: 0 0 0.2rem; }
.keys-feedback-sucesso-texto { font-size: 0.85rem; color: var(--keys-texto-2); margin: 0 0 0.75rem; }

@media (prefers-reduced-motion: reduce) {
    .keys-feedback-lancador { transition: none; }
}

/* ---------- Ajustes de celular (§5.18 / §5.3) ----------
   Um único ponto de corte (Bootstrap sm). Sem cor literal — só tamanhos/espaços. */
@media (max-width: 575.98px) {
    /* Tabelas: mais compactas para caber mais coluna por tela; a rolagem horizontal do
       .table-responsive (overflow-x do Bootstrap) continua disponível para o resto. */
    .table-responsive {
        padding: 0.25rem 0.5rem;
        -webkit-overflow-scrolling: touch;
    }
    .table { font-size: 0.85rem; }
    /* F150.1 (auditoria mobile): 0.66rem era ilegível como regra GENÉRICA de cabeçalho. */
    .table thead th { font-size: 0.72rem; padding-top: 0.45rem; padding-bottom: 0.45rem; }
    .table th, .table td { padding-left: 0.5rem; padding-right: 0.5rem; }
    .table td { padding-top: 0.5rem; padding-bottom: 0.5rem; }

    /* KPIs da home: número e caixa menores para o valor caber sem cortar. O ícone (decorativo)
       sai no celular para liberar toda a largura ao número monetário. */
    .keys-kpi .card-body { padding: 0.7rem 0.85rem; }
    .keys-kpi .keys-kpi-icone { display: none; }
    .keys-kpi-numero { font-size: 1.15rem; }

    /* Gráficos um pouco mais baixos que no desktop, mas ainda altos o bastante para ler. */
    .keys-grafico { height: 260px; }
    .keys-grafico-alto { height: 280px; }

    /* Inputs compactos a 16px: trava o zoom automático do iOS ao focar (acessibilidade). */
    .form-control-sm, .form-select-sm { font-size: 1rem; }

    /* Navegação por submenu no celular = alvo de toque AA (classe exclusiva dos menus do layout). */
    .dropdown-item {
        min-height: 44px;
        display: flex;
        align-items: center;
        padding-top: 0.65rem;
        padding-bottom: 0.65rem;
    }
    /* Dropdown nunca estoura a tela estreita (neutraliza pisos min-width inline, ex.: sino). */
    .dropdown-menu {
        min-width: 0 !important;
        max-width: calc(100vw - 1.5rem) !important;
    }

    /* Botões-ícone da navbar (sino, discrição, menu do usuário, hambúrguer) com 44px de toque.
       Mira por atributo — NÃO infla o .btn-sm das tabelas (que a v2.6 comprimiu).
       F150.1 (auditoria mobile): entram os VIZINHOS da mesma fileira que tinham ficado de fora —
       busca, "+ Novo lançamento" (CTA primário!), guia "Entenda esta tela" e a lente de horas —
       e a classe utilitária .keys-toque p/ botões-ícone espalhados pelas telas. */
    .navbar-toggler,
    [data-notif-toggle],
    [data-discricao],
    [data-lente-horas],
    [data-busca-toggle],
    [data-novo-lancamento],
    [data-guia-botao],
    [data-notif-marcar],
    .keys-toque,
    [data-tour-alvo="tema"] > .btn {
        min-height: 44px;
        min-width: 44px;
    }

    /* Faixa de instalação (§6.1): no celular empilha e ENTRA NO FLUXO (relative) para empurrar a
       navbar em vez de cobri-la — o pwa.js usa o atributo `hidden` no repouso, então não sobra
       faixa vazia. Botões com 44px. */
    .keys-instalar-banner { position: relative; }
    .keys-instalar-banner .container {
        flex-direction: column;
        align-items: stretch;
        gap: 0.5rem;
    }
    .keys-instalar-banner [data-pwa-instalar],
    .keys-instalar-banner [data-pwa-depois],
    .keys-instalar-banner [data-pwa-nunca] { min-height: 44px; }

    /* Modais: trava de altura p/ o teclado do celular não empurrar o botão de ação para fora.
       100dvh (viewport dinâmico) com fallback silencioso ao 100vh em navegadores antigos. */
    .modal-content {
        display: flex;
        flex-direction: column;
        max-height: calc(100vh - 1rem);
        max-height: calc(100dvh - 1rem);
    }
    .modal-body { overflow-y: auto; }

    /* Grupos de botões do cabeçalho de página quebram linha em vez de espremer/estourar. */
    .keys-page-header .d-flex.gap-2,
    .keys-page-header form.d-flex { flex-wrap: wrap; }

    /* Switches/checkboxes: mais altura tocável (o label já alterna; aqui só folga vertical,
       sem mudar o modelo de layout do Bootstrap para não deslocar o switch). */
    .form-check { padding-top: 0.35rem; padding-bottom: 0.35rem; }

    /* CTA primário do formulário com alvo de 44px. */
    form .btn-primary[type="submit"] { min-height: 44px; }

    /* Ícone de ação vira alvo de 44px só no celular (editar/excluir/voltar em p-0). */
    .keys-acao-icone { width: 2.75rem; height: 2.75rem; border-radius: var(--keys-raio-xs); }

    /* Descrição truncada mais curta no celular (menos rolagem horizontal). */
    .keys-col-desc { max-width: 9rem; }

    /* KPIs de meia-linha (relatório Anual) com número que não quebra feio. */
    .col-6 .card .h5.keys-valor { font-size: 1.05rem; }

    /* Container/rodapé: ganha ~8px de largura útil no celular. */
    .container { --bs-gutter-x: 1rem; }
    main.py-4 { padding-top: 1rem !important; padding-bottom: 1.25rem !important; }

    /* Toast (desfazer) nunca ultrapassa a tela. */
    .toast-container { max-width: calc(100vw - 1.5rem); }

    /* A planilha de lançamentos vira cartão AQUI EMBAIXO, num bloco próprio que vai até 991,98px
       — ver "MECANISMO DE CARTÃO Nº 2" logo após o fim deste @media. Ela morava dentro deste
       bloco de ≤575,98px, e era por isso que /visitas voltava a ser tabela larga a partir de 576. */

    /* Balão de feedback (item 7): painel quase em tela cheia no celular (o teclado já toma
       metade da tela ao focar o textarea — largura fixa de desktop ficaria espremida).
       O tamanho e a distância da borda saíram daqui: agora vêm dos tokens `--keys-fab-*` no topo
       do arquivo, junto com os dos outros dois flutuantes (um lugar só, sem drift). */
    .keys-feedback-painel {
        width: calc(100vw - 1.5rem);
        max-height: calc(100dvh - 6.5rem);
        max-height: calc(100vh - 6.5rem);
    }
}

/* ===== MECANISMO DE CARTÃO Nº 2 — a planilha de lançamentos (P2), e o alcance dele =====
 *
 * Cada linha (6 colunas: status, vencimento, descrição, plano, valor, ações) vira um cartão.
 * Linha 1 do cartão: status + vencimento à esquerda, valor em destaque à direita; depois descrição
 * e plano em largura cheia; por fim as ações à direita com 44px de toque. Diferente do mecanismo
 * genérico (item 42/42-B, `.keys-cards-mobile` + `data-rotulo`), este é POSICIONAL: as regras
 * dependem da ordem das 6 colunas. Por isso ele não é o padrão para tela nova — é o padrão desta
 * planilha, e de quem já o herdou.
 *
 * ⚠️ 05/08/2026 (rodada 4): este bloco vivia dentro do `@media (max-width: 575.98px)`, e essa era
 * a metade escondida do defeito de largura. A 575 a tela era cartão; a 576 virava tabela larga
 * outra vez — e entre 576 e 767 o `.container` do Bootstrap fica travado em 540, então o corte a
 * 700px é EXATAMENTE o de 576. Medido na conta de demonstração: `/visitas` (tela de CAMPO) deu
 * `innerWidth` **597 numa viewport de 576**, com o `<th>` "Ações" em x=610 — fora da tela.
 *
 * O mecanismo nº 1 já tinha aprendido isso em 04/08 (`.keys-cards-tablet`, até 991,98px, que é
 * onde ESTE app deixa de ser móvel: a tabbar é `.d-lg-none`). O nº 2 não foi junto porque o teste
 * de largura nunca citou esta classe — nem uma vez. Agora a REGRA 13 do
 * `tests/Unit/LarguraCelularTest.php` VARRE o CSS atrás de qualquer `<X> thead { display: none }`
 * e cobra de TODO mecanismo que encontrar o alcance até 991,98px, sem lista de nomes: a próxima
 * invenção de "tabela vira cartão" é pega no mesmo dia.
 */
@media (max-width: 991.98px) {
    .keys-tabela-cards thead { display: none; }
    .keys-tabela-cards,
    .keys-tabela-cards tbody { display: block; width: 100%; }
    .keys-tabela-cards tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.1rem 0.5rem;
        border: 1px solid var(--keys-borda);
        border-radius: var(--keys-raio-sm);
        background: var(--keys-superficie);
        padding: 0.6rem 0.75rem;
        margin-bottom: 0.5rem;
    }
    .keys-tabela-cards td {
        border: 0;
        padding: 0;
        text-align: left !important;
        /* Palavra sem espaço (e-mail, URL, código) não quebra sozinha e sai por cima do vizinho —
           mesma razão da REGRA 11 no mecanismo nº 1, e aqui dói igual. */
        overflow-wrap: anywhere;
    }
    .keys-tabela-cards td:nth-child(2) { font-weight: 500; }   /* vencimento */
    .keys-tabela-cards td:nth-child(5) {                       /* valor → à direita, em destaque */
        order: 1;
        margin-left: auto;
        font-size: 1rem;
        font-weight: 600;
    }
    .keys-tabela-cards td:nth-child(3),                        /* descrição */
    .keys-tabela-cards td:nth-child(4) {                       /* plano */
        order: 2;
        width: 100%;
    }
    .keys-tabela-cards td:nth-child(6) {                       /* ações */
        order: 3;
        width: 100%;
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 0.4rem;
        margin-top: 0.35rem;
    }
    .keys-tabela-cards td:nth-child(6) .btn {
        min-width: 2.75rem;
        min-height: 2.75rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    .keys-tabela-cards td:nth-child(6) form.d-inline { display: inline-flex !important; }
}

/* ---------- Baixa em LOTE (§5.4): checkbox na célula de status + barra flutuante ---------- */
/* O checkbox mora junto da bolinha de status (não é coluna nova → não desloca os nth-child do
   layout-card no mobile). Cores só via tokens (fiscalizado pelo linter de tema, 2 temas). */
.keys-sel-cel { white-space: nowrap; }
.keys-sel-cel .keys-sel {
    margin-right: 0.4rem;
    vertical-align: middle;
    cursor: pointer;
}
.keys-sel-cel.keys-sel-bloqueado { opacity: 0.4; }   /* tipo oposto trancado (receita×despesa) */
.keys-lote-toolbar kbd { font-size: 0.7em; padding: 0.05rem 0.3rem; }

.keys-barra-lote {
    position: fixed;
    bottom: 1.25rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1045;                       /* acima do conteúdo, abaixo do backdrop de modal (1050) */
    max-width: calc(100% - 1.5rem);
    background: var(--keys-superficie);
    border: 1px solid var(--keys-borda-forte);
    border-radius: var(--keys-raio);
    box-shadow: var(--keys-sombra-3);
    padding: 0.6rem 0.9rem;
}
.keys-barra-lote-inner {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}
.keys-barra-lote-info { color: var(--keys-tinta); }

@media (max-width: 575.98px) {
    /* Sobe pra não colidir com o balão de feedback (canto inferior direito). */
    .keys-barra-lote { bottom: 5rem; width: calc(100% - 1.5rem); }
    .keys-barra-lote-inner { justify-content: space-between; }
    /* F150.1 (auditoria mobile, ALTO): com a TABBAR presente, 5rem não descontava a barra fixa
       (56px + safe-area + o "+" central elevado) — a barra de lote ficava colada/encoberta.
       04/08/2026: passou a descontar também a DOCA dos flutuantes, que virou fileira horizontal
       e cobria a metade direita desta barra (onde ficam os botões da ação em lote).
       05/08/2026: mesma base da doca (`--keys-tabbar-topo`) — acompanha a fileira que subiu. */
    body:has(.keys-tabbar) .keys-barra-lote {
        bottom: calc(var(--keys-tabbar-topo) + var(--keys-doca-alt));
    }
    /* F150.1: checkbox da baixa em lote com área de toque decente (era o tamanho nativo ~13px). */
    .keys-sel-cel .keys-sel { width: 1.3rem; height: 1.3rem; }

    /* F150.3 (refino): a linha de edição de item da lista de compras somava ~364px de campos
       fixos e quebrava em 3 níveis no celular — encolhe os três (mira nos data-* já existentes). */
    [data-qtd] { width: 4rem !important; }
    [data-preco] { width: 5rem !important; }
    [data-subtotal] { width: 4.5rem !important; }
}

/* ---------- Estado ativo do menu (quick win 28-QW): reforço além da cor do Bootstrap ----------
   Sem cor literal (o .active do Bootstrap já usa --bs-navbar-active-color, tematizado);
   o peso + sublinhado dão o "onde estou" mesmo pra quem não distingue a diferença de cor (P9). */
.navbar .nav-link.active,
.navbar .nav-link[aria-current="page"] {
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 0.45em;
    text-decoration-thickness: 2px;
}

/* ---------- Calendário de vencimentos (item 36, §7.7): grade do mês em Lançamentos ----------
   Só var(--keys-*) (linter de tema). As bolinhas reusam as classes semânticas keys-receita/
   keys-despesa (cor por tipo, já definidas acima) via `currentColor` — sem token novo; ✅ baixado
   = opacidade reduzida; 🔁 virtual (recorrência ainda não lançada) = borda tracejada em vez de
   preenchida. `.keys-clicavel` (já existe) cobre cursor + foco de teclado. */
.keys-calendario { display: flex; flex-direction: column; gap: 0.25rem; }

.keys-calendario-cabecalho-linha,
.keys-calendario-semana {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 0.25rem;
}

.keys-calendario-cabecalho {
    text-align: center;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--keys-texto-2);
    text-transform: uppercase;
    padding: 0.15rem 0;
}

.keys-calendario-dia {
    min-height: 4.5rem;
    border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio-xs);
    background: var(--keys-superficie);
    padding: 0.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.keys-calendario-dia.keys-cal-fora { background: transparent; border-color: transparent; }
.keys-calendario-dia.keys-cal-hoje { border-color: var(--keys-primaria); box-shadow: inset 0 0 0 1px var(--keys-primaria); }
.keys-calendario-dia.keys-clicavel:hover { background: var(--keys-superficie-2); }

.keys-cal-numero { font-size: 0.8rem; font-weight: 600; color: var(--keys-texto-2); }
.keys-calendario-dia.keys-cal-hoje .keys-cal-numero { color: var(--keys-primaria-texto); }

.keys-cal-bolinhas { display: flex; flex-wrap: wrap; gap: 0.2rem; align-items: center; }
.keys-cal-bolinha {
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    display: inline-block;
    background-color: currentColor;
}
.keys-cal-bolinha--baixado { opacity: 0.45; }
.keys-cal-bolinha--virtual { background-color: transparent; border: 1.5px dashed currentColor; }
.keys-cal-mais { font-size: 0.7rem; color: var(--keys-texto-3); font-weight: 600; }

/* Painel "Lançamentos do dia" (modal reaproveitado, ver principal.php) */
.keys-cal-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--keys-borda);
    flex-wrap: wrap;
}
.keys-cal-item:last-child { border-bottom: none; }
.keys-cal-item-info { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.keys-cal-item-desc { font-weight: 500; overflow-wrap: anywhere; }
.keys-cal-item-valor { white-space: nowrap; }
.keys-cal-item-acoes { display: flex; gap: 0.25rem; align-items: center; flex-wrap: wrap; }

@media (max-width: 575.98px) {
    /* Encolhe a grade em vez de virar lista por dia — 7 colunas continuam cabendo a 360px. */
    .keys-calendario-dia { min-height: 3.25rem; padding: 0.15rem; }
    .keys-cal-bolinha { width: 0.4rem; height: 0.4rem; }
    .keys-cal-numero { font-size: 0.7rem; }
    .keys-cal-mais { font-size: 0.62rem; }
}

/* item 25l-b (a11y, §2.5 da análise de melhorias de 13/07): valor "excluído" do simulador "e se"
 * do DRE (dre-detalhe.js). Antes era 'text-decoration-line-through opacity-50' cru — a opacity
 * baixava o contraste efetivo a ~3,2:1 no tema claro (< 4,5:1 AA). Sem opacity: só cor tokenizada
 * (--keys-texto-2, já ≥4,5:1 nos 2 temas) + risco — o risco sozinho já comunica "fora da soma". */
.keys-sim-excluido { color: var(--keys-texto-2); text-decoration: line-through; }

/* ===== item 42 / §9.2 (análise de melhorias 2026-07-13): padrão REUTILIZÁVEL de tabela → cards
 * no celular. O .keys-tabela-cards (acima, ~linha 789) é específico da planilha de lançamentos
 * (regras por nth-child amarradas a 6 colunas fixas). Este aqui é genérico e DATA-DRIVEN — serve
 * para qualquer tabela, sem depender da ordem/quantidade de colunas:
 *   1. marque a <table> com a classe .keys-cards-mobile;
 *   2. em CADA <td>, um atributo data-rotulo="<rótulo>" — reuse a MESMA chave i18n do <th> da
 *      coluna (zero texto novo); o rótulo aparece via ::before, pequeno e discreto
 *      (--keys-texto-2), acima do conteúdo original da célula, que fica intocado (badges,
 *      ícones, formulários — o que já estiver lá continua funcionando);
 *   3. a célula de ações leva data-acoes (sem data-rotulo) e vira uma faixa de botões com alvo
 *      de toque ≥2,75rem (AA) em vez de rótulo+valor.
 * Só ativa abaixo de 575,98px — o desktop não muda uma linha. */
@media (max-width: 575.98px) {
    .keys-cards-mobile thead { display: none; }
    .keys-cards-mobile,
    .keys-cards-mobile tbody { display: block; width: 100%; }
    .keys-cards-mobile tr {
        display: block;
        border: 1px solid var(--keys-borda);
        border-radius: var(--keys-raio-sm);
        background: var(--keys-superficie);
        padding: 0.6rem 0.75rem;
        margin-bottom: 0.5rem;
    }
    .keys-cards-mobile td {
        display: block;
        border: 0;
        padding: 0.3rem 0;
        text-align: left !important;
        /* E-mail e URL não têm onde quebrar: `contato@suldeminasmanutencao.com.br` é UMA palavra
           de 247px, e num cartão estreito ela sai por cima do vizinho em vez de passar para a
           linha de baixo. `anywhere` (e não `break-all`) só quebra quando não há alternativa —
           palavra normal continua quebrando por espaço. Medido no /pessoas: sem isto o cartão
           empurrava a tabela para 505px numa caixa de 482. */
        overflow-wrap: anywhere;
    }
    .keys-cards-mobile td + td { border-top: 1px dashed var(--keys-borda); }
    .keys-cards-mobile td::before {
        content: attr(data-rotulo);
        display: block;
        font-size: 0.7rem;
        font-weight: 600;
        color: var(--keys-texto-2);
        text-transform: uppercase;
        letter-spacing: .02em;
        margin-bottom: 0.15rem;
    }
    /* Célula sem rótulo (não deveria existir fora de data-acoes, mas por segurança) não sobra
       com um ::before vazio ocupando espaço/borda. */
    .keys-cards-mobile td:not([data-rotulo]):not([data-acoes])::before,
    .keys-cards-mobile td[data-rotulo=""]::before { content: none; margin: 0; }

    /* Ações: sem rótulo — vira faixa de botões com toque confortável, alinhada à direita. */
    .keys-cards-mobile td[data-acoes] {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 0.4rem;
        padding-top: 0.5rem;
    }
    .keys-cards-mobile td[data-acoes]::before { content: none; }
    .keys-cards-mobile td[data-acoes] .btn {
        min-width: 2.75rem;
        min-height: 2.75rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    .keys-cards-mobile td[data-acoes] form.d-inline { display: inline-flex !important; }
}

/* ===== item 42-B (04/08/2026): O CARTÃO NÃO PARA NO TELEFONE.
 *
 * O conserto de julho valeu só abaixo de 576px, e o corte voltava inteiro no primeiro pixel
 * acima — que é justamente onde a gerente trabalha (tablet e notebook pequeno). Medido por CDP na
 * conta de demonstração, `scrollWidth` da caixa × largura da caixa:
 *
 * | tela                | 390px      | 576px      | 700px      | 844px |
 * |---------------------|------------|------------|------------|-------|
 * | /vistoria           | 595 em 372 | 671 em 514 | 671 em 514 | cabe  |
 * | /captacao           | 507 em 372 | 553 em 514 | 553 em 514 | cabe  |
 * | /portal             | 525 em 338 | 514 em 480 | 514 em 480 | cabe  |
 * | /vitrine            | 562 em 338 | 599 em 480 | 599 em 480 | cabe  |
 * | /negocios           | 593 em 372 | 692 em 514 | 692 em 514 | cabe  |
 * | /relatorios/fluxo   | 509 em 372 | 549 em 514 | 549 em 514 | cabe  |
 *
 * A caixa não muda entre 576 e 767 porque o `.container` do Bootstrap fica travado em 540 nessa
 * faixa inteira — por isso o defeito é IGUAL a 576 e a 700. E o que sobra da caixa não some: fica
 * cortado. Foi o que a 11ª aula do curso gravou na fila de vistorias — o espectador leu "RIO",
 * "ada" e "parativo" em três passos e nunca uma linha inteira.
 *
 * `.keys-cards-tablet` é MODIFICADOR de `.keys-cards-mobile` (sozinho não faz nada, de propósito:
 * se alguém puser só a segunda, a tabela seguiria cortando no TELEFONE, que é o pior dos dois
 * mundos e em silêncio). Ele estende o mesmo padrão — mesmo `data-rotulo`, mesmo `data-acoes`,
 * zero texto novo — até 991,98px, que é onde este app PRÓPRIO deixa de ser móvel: é o corte da
 * barra fixa de baixo (`.d-lg-none`) e do menu hambúrguer. Escolher esse corte, e não 767,98
 * (onde as tabelas voltam a caber por 2px de folga), é o que impede o corte de voltar calado
 * quando um rótulo em espanhol for mais longo que o mesmo rótulo em português.
 *
 * ⚠️ 05/08/2026 — AS DUAS CLASSES ANDAM SEMPRE JUNTAS. A redação anterior dizia que a tela
 * "declara uma vez que a tabela vira cartão, e depois ATÉ ONDE", como se o alcance fosse escolha
 * de cada tela. Não é, e essa leitura custou caro: das 25 views com `.keys-cards-mobile`, 18
 * ficaram sem `.keys-cards-tablet` e o corte sobreviveu inteiro — /pessoas 770px numa caixa de 514
 * (131 elementos de texto cortados), /imoveis 880 em 514, /repasses 806 em 514, /cobranca/regua
 * com a PÁGINA a 819px numa viewport de 576. As duas primeiras são as telas das aulas 04 e 06.
 * O par virou invariante varrida em `LarguraCelularTest::testTodaTabelaCartaoVaiAteOTablet()`:
 * quem tem `mobile` tem `tablet`, sem lista de exceção — foi lista fixa que deixou isto passar.
 *
 * Do tablet para cima o cartão vira DUAS COLUNAS: empilhar uma coluna só a 900px desperdiçaria
 * metade da tela e dobraria a rolagem vertical — o ganho de não cortar não pode custar isso. */
@media (min-width: 576px) and (max-width: 991.98px) {
    .keys-cards-mobile.keys-cards-tablet thead { display: none; }
    .keys-cards-mobile.keys-cards-tablet { display: block; width: 100%; }
    .keys-cards-mobile.keys-cards-tablet tbody {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.5rem;
        align-items: start;
    }
    .keys-cards-mobile.keys-cards-tablet tr {
        display: block;
        border: 1px solid var(--keys-borda);
        border-radius: var(--keys-raio-sm);
        background: var(--keys-superficie);
        padding: 0.6rem 0.75rem;
        margin-bottom: 0;
    }
    .keys-cards-mobile.keys-cards-tablet td {
        display: block;
        border: 0;
        padding: 0.3rem 0;
        text-align: left !important;
        /* Aqui o teto é MENOR que no telefone, não maior: o cartão de tablet tem duas colunas, e
           cada uma mede ~237px contra os ~330 de um cartão de 390px. Foi onde o e-mail longo do
           /pessoas apareceu — 247px de palavra única em 211 de célula. Ver o comentário gêmeo no
           bloco de celular. */
        overflow-wrap: anywhere;
    }
    .keys-cards-mobile.keys-cards-tablet td + td { border-top: 1px dashed var(--keys-borda); }
    .keys-cards-mobile.keys-cards-tablet td::before {
        content: attr(data-rotulo);
        display: block;
        font-size: 0.7rem;
        font-weight: 600;
        color: var(--keys-texto-2);
        text-transform: uppercase;
        letter-spacing: .02em;
        margin-bottom: 0.15rem;
    }
    .keys-cards-mobile.keys-cards-tablet td:not([data-rotulo]):not([data-acoes])::before,
    .keys-cards-mobile.keys-cards-tablet td[data-rotulo=""]::before { content: none; margin: 0; }

    .keys-cards-mobile.keys-cards-tablet td[data-acoes] {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 0.4rem;
        padding-top: 0.5rem;
    }
    .keys-cards-mobile.keys-cards-tablet td[data-acoes]::before { content: none; }
    .keys-cards-mobile.keys-cards-tablet td[data-acoes] .btn {
        min-width: 2.75rem;
        min-height: 2.75rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    .keys-cards-mobile.keys-cards-tablet td[data-acoes] form.d-inline { display: inline-flex !important; }
}

/* ===== item 42-C (05/08/2026, rodada 3): as DUAS peças que faltavam para um RELATÓRIO virar cartão.
 *
 * O padrão 42/42-B nasceu das telas de cadastro, e nelas a tabela é `thead` + `tbody` e ponto. Um
 * relatório tem mais duas coisas, e sem elas a conversão sai quebrada em silêncio:
 *
 * 1. `<tfoot>` — a linha de TOTAL. Com a `<table>` em `display: block`, um `tfoot` deixado em
 *    `table-footer-group` vira uma tabela anônima no meio da pilha de cartões: os totais perdem a
 *    caixa e encostam no cartão de cima. Ele passa a ser bloco, com a mesma moldura de cartão (a
 *    regra `tr` do 42/42-B já o alcança) e um destaque de superfície, porque "o total" precisa se
 *    distinguir de "mais uma linha" — e ocupa a LARGURA INTEIRA no tablet, onde o corpo é grade de
 *    duas colunas: total espremido em meia largura ao lado de um registro qualquer seria pior do
 *    que a tabela original.
 *
 * 2. `.keys-col-desc` — a coluna de descrição das matrizes é `nowrap` + `text-overflow: ellipsis`
 *    com teto de 18rem (9rem no celular). Isso é certo numa TABELA, onde a coluna é estreita e o
 *    resto está na mesma linha. Dentro de um cartão é o contrário: a célula tem a largura toda do
 *    cartão e o nome apareceria cortado em "Bruno Salga…" a 390px, com espaço sobrando embaixo.
 *    No cartão ela volta a ser texto que quebra — que é o ganho de P9 que a conversão promete.
 *
 * Genérico de propósito: quem escrever o próximo relatório-cartão herda os dois, sem descobrir o
 * defeito na aula gravada. */
@media (max-width: 575.98px) {
    .keys-cards-mobile tfoot { display: block; }
    .keys-cards-mobile tfoot tr { background: var(--keys-superficie-2); border-color: var(--keys-borda-forte); }
    .keys-cards-mobile .keys-col-desc {
        max-width: none;
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }
}

@media (min-width: 576px) and (max-width: 991.98px) {
    .keys-cards-mobile.keys-cards-tablet tfoot { display: block; margin-top: 0.5rem; }
    .keys-cards-mobile.keys-cards-tablet tfoot tr { background: var(--keys-superficie-2); border-color: var(--keys-borda-forte); }
    .keys-cards-mobile.keys-cards-tablet .keys-col-desc {
        max-width: none;
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }
}

/* ===== item 28-B (Proposta B, tools/analise-melhorias-2026-07-13.md §4.3): bottom tab bar
 * MOBILE do app autenticado — partial app/Views/partials/tabbar.php. SEM JS: 5 âncoras, estado
 * ativo resolvido no servidor, destaque do slot central por CSS (o antigo tabbar.js foi removido).
 * Fixa no rodapé só abaixo de lg (o utilitário Bootstrap `.d-lg-none` no próprio `<nav>` já cuida
 * do desktop); o menu do topo continua existindo em toda largura. 5 slots (F1.2b, imobiliária):
 * Início / Imóveis / "+ Novo imóvel" central elevado e destacado / Financeiro (com cadeia de
 * fallback até Notificações) / Mais (tela-hub /menu). Slot ativo NUNCA só
 * por cor (P9): peso de fonte 700 + barrinha indicadora no topo do slot + aria-current="page"
 * (mesma detecção do menu do topo). Safe-area iOS via env(safe-area-inset-bottom) — ⚠️ só tem
 * efeito com `viewport-fit=cover` no <meta name="viewport">, ver WIRING SPEC. */
.keys-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1030;
    display: flex;
    align-items: stretch;
    height: 56px;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--keys-superficie);
    border-top: 1px solid var(--keys-borda);
    box-shadow: var(--keys-sombra-2);
}
.keys-tabbar-item {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    padding: 0.25rem 0.15rem;
    border: 0;
    background: transparent;
    color: var(--keys-texto-2);
    font-size: 0.66rem;
    font-weight: 500;
    line-height: 1.1;
    text-decoration: none;
    min-width: 0;
}
.keys-tabbar-item i { font-size: 1.15rem; }
.keys-tabbar-item span {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.keys-tabbar-item:hover,
.keys-tabbar-item:focus-visible {
    color: var(--keys-tinta);
    background: var(--keys-superficie-2);
}
.keys-tabbar-item:focus-visible {
    outline: 2px solid var(--keys-anel-foco);
    outline-offset: -2px;
}

/* Slot ativo: cor de marca + peso maior + barrinha indicadora — nunca só cor (P9). */
.keys-tabbar-item.keys-tabbar-ativo {
    color: var(--keys-primaria-texto);
    font-weight: 700;
    position: relative;
}
.keys-tabbar-item.keys-tabbar-ativo::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 1.75rem;
    height: 3px;
    border-radius: 0 0 3px 3px;
    background: var(--keys-primaria);
}

/* AÇÃO central — ícone elevado num círculo de marca (mesmo par de cores do botão primário do
 * header: fundo ouro + texto --keys-sobre-primaria, ~11:1 nos 2 temas).
 * F1.2b: chamava-se `.keys-tabbar-lancar` porque a ação central do Cents era "Lançar". No Keys ela
 * é "Novo imóvel" e na F1.3 vira "Nova visita" — o nome passou a ser o do PAPEL do slot (ação),
 * não o da ação da vez, para não virar mentira a cada mudança de produto. */
.keys-tabbar-acao-icone {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    margin-top: -1.35rem;
    border-radius: 50%;
    background: var(--keys-primaria);
    color: var(--keys-sobre-primaria);
    box-shadow: var(--keys-sombra-2);
}
.keys-tabbar-acao-icone i { font-size: 1.3rem; }
.keys-tabbar-acao span { font-weight: 600; }
.keys-tabbar-acao:hover .keys-tabbar-acao-icone,
.keys-tabbar-acao:focus-visible .keys-tabbar-acao-icone {
    background: var(--keys-primaria-escura);
}

/* F1.2b: aqui havia `.keys-tabbar-mais[aria-expanded="true"]`, regra MORTA desde a F150 — o slot
 * "Mais" deixou de abrir o hambúrguer (não tem mais aria-expanded) e virou link para /menu, onde o
 * estado ativo é o mesmo `.keys-tabbar-ativo` dos outros slots. */

@media (max-width: 991.98px) {
    /* Compensa a altura fixa da tabbar para o <main> não ficar por baixo dela. `:has()` (já
       usado em app.css, ex.: .keys-brand:has(...)) detecta a barra sem exigir classe extra no
       <body> (arquivo compartilhado, fora do escopo desta entrega) — some sozinho se o partial
       não estiver incluído no layout, então nunca afeta telas sem a tabbar (ex.: login/master). */
    /* F150.1 (auditoria mobile, ALTO): o !important aqui é OBRIGATÓRIO — sem ele, tanto o
       utilitário .py-4 do Bootstrap quanto o ajuste mobile `main.py-4` (ambos !important)
       venciam esta regra e o padding real caía a 20px: o FIM de toda página ficava escondido
       atrás da tabbar de 56px, sem como rolar além. Com !important dos dois lados, decide a
       especificidade — e esta (body:has+main) é maior. */
    /* 04/08/2026: a reserva passou a incluir a DOCA dos flutuantes (`--keys-doca-alt`), e não só
       a tabbar. Reservar a barra e ignorar os botões que flutuam ACIMA dela é o que fazia o fim
       de /visitas, /agenda/roteiro e /relatorios/dre morrer debaixo de um círculo sem ter para
       onde rolar — 17 elementos e 6 linhas de texto encobertos nas quatro telas, medidos por CDP
       (`tools/medir_flutuantes.ps1`); com a doca na conta, zero.
       05/08/2026: a base virou `--keys-tabbar-topo` porque a doca subiu a saliência do "+" — a
       reserva tem de subir JUNTO, senão o conserto da colisão devolve o corte do fim da página. */
    body:has(.keys-tabbar) main {
        padding-bottom: calc(var(--keys-tabbar-topo) + var(--keys-doca-alt)) !important;
    }
    /* O toast de "desfazer" (.toast-container.bottom-0 end-0, layout_app.php) nasce no MESMO
       canto da doca: sobe a faixa inteira, senão os círculos pousam em cima do botão "Desfazer"
       — que é a única chance de reverter a ação. (05/08/2026: sobe pela mesma base da doca,
       `--keys-tabbar-topo`, senão o toast reencosta nos círculos que subiram.) */
    body:has(.keys-tabbar) .toast-container.bottom-0 {
        bottom: calc(var(--keys-tabbar-topo) + var(--keys-doca-alt));
    }
}

/* ===================================================================
   Onboarding / setup inicial (§5.2, v3.87) — vitrine convidativa e OPCIONAL.
   Cada etapa mostra POR QUE + O QUE a pessoa vai ter, com prévia fiel do
   resultado. Zero cor crua: só var(--keys-*) (linter de tema), nasce nos 2 temas.
   =================================================================== */
.keys-ob-top {
    display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
    background: var(--keys-superficie); border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio); padding: 0.85rem 1.1rem;
    box-shadow: var(--keys-sombra-1); margin-bottom: 1.4rem;
}
.keys-ob-brand { font-family: var(--keys-fonte-titulo); font-weight: 800; display: inline-flex; gap: 0.5rem; align-items: center; color: var(--keys-tinta); white-space: nowrap; }
.keys-ob-prog { flex: 1; min-width: 190px; }
.keys-ob-prog-lab { display: flex; justify-content: space-between; font-size: 0.78rem; color: var(--keys-texto-2); margin-bottom: 0.35rem; }
.keys-ob-later { font-weight: 700; white-space: nowrap; color: var(--keys-primaria-texto); background: var(--keys-superficie); border: 1.5px solid var(--keys-borda-forte); }
.keys-ob-later:hover { border-color: var(--keys-primaria); color: var(--keys-primaria-texto); }

.keys-ob-hero {
    background: var(--keys-superficie); border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio); padding: 2rem 1.9rem; box-shadow: var(--keys-sombra-2);
    margin-bottom: 1.6rem; position: relative; overflow: hidden;
}
.keys-ob-hero::before {
    content: ""; position: absolute; inset: -45% 55% auto -12%; height: 240px; border-radius: 50%;
    background: radial-gradient(closest-side, var(--keys-primaria-suave), transparent); pointer-events: none;
}
.keys-ob-eyebrow { font-size: 0.72rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--keys-texto-3); font-weight: 700; margin: 0 0 0.35rem; }
.keys-ob-h1 { font-family: var(--keys-fonte-titulo); font-weight: 800; letter-spacing: -0.02em; font-size: clamp(1.5rem, 3vw, 2.2rem); position: relative; text-wrap: balance; }
.keys-ob-sub { margin-top: 0.65rem; font-size: 1.05rem; color: var(--keys-texto-2); max-width: 62ch; position: relative; }
.keys-ob-settings { margin-top: 1.3rem; display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-end; position: relative; }
.keys-ob-settings > div { min-width: 150px; }
.keys-ob-hero-cta { align-self: flex-end; }
.keys-ob-hero-alt { margin-top: 1rem; display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; position: relative; }

.keys-ob-secao { margin-bottom: 1.4rem; }
.keys-ob-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.1rem; }
.keys-ob-pcard {
    background: var(--keys-superficie); border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio); overflow: hidden; box-shadow: var(--keys-sombra-1);
    display: flex; flex-direction: column; transition: transform 0.18s, box-shadow 0.18s;
}
.keys-ob-pcard:hover { transform: translateY(-3px); box-shadow: var(--keys-sombra-2); }
.keys-ob-pcard-body { padding: 1.1rem 1.1rem 0.3rem; }
.keys-ob-pcard-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.5rem; }
.keys-ob-pcard-foot { padding: 0.7rem 1.1rem 1.1rem; display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.keys-ob-picon { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; font-size: 1.1rem; flex: none; }
.keys-ob-picon.tom-dia { background: var(--keys-primaria-suave); color: var(--keys-primaria-texto); }
.keys-ob-picon.tom-mes { background: var(--keys-info-suave); color: var(--keys-info); }
.keys-ob-picon.tom-inv { background: var(--keys-receita-suave); color: var(--keys-receita); }
.keys-ob-picon.tom-pat { background: var(--keys-aviso-suave); color: var(--keys-aviso-forte); }
.keys-ob-why { color: var(--keys-texto-2); font-size: 0.95rem; margin: 0.35rem 0 0; }
.keys-ob-badge { font-size: 0.6rem; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; color: var(--keys-sobre-primaria); background: var(--keys-primaria); padding: 0.12rem 0.4rem; border-radius: 6px; vertical-align: middle; }

.keys-ob-shot { background: var(--keys-superficie-2); border-top: 1px solid var(--keys-borda); padding: 0.9rem 0.9rem 0; position: relative; margin-top: 0.6rem; }
.keys-ob-shot .keys-ob-dots { position: absolute; top: 0.6rem; left: 0.75rem; display: flex; gap: 0.3rem; z-index: 1; }
.keys-ob-shot .keys-ob-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--keys-borda-forte); display: block; }
.keys-ob-shot img { display: block; width: 100%; height: auto; border-radius: 10px 10px 0 0; border: 1px solid var(--keys-borda); border-bottom: 0; box-shadow: var(--keys-sombra-1); }
.keys-ob-mock { background: var(--keys-superficie); border: 1px solid var(--keys-borda); border-radius: 10px; padding: 0.7rem 0.8rem; font-size: 0.78rem; box-shadow: var(--keys-sombra-1); margin-bottom: 0.6rem; font-variant-numeric: tabular-nums; }
.keys-ob-mtitle { font-weight: 800; font-size: 0.82rem; margin-bottom: 0.4rem; }
.keys-ob-mrow { display: flex; justify-content: space-between; gap: 0.6rem; padding: 0.28rem 0; border-bottom: 1px dashed var(--keys-borda); }
.keys-ob-mrow:last-child { border-bottom: 0; }

.keys-ob-panel { border: 1px solid var(--keys-borda); box-shadow: var(--keys-sombra-1); margin-bottom: 1.4rem; }
.keys-ob-vitrine { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 1.3rem; align-items: start; margin: 0.9rem 0 1.3rem; }
.keys-ob-gains { list-style: none; margin: 0.7rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.keys-ob-gains li { position: relative; padding-left: 1.5rem; font-size: 0.92rem; }
.keys-ob-gains li::before { content: "\2713"; position: absolute; left: 0; color: var(--keys-receita); font-weight: 900; }
.keys-ob-prevcap { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--keys-texto-3); font-weight: 700; margin: 0 0 0.5rem; }
.keys-ob-dica { font-size: 0.84rem; color: var(--keys-texto-3); margin-top: 0.8rem; }
.keys-ob-controls { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; margin-top: 1rem; }
.keys-ob-callout { border: 1px dashed var(--keys-borda-forte); border-radius: 12px; padding: 0.85rem 1rem; color: var(--keys-texto-2); font-size: 0.92rem; background: var(--keys-superficie-2); }

/* Categorias sugeridas com meta/previsão por linha (v3.92): checkbox + nome à esquerda, valor
   mensal à direita — o setup já nasce com fluxo de caixa. */
.keys-ob-cats { display: flex; flex-direction: column; gap: 0.4rem; }
.keys-ob-cat { display: flex; align-items: center; gap: 0.8rem; padding: 0.4rem 0.7rem; border: 1px solid var(--keys-borda); border-radius: 10px; background: var(--keys-superficie); }
.keys-ob-cat .form-check { flex: 1; min-width: 0; margin: 0; }
.keys-ob-cat .form-check-label { cursor: pointer; }
.keys-ob-cat-meta { display: flex; align-items: center; gap: 0.35rem; flex: none; }
.keys-ob-cat-meta input { width: 118px; text-align: right; }
.keys-ob-cat-mes { font-size: 0.78rem; color: var(--keys-texto-3); white-space: nowrap; }
@media (max-width: 420px) { .keys-ob-cat-meta input { width: 92px; } }

.keys-ob-kpis { display: flex; gap: 0.6rem; }
.keys-ob-k { flex: 1; background: var(--keys-superficie-2); border: 1px solid var(--keys-borda); border-radius: 10px; padding: 0.6rem 0.7rem; text-align: center; }
/* F150.1 (auditoria mobile): 0.62rem era pequeno demais p/ rótulo de KPI no celular. */
.keys-ob-kl { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--keys-texto-3); }
/* F150.3 (refino): nome livre (conta/forma/cartão/categoria) sem espaço quebrava a linha das
   listas do wizard e empurrava o botão de excluir para fora da tela em 360px. */
.keys-ob .list-group-item,
.keys-ob-cat { word-break: break-word; }
.keys-ob .list-group-item > span,
.keys-ob-cat > span { min-width: 0; }
.keys-ob-kv { font-weight: 800; font-size: 1.05rem; font-variant-numeric: tabular-nums; }

.keys-ob-done-coin { width: 60px; height: 60px; border-radius: 18px; margin: 0 auto 0.8rem; display: grid; place-items: center; font-size: 1.7rem; color: var(--keys-sobre-primaria); background: linear-gradient(135deg, var(--keys-grad-a), var(--keys-grad-b)); box-shadow: var(--keys-sombra-1); }

/* ===== F151 fase 2 — chips de perfil (etapa 1) =====
   `radio` invisível + `label` estilizada: a seleção é 100% CSS (:checked + label), sem uma linha
   de JS — o caminho mais CSP-safe possível para um controle de escolha. */
.keys-ob-perfil { margin-top: 1.4rem; position: relative; }
.keys-ob-perfil-linhas { display: flex; flex-direction: column; gap: 0.55rem; }
.keys-ob-perfil-linha { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.keys-ob-perfil-lab { font-size: 0.86rem; color: var(--keys-texto-2); min-width: 10.5rem; }
.keys-ob-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.keys-ob-chip-in { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
.keys-ob-chip {
  display: inline-block; cursor: pointer; user-select: none;
  padding: 0.32rem 0.8rem; border-radius: 999px; font-size: 0.85rem; font-weight: 600;
  border: 1.5px solid var(--keys-borda-forte); background: var(--keys-superficie); color: var(--keys-texto-2);
  transition: background 0.12s, border-color 0.12s, color 0.12s;
  min-height: 2.15rem; line-height: 1.45; /* alvo de toque confortável (§mobile-first) */
}
.keys-ob-chip:hover { border-color: var(--keys-primaria); color: var(--keys-primaria-texto); }
.keys-ob-chip-in:checked + .keys-ob-chip {
  background: var(--keys-primaria-suave); border-color: var(--keys-primaria); color: var(--keys-primaria-texto);
}
/* Foco visível pelo teclado: o input é invisível, então o anel tem de ir para a label. */
.keys-ob-chip-in:focus-visible + .keys-ob-chip { outline: 2px solid var(--keys-primaria); outline-offset: 2px; }

/* ===== F151 fase 2 — catálogo v2 da etapa 4 ===== */
.keys-ob-grupo { border: 1px solid var(--keys-borda); border-radius: 12px; background: var(--keys-superficie); margin-bottom: 0.7rem; overflow: hidden; }
.keys-ob-grupo-cab {
  cursor: pointer; list-style: none; padding: 0.7rem 0.9rem; display: flex; align-items: center;
  gap: 0.7rem; flex-wrap: wrap; background: var(--keys-superficie-2); font-weight: 700;
}
.keys-ob-grupo-cab::-webkit-details-marker { display: none; }
.keys-ob-grupo-cab::before { content: "\203A"; font-size: 1.15rem; line-height: 1; color: var(--keys-texto-3); transition: transform 0.15s; }
.keys-ob-grupo[open] > .keys-ob-grupo-cab::before { transform: rotate(90deg); }
.keys-ob-grupo-nome { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.keys-ob-grupo-cont { margin-left: auto; font-size: 0.78rem; font-weight: 600; color: var(--keys-texto-3); white-space: nowrap; }
.keys-ob-grupo-desc { margin: 0.7rem 0.9rem 0; font-size: 0.85rem; color: var(--keys-texto-3); }
.keys-ob-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: 0.6rem; padding: 0.8rem 0.9rem 0.95rem; }
.keys-ob-card {
  border: 1px solid var(--keys-borda); border-radius: 11px; padding: 0.65rem 0.75rem;
  background: var(--keys-superficie); display: flex; flex-direction: column; gap: 0.45rem;
  transition: border-color 0.12s, background 0.12s;
}
.keys-ob-card.marcado { border-color: var(--keys-primaria); background: var(--keys-primaria-suave); }
.keys-ob-card-top { display: flex; align-items: flex-start; gap: 0.5rem; }
.keys-ob-card-top .form-check { flex: 1; min-width: 0; margin: 0; }
.keys-ob-card-desc { margin: 0; font-size: 0.8rem; color: var(--keys-texto-3); line-height: 1.4; }
.keys-ob-ja { color: var(--keys-receita); flex: none; font-size: 0.95rem; }
.keys-ob-chips-valor { gap: 0.3rem; }
.keys-ob-chipv {
  cursor: pointer; padding: 0.26rem 0.6rem; border-radius: 999px; font-size: 0.78rem; font-weight: 700;
  border: 1.5px solid var(--keys-borda-forte); background: var(--keys-superficie); color: var(--keys-texto-2);
  font-variant-numeric: tabular-nums; min-height: 2rem;
}
.keys-ob-chipv:hover { border-color: var(--keys-primaria); color: var(--keys-primaria-texto); }
.keys-ob-chipv.ativo { background: var(--keys-primaria); border-color: var(--keys-primaria); color: var(--keys-sobre-primaria); }
.keys-ob-card .keys-ob-cat-meta { justify-content: flex-end; }

/* ===== F151 fase 3 — "Seu próximo ano" (etapa 7) ===== */
.keys-ob-veredito {
  border: 1px solid var(--keys-borda); border-left-width: 5px; border-radius: 12px;
  padding: 1rem 1.1rem; margin-top: 1rem; display: flex; flex-direction: column; gap: 0.2rem;
  background: var(--keys-superficie-2);
}
/* Superávit e déficit têm o MESMO peso visual (risco nº8 da spec): mesma caixa, mesmo tamanho de
   número, só a cor do token muda. Quem está no vermelho é quem mais precisa da ferramenta — a
   tela não pode parecer uma punição. */
.keys-ob-veredito.e-sobra { border-left-color: var(--keys-receita); }
.keys-ob-veredito.e-deficit { border-left-color: var(--keys-despesa); }
.keys-ob-veredito.e-neutro { border-left-color: var(--keys-borda-forte); }
.keys-ob-ver-num { font-family: var(--keys-fonte-titulo); font-weight: 800; font-size: clamp(1.7rem, 4.5vw, 2.5rem); line-height: 1.1; font-variant-numeric: tabular-nums; }
.keys-ob-veredito.e-sobra .keys-ob-ver-num { color: var(--keys-receita); }
.keys-ob-veredito.e-deficit .keys-ob-ver-num { color: var(--keys-despesa); }
.keys-ob-ver-txt { color: var(--keys-texto-2); font-size: 0.95rem; }
.keys-ob-futuro .keys-ob-kpis { flex-wrap: wrap; }
.keys-ob-futuro .keys-ob-k { min-width: 130px; }
.keys-ob-plano { background: var(--keys-superficie); border-style: solid; }
.keys-ob-como > summary { cursor: pointer; font-size: 0.88rem; color: var(--keys-texto-2); font-weight: 600; }
.keys-ob-como ol { font-size: 0.86rem; color: var(--keys-texto-3); padding-left: 1.2rem; }
.keys-ob-como ol li + li { margin-top: 0.35rem; }

@media (max-width: 820px) { .keys-ob-grid { grid-template-columns: 1fr; } }
@media (max-width: 720px) {
  .keys-ob-vitrine { grid-template-columns: 1fr; }
  .keys-ob-hero-cta { align-self: stretch; }
  .keys-ob-hero-cta .btn { width: 100%; }
  .keys-ob-perfil-lab { min-width: 100%; margin-bottom: -0.15rem; }
  .keys-ob-cards { grid-template-columns: 1fr; }
}

/* ===== Quadro "Primeiros passos" (§5.20.13, v3.87) na tela inicial ===== */
.keys-pp { background: var(--keys-superficie); border: 1px solid var(--keys-borda); border-radius: var(--keys-raio); box-shadow: var(--keys-sombra-1); overflow: hidden; margin-bottom: 1.25rem; }
.keys-pp-head { display: flex; align-items: center; gap: 0.9rem; padding: 1rem 1.15rem; border-bottom: 1px solid var(--keys-borda); }
.keys-pp-head h2 { font-size: 1.05rem; font-weight: 800; margin: 0; }
.keys-pp-ring { width: 46px; height: 46px; border-radius: 50%; flex: none; display: grid; place-items: center; background: conic-gradient(var(--keys-primaria) calc(var(--pp, 0) * 1%), var(--keys-superficie-2) 0); }
.keys-pp-ring span { width: 34px; height: 34px; border-radius: 50%; background: var(--keys-superficie); display: grid; place-items: center; font-size: 0.7rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.keys-pp-count { margin-left: auto; font-weight: 800; font-size: 0.85rem; color: var(--keys-primaria-texto); background: var(--keys-primaria-suave); border: 1px solid var(--keys-primaria); padding: 0.2rem 0.6rem; border-radius: 999px; }
.keys-pp-x { background: none; border: 0; color: var(--keys-texto-3); cursor: pointer; font-size: 1.05rem; line-height: 1; padding: 0.2rem 0.45rem; }
.keys-pp-x:hover { color: var(--keys-tinta); }
.keys-pp-list { list-style: none; margin: 0; padding: 0.4rem; }
.keys-pp-list li { display: flex; align-items: center; gap: 0.8rem; padding: 0.65rem 0.8rem; border-radius: 11px; }
.keys-pp-list li:hover { background: var(--keys-superficie-2); }
.keys-pp-check { width: 24px; height: 24px; border-radius: 50%; flex: none; display: grid; place-items: center; font-size: 0.8rem; font-weight: 900; border: 2px solid var(--keys-borda-forte); color: transparent; }
.keys-pp-list li.feito .keys-pp-check { background: var(--keys-receita); border-color: var(--keys-receita); color: var(--keys-superficie); }
.keys-pp-list li.feito .keys-pp-t { color: var(--keys-texto-3); text-decoration: line-through; }
.keys-pp-t { font-weight: 700; }
.keys-pp-d { font-size: 0.83rem; color: var(--keys-texto-2); }
.keys-pp-go { margin-left: auto; font-weight: 700; font-size: 0.83rem; white-space: nowrap; color: var(--keys-primaria-texto); border: 1px solid var(--keys-borda-forte); background: var(--keys-superficie); border-radius: 8px; padding: 0.35rem 0.7rem; text-decoration: none; }
.keys-pp-go:hover { border-color: var(--keys-primaria); }
.keys-pp-list li.feito .keys-pp-go { visibility: hidden; }

/* ===== Comparativo de planos — tela /assinatura (§5.22/D33, F13.2) ===== */
.keys-plano-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.25rem; align-items: stretch; margin: 1rem 0 1.5rem; }
.keys-plano-card {
    background: var(--keys-superficie); border: 1px solid var(--keys-borda);
    border-radius: var(--keys-raio); box-shadow: var(--keys-sombra-1);
    display: flex; flex-direction: column; padding: 1.35rem 1.25rem; position: relative;
    transition: transform 0.18s, box-shadow 0.18s;
}
.keys-plano-card:hover { transform: translateY(-3px); box-shadow: var(--keys-sombra-2); }
.keys-plano-card--destaque { border: 2px solid var(--keys-primaria); box-shadow: var(--keys-sombra-3); }
.keys-plano-card--alvo:not(.keys-plano-card--destaque) { border: 2px solid var(--keys-primaria); }
.keys-plano-selo {
    position: absolute; top: -0.7rem; left: 50%; transform: translateX(-50%);
    background: var(--keys-primaria); color: var(--keys-sobre-primaria);
    font-size: 0.72rem; font-weight: 800; letter-spacing: 0.03em; text-transform: uppercase;
    padding: 0.3rem 0.8rem; border-radius: 999px; box-shadow: var(--keys-sombra-1); white-space: nowrap;
}
.keys-plano-atual-selo {
    align-self: flex-start; font-size: 0.68rem; font-weight: 800; letter-spacing: 0.03em; text-transform: uppercase;
    color: var(--keys-texto-2); background: var(--keys-superficie-2); border: 1px solid var(--keys-borda);
    padding: 0.2rem 0.55rem; border-radius: 999px; margin-bottom: 0.5rem;
}
.keys-plano-nome { font-family: var(--keys-fonte-titulo); font-weight: 800; font-size: 1.2rem; margin-bottom: 0.3rem; }
.keys-plano-preco-linha { display: flex; align-items: baseline; gap: 0.3rem; flex-wrap: wrap; }
.keys-plano-preco { font-size: 2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.1; }
.keys-plano-preco-sufixo { font-size: 0.9rem; font-weight: 600; color: var(--keys-texto-2); }
.keys-plano-anual { font-size: 0.83rem; color: var(--keys-texto-2); margin-top: 0.4rem; min-height: 1.2em; }
.keys-plano-off { color: var(--keys-receita-forte); font-weight: 700; }
.keys-plano-itens { list-style: none; margin: 1.1rem 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; flex: 1; }
.keys-plano-itens li { display: flex; align-items: flex-start; gap: 0.5rem; font-size: 0.87rem; }
.keys-plano-itens li i { flex: none; margin-top: 0.15rem; }
.keys-plano-itens li .bi-check-circle-fill { color: var(--keys-receita); }
.keys-plano-itens li .bi-x-circle { color: var(--keys-texto-3); }
.keys-plano-item-nao { color: var(--keys-texto-3); }
@media (max-width: 900px) { .keys-plano-grid { grid-template-columns: 1fr; } .keys-plano-card--destaque { order: -1; } }

/* ---------- Balão do consultor de IA (F14.7, contrato E1/E2/E3) ----------
   NO MONITOR: mesmo canto do balão de feedback (item 7, `.keys-feedback` acima), empilhado UM
   slot acima dele — ali sobra gutter à direita do `.container`, e a coluna de três não morde
   texto. NO TELEFONE: slot 1 da DOCA horizontal (ver o bloco "doca" no fim desta seção); a
   coluna vertical era o defeito de 04/08/2026 documentado lá em cima.
   z-index um degrau acima do feedback (1041) — quando os dois painéis abrem ao mesmo tempo, o
   mais recente (chat, item mais novo) fica por cima. Só var(--keys-*) — linter de tema. */
.keys-chat-ia {
    position: fixed;
    right: var(--keys-fab-borda);
    bottom: calc(var(--keys-fab-borda) + var(--keys-fab-linha));
    z-index: 1041;
}
.keys-chat-ia-lancador {
    width: var(--keys-fab-tam);
    height: var(--keys-fab-tam);
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: var(--keys-fab-icone);
    color: var(--keys-superficie);
    background: linear-gradient(135deg, var(--keys-grad-a), var(--keys-grad-b));
    border: none;
    box-shadow: var(--keys-sombra-3);
    cursor: pointer;
    transition: transform 0.15s ease;
}
.keys-chat-ia-lancador:hover { transform: scale(1.06); }
.keys-chat-ia-lancador:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: 3px; }

/* TELA CHEIA estilo WhatsApp (W2): overlay que cobre TUDO (nunca popup parcial). Aberto/minimizado
   é só `hidden` no elemento (o painel nunca é destruído — histórico e scroll preservados). */
.keys-chat-ia-painel {
    position: fixed;
    inset: 0;
    width: 100%;
    max-width: 100%;
    max-height: none;
    z-index: 1060;
    display: flex;
    flex-direction: column;
    background: var(--keys-fundo);
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
}
/* Trava o scroll do app atrás enquanto o chat está em tela cheia. */
body.keys-chat-ia-aberto { overflow: hidden; }

/* Cabeçalho estilo WhatsApp: avatar redondo + nome/status + saldo discreto + minimizar. */
.keys-chat-ia-cabecalho {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.7rem 1rem;
    padding-top: calc(0.7rem + env(safe-area-inset-top, 0px));
    background: var(--keys-superficie);
    border-bottom: 1px solid var(--keys-borda);
    box-shadow: var(--keys-sombra-1);
}
.keys-chat-ia-avatar {
    flex: none;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
    color: var(--keys-sobre-primaria);
    background: linear-gradient(135deg, var(--keys-grad-a), var(--keys-grad-b));
}
.keys-chat-ia-ident { flex: 1 1 auto; min-width: 0; }
.keys-chat-ia-titulo { font-size: 1rem; font-weight: 700; color: var(--keys-tinta); margin: 0; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.keys-chat-ia-status { font-size: 0.75rem; color: var(--keys-receita-forte); margin: 0.1rem 0 0; }
.keys-chat-ia-medidor { flex: none; font-size: 0.72rem; color: var(--keys-texto-2); margin: 0; font-variant-numeric: tabular-nums; white-space: nowrap; }
.keys-chat-ia-minimizar {
    flex: none; display: grid; place-items: center;
    width: 2.2rem; height: 2.2rem; border-radius: 50%; padding: 0;
    border: none; background: transparent; color: var(--keys-texto-2); font-size: 1.1rem; cursor: pointer;
}
.keys-chat-ia-minimizar:hover { background: var(--keys-superficie-2); color: var(--keys-tinta); }
.keys-chat-ia-minimizar:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: 2px; }

.keys-chat-ia-historico {
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
    max-height: none;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 1rem clamp(0.75rem, 5vw, 4rem);
    /* Fundo com padrão sutil (pontinhos derivados da marca via color-mix — SEM imagem externa,
       SEM cor hardcoded: só tokens; CSP-safe e claro/escuro automático). */
    background:
        radial-gradient(color-mix(in srgb, var(--keys-primaria) 6%, transparent) 1.2px, transparent 1.2px) 0 0 / 22px 22px,
        var(--keys-fundo);
}
.keys-chat-ia-historico > * { max-width: min(680px, 100%); width: 100%; margin-inline: auto; }
.keys-chat-ia-vazio { font-size: 0.85rem; color: var(--keys-texto-2); text-align: center; margin: 2rem auto; }

/* Separador de data ("Hoje"/data) entre grupos de mensagens. */
.keys-chat-ia-data-sep { text-align: center; margin: 0.6rem auto; }
.keys-chat-ia-data-sep span {
    display: inline-block; font-size: 0.72rem; color: var(--keys-texto-2);
    background: var(--keys-superficie-2); border-radius: 999px; padding: 0.2rem 0.7rem;
}

/* Bolhas estilo WhatsApp COM CAUDA (CSS puro): usuário à direita em verde-marca (menta =
   receita/positivo, evoca o balão enviado do WhatsApp on-brand), consultor à esquerda em
   superfície neutra. `white-space: pre-wrap` preserva quebras de linha do usuário sem HTML. */
.keys-chat-ia-bolha {
    position: relative;
    max-width: 78%;
    padding: 0.45rem 0.7rem 0.35rem;
    border-radius: 0.85rem;
    font-size: 0.9rem;
    box-shadow: var(--keys-sombra-1);
}
.keys-chat-ia-bolha p { margin: 0; white-space: pre-wrap; word-break: break-word; line-height: 1.35; }
/* Markdown leve nas respostas do consultor (v4.62): parágrafos/listas com respiro, sem
   estourar a bolha; código com o mono do tema. Herda a cor da bolha nos 2 temas (P9). */
.keys-chat-ia-md p + p, .keys-chat-ia-md p + ul, .keys-chat-ia-md p + ol,
.keys-chat-ia-md ul + p, .keys-chat-ia-md ol + p { margin-top: 0.45rem; }
.keys-chat-ia-md ul, .keys-chat-ia-md ol { margin: 0; padding-left: 1.15rem; }
.keys-chat-ia-md li { margin: 0.12rem 0; line-height: 1.35; word-break: break-word; }
.keys-chat-ia-md code { font-family: var(--bs-font-monospace, monospace); font-size: 0.85em; color: var(--keys-primaria-texto); background: var(--keys-primaria-suave); padding: 0 0.25rem; border-radius: 0.25rem; }
/* Gravação de voz estilo WhatsApp (v4.69): segurar grava, ↑ trava, lado cancela. O botão de mic
   não pode rolar a página enquanto seguro (touch-action) nem selecionar texto no arrasto. */
.keys-chat-ia-acao { touch-action: none; -webkit-user-select: none; user-select: none; }
.keys-chat-ia-campo-oculto { display: none !important; }
.keys-chat-ia-gravando { display: flex; align-items: center; gap: 0.5rem; flex: 1; min-width: 0; padding: 0.3rem 0.6rem; border-radius: 1.2rem; background: var(--keys-primaria-suave); }
.keys-chat-ia-rec-ponto { flex: 0 0 auto; width: 0.65rem; height: 0.65rem; border-radius: 50%; background: var(--bs-danger); animation: keys-rec-pulso 1.1s ease-in-out infinite; }
@keyframes keys-rec-pulso { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.75); } }
@media (prefers-reduced-motion: reduce) { .keys-chat-ia-rec-ponto { animation: none; } }
.keys-chat-ia-rec-tempo { flex: 0 0 auto; font-variant-numeric: tabular-nums; font-weight: 600; font-size: 0.85rem; }
.keys-chat-ia-rec-dica { flex: 1; min-width: 0; font-size: 0.8rem; opacity: 0.85; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.keys-chat-ia-rec-cancelar { flex: 0 0 auto; text-decoration: none; }
.keys-chat-ia-rec-parar { flex: 0 0 auto; border-radius: 50%; width: 2.3rem; height: 2.3rem; padding: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 1.05rem; }
.keys-chat-ia-hora { display: block; font-size: 0.66rem; opacity: 0.7; text-align: right; margin-top: 0.15rem; font-variant-numeric: tabular-nums; }
.keys-chat-ia-bolha-usuario {
    align-self: flex-end; margin-right: 0.4rem;
    background: var(--keys-receita-suave); color: var(--keys-tinta);
    border-bottom-right-radius: 0.2rem;
}
.keys-chat-ia-bolha-assistente {
    align-self: flex-start; margin-left: 0.4rem;
    background: var(--keys-superficie); color: var(--keys-tinta);
    border-bottom-left-radius: 0.2rem;
}
/* Caudas (triângulo no canto inferior, do lado do dono da bolha). */
.keys-chat-ia-bolha-usuario::after,
.keys-chat-ia-bolha-assistente::after {
    content: ""; position: absolute; bottom: 0; width: 0.55rem; height: 0.7rem;
}
.keys-chat-ia-bolha-usuario::after {
    right: -0.4rem; background: var(--keys-receita-suave);
    clip-path: polygon(0 0, 0 100%, 100% 100%);
}
.keys-chat-ia-bolha-assistente::after {
    left: -0.4rem; background: var(--keys-superficie);
    clip-path: polygon(100% 0, 0 100%, 100% 100%);
}

/* Feedback 👍👎 (§5.14, Brief B4) — par discreto dentro da bolha do consultor; some quando avaliado. */
.keys-chat-ia-feedback { display: flex; align-items: center; gap: 0.3rem; margin-top: 0.35rem; }
.keys-chat-ia-feedback-btn {
    flex: none; display: grid; place-items: center;
    width: 1.7rem; height: 1.7rem; border-radius: 50%; padding: 0;
    border: 1px solid var(--keys-borda); background: transparent; color: var(--keys-texto-2);
    font-size: 0.8rem; cursor: pointer;
}
.keys-chat-ia-feedback-btn:hover { background: var(--keys-superficie-3); color: var(--keys-tinta); }
.keys-chat-ia-feedback-btn:disabled { opacity: 0.5; cursor: default; }
.keys-chat-ia-feedback-ok { font-size: 0.75rem; color: var(--keys-texto-2); font-style: italic; }

/* Cartão de proposta (§5.11 — escrita só com confirmação explícita): destaque visual claro de
   que aquilo ali exige uma decisão do usuário, distinto das bolhas de conversa comuns. */
.keys-chat-ia-proposta {
    align-self: stretch;
    border: 1px solid var(--keys-primaria);
    background: var(--keys-primaria-suave);
    border-radius: var(--keys-raio-sm);
    padding: 0.6rem 0.7rem;
}
.keys-chat-ia-proposta-resolvida-estado { border-style: dashed; opacity: 0.8; }
.keys-chat-ia-proposta-texto { margin: 0 0 0.55rem; font-size: 0.85rem; color: var(--keys-tinta); font-weight: 500; }
.keys-chat-ia-proposta-acoes { display: flex; gap: 0.5rem; justify-content: flex-end; flex-wrap: wrap; }
.keys-chat-ia-proposta-resolvida { margin: 0; font-size: 0.8rem; color: var(--keys-texto-2); font-style: italic; }

/* Indicador "pensando": 3 pontinhos pulsando (CSS puro, sem GIF/lib). */
.keys-chat-ia-pensando { display: flex; align-items: center; gap: 0.45rem; font-size: 0.8rem; color: var(--keys-texto-2); margin-bottom: 0.4rem; }
.keys-chat-ia-pontinhos { display: inline-flex; gap: 0.15rem; }
.keys-chat-ia-pontinhos span { width: 0.35rem; height: 0.35rem; border-radius: 50%; background: var(--keys-texto-2); animation: keys-chat-ia-piscar 1.1s infinite ease-in-out; }
.keys-chat-ia-pontinhos span:nth-child(2) { animation-delay: 0.15s; }
.keys-chat-ia-pontinhos span:nth-child(3) { animation-delay: 0.3s; }
@keyframes keys-chat-ia-piscar { 0%, 80%, 100% { opacity: 0.25; } 40% { opacity: 1; } }

.keys-chat-ia-msg { font-size: 0.8rem; color: var(--keys-despesa-forte); margin: 0 auto 0.4rem; max-width: min(680px, 100%); padding: 0 clamp(0.75rem, 5vw, 4rem); }
.keys-chat-ia-pensando { padding-left: clamp(0.75rem, 5vw, 4rem); }

/* Barra de input estilo WhatsApp: campo arredondado crescível + UM botão circular à direita. */
.keys-chat-ia-rodape {
    background: var(--keys-superficie);
    border-top: 1px solid var(--keys-borda);
    padding: 0.55rem clamp(0.75rem, 5vw, 4rem);
    padding-bottom: calc(0.55rem + env(safe-area-inset-bottom, 0px));
}
.keys-chat-ia-campo { display: flex; gap: 0.5rem; align-items: flex-end; max-width: min(720px, 100%); margin-inline: auto; }
.keys-chat-ia-campo textarea {
    flex: 1 1 auto; resize: none; font-size: 0.9rem; line-height: 1.35;
    min-height: 2.6rem; max-height: 140px; padding: 0.55rem 0.9rem;
    border-radius: 1.3rem; overflow-y: auto;
}
.keys-chat-ia-acao {
    flex: none; display: grid; place-items: center;
    width: 2.7rem; height: 2.7rem; border-radius: 50%; padding: 0; border: none;
    color: var(--keys-sobre-primaria);
    background: linear-gradient(135deg, var(--keys-grad-a), var(--keys-grad-b));
    font-size: 1.1rem; cursor: pointer; transition: transform 0.12s ease;
}
.keys-chat-ia-acao:hover { transform: scale(1.06); }
.keys-chat-ia-acao:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: 2px; }
.keys-chat-ia-acao:disabled { opacity: 0.55; cursor: default; transform: none; }
/* Microfone (entrada por voz) — realce enquanto ouve. */
.keys-chat-ia-acao-mic { background: var(--keys-superficie-2); color: var(--keys-primaria-texto); }

.keys-chat-ia-disclaimer {
    font-size: 0.68rem; color: var(--keys-texto-2); text-align: center;
    margin: 0; padding: 0.35rem 1rem calc(0.4rem + env(safe-area-inset-bottom, 0px));
    background: var(--keys-superficie);
}

@media (prefers-reduced-motion: reduce) {
    .keys-chat-ia-lancador, .keys-chat-ia-acao { transition: none; }
    .keys-chat-ia-pontinhos span { animation: none; }
}

/* F149-W1 §2.2.1 — câmera flutuante global: slot 2 (o mais distante da borda). No monitor,
   empilhada acima do chat; no telefone, terceiro lugar da doca horizontal. Tamanho, ícone e
   distância da borda vêm dos tokens `--keys-fab-*` — a câmera já ficou parada em cima da
   estrelinha do chat (v4.18) por ter a própria cópia dessa aritmética. */
.keys-camera-doc { position: fixed; right: var(--keys-fab-borda); bottom: calc(var(--keys-fab-borda) + 2 * var(--keys-fab-linha)); z-index: 1042; }
.keys-camera-doc-lancador { width: var(--keys-fab-tam); height: var(--keys-fab-tam); border-radius: 50%; display: grid; place-items: center; font-size: var(--keys-fab-icone); color: var(--keys-superficie); background: linear-gradient(135deg, var(--keys-grad-a), var(--keys-grad-b)); border: none; box-shadow: var(--keys-sombra-3); cursor: pointer; transition: transform 0.15s ease; }
.keys-camera-doc-lancador:hover { transform: scale(1.06); }
.keys-camera-doc-lancador:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: 3px; }

/* =========================================================================================
   A DOCA — no telefone os três viram FILEIRA, e a fileira é chrome, não conteúdo.
   (o porquê inteiro, com a medição antes/depois, está no cabeçalho lá em cima)

   Vale onde existe a barra de baixo (`body:has(.keys-tabbar)` dentro do corte ≤991.98px, mesmo
   par usado pelo padding do main e pelo toast). Uma linha só de `bottom` para os três, e um
   `right` por slot — contados da borda DIREITA para dentro, na ordem de quem tem mais chance de
   existir: feedback (todo papel tem) · chat · câmera.

   O `:not(:has(.keys-chat-ia))` não é firula: o chat tem porteiro de PERMISSÃO (`ia.chat_consultor`,
   trava no próprio partial), e sem essa regra a fileira ficaria com um buraco no meio para quem
   lê documento mas não fala com o consultor. Sem wrapper novo no layout justamente porque os três
   partials são irmãos diretos no `<body>` e cada um se posiciona sozinho — nenhum contêiner novo,
   nenhum contexto de empilhamento novo, nenhum JS.
   ========================================================================================= */
@media (max-width: 991.98px) {
    /* `--keys-tabbar-topo` e não `--keys-tabbar-alt`: a linha da doca se apoia no ponto mais alto
       do chrome de baixo, que é o "+" ELEVADO — ver o token lá no topo do arquivo. Ancorada na
       ALTURA DA BARRA, esta mesma linha punha a câmera a 1px do "+" e 5px sobreposta a ele.
       A regra que morava aqui antes da doca (`.keys-feedback` sozinho, 0.75rem acima da barra)
       trazia no comentário a queixa do dono medida em celular real — "o balão cobria o Mais por
       inteiro". A razão dela continua valendo e continua atendida: `--keys-fab-gap` É aquele
       0,75rem, e o feedback (slot 0) segue limpo sobre o "Mais". O que a doca não herdou foi a
       OUTRA testemunha, a da saliência — e ela só cobra de quem avança para o meio da barra, que
       são exatamente os dois slots novos (chat e câmera). */
    body:has(.keys-tabbar) .keys-feedback,
    body:has(.keys-tabbar) .keys-chat-ia,
    body:has(.keys-tabbar) .keys-camera-doc {
        bottom: calc(var(--keys-tabbar-topo) + var(--keys-fab-gap));
    }
    body:has(.keys-tabbar) .keys-feedback    { right: var(--keys-fab-borda); }
    body:has(.keys-tabbar) .keys-chat-ia     { right: calc(var(--keys-fab-borda) + var(--keys-fab-linha)); }
    body:has(.keys-tabbar) .keys-camera-doc  { right: calc(var(--keys-fab-borda) + 2 * var(--keys-fab-linha)); }
    body:has(.keys-tabbar):not(:has(.keys-chat-ia)) .keys-camera-doc {
        right: calc(var(--keys-fab-borda) + var(--keys-fab-linha));
    }
}

/* ---------- Hub de módulos /menu (F150 — "Mais" da tabbar) ----------
   Tiles quadrados p/ polegar (alvo ≥72px), rótulo SEMPRE visível (P9), sem depender de hover;
   cadeado de plano no canto do ícone (regra F149.3). Só var(--keys-*) — linter de tema. */
.keys-hub-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    min-height: 4.9rem;
    padding: 0.6rem 0.25rem;
    border-radius: 0.75rem;
    background: var(--keys-superficie-2);
    border: 1px solid var(--keys-borda);
    text-decoration: none;
    color: var(--keys-tinta);
    text-align: center;
}
.keys-hub-tile:active { background: var(--keys-superficie-3); }
.keys-hub-tile:focus-visible { outline: 2px solid var(--keys-anel-foco); outline-offset: 2px; }
.keys-hub-ico { position: relative; font-size: 1.45rem; line-height: 1; color: var(--keys-primaria-texto); }
.keys-hub-cadeado {
    position: absolute;
    right: -0.55rem;
    bottom: -0.3rem;
    font-size: 0.6rem;
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    color: var(--keys-superficie);
    background: var(--keys-tinta);
}
.keys-hub-rotulo {
    font-size: 0.74rem;
    line-height: 1.15;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* F149.3 — flutuante BLOQUEADO por plano (regra do dono: visível + clique leva ao upgrade).
   É uma âncora com o MESMO visual do lançador real, esmaecida, com um selinho de cadeado no
   canto — nunca some, nunca "disabled" morto. Só var(--keys-*) (linter de tema). */
a.keys-chat-ia-lancador, a.keys-camera-doc-lancador { text-decoration: none; }
.keys-flutuante-bloqueado { position: relative; opacity: 0.72; filter: saturate(0.55); }
.keys-flutuante-bloqueado:hover { opacity: 1; filter: none; transform: scale(1.06); }
.keys-flutuante-cadeado {
    position: absolute;
    right: -0.15rem;
    bottom: -0.15rem;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 0.6rem;
    color: var(--keys-superficie);
    background: var(--keys-tinta);
    border: 2px solid var(--keys-superficie);
}

/* F1.3 (§5.3–§5.5) — PAD DE ASSINATURA compartilhado (captação e vistoria; a ficha de visita tem
   o dela em ficha-visita.css, mais elaborado, com linha-base). Vive aqui porque é usado por mais
   de um módulo e app.css é a folha de todas as telas autenticadas.
   `color` é LOAD-BEARING: `assinatura-tela.js` lê `getComputedStyle(canvas).color` para pintar o
   traço, então a assinatura sai legível nos dois temas sem uma linha de cor no script.
   `touch-action: none` é o que impede o navegador de ROLAR a página enquanto o dedo desenha. */
.keys-pad-assinatura {
    border: 1px dashed var(--keys-borda-forte);
    border-radius: 0.5rem;
    background: var(--keys-superficie);
    color: var(--keys-tinta);
    touch-action: none;
    cursor: crosshair;
}

/* §5.5 — A FOTO DO DANO NA LINHA DO ITEM (21/08/2026, migration 3140).
   Até esta data a foto era um `badge` com o NOME do arquivo, porque não havia bytes em lugar
   nenhum: nada para mostrar. Agora ela é a miniatura de verdade, e a miniatura é o ponto — o
   vistoriador confere na hora se pegou o dano ou a sombra do próprio braço, ainda dentro do imóvel,
   que é o único momento em que refazer a foto custa dez segundos.

   O tamanho é fixo e pequeno de propósito: a linha do item tem de continuar cabendo num celular em
   pé com uma mão (P10), e uma tira de fotos que quebra em três linhas empurra o botão de estado
   para fora da tela. `object-fit: cover` recorta em vez de deformar — dano deformado engana.

   A cor da borda vem do token, nos dois temas; a marca em si troca de `text-bg-light` para
   `text-bg-success` quando o servidor confirma os bytes, e é o JS que troca a classe do Bootstrap. */
.keys-vistoria-foto {
    padding: 0.15rem;
    line-height: 0;
    border: 1px solid var(--keys-borda);
}

.keys-vistoria-foto-mini {
    width: 44px;
    height: 44px;
    object-fit: cover;
    border-radius: 0.25rem;
    display: block;
}

/* A PRÉVIA DO TREINAMENTO no fim do setup (29/08/2026).
   A capa é um botão de verdade — foco por teclado, rótulo lido por leitor de tela — e não uma
   imagem clicável. Cor só por token, contraste nos dois temas (P9). */
.keys-previa { border-radius: .75rem; overflow: hidden; }
.keys-previa-capa {
  width: 100%; height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: .5rem;
  border: 1px solid var(--keys-borda); background: var(--keys-superficie-2);
  color: var(--keys-tinta); cursor: pointer;
}
.keys-previa-capa:hover, .keys-previa-capa:focus-visible { background: var(--keys-superficie-3); }
.keys-previa-play { font-size: 2.5rem; line-height: 1; color: var(--keys-primaria); }
.keys-previa-rotulo { font-size: .95rem; font-weight: 600; }
.keys-previa-quadro { width: 100%; height: 100%; border: 0; }

/* =====================================================================================
   AS TELAS DE CONFIGURAÇÃO: A FAIXA DE ABAS E A BARRA DE SALVAR
   (R4.3 · R6 · R6.1 · R6.2 · R12 — 01/09/2026)
   =====================================================================================
   O dono achou o mesmo defeito quatro vezes: *"está muito solto"*, *"o salvar vitrine, o botão
   ficou no meio ali"*, *"o funil de leads tem um configurar dentro dele, igual o WhatsApp"*,
   *"anota aí que é pra TODAS as telas"*. Estas duas peças são a resposta única.

   Cor SÓ por token, nos dois temas (linter de tema): a faixa usa as mesmas superfícies do resto
   do app, e o estado "aqui" da pílula é FUNDO CHEIO — nunca cor sozinha (P9: quem não distingue a
   diferença de matiz continua vendo onde está, porque o que muda é a forma preenchida). */

/* ---------- A faixa de abas ----------
   ⚠️ REDESENHADA EM 02/09/2026, e o dono reprovou a versão anterior olhando a tela:
   *"Essas abas podiam ficar melhor! e também ter um padrão, olha por exemplo o funil de vendas.
   nas configurações ficou bem esquisito!"*

   Eram três defeitos somados, e todos os três estavam medidos no código:

   1. **DIALETO PARALELO.** A faixa tinha classes só dela (`__aba`, `--on`, `--travada`) enquanto a
      casa já falava `nav nav-pills` / `nav-link` / `nav-link active` em SETE telas — o funil que
      ele citou (`leads/index.php:101`), `leads/config.php`, `imoveis/ficha.php` e quatro do
      `master/`. Uma faixa que reimplementa a pílula do zero envelhece sozinha: quando o funil
      mudar, ela não muda junto.
   2. **DOIS TÍTULOS ANTES DO CONTEÚDO.** A faixa era uma CAIXA (fundo, borda, raio) com letreiro
      próprio — "⚙ CONFIGURAÇÕES" — ACIMA do `<h1>` da página. O funil põe o título primeiro e as
      abas embaixo; aqui a tela abria com dois letreiros disputando quem nomeava a página.
   3. **RÓTULO DE GRUPO LENDO-SE COMO ABA APAGADA.** Já medido pela câmera em 01/09 (a tabela
      inteira está mais abaixo, na regra do bloco).

   O desenho escolhido, com três formatos desenhados na frente dele:

   · **as pílulas são as do Bootstrap** — `nav nav-pills`. O que sobra aqui é o que ele não tem;
   · **acabou a caixa e o segundo título** — a faixa não pinta fundo nem borda, e passou a entrar
     DEPOIS do `.keys-page-header` de cada view. `margin-bottom: 1rem` = o `mb-3` que o funil
     escreve como classe utilitária; a folga de cima é o `1.25rem` do próprio `.keys-page-header`,
     então a distância título → pílulas é a MESMA do funil, sem uma linha de CSS a mais;
   · **os quatro rótulos de grupo saíram da TELA** (viraram `.visually-hidden` na marcação) e o que
     resta deles é o filete entre blocos.

   ⚠️ E O QUE NÃO PODIA SUMIR NO REARRANJO, porque cada um custou uma rodada: o `role="group"` com
   o rótulo escrito (quem não olha a tela continua ouvindo em que seção está), a quebra em linhas,
   o véu de rolagem, o cadeado de plano com o caso inerte, o grupo vazio que não renderiza e o
   `aria-current`. O portão de tudo isso é o `FaixaConfigNaoEscondeAbaTest`. */
.keys-cfg-abas {
    /* O mesmo `mb-3` que o funil escreve na classe. Fica em CSS, e não como utilitário no `<nav>`,
       porque a faixa é um parcial: uma margem em CSS se corrige num lugar só. */
    margin-bottom: 1rem;
}

/* ⚠️ A FAIXA CORTAVA CINCO DAS DOZE ÁREAS, EM TODA LARGURA (achado da CÂMERA, 01/09/2026).
   A primeira versão desta tira era uma linha só que rolava de lado, e a rolagem não tinha sinal
   nenhum — nem sombra, nem seta, nem contador. Medido por CDP na conta de demonstração, contando
   quantas das 12 abas aparecem INTEIRAS dentro da caixa:

   | viewport | caixa | conteúdo | abas inteiras | cortada ao meio      | fora da tela |
   |----------|-------|----------|---------------|----------------------|--------------|
   | 1920     | 1273  | **2079** | 7             | Assinatura eletrônica| 4            |
   | 1366     | 1093  | 2079     | 6             | Portais de anúncio   | 5            |
   | 1280     | 1093  | 2079     | 6             | Portais de anúncio   | 5            |
   | 992      |  913  | 2079     | 5             | WhatsApp             | 6            |
   | 768      |  673  | 2079     | 4             | Roleta de leads      | 7            |
   | 390      |  351  | 2079     | 2             | Site público         | 9            |

   Repare na primeira linha: **nem num monitor de 1920 a faixa cabia**, porque quem limita não é a
   tela, é o `.container` do Bootstrap (1320 no maior ponto de corte, 1273 úteis). Ou seja, não
   existia UMA largura em que o cliente visse as doze — e, sem sinal de rolagem, quem abria a tela
   aprendia que Nota fiscal, Inteligência artificial, Plano de contas e Repasses NÃO EXISTIAM. É a
   régua 7 do dono reprovando: *"um estranho faria isto sem ter visto a aula?"* — não faria.

   (Os 2079px são da faixa ANTIGA, com os quatro rótulos na tela e recheio de 0,7rem por pílula. O
   redesenho de 02/09/2026 tirou os rótulos e adotou o recheio do Bootstrap, que é MAIOR: a conta
   se mexeu nos dois sentidos e continua muito acima dos 1273 da maior caixa — o que sustenta a
   regra é essa desigualdade, não o número exato. Remedir é dívida da próxima passagem da câmera.)

   O conserto tem duas metades, e cada uma resolve uma pergunta diferente:

   1. **DE 992px PARA CIMA A TIRA QUEBRA EM LINHAS**, e quebra PELOS GRUPOS que o catálogo já
      declarava (`ConfiguracoesAbas::GRUPOS`). Não sobra aba escondida: 12 de 12 visíveis a 992,
      1280, 1366 e 1920. Quebrar pelo GRUPO, e não pílula a pílula, é o que impede as cinco
      pílulas de INTEGRAÇÕES de se partirem entre duas linhas — daí o `__bloco`. Depois que o
      rótulo saiu da tela, essa agrupação passou a ser a ÚNICA que resta: ela não pode desmanchar.
   2. **ABAIXO DE 992px A TIRA CONTINUA ROLANDO** (medido: quebrar em linhas num telefone de 390
      dá QUATRO linhas, 181px de faixa, e ainda transborda; o documento saiu de escala,
      `scrollWidth` 857 numa viewport de 390, que é exatamente o defeito que o `LarguraCelularTest`
      existe para barrar). O que muda é que a rolagem **se anuncia**: um véu na borda que só
      aparece quando há conteúdo além dela, e some sozinho ao chegar na ponta.

   Por que o véu e não setas de rolagem: seta precisa de JS para saber se há o que rolar e para
   sumir nas pontas, e abaixo de 992px o aparelho tem DEDO — arrastar já é o gesto nativo, e o que
   faltava era só saber que havia o que arrastar. O véu é CSS puro, funciona sem JS e nunca mente.
   Por que não esconder abas num "..." : a ordem do dono é literal — *"tirar parte daquilo que foi
   desenvolvido, função — e não é essa a ideia"*. As doze continuam alcançáveis em toda largura.

   ⚠️ `flex-wrap: nowrap` está ESCRITO, e não herdado: o `.nav` do Bootstrap quebra SEMPRE
   (`flex-wrap: wrap`), e aqui a quebra é condicional. Sem esta linha o telefone quebraria em
   quatro linhas — o caso medido acima. */
.keys-cfg-abas__tira {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 0.3rem;
    /* ⚠️ A FOLGA ENTRE BLOCOS É MAIOR QUE A FOLGA ENTRE PÍLULAS, E ISSO É O MECANISMO — não
       estética. Os filhos diretos da tira são só os `__bloco`, então este `column-gap` age SÓ nas
       FRONTEIRAS DE GRUPO; dentro do bloco continua valendo o `gap: 0.3rem` dele. Com as duas
       folgas iguais, o olho lê uma fila única de doze pílulas e a agrupação não existe — que é
       precisamente o que sobra de risco depois que o rótulo saiu da tela.
       `column-gap`, e não `margin-left` no bloco: margem de item aparece TAMBÉM no começo de cada
       linha quebrada, indentando as linhas 2 e 3 contra a 1. Folga de flex, não. */
    column-gap: 0.7rem;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding-bottom: 0.15rem;
    -webkit-overflow-scrolling: touch;
    /* O VÉU QUE DIZ "TEM MAIS PARA CÁ" — quatro camadas, e a ordem delas É o mecanismo.
       As duas primeiras são COBERTORES da cor de trás e rolam JUNTO com o conteúdo
       (`background-attachment: local`): enquanto a tira está encostada numa ponta, o cobertor
       daquele lado está exatamente em cima da sombra e a apaga. Ao arrastar, ele sai de baixo e a
       sombra aparece. As duas últimas são as SOMBRAS, paradas na borda da caixa (`scroll`).
       Resultado: véu à direita quando há pílula adiante, à esquerda quando há pílula atrás, nenhum
       dos dois quando tudo cabe — inclusive de 992px para cima, onde a tira quebra e não rola.

       ⚠️ O COBERTOR É `--keys-fundo`, e não `--keys-superficie-2` como era até 02/09/2026. Ele só
       apaga a sombra se for EXATAMENTE a cor de trás, e a cor de trás mudou junto com o desenho: a
       faixa deixou de ser uma caixa com fundo próprio e passou a viver direto sobre o corpo da
       página (`--bs-body-bg: var(--keys-fundo)`, tokens.css). Com o token antigo o cobertor
       viraria um retângulo creme visível nas duas pontas da tira, no telefone. */
    background:
        linear-gradient(to right, var(--keys-fundo) 55%, transparent) left center / 1.6rem 100% no-repeat local,
        linear-gradient(to left,  var(--keys-fundo) 55%, transparent) right center / 1.6rem 100% no-repeat local,
        linear-gradient(to right, var(--keys-veu-rolagem), transparent) left center / 1.1rem 100% no-repeat scroll,
        linear-gradient(to left,  var(--keys-veu-rolagem), transparent) right center / 1.1rem 100% no-repeat scroll;
    /* ⚠️ NÃO REMOVER — e o motivo não é estético (REGRA 3-C do LarguraCelularTest, que reprovou
       esta faixa na primeira execução). Caixa que rola de lado não recorta um descendente
       `position: absolute` cujo bloco contêiner esteja FORA dela: ele fica no lugar estático, além
       da borda da tela, o `scrollWidth` do documento cresce, e o celular afasta o zoom levando a
       página inteira — e a `.keys-tabbar`, que é `fixed` — junto.
       Os fugitivos são o padrão de acessibilidade da casa: o `.visually-hidden` de cada aba travada
       ("Disponível em outro plano") e, desde 02/09/2026, os QUATRO RÓTULOS DE GRUPO, que agora só
       existem em `.visually-hidden`. Ou seja: o redesenho MULTIPLICOU os fugitivos, e esta linha
       vale mais do que valia. `relative` sem deslocamento não move nada e não cria contexto de
       empilhamento — só dá a eles um bloco contêiner dentro do scroller. */
    position: relative;
}

/* O BLOCO DE UM GRUPO — as pílulas de uma seção, e elas não se separam.
   É o que permite quebrar em linhas de 992px para cima sem partir INTEGRAÇÕES ao meio. Abaixo de
   992 ele é neutro: a tira rola, e um `inline-flex` que não quebra tem exatamente a mesma largura
   que os filhos soltos teriam.

   `align-self: stretch` não é alinhamento: é o que dá ALTURA ao filete da regra seguinte. Sem ele
   a borda esquerda mede a altura do conteúdo do bloco e o filete vira um traço curto entre duas
   pílulas, em vez de uma marca de seção que acompanha a linha. */
.keys-cfg-abas__bloco {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    align-self: stretch;
    gap: 0.3rem;
    /* ⚠️ MEDIDO, e é defeito de verdade: sem isto a faixa NASCIA ROLADA no telefone. O
       `scroll-snap-type` encosta a tira no ponto de encaixe mais próximo, e os únicos pontos eram
       as PÍLULAS — então, ao abrir `/parametros` num aparelho de 390px, a tira já vinha com
       `scrollLeft: 100`, começando a leitura no meio da faixa sem ninguém ter arrastado nada. Com
       o BLOCO também sendo ponto de encaixe, o zero volta a ser um lugar válido de repouso: a
       faixa abre no começo, e o véu da esquerda deixa de aparecer sem motivo. De brinde, no
       telefone cada arrasto passa a poder parar num GRUPO inteiro. */
    scroll-snap-align: start;
}

/* ⚠️ O RÓTULO DE GRUPO LIA-SE COMO UMA ABA DESABILITADA (achado da CÂMERA, 01/09/2026), E EM
   02/09/2026 O DONO ESCOLHEU A SAÍDA MAIS CURTA: TIRAR O RÓTULO DA TELA.

   O que a câmera mediu, por CDP em `/parametros`, a 1280px, no tema claro:

   | o que                                   | rótulo            | aba                |
   |-----------------------------------------|-------------------|--------------------|
   | centro vertical na linha 1              | **cy = 73**       | **cy = 73**        |
   | folga até o vizinho da esquerda         | **5px**           | 4–5px              |
   | folga até o vizinho da direita          | **5px**           | 4–5px              |
   | cor do texto                            | texto-3 (3,89:1)  | texto-2 (5,37:1)   |
   | filete separador                        | 1px borda-forte — **1,42:1 claro · 1,58:1 escuro** |

   Mesmo eixo, mesmo espaçamento dos dois lados e um cinza MAIS FRACO que o das abas: a receita
   exata de um controle desabilitado. `ATENDIMENTO` caía entre "Site público" e "Funil de leads" e,
   de relance, lia como uma aba que não responde.

   O conserto de 01/09 foi tipográfico (rótulo mais forte, filete mais alto e visível, fronteira
   maior que a folga). Funcionou, e mesmo assim o dono reprovou a tela inteira no dia seguinte —
   porque a pergunta certa não era "como faço um letreiro que não pareça aba?", e sim **"o letreiro
   precisa estar na tela?"**. Não precisa: as quatro seções continuam legíveis pela ORDEM e pelo
   filete, e um letreiro a menos é um concorrente a menos do que a faixa existe para mostrar.

   ➜ **O rótulo saiu da TELA e ficou na ACESSIBILIDADE** (`.visually-hidden` + `aria-labelledby` no
   bloco). Quem usa leitor de tela continua ouvindo "Atendimento, grupo"; quem olha a tela vê o
   filete. Repare que isto RESOLVE o defeito da câmera em vez de atenuá-lo: um rótulo que não é
   pintado não pode ser confundido com uma pílula apagada, em nenhum idioma e em nenhuma largura.

   ➜ **O FILETE MUDOU DE DONO.** Era a borda esquerda do rótulo; virou a borda esquerda do BLOCO
   SEGUINTE (`__bloco + __bloco`), que é onde mora uma fronteira. Duas consequências pensadas:

   · `+` e não em todos: um filete antes do PRIMEIRO grupo não separa nada e ainda empurraria a
     primeira pílula uns 12px para dentro, desalinhando-a do `<h1>` logo acima — e é justamente
     esse alinhamento que faz a faixa parecer a do funil. (A regra de 01/09 era o oposto — "o rail
     é de todos os grupos, inclusive o primeiro" — e ela estava certa PARA AQUELE desenho, onde o
     filete era a marca do letreiro. Sem letreiro, o filete é só fronteira.)
   · a cor é `--keys-texto-3`: 4,17:1 sobre o marfim do tema claro e 4,50:1 sobre o petróleo do
     escuro, medidos sobre `--keys-fundo`, que é o que está atrás da faixa desde que a caixa
     acabou. A régua de gráfico não-textual é 3:1. `--keys-borda` e `--keys-borda-forte` reprovam
     (1,42:1 e 1,58:1) e é por isso que o teste as proíbe pelo nome: já foram usadas aqui, e o
     resultado foi um separador que ninguém enxergava. */
.keys-cfg-abas__bloco + .keys-cfg-abas__bloco {
    border-left: 1px solid var(--keys-texto-3);
    padding-left: 0.7rem;
}

/* ---------- A PÍLULA ----------
   Forma, recheio, raio e o estado "aqui" são do `nav-pills` do Bootstrap, sem retoque: é o mesmo
   objeto que o funil desenha, e quando o funil mudar, esta faixa muda junto. O que está aqui é só
   o que o Bootstrap não tem, e cada linha responde por si:

   · `inline-flex` + `gap` — `.nav-link` é `display: block`, e cada pílula daqui tem ícone, texto e
     às vezes cadeado; sem isto o ícone não fica no eixo do texto;
   · `white-space: nowrap` — nome de área não quebra no meio dentro de uma pílula;
   · `scroll-snap-align` — abaixo de 992px cada pílula para inteira sob o dedo;
   · `color` — ⚠️ este é o único que muda a APARÊNCIA em relação ao funil, e é medido: a cor de
     link padrão do Bootstrap (`--bs-link-color`, o azul dele) sobre o marfim do tema claro dá
     **4,21:1**, abaixo dos 4,5:1 que texto pequeno pede. `--keys-texto-2` dá **5,76:1 no claro e
     7,59:1 no escuro**. A pílula ACESA continua sendo a do Bootstrap, intocada — branco sobre
     `--bs-nav-pills-link-active-bg`, 4,50:1 —, então o estado que o cliente procura é pixel a
     pixel o do funil.
     (Os valores dos tokens do Bootstrap não se escrevem aqui: o linter de tema — com razão —
     não distingue cor citada em comentário de cor chumbada em declaração.) */
.keys-cfg-abas .nav-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    white-space: nowrap;
    scroll-snap-align: start;
    color: var(--keys-texto-2);
}

.keys-cfg-abas .nav-link:hover,
.keys-cfg-abas .nav-link:focus-visible {
    background: var(--keys-superficie-3);
    color: var(--keys-tinta);
}

/* ---- A QUEBRA EM LINHAS, DE 992px PARA CIMA ----
   992px é o mesmo ponto em que o app inteiro deixa de ser móvel (é o corte do `.keys-cards-tablet`
   e da `.keys-tabbar`), e aqui ele coincide com a medida: o maior bloco — INTEGRAÇÕES, com cinco
   pílulas — cabe na caixa de 913px que a faixa passa a ter a partir de 992 de viewport, e não cabe
   nos 673px de uma janela de 768.

   Abaixo de 992 não existe quebra que caiba, e isso foi MEDIDO, não suposto: com a variante
   "quebra também no tablet" injetada na mesma carga de página, a 768px a faixa deu quatro linhas,
   183px de altura **e ainda oito abas cortadas** — pior nos dois eixos ao mesmo tempo. */
@media (min-width: 992px) {
    .keys-cfg-abas__tira {
        flex-wrap: wrap;
        row-gap: 0.35rem;
    }
}

/* Área de recurso que a casa não contratou: continua legível (não é "desabilitada", é um convite),
   só mais discreta que as demais. Ver o cabeçalho de Support\ConfiguracoesAbas. */
.keys-cfg-abas__travada { opacity: 0.72; }
.keys-cfg-abas__travada[aria-disabled="true"] { cursor: default; }
.keys-cfg-abas__cadeado { font-size: 0.72rem; }

/* ---------- A barra de salvar ----------
   Fixa no rodapé da janela, largura inteira, e só existe no DOM VISÍVEL quando há alteração
   pendente (`hidden` sai/entra pelo config-salvar.js). Largura inteira, e não pílula centrada
   como a `.keys-barra-lote`, de propósito: a barra de lote é uma ferramenta que aparece sobre uma
   lista; esta é o rodapé do formulário, e precisa parecer o fim da leitura. */
.keys-cfg-barra {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1045;                 /* acima do conteúdo, abaixo do backdrop de modal (1050) */
    background: var(--keys-superficie);
    border-top: 1px solid var(--keys-borda-forte);
    box-shadow: var(--keys-sombra-3);
    padding: 0.6rem 1rem calc(0.6rem + env(safe-area-inset-bottom, 0px));
}

.keys-cfg-barra__interna {
    max-width: 60rem;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

.keys-cfg-barra__aviso {
    margin: 0;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--keys-texto-2);
    font-size: 0.9rem;
}

.keys-cfg-barra__acoes {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
}

.keys-cfg-barra__descartar { text-decoration: none; }

/* O último campo do formulário não pode ficar sob a barra. Só reserva o espaço quando a barra
   está de fato visível — página sem alteração pendente não ganha um vão no fim. */
body:has(.keys-cfg-barra:not([hidden])) main { padding-bottom: 5.5rem; }

@media (max-width: 575.98px) {
    /* Com a TABBAR no ar, a barra sobe para não ficar sob ela nem sob a doca dos flutuantes —
       mesma base que a `.keys-barra-lote` usa (`--keys-tabbar-topo`, nunca `-alt`). */
    body:has(.keys-tabbar) .keys-cfg-barra {
        bottom: calc(var(--keys-tabbar-topo) + var(--keys-doca-alt));
    }
    body:has(.keys-tabbar):has(.keys-cfg-barra:not([hidden])) main {
        padding-bottom: calc(var(--keys-tabbar-topo) + var(--keys-doca-alt) + 5rem);
    }
    .keys-cfg-barra__aviso { font-size: 0.82rem; }
}
