/*
 * Numo — Sistema de Design
 * Rails 8 + Propshaft, CSS puro, sem build step, sem Tailwind.
 * Fonte da verdade dos tokens e componentes: DESIGN.md (raiz do repo).
 * Mobile-first, PWA, dark mode via toggle manual (data-theme, default claro).
 */

/* ===== Reset enxuto ===== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  color-scheme: light;
}

body {
  min-height: 100vh;
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
picture,
svg {
  display: block;
  max-width: 100%;
}

/* `img { display: block }` acima empata com a regra `[hidden]` do UA e vence por vir
   depois: o <img hidden> do preview de avatar continuava ocupando lugar e empurrava as
   iniciais para fora do círculo. Mesmo empate já documentado em `.field[hidden]`. */
img[hidden] {
  display: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
  background: none;
  border: none;
}

/* O UA desenha borda em groove, padding e margin próprios no <fieldset>, e um
   min-width:min-content que ignora overflow-x — nada disso pertence ao sistema visual.
   Reset global: quem quiser caixa (ex.: .field-group) declara a sua depois. */
fieldset {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

table {
  border-collapse: collapse;
}

/* ===== Tokens ===== */

:root {
  /* Cor — base neutra */
  --color-ink: #141414; --color-ink-soft: #2b2b2b; --color-body: #52525b; --color-muted: #71717a;
  --color-hairline: #e5e5e5; --color-hairline-strong: #d4d4d8;
  --color-canvas: #ffffff; --color-surface: #f7f7f7; --color-surface-2: #efefef; --color-surface-pressed: #e4e4e7;
  --color-on-ink: #ffffff;

  /* Cor — marca "Marinho": hero, chips ativos, botões primários (--cor-marca do spec). */
  --color-brand: #1e3a6e; --color-brand-hover: #17305c; --color-brand-active: #11254a;
  --color-brand-subtle-bg: #e8eefb; --color-on-brand: #ffffff;

  /* Cor — acento: links, ações secundárias. Mesma matiz do marinho, mais claro (--cor-acento do spec). */
  --color-accent: #3563b8; --color-accent-hover: #2b53a0; --color-accent-active: #1f4489;
  --color-accent-subtle-bg: #e8eefb; --color-on-accent: #ffffff;

  /* Cor — status financeiro (fg = texto/ícone, bg = badge) */
  --color-status-pendente-fg: #b45309; --color-status-pendente-bg: #fef3e2;
  --color-status-baixado-fg: #047857; --color-status-baixado-bg: #e7f6ef;
  --color-status-estornado-fg: #57534e; --color-status-estornado-bg: #f1f0ee;
  --color-status-comprometido-fg: #1d4ed8; --color-status-comprometido-bg: #e9effd;
  /* Neutro: arquivado/inativo. Não reusa --color-status-estornado-* — semânticas diferentes
     (estornado é "anulado", arquivado é só "fora de uso"). */
  --color-status-neutro-fg: #6b7280; --color-status-neutro-bg: #eef0f3;

  /* Cor — valor monetário */
  --color-value-despesa: var(--color-ink); --color-value-receita: #047857; --color-value-negativo: #b91c1c;

  /* Divisor fino entre itens dentro do mesmo card (ex.: linhas do _card_dia) — mais discreto
     que --color-hairline, que separa blocos maiores. */
  --color-divisor: #eef0f3;

  /* Tipografia */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --text-display: 2rem;      /* 32px — valor total no card de métrica */
  --text-title: 1.25rem;     /* 20px — saldo em destaque, título de seção */
  --text-value-lg: 1.5rem;   /* 24px — campo de valor em foco no form */
  --text-value-md: 1rem;     /* 16px — valor na linha de lançamento */
  --text-body: 0.9375rem;    /* 15px — texto corrido, labels de input */
  --text-label: 0.8125rem;   /* 13px — legendas, metadados */
  --text-caption: 0.75rem;   /* 12px — timestamp, texto auxiliar mínimo */
  --leading-tight: 1.15; --leading-base: 1.4; --leading-loose: 1.6;
  --weight-regular: 400; --weight-medium: 500; --weight-bold: 700;

  /* Espaçamento — base 4px */
  --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem;
  --space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem; --space-10: 2.5rem; --space-12: 3rem;

  /* Raios */
  --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-full: 999px;
  /* Raio dos componentes do redesign (card-dia, item-extrato, chips) — --raio do spec. */
  --radius-card: 14px;

  /* Sombra — elevação única, sem tiers múltiplos */
  --shadow-card: 0 1px 2px rgba(20,20,20,.04), 0 2px 8px rgba(20,20,20,.06);
  --shadow-nav: 0 -1px 0 rgba(20,20,20,.06), 0 -4px 12px rgba(20,20,20,.05);

  /* Altura visual da bottom-nav flutuante e a folga entre ela e a borda inferior.
     --bottom-nav-espaco é o footprint total (altura + gap): tudo que reserva espaço no
     rodapé (conteúdo, rodapé sticky do form) usa o espaço, não só a altura. */
  --bottom-nav-altura: 56px;
  --bottom-nav-gap: 16px;
  --bottom-nav-espaco: calc(var(--bottom-nav-altura) + var(--bottom-nav-gap));
  --shadow-modal: 0 8px 24px rgba(20,20,20,.16);

  /* Transição */
  --duration-fast: 120ms; --duration-base: 200ms; --duration-slow: 320ms;
  --ease-standard: cubic-bezier(.2, 0, 0, 1);

  /* Alvo de toque mínimo */
  --tap-min: 44px;

  /* Altura de linha de leitura (não de controle). Default = --tap-min; o gate
     de ponteiro fino (abaixo) redefine para 38px acima de 900px. */
  --row-min: var(--tap-min);
}

/* ===== Dark mode ===== */
/* Default é claro. Escuro entra por toggle manual (data-theme="dark" no <html>,
   persistido em localStorage por tema_controller). Não segue mais o SO. */

/* Isolado em @media not print: sem isso, a especificidade de :root[data-theme="dark"]
   ((0,2,0) contra (0,1,0) de :root sozinho) venceria a cascata mesmo dentro de um
   @media print que tentasse resetar as mesmas variáveis, e o dark mode vazaria pro
   papel. Não casando em impressão, a cascata cai direto pro :root claro — sem duplicar
   nenhum valor. */
@media not print {
  :root[data-theme="dark"] {
    color-scheme: dark;
    --color-ink: #f5f5f4; --color-ink-soft: #d4d4d4; --color-body: #b4b4b8; --color-muted: #8a8a93;
    --color-hairline: #2a2a2a; --color-hairline-strong: #383838;
    /* Superfície com leve tinta marinho, não cinza puro: o app carrega a matiz da marca
       também no escuro em vez de virar um app genérico "dark theme". */
    --color-canvas: #11151f; --color-surface: #171c29; --color-surface-2: #1e2434; --color-surface-pressed: #262d40;
    --color-on-ink: #121212;
    --color-divisor: #232a3a;

    /* Marca e acento viram a mesma matiz em versão neon: sobre fundo escuro, o marinho
       sólido (#1e3a6e) não tem contraste suficiente para texto/ícone — precisa clarear.
       Mantém uso de --color-brand para fundos sólidos (chip ativo, botão primário, hero)
       e --color-accent para texto/link, os dois já claros o bastante (4.5:1 texto / 3:1 UI). */
    --color-brand: #3f6bb8; --color-brand-hover: #4d78c2; --color-brand-active: #2f5296;
    --color-brand-subtle-bg: #1a2740; --color-on-brand: #f5f8ff;

    --color-accent: #6ea8ff; --color-accent-hover: #8ab8ff; --color-accent-active: #588fe0;
    --color-accent-subtle-bg: #1a2740; --color-on-accent: #0b1220;

    --color-status-pendente-fg: #fbbf24; --color-status-pendente-bg: #3a2a0f;
    --color-status-baixado-fg: #34d399; --color-status-baixado-bg: #0f2c22;
    --color-status-estornado-fg: #a8a29e; --color-status-estornado-bg: #262523;
    --color-status-comprometido-fg: #60a5fa; --color-status-comprometido-bg: #10203d;
    --color-status-neutro-fg: #9ca3af; --color-status-neutro-bg: #232a3a;

    --color-value-receita: #34d399; --color-value-negativo: #f87171;

    --shadow-card: 0 1px 2px rgba(0,0,0,.4), 0 2px 8px rgba(0,0,0,.3);
    --shadow-nav: 0 -1px 0 rgba(0,0,0,.4), 0 -4px 16px rgba(0,0,0,.35);
    --shadow-modal: 0 8px 32px rgba(0,0,0,.5);
  }
}

/* ===== Base ===== */

html {
  font-family: var(--font-sans);
}

body {
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: var(--text-body);
  line-height: var(--leading-base);
  font-weight: var(--weight-regular);
}

h1, h2, h3 {
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  color: var(--color-ink);
}

/* Título de seção: menor que o padrão do navegador e com ritmo vertical próprio —
   é ele que separa os blocos de uma tela, não caixas coloridas. */
h2 {
  font-size: var(--text-title);
  margin: var(--space-8) 0 var(--space-3);
}

/* Dentro de .form (flex column) margem não colapsa: ela SOMA ao gap e o título de seção
   do formulário ganha o dobro do ritmo. O gap já dá o espaçamento ali. */
.form > h2 {
  margin: 0;
}

a {
  color: var(--color-accent);
}

/* ===== Acessibilidade (não-negociável) ===== */

/* Foco visível: nunca outline: none sem substituto. */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Alvo de toque mínimo em controles interativos. */
button,
.btn,
input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  min-height: var(--tap-min);
}

/* Movimento respeita preferência do usuário. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    scroll-behavior: auto !important;
  }

  /* ::view-transition-* vive numa árvore de pseudo-elementos própria, fora do DOM normal
     -- o `*` acima não alcança. Sem isto, o cross-fade do onboarding (Turbo 8 View
     Transitions) ignoraria a preferência do usuário. */
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation-duration: .01ms !important;
  }
}

/* ===== Layout ===== */

.page {
  max-width: 480px;
  margin-inline: auto;
  padding: var(--space-4);
  /* A bottom-nav é fixa: sem esta folga ela cobre o último elemento da página — o botão
     de salvar de qualquer formulário fica intocável no celular. Inclui a barra home do
     iPhone (safe-area) além da altura da própria nav. */
  /* --convite-espaco é a altura do convite de instalação, medida pelo instalar_controller
     enquanto ele está visível (0 no resto do tempo): o convite também é fixo e, sem esta
     folga, cobre o mesmo botão de salvar que a nav cobriria. */
  padding-bottom: calc(var(--bottom-nav-espaco) + var(--convite-espaco, 0px) + var(--space-6) + env(safe-area-inset-bottom, 0px));
  min-height: 100vh;
  min-height: 100dvh;
}

.page__header {
  margin-bottom: var(--space-6);
}

/* Título grande à la iOS: a tela se apresenta sem depender de barra de topo. */
.page__header h1 {
  font-size: 1.75rem;
  letter-spacing: -0.02em;
  /* O h1 de aceitar convite interpola o nome da empresa, que não tem limite de tamanho.
     Sem isto, um nome de palavra única com ~27 caracteres estoura os 390px e a página
     inteira rola na horizontal — mesmo motivo do valor no hero do dashboard. */
  overflow-wrap: anywhere;
}

/* Linha de contexto acima do título (empresa, marca): pequena, nunca compete com o h1. */
.page__header__contexto {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: var(--space-1);
}

/* ===== Flash ===== */

/* Base inline, usada por erros de formulário embutidos na página (registrations/new,
   purchases/edit, invitation_acceptances/show etc.) — não flutua, não some sozinha. */
.flash {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  margin-bottom: var(--space-4);
}

.flash--notice {
  background: var(--color-status-baixado-bg);
  color: var(--color-status-baixado-fg);
}

.flash--alert {
  background: color-mix(in srgb, var(--color-value-negativo) 12%, var(--color-canvas));
  color: var(--color-value-negativo);
}

/* ===== Toast (flash de notice/alert do topo, via shared/_flash e onboardings/_flash) ===== */

/* #flash é o container flutuante fixo; cada .toast dentro dele é independente
   (própria barra, próprio timer) para empilhar sem se atrapalhar. */
#flash {
  position: fixed;
  top: var(--space-4);
  right: var(--space-4);
  left: var(--space-4);
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: flex-end;
  pointer-events: none;
}

.toast {
  position: relative;
  overflow: hidden;
  width: 100%;
  max-width: 22rem;
  padding: var(--space-3) var(--space-8) var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  box-shadow: var(--shadow-modal);
  pointer-events: auto;
  animation: toast-entrar 220ms cubic-bezier(0.16, 1, 0.3, 1);
}

.toast--saindo {
  animation: toast-sair 180ms cubic-bezier(0.7, 0, 0.84, 0) forwards;
}

.toast__mensagem {
  margin: 0;
}

.toast__fechar {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: inherit;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.toast__fechar:hover {
  background: color-mix(in srgb, currentColor 12%, transparent);
}

/* Barra de countdown: nasce em scaleX(1) e o controller anima pra scaleX(0) na duração
   configurada — a barra encolhendo É o timer, não uma decoração separada dele. */
.toast__barra {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: currentColor;
  opacity: 0.35;
  transform-origin: left;
  transform: scaleX(1);
  transition-property: transform;
  transition-timing-function: linear;
}

.toast--notice {
  background: var(--color-status-baixado-bg);
  color: var(--color-status-baixado-fg);
}

.toast--alert {
  background: color-mix(in srgb, var(--color-value-negativo) 12%, var(--color-canvas));
  color: var(--color-value-negativo);
}

@keyframes toast-entrar {
  from { opacity: 0; transform: translateY(-8px) scale(0.98); }
}

@keyframes toast-sair {
  to { opacity: 0; transform: translateY(-8px) scale(0.98); }
}

@media (prefers-reduced-motion: reduce) {
  .toast, .toast--saindo { animation: none; }
  .toast__barra { transition: none; }
}

/* ===== Dialog de aviso ===== */
.form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* `.field` empata em especificidade com a regra `[hidden]` do UA stylesheet e vence por
   vir depois — sem isto, o atributo `hidden` que o preview_controller usa para sumir com
   "1ª parcela em" perderia a batalha e o campo continuaria visível. */
.field[hidden] {
  display: none;
}

/* .rotulo-campo: mesmo rótulo, sem <label> — para campo cujo controle é um iframe de
   terceiro (cartão no MercadoPago), onde não existe elemento nosso para o `for` apontar. */
.field label,
.field .rotulo-campo {
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  color: var(--color-ink-soft);
}

/* 16px mínimo: abaixo disso o Safari iOS dá zoom automático ao focar. */
.field input,
.field select,
.field textarea {
  font-size: 1rem;
  padding: var(--space-3);
  border: 1px solid var(--color-hairline-strong);
  border-radius: var(--radius-md);
  background: var(--color-canvas);
  color: var(--color-ink);
  width: 100%;
}

.field input::placeholder,
.field textarea::placeholder {
  color: var(--color-muted);
}

/* Radio e checkbox são glifos nativos, não caixas de texto: a regra acima os esticava para
   a largura do campo e a bolinha aparecia centralizada, longe do próprio rótulo — era o
   que acontecia com "No cartão" na tela de editar. Reset em regra própria, e não com
   :not() na regra de cima, porque o :not() subiria a especificidade dela a ponto de
   derrubar o estilo do campo de valor. */
.field input[type="radio"],
.field input[type="checkbox"] {
  width: auto;
  padding: 0;
  border: none;
  border-radius: 0;
  background: none;
}

/* ===== Botões ===== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  /* Rótulo de botão nunca quebra em duas linhas: quebrado, deixa de parecer botão. */
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}

/* `display: inline-flex` acima empata em especificidade com a regra `[hidden]` do UA
   stylesheet e vence por vir depois — sem isto, `botao.hidden = true` no JS não esconde
   nada. Foi assim que o botão "Instalar" do convite PWA continuou visível no iPhone (onde
   o controller o esconde por não existir beforeinstallprompt) e não fazia nada ao toque.
   Mesmo empate já documentado em `.field[hidden]` e `img[hidden]`. */
.btn[hidden] {
  display: none;
}

.btn--primary {
  width: 100%;
  background: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-on-brand);
}

.btn--primary:hover {
  background: var(--color-brand-hover);
  border-color: var(--color-brand-hover);
}

.btn--primary:active {
  background: var(--color-brand-active);
  border-color: var(--color-brand-active);
}

.btn--ghost {
  background: transparent;
  border-color: var(--color-hairline-strong);
  color: var(--color-body);
}

.btn--ghost:hover {
  background: var(--color-surface-2);
  color: var(--color-ink);
}

.btn--ghost:active {
  background: var(--color-surface-pressed);
}

/* U5 — botão "Fechamento do mês" na toolbar do Resumo: desktop-only (R4/R9). Abaixo do
   piso da toolbar (980px, mesmo degrau de .page__toolbar — ver
   docs/solutions/ui-bugs/breakpoint-zona-morta-980px-modo-desktop-chrome.md) some, e o
   link .rodape-fechamento-mobile no fim da página assume o lugar dele; o inverso ocorre
   acima do piso (bloco @media 980px abaixo). */
.toolbar__fechamento { display: none; }

/* Ação dentro de linha de lista: compacta, sem ocupar a largura do card. */
.btn--sm {
  width: auto;
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-label);
}

/* Sair, excluir: vermelho de texto basta — fundo vermelho gritaria numa tela de rotina.
   Os estados vêm junto porque .btn--ghost:hover é mais específico e devolvia a cor normal
   bem na hora em que o cursor está sobre o botão, que é quando o aviso mais importa. */
.btn--destrutivo,
.btn--destrutivo:hover,
.btn--destrutivo:active {
  color: var(--color-value-negativo);
}

/* ===== Utilitários ===== */

.muted {
  color: var(--color-muted);
  font-size: var(--text-body);
}

/* ===== Tipografia de valores monetários ===== */
/* Números tabulares evitam o "pulo" de largura em update via Turbo Stream. */

.valor-monetario {
  font-variant-numeric: tabular-nums lining-nums;
  font-weight: var(--weight-medium);
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.valor-monetario--pendente {
  color: var(--color-status-pendente-fg);
}

/* ===== Card de métrica ===== */
/* ===== Linha de lançamento em lista ===== */
/* Lista densa dentro de um único card; divisórias hairline, não card-por-linha. */

.lista-lancamentos {
  background: var(--color-canvas);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.linha-lancamento {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-hairline);
}

.linha-lancamento:last-child {
  border-bottom: none;
}

.linha-lancamento__valor {
  margin-left: auto;
}

.form-acerto-parcial {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

/* Selo de marca do Mercado Pago no caminho hospedado (morto atrás de MP_CHECKOUT_HOSPEDADO)
   — hoje só o texto .motivo; o caminho do Brick usa a .moldura-mp abaixo, não esta classe. */
.assinatura-processador {
  margin-block-end: var(--space-3);
}

/* Container onde o Payment Brick do Mercado Pago monta (cardform_controller.js) — o
   Brick é totalmente responsivo dentro do elemento que o contém (doc oficial do SDK),
   então não precisa de altura/borda própria como os antigos .campo-cardform (iframes
   avulsos por campo, que o CardForm exigia e o Brick não usa mais). */
#cardPaymentBrick {
  min-height: var(--tap-min);
}

/* ===== Moldura Mercado Pago no Payment Brick (/planos) =====
   Plano 2026-08-09-001. Amarelo/marinho são cor de marca de terceiro (KTD1, DESIGN.md
   princípio 2) — valores literais de propósito, nunca token do Numo, confinados a esta
   moldura. Mockup aprovado: docs/plans/assets/2026-08-09-mockup-brick-moldura-mp.html */
.moldura-mp {
  margin-top: var(--space-4);
  border: 2px solid #FFE600; /* marca MP */
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--color-canvas);
}

.moldura-mp__cabecalho {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: #FFE600; /* marca MP */
  color: #00274f; /* marca MP */
  padding: var(--space-2) var(--space-4);
}

.moldura-mp__wordmark {
  font-weight: var(--weight-bold);
  font-size: var(--text-body);
  letter-spacing: -0.2px;
}

.moldura-mp__selo {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-caption);
  font-weight: var(--weight-medium);
}

/* padding único do corpo: as três variantes (esqueleto, fieldset, erro) herdam o mesmo
   respiro, sem repetir o valor em cada uma. position: relative é a âncora do fieldset
   (absolute) enquanto carrega — ver comentário abaixo. */
.moldura-mp__corpo {
  position: relative;
  padding: var(--space-4) var(--space-4) var(--space-5);
}

.moldura-mp .grupo-campos {
  /* .grupo-campos já traz gap/flex do form de lançamento; o padding vem do __corpo acima
     — sem o margin-bottom de 32px que o padrão usa entre grupos. */
  margin-bottom: 0;
}

/* O FIELDSET sai do fluxo, sobreposto e invisível (nunca `display:none`) sempre que não
   deve estar à vista — antes de carregar OU com erro visível (regra de baixo). Um Payment
   Brick precisa montar um iframe PCI dentro de #cardPaymentBrick, e um container com
   `display:none`/altura zero é o mesmo tipo de falha silenciosa já documentado em
   docs/solutions/integration-issues/cardform-mercadopago-nao-monta-installments-select.md
   — `position:absolute; inset:0` aqui dá ao Brick uma caixa cheia (a área toda do corpo),
   só que fora da visão (visibility:hidden), então um `create()`/mount em voo quando o
   watchdog estoura (ou um `onReady` tardio depois de `desabilitar()`) sempre encontra o
   container com tamanho real — nunca zero. Ao ficar pronto sem erro, nenhuma das duas
   regras bate e o fieldset volta ao fluxo normal sozinho. */
.moldura-mp:not(.moldura-mp--carregada) .grupo-campos,
.moldura-mp:has(.erro-campo:not([hidden])) .grupo-campos {
  position: absolute;
  inset: 0;
  visibility: hidden;
}

.moldura-mp__barra {
  height: 14px;
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-3);
  background: linear-gradient(90deg, var(--color-surface) 25%, var(--color-surface-2) 50%, var(--color-surface) 75%);
  background-size: 200% 100%;
  animation: moldura-mp-brilho 1.2s infinite;
}

.moldura-mp__barra--alta {
  height: 40px;
}

.moldura-mp__barra:last-child {
  margin-bottom: 0;
}

@keyframes moldura-mp-brilho {
  to { background-position: -200% 0; }
}

/* Alternância esqueleto ↔ conteúdo real, disparada pelo cardform_controller (onReady,
   desabilitar(), watchdog do KTD3) via classe na raiz da moldura. */
.moldura-mp--carregada .moldura-mp__esqueleto {
  display: none;
}

.moldura-mp--carregada .grupo-campos {
  animation: moldura-mp-surge var(--duration-base) var(--ease-standard);
}

@keyframes moldura-mp-surge {
  from { opacity: 0; transform: translateY(6px); }
}

/* R4: erro visível troca de lugar com o fieldset (nunca os dois juntos, nunca erro solto
   fora da moldura) — regra de ocultar o fieldset está unificada com o estado "ainda não
   carregada" acima (mesmo `position:absolute; visibility:hidden`, nunca `display:none`).
   Cabeçalho e rodapé continuam sempre visíveis (fora deste :has). */

.moldura-mp__rodape {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-caption);
  color: var(--color-muted);
  border-top: 1px dashed var(--color-hairline);
  padding: var(--space-3) var(--space-4);
}

.moldura-mp__rodape svg {
  flex-shrink: 0;
}

/* Shimmer é `infinite`: o reset global (linha ~264) zera a DURAÇÃO mas não a repetição —
   sem isto a barra piscaria pra sempre em vez de ficar parada, pior que a animação normal
   pra quem pediu menos movimento. Mesmo padrão do .toast (linha ~433). */
@media (prefers-reduced-motion: reduce) {
  .moldura-mp__barra {
    animation: none;
  }
}

/* Campo de dinheiro: 16px é o piso do princípio "o número é o conteúdo", e abaixo disso
   o Safari do iPhone ainda dá zoom no foco e joga o resto da linha para fora da tela.
   Fora de .field, então as declarações de caixa são repetidas aqui de propósito.
   Largura em ch contra "R$ 12.345,67", não contra o valor da tela de demonstração. */
.form-acerto-parcial__valor {
  flex: 0 1 13ch;
  min-width: 9ch;
  font-size: var(--text-value-md);
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding: var(--space-2);
  border: 1px solid var(--color-hairline-strong);
  border-radius: var(--radius-md);
  background: var(--color-canvas);
  color: var(--color-ink);
}

.form-acerto-parcial__valor::placeholder {
  color: var(--color-muted);
  font-weight: var(--weight-regular);
}

/* .btn--primary é width:100% por padrão (é botão de bloco no resto do app); aqui ele
   divide a linha com o campo. */
.form-acerto-parcial .btn--primary {
  width: auto;
  flex: 0 0 auto;
}

/* ===== Form de lançamento ===== */
/* Caminho de "menos de 1 minuto": valor grande, categoria em chips, salvar de largura total. */

/* Prefixado com .field para vencer `.field input`, que devolvia a caixa com borda e
   anulava o estilo de sublinhado deste campo. */
.field .form-lancamento__valor-input {
  font-size: var(--text-value-lg);
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-bold);
  border: none;
  border-bottom: 2px solid var(--color-hairline-strong);
  border-radius: 0;
  padding: var(--space-2) 0;
  background: transparent;
  width: 100%;
}

/* O foco vive no próprio sublinhado — outline em volta viraria anel duplo. */
.field .form-lancamento__valor-input:focus-visible {
  outline: none;
  border-color: var(--color-accent);
}

.chips-categoria {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Chip de seleção única (categoria e forma de pagamento): mesmo componente visual,
   um radio nativo escondido dentro do label. */
.chip-categoria,
.chip-pagamento {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-hairline-strong);
  background: var(--color-canvas);
  font-size: var(--text-body);
  color: var(--color-ink);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard);
}

/* O estado vem do radio nativo escondido dentro do label: teclado, leitor de tela e
   validação `required` continuam sendo os do navegador, sem JavaScript de estado. */
.chip-categoria:has(input:checked),
.chip-pagamento:has(input:checked) {
  background: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-on-brand);
}

.chip-categoria:has(input:focus-visible),
.chip-pagamento:has(input:focus-visible) {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ===== Bottom-nav ===== */
/* Pill flutuante: 2 destinos (Relatórios / Você) + FAB central. Total ímpar = simétrico.
   Descolada das bordas para respirar; conteúdo reserva --bottom-nav-espaco no rodapé. */

.bottom-nav {
  position: fixed;
  left: var(--bottom-nav-gap);
  right: var(--bottom-nav-gap);
  bottom: calc(var(--bottom-nav-gap) + env(safe-area-inset-bottom, 0px));
  display: flex;
  background: var(--color-canvas);
  box-shadow: var(--shadow-modal);
  border-radius: var(--radius-full);
  padding: var(--space-1);
}

.bottom-nav__item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--bottom-nav-altura);
  justify-content: center;
  color: var(--color-muted);
  font-size: var(--text-caption);
}

.bottom-nav__item[aria-current="page"] {
  color: var(--color-accent);
  font-weight: var(--weight-medium);
}

/* Wordmark e rodapé de empresa só existem no rail (900px+) — a pill flutuante do
   celular não tem espaço nem função para eles. */
.bottom-nav .wordmark,
.bottom-nav__empresa {
  display: none;
}

/* A barra de polegar mantém 3 destinos ímpares (Resumo/Lançar/Você) para a FAB
   ficar centralizada (DESIGN.md princípio 4) — "Lançamentos" só existe como
   destino próprio a partir do rail (900px, U3/KTD4). */
@media (max-width: 899px) {
  .bottom-nav__item--lancamentos {
    display: none;
  }
}

/* ===== Segmented (sub-seções de Relatórios: Resumo / Lançamentos) ===== */
/* Toggle entre dashboard e extrato. Substitui a antiga aba "Lançamentos" da bottom-nav. */

.segmented {
  display: flex;
  gap: var(--space-1);
  background: var(--color-surface);
  padding: var(--space-1);
  border-radius: var(--radius-full);
  margin-bottom: var(--space-4);
}

.segmented__item {
  flex: 1;
  /* alternador principal do app: tinha 33px de altura, abaixo do mínimo de toque */
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  text-align: center;
  padding: var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--text-caption);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
}

.segmented__item[aria-current="page"] {
  background: var(--color-canvas);
  color: var(--color-accent);
  box-shadow: var(--shadow-card);
}

/* ===== Badge de status ===== */
/* Rótulo sempre por extenso — cor nunca é o único sinal. */

.badge-status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--text-caption);
  font-weight: var(--weight-medium);
}

.badge-status--pendente {
  background: var(--color-status-pendente-bg);
  color: var(--color-status-pendente-fg);
}

.badge-status--baixado {
  background: var(--color-status-baixado-bg);
  color: var(--color-status-baixado-fg);
}

.badge-status--parcial {
  background: var(--color-status-pendente-bg);
  color: var(--color-status-pendente-fg);
}

.badge-status--estornado {
  background: var(--color-status-estornado-bg);
  color: var(--color-status-estornado-fg);
}

.badge-status--comprometido {
  background: var(--color-status-comprometido-bg);
  color: var(--color-status-comprometido-fg);
}

/* Marinho sólido, não accent-subtle: o badge vive sobre o cartão selecionado, cujo
   fundo é --color-brand-subtle-bg — dois azuis-pálidos se anulam e o "recomendado"
   sumia. Mesma matiz da marca, papel de ação primária: a recomendação é a voz da casa. */
.badge-status--recomendado {
  background: var(--color-brand);
  color: var(--color-on-brand);
}

.confirmacao-assinatura {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-status-baixado-fg);
  font-weight: var(--weight-medium);
}

.confirmacao-assinatura__icone {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
}

.linha-config--recomendada .linha-config {
  background: var(--color-accent-subtle-bg);
  font-weight: var(--weight-bold);
}

/* ===== Totais do fechamento do mês ===== */
/* Dois valores de destaque da página — mesma hierarquia de --text-display do .hero__valor. */

.fechamento-totais {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-4);
  margin: var(--space-6) 0;
}

.fechamento-totais dt {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
}

.fechamento-totais__gasto,
.fechamento-totais__comprometido {
  font-size: var(--text-display);
  font-weight: var(--weight-bold);
  overflow-wrap: anywhere;
}

/* ===== Tabela de relatório com scroll horizontal próprio ===== */
/* A tabela nunca estoura a página — scroll contido no próprio wrapper. */

.tabela-relatorio-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-hairline);
  background: var(--color-canvas);
}

.tabela-relatorio {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body);
}

.tabela-relatorio caption {
  text-align: left;
  padding: var(--space-3) var(--space-4) 0;
  font-size: var(--text-label);
}

.tabela-relatorio thead th {
  position: sticky;
  top: 0;
  background: var(--color-surface);
  text-align: left;
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-label);
  color: var(--color-muted);
  font-weight: var(--weight-medium);
  border-bottom: 1px solid var(--color-hairline);
}

.tabela-relatorio td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-hairline);
}

/* th de linha herda do navegador centro + negrito; aqui é rótulo, não destaque. */
.tabela-relatorio tbody th {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-hairline);
  text-align: left;
  font-weight: var(--weight-regular);
}

.tabela-relatorio tfoot th {
  padding: var(--space-3) var(--space-4);
  text-align: left;
}

/* Alinhado à direita e sem quebra: sem o nowrap, "R$ 340,00" partia em duas linhas na
   tabela de parcelas da tela de editar, que tem quatro colunas em 390px. */
.tabela-relatorio .col-valor {
  text-align: right;
  white-space: nowrap;
}

/* ===== Empty state ===== */
/* Sem ilustração pesada — ícone simples, uma frase, uma ação. */

.empty-state {
  /* U16 — um eixo só por tela: o vazio partilha o eixo esquerdo dos campos e das listas,
     em vez de misturar centralizado com alinhado à esquerda na mesma tela. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-10) var(--space-4);
  color: var(--color-muted);
}

.empty-state__titulo {
  font-size: var(--text-title);
  color: var(--color-ink);
  font-weight: var(--weight-medium);
}

/* ============================================================
   Utilitários e componentes de relatório
   ============================================================ */

/* Rótulo que só o leitor de tela precisa (campos cujo contexto já é visual).
   !important porque a função inteira deste utilitário é vencer: `.field input` é mais
   específico e estava dando 375px de largura aos radios escondidos dos chips, empurrando
   a tela de lançar para 537px num viewport de 375 e criando rolagem horizontal. */
.visually-hidden {
  position: absolute;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0 !important;
}

/* Formulário de uma linha só: renomear categoria, escolher mês. */
.form--inline {
  display: flex;
  /* Precisa vir depois de .form, que é coluna: sem isto o `flex: 1 1` do campo cresce no
     eixo vertical e o input vira uma caixa alta. */
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* O rótulo ocupa a linha inteira; campo e botão dividem a linha de baixo. */
.form--inline label {
  flex: 1 0 100%;
  font-size: var(--text-label);
  color: var(--color-muted);
}

.form--inline input {
  flex: 1 1 10rem;
  min-width: 0;
}

.form--inline input[type="submit"],
.form--inline .btn {
  flex: 0 0 auto;
}

/* Stepper de mês: um controle só no lugar de form + input nativo + botões de atalho.
   O input type="month" nativo aparecia em inglês fora do celular e pedia três linhas de
   tela para uma escolha de um toque. */
.seletor-mes {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  background: var(--color-canvas);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-card);
  padding: var(--space-1) var(--space-2);
  margin-bottom: var(--space-4);
}

/* Variante fixa no topo (extrato, dashboard): fundo marinho para se destacar do conteúdo
   que passa por baixo ao rolar. z-index acima do conteúdo normal mas abaixo de qualquer
   modal/toast. */
.seletor-mes--sticky {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--color-brand);
  box-shadow: var(--shadow-nav);
}

.seletor-mes--sticky .seletor-mes__seta {
  color: var(--color-on-brand);
}

.seletor-mes--sticky .seletor-mes__seta:hover,
.seletor-mes--sticky .seletor-mes__seta:active {
  background: rgb(255 255 255 / .12);
}

.seletor-mes--sticky .seletor-mes__atual {
  color: var(--color-on-brand);
}

.seletor-mes__seta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  min-height: var(--tap-min);
  border-radius: var(--radius-full);
  color: var(--color-ink-soft);
  transition: background var(--duration-fast) var(--ease-standard);
}

.seletor-mes__seta:hover {
  background: var(--color-surface-2);
}

.seletor-mes__seta:active {
  background: var(--color-surface-pressed);
}

.seletor-mes__atual {
  font-size: var(--text-value-md);
  font-weight: var(--weight-bold);
  color: var(--color-ink);
}

/* Link discreto logo abaixo do stepper: voltar ao mês atual, limpar filtro.
   A folga de baixo é sempre a mesma do .seletor-mes sozinho — o link entra e sai da tela
   conforme o mês, e se ele somasse margem o conteúdo saltaria a cada toque na seta. */
.seletor-mes__acao {
  /* U16 — no extrato "Ver todos os meses" segue o eixo esquerdo do resto. No hero do
     dashboard (showcase centrado) o override abaixo mantém o centro. */
  text-align: left;
  font-size: var(--text-label);
  margin-top: calc(-1 * var(--space-2));
  margin-bottom: var(--space-2);
}

.hero .seletor-mes__acao {
  text-align: center;
}

/* O alvo tocável é do link, não do parágrafo: em caixa inline o min-height não vale. */
.seletor-mes__acao a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding-inline: var(--space-3);
}

/* Chips de destino secundário no topo de uma lista (reembolsos, estornadas, filtro).
   Rolável na horizontal: numa tela de 375px o grupo de status (Todas/Pendentes/Baixadas/
   Estornadas) + drill-down removível não cabe sem quebrar linha, e quebrar linha empurra
   o conteúdo abaixo a cada filtro trocado. nowrap + overflow-x resolve sem JS. */
.chips-filtro {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* O min-width:0 do reset global de fieldset é obrigatório aqui: com o min-content do UA
     o navegador ignora overflow-x:auto e estica o fieldset até caber todos os chips,
     rolando a PÁGINA inteira na horizontal em vez do chip-bar. */
}

/* U7 — título de grupo dos filtros ("Status" / "Categoria e cartão"): só existe em
   desktop (≥900px, regra na E3/KTD5 abaixo). No mobile o <details> de refino já nomeia
   a si mesmo no <summary>, e o grupo de status não precisa de rótulo pra um fieldset só —
   duplicar seria ruído. */
.filtros-extrato__titulo {
  display: none;
}

/* "Limpar filtros" também é de desktop: no mobile a faixa de chips é rolável na
   horizontal e um item a mais competiria com os próprios chips pelo espaço. A saída
   no mobile continua sendo desmarcar o chip ou o botão dentro do vazio (U10). */
.filtros-extrato__limpar {
  display: none;
}

/* Refino do extrato (categoria, cartão) recolhido num <details> nativo: o marcador padrão
   do navegador é substituído por um chevron em CSS, na mesma linha de base do resumo. */
.filtros-refino {
  margin-bottom: var(--space-5);
}

.filtros-refino > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  color: var(--color-accent);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  cursor: pointer;
  list-style: none;
}

.filtros-refino > summary::-webkit-details-marker {
  display: none;
}

.filtros-refino > summary::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  /* O quadrado rotacionado fica ~3px acima da linha ótica do texto; a margem compensa. */
  margin-bottom: 3px;
  transform: rotate(45deg);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.filtros-refino[open] > summary::after {
  transform: rotate(-135deg);
}

.filtros-refino > summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-full);
}

/* O que está aplicado precisa ser legível com o painel fechado. */
.filtros-refino__ativo {
  margin-left: auto;
  color: var(--color-body);
  font-weight: var(--weight-regular);
}

.filtros-refino > .chips-filtro:first-of-type {
  margin-top: var(--space-2);
}

/* U2/KTD3 — chips de categoria/cartão quebram linha em qualquer largura, não só ≥900px
   (a regra de desktop mais abaixo, ~E3, já fazia isso lá; esta estende pro mobile).
   Cresce com o cadastro da empresa, ao contrário dos 4 chips fixos de Status (que
   continuam nowrap + scroll, regra base de .chips-filtro acima — não tocada aqui).
   Duas classes bastam pra vencer a base sem depender de @media nem mexer na regra de
   desktop existente. */
.filtros-refino .chips-filtro {
  flex-wrap: wrap;
  overflow-x: visible;
}
.filtros-refino .chips-filtro--rolavel {
  mask-image: none;
  -webkit-mask-image: none;
}

/* U2 — título de grupo (Categoria/Cartão) some no mobile por padrão (regra base de
   .filtros-extrato__titulo, acima): duplicava "Status" ali (comentário original). Dentro
   do painel de refino ele volta a aparecer, porque agora tem um link "Limpar <dimensão>"
   ao lado — sem o título esse link ficaria sem contexto. `order` reordena os três filhos
   de `.filtros-extrato__linha` (título, chips, limpar — mesma ordem do DOM da linha de
   Status, pra não desalinhar o teste de desktop que espera os chips começando no mesmo x
   nas três linhas) sem mexer no DOM: título e "Limpar" dividem a primeira linha, os chips
   forçados pro início da segunda via flex-basis:100%. Escopado a `.filtros-refino` e a
   <900px pra não tocar a calha de desktop (regra ~E3, mais abaixo) nem as faixas de
   Status/Ver por, que continuam sem título no mobile. */
@media (max-width: 899px) {
  .filtros-refino .filtros-extrato__linha {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2) var(--space-3);
  }
  .filtros-refino .filtros-extrato__titulo {
    display: block;
    order: 1;
  }
  .filtros-refino .filtros-extrato__limpar {
    display: inline-block;
    order: 2;
    margin-left: auto;
  }
  .filtros-refino .chips-filtro {
    order: 3;
    flex-basis: 100%;
  }
}

.chips-filtro::-webkit-scrollbar {
  display: none;
}

/* Fade na margem direita só quando a faixa transborda (marcado pelo chips_scroll_controller):
   sem barra de rolagem, é o único sinal de que há mais chip fora da vista. U11/R10. */
.chips-filtro--rolavel {
  -webkit-mask-image: linear-gradient(to right, #000 88%, transparent);
  mask-image: linear-gradient(to right, #000 88%, transparent);
}

/* Borda em --color-muted e texto em acento, não hairline sobre canvas: a pílula precisa
   se ler como destino navegável. Hairline sobre canvas dá 1.5:1 de contorno e canvas
   sobre a superfície da página dá 1.04:1 — nos dois casos o chip some e vira texto solto,
   que é justamente o que os links laranja de antes NÃO faziam. */
.chip-filtro {
  /* position:relative é o que faz o <input class="visually-hidden"> (absolute) ficar
     preso dentro do label em vez de escapar pro viewport inteiro: sem isso, o input do
     ÚLTIMO chip de um fieldset com scroll interno se posiciona na coordenada estática
     que teria SEM o scroll — geralmente fora dos 375px da tela — e o navegador conta
     isso como overflow da PÁGINA, não do chip-bar (que já rola direitinho sozinho). */
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--tap-min);
  padding: var(--space-1) var(--space-4);
  border: 1px solid var(--color-muted);
  border-radius: var(--radius-full);
  background: var(--color-canvas);
  color: var(--color-accent);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  flex: 0 0 auto;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}

.chip-filtro:active {
  background: var(--color-surface-pressed);
}

/* U7/R9 — bolinha de cor no chip de categoria do filtro: mesmo par cor↔categoria de
   .chip-categoria__ponto (form de lançar), mas sem margin própria — o respiro já vem do
   `gap: var(--space-1)` do .chip-filtro, então uma margin duplicaria o espaço. Aditiva em
   toda largura (inclusive mobile): o moc só mostrou o desktop, mas nada nele é exclusivo
   de largura, e repetir a cor aqui é a mesma leitura cor↔categoria do resto do app. */
.chip-filtro__ponto {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--cor-categoria, var(--color-muted));
  flex-shrink: 0;
}

/* Chip como <input type="radio">/checkbox nativo dentro do <label>: estado ativo vem do
   próprio input (teclado, leitor de tela e submit de formulário grátis, sem JS de estado) —
   mesmo padrão de .chip-categoria. Fundo marinho comunica "ativo" sem depender só de cor
   (o texto do chip muda de cor junto, e o input mantém foco visível). */
.chip-filtro:has(input:checked) {
  background: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-on-brand);
}

.chip-filtro:has(input:focus-visible) {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Stepper do onboarding: mesmo chip, mas navegação (link pra outra etapa, não seleção
   de filtro), então o estado ativo vem de aria-current em vez de input:checked. */
.chip-filtro[aria-current="step"] {
  background: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-on-brand);
}

/* Barra proporcional da série mensal: CSS puro, sem biblioteca de gráfico —
   a leitura que importa é "qual mês pesa mais", e para isso largura basta. */
.barra {
  display: block;
  height: 0.6rem;
  width: var(--proporcao, 0%);
  min-width: 2px;
  border-radius: var(--radius-full);
  /* U12 — a barra usa a cor da categoria quando informada (mesma da bolinha e do ícone),
     e cai no marinho da marca onde não há categoria (ex.: gráfico de competências). */
  background: var(--cor-categoria, var(--color-brand));
}

.tabela-relatorio tfoot th,
.tabela-relatorio tfoot td {
  border-top: 2px solid var(--color-hairline-strong);
  font-weight: var(--weight-medium);
}

/* Desktop: a coluna de 480px é a medida certa para lançar no celular, mas espreme as
   tabelas de relatório. Alargar aqui atende os dois usos sem uma segunda folha. */
@media (min-width: 760px) {
  .page {
    max-width: 1180px;
  }
}

/* ============================================================
   Formulário de lançamento: agrupamento e revelação progressiva
   ============================================================ */

/* Nove campos seguidos sem agrupamento viram uma parede. Três blocos curtos
   ("Quanto", "O que", "Como foi pago") deixam o formulário legível de relance. */
.grupo-campos {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}

.grupo-campos__titulo {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0;
  margin-bottom: var(--space-1);
}

/* minmax(0, 1fr), não 1fr: a coluna de grid tem min-width auto por padrão, e o
   <input type="month"> nativo tem largura mínima intrínseca grande. Com 1fr puro, as duas
   colunas se recusavam a encolher e empurravam a página inteira para 537px num viewport
   de 375 — a tela mais usada do app rolando na horizontal. */
.dupla-campos {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-3);
}

.dupla-campos input {
  min-width: 0;
  width: 100%;
}

.rotulo-grupo {
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  color: var(--color-ink-soft);
}

.opcao-pagamento {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  font-size: var(--text-body);
  color: var(--color-ink-soft);
  cursor: pointer;
}

/* O cartão só existe depois de escolher "No cartão". Antes disso ele era um campo que
   parecia opcional, ficava vazio, e o lançamento só falhava no fim. */
.campo-cartao {
  display: none;
  padding-left: var(--space-6);
}

.escolha-pagamento:has(#forma_cartao:checked) .campo-cartao {
  display: block;
}

/* Botão de largura total pode quebrar: "Acertar tudo com <apelido>" carrega a parte local
   do e-mail, que não tem limite de tamanho. Com nowrap ele vazaria a caixa e, em apelido
   longo, a página inteira rolaria na horizontal. */
.btn--bloco {
  width: 100%;
  white-space: normal;
}

/* Botão desabilitado precisa PARECER desabilitado: sem isto o usuário toca num botão de
   aparência normal e conclui que o app travou. */
.btn:disabled,
.btn[disabled],
input[type="submit"]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Linha de lançamento: o toque vale na linha inteira, não só no link "Editar", e a altura
   não muda por causa do badge. */
.linha-lancamento {
  position: relative;
  align-items: flex-start;
  flex-wrap: wrap;
}

.linha-lancamento .field {
  flex: 1 1 100%;
  min-width: 0;
}

.linha-lancamento__valor {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-1);
  margin-left: auto;
}

.linha-lancamento .link-linha::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* Os botões reais da linha precisam ficar acima da área de toque estendida. */
.linha-lancamento form,
.linha-lancamento .btn:not(.link-linha) {
  position: relative;
  z-index: 1;
}

.linha-lancamento__info {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  align-items: flex-start;
  min-width: 0;
}

.linha-lancamento__acao {
  display: flex;
  align-items: center;
  color: var(--color-muted);
  min-height: var(--tap-min);
}

/* Lista de navegação das configurações: padrão de app (rótulo à esquerda, contagem e
   chevron à direita), no lugar de cards de estatística que ninguém consultava. */
.lista-config {
  background: var(--color-canvas);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  margin-bottom: var(--space-6);
}

.lista-config li + li {
  border-top: 1px solid var(--color-hairline);
}

.linha-config {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--row-min);
  padding: var(--space-3) var(--space-4);
  color: var(--color-ink);
  font-weight: var(--weight-medium);
  transition: background var(--duration-fast) var(--ease-standard);
}

.linha-config:active {
  background: var(--color-surface-pressed);
}

.linha-config__meta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-muted);
  font-weight: var(--weight-regular);
  font-variant-numeric: tabular-nums;
}

/* Valor de compra estornada: riscado, sem cor de alerta — não é erro, é registro anulado. */
.valor-monetario--estornado {
  color: var(--color-status-estornado-fg);
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
}

/* Aviso colado no botão de salvar: quando ele trava, é para cá que a pessoa olha. */
.status-lancamento:empty {
  display: none;
}

/* Glossário curto na tela de configurações. Os três termos que o produto não consegue
   evitar, em <details> nativo: fechado por padrão, a tela deixa de ser um manual. */
.ajuda {
  margin: var(--space-8) 0;
}

.ajuda__item {
  border-bottom: 1px solid var(--color-hairline);
}

.ajuda__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: var(--tap-min);
  padding: var(--space-3) 0;
  font-weight: var(--weight-medium);
  color: var(--color-ink);
  cursor: pointer;
  list-style: none;
}

.ajuda__item summary::-webkit-details-marker {
  display: none;
}

.ajuda__item summary::after {
  content: "+";
  color: var(--color-muted);
  font-size: var(--text-title);
  line-height: 1;
}

.ajuda__item[open] summary::after {
  content: "–";
}

.ajuda__item p {
  color: var(--color-body);
  padding-bottom: var(--space-4);
}

/* O preview nasce vazio e só chega ~300ms depois. Sem altura reservada, ele empurra os
   campos seguintes para baixo bem na hora em que o dedo já está descendo para tocá-los. */
#preview_parcelas {
  display: block;
  min-height: 7rem;
}

/* A bottom-nav é fixa, então "rolar até o elemento" pode parar com ele embaixo dela —
   tanto no scroll do navegador (âncora, foco por teclado) quanto no do Selenium, que
   passava a clicar na nav em vez do botão. scroll-margin faz a rolagem parar antes. */
a,
button,
input,
select,
textarea,
[tabindex] {
  scroll-margin-bottom: calc(var(--bottom-nav-espaco) + var(--space-8) + env(safe-area-inset-bottom, 0px));
  scroll-margin-top: var(--space-6);
}

/* Avatar: foto se houver, iniciais como fallback — tokens do DESIGN.md, sem lib. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--color-accent-subtle-bg, var(--color-surface-2));
  color: var(--color-accent, var(--color-ink));
  font-weight: var(--weight-medium);
  font-size: var(--text-caption, 0.8rem);
  overflow: hidden;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar--lg {
  width: 64px;
  height: 64px;
  font-size: 1.25rem;
}

/* Linha de perfil no menu Você: avatar + nome/e-mail empilhados, chevron à direita. */
.linha-config--perfil {
  justify-content: flex-start;
}

.linha-config__perfil-texto {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.linha-config__perfil-texto .muted {
  font-weight: var(--weight-regular);
  font-size: var(--text-caption, 0.8rem);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.field--avatar {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* Sem .variant (ver plano do comprovante): a imagem original é limitada por CSS. */
.comprovante-atual__preview {
  display: block;
  max-width: 240px;
  max-height: 240px;
  border-radius: var(--radius-md);
}

/* Segunda saída da conferência de um pendente do Telegram, fora do <form> de lançar.
   Só espaçamento: o botão já é o ghost padrão. */
.acoes-descartar-telegram {
  margin-top: var(--space-3);
}

/* Miniatura da foto que chegou pelo Telegram: menor que o preview do formulário — aqui
   é uma lista, e a imagem grande fica a um toque de distância. */
.telegram-pendente__foto {
  display: block;
  max-width: 140px;
  max-height: 140px;
  margin: var(--space-2) 0;
  border-radius: var(--radius-md);
}

.comprovante-atual__acoes {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* U17 — botão nativo de arquivo estilizado no lugar (não escondido): mais acessível que
   esconder o input e falsear um <label>. Segue a linguagem do .btn--ghost. */
.field--avatar input[type="file"] {
  font-size: var(--text-label);
  color: var(--color-muted);
}

.field--avatar input[type="file"]::file-selector-button {
  margin-right: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-muted);
  border-radius: var(--radius-full);
  background: var(--color-canvas);
  color: var(--color-ink);
  font: inherit;
  font-weight: var(--weight-medium);
  cursor: pointer;
}

.field--avatar input[type="file"]::file-selector-button:hover {
  background: var(--color-surface);
}

/* ============================================================
   Redesign — extrato bancário (U1: fundação)
   ============================================================ */

/* Badge neutro: arquivado, inativo. Cor cinza — nunca vermelho, que é reservado a
   "estornado"/erro. */
.badge-status--neutro {
  background: var(--color-status-neutro-bg);
  color: var(--color-status-neutro-fg);
}

/* Ação do plano pago mostrada no lugar real dela (R5): parece a ação, não é a ação — o
   selo ao lado é o que separa as duas. Sem sublinhado de link: numa lista de 30 acertos,
   30 sublinhados viram ruído (R6). */
.selo-plano {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-muted);
  text-decoration: none;
  font-size: var(--text-caption);
}
.selo-plano:hover { color: var(--color-brand); }

/* .badge-plano reusa a forma de .badge-status--recomendado (mesma pílula marinho
   sólida) — só o svg e o encolhimento em uso compacto (:has) são novos daqui. */
.badge-plano svg { width: .85em; height: .85em; }
.badge-plano:has(.visually-hidden) { padding: var(--space-1); }

/* Card do dia no extrato: cabeçalho (data por extenso + subtotal) separado do corpo por
   --color-divisor, itens dentro do mesmo card separados pela mesma linha fina — não
   card-por-lançamento, que é pesado demais numa lista de 15+ itens por mês. */
.card-dia {
  background: var(--color-canvas);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  margin-bottom: var(--space-4);
}

/* <summary> nativo: remove o triângulo default e vira área de toque do acordeão. */
.card-dia__cabecalho {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-divisor);
  cursor: pointer;
  list-style: none;
}

.card-dia__cabecalho::-webkit-details-marker {
  display: none;
}

/* Fechado: o cabeçalho é a única borda visível — sem o divisor pendurado embaixo. */
.card-dia:not([open]) .card-dia__cabecalho {
  border-bottom: none;
}

.card-dia__data {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* R6 — duas versões do texto da data: curta ("18 MAI"/"HOJE", herda o uppercase acima)
   por padrão, por extenso ("Sex, 24 de julho") só a partir do piso de 900px — mesmo
   piso em que o extrato ganha o grid de colunas (E4/U6). A versão por extenso cancela o
   uppercase herdado: ela já vem com a caixa certa do `l()` + `.capitalize` no ERB, e o
   moc mostra "Sex, 24 de julho" em vez de "SEX, 24 DE JULHO". */
.card-dia__data-longa { display: none; }

@media (min-width: 900px) {
  .card-dia__data-curta { display: none; }
  .card-dia__data-longa {
    display: inline;
    text-transform: none;
  }
}

.card-dia__subtotal {
  font-size: var(--text-value-md);
  font-weight: var(--weight-medium);
}

.card-dia__itens > * + * {
  border-top: 1px solid var(--color-divisor);
}

/* Plano "Agrupar Lançamentos Futuros" — R2 pede o bucket "dimmed" quando fechado. A
   primeira tentativa usava opacity: 0.45 (mesmo valor de .btn:disabled) no container
   inteiro, mas opacity multiplica TODO texto por baixo, inclusive --color-muted (que já
   está perto do piso — 4.8:1 sem diluir) — o resultado caía abaixo do mínimo de contraste
   AA de 4.5:1 (achado do ce-code-review). Em vez de opacity, o efeito vem só de cor: o
   subtotal usa --color-muted + peso regular em vez de --color-ink + negrito, e o
   cabeçalho ganha um fundo --color-surface — nada de texto diluído, só menos ênfase
   tipográfica. Escopado a :not([open]): ao expandir, o cabeçalho volta ao peso/cor normal
   dos demais dias — os lançamentos reais por dentro (KTD3) nunca são afetados por isto. */
.card-dia--futuros:not([open]) .card-dia__cabecalho {
  background: var(--color-surface);
}

.card-dia--futuros:not([open]) .card-dia__subtotal {
  color: var(--color-muted);
  font-weight: var(--weight-regular);
}

/* Sub-cards de dia dentro do bucket já são .card-dia com seu próprio margin-bottom —
   gap aqui dobraria o espaçamento entre eles. */
.card-dia-futuros__dias {
  padding: var(--space-3) var(--space-4);
}

.card-dia-futuros__dias .card-dia:last-child {
  margin-bottom: 0;
}

/* Grupo de pendentes por quem pagou: mesma estrutura visual de .card-dia (canvas, sombra,
   summary sem marcador), mas SEM a regra ::details-content de forçar-aberto no desktop
   (DESIGN.md:311, aplicada a .card-dia e .filtros-refino). Desvio consciente (KTD2 do
   plano de agrupamento de pendentes): naquele padrão o colapso existe pra economizar
   espaço em tela estreita; aqui o colapso É a função pedida, e o pedido partiu de um
   monitor — forçar aberto no desktop devolveria a mesma parede de rolagem que motivou
   o trabalho. */
.grupo-pendente {
  background: var(--color-canvas);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  margin-bottom: var(--space-4);
}

.grupo-pendente__cabecalho {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-divisor);
  cursor: pointer;
  list-style: none;
}

/* Nome ocupa a folga: empurra ação, valor e chevron pra direita e mantém a coluna de
   valores alinhada entre os grupos. */
.grupo-pendente__cabecalho h2 {
  flex: 1;
  min-width: 0;
}

/* Fica visível mesmo com o grupo colapsado (é o pedido) — por isso mora dentro do
   <summary> em vez de ao lado da lista. stopPropagation no botão evita que o clique
   também dispare o toggle do <details>. */
.grupo-pendente__acertar-tudo {
  margin: 0;
  flex-shrink: 0;
}

/* Em tela estreita nome + ação + valor não cabem numa linha: a ação quebra pra segunda
   linha, largura cheia. */
@media (max-width: 640px) {
  .grupo-pendente__acertar-tudo {
    order: 1;
    flex-basis: 100%;
  }

  .grupo-pendente__acertar-tudo .btn {
    width: 100%;
  }
}

.grupo-pendente__cabecalho::-webkit-details-marker {
  display: none;
}

.grupo-pendente:not([open]) .grupo-pendente__cabecalho {
  border-bottom: none;
}

.grupo-pendente__cabecalho::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--color-muted);
  border-bottom: 2px solid var(--color-muted);
  transform: rotate(45deg);
  transition: transform var(--duration-fast) var(--ease-standard);
  flex-shrink: 0;
}

.grupo-pendente[open] .grupo-pendente__cabecalho::after {
  transform: rotate(-135deg);
}

.grupo-pendente__cabecalho h2 {
  font-size: var(--text-value-md);
  font-weight: var(--weight-medium);
}

.grupo-pendente .lista-lancamentos > * + * {
  border-top: 1px solid var(--color-divisor);
}

/* Linha de lançamento no extrato: ícone circular da categoria, título forte, meta
   discreta, valor à direita. Deliberadamente SEM borda-esquerda colorida (decisão do
   usuário) — a cor já está no ícone, uma segunda faixa colorida ao lado é redundante e
   deixa a lista com cara de planilha de novo. */
.item-extrato {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: var(--space-2);
  gap: var(--space-3);
  min-height: var(--row-min);
  padding: var(--space-3) var(--space-4);
  position: relative;
}

/* Estornada tem badge + botão "Reverter" além do valor: em 375px isso não cabe numa
   linha só. wrap evita rolagem horizontal, e o botão fica sozinho na 2ª linha, ocupando
   a largura toda em vez de espremido ao lado do badge. */
.item-extrato > .btn {
  flex-basis: 100%;
}

.item-extrato__icone {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  /* Cor da categoria chega via --cor-categoria inline no elemento; fallback neutro
     evita ícone invisível quando a categoria não define cor ainda. */
  background: color-mix(in srgb, var(--cor-categoria, var(--color-muted)) 16%, var(--color-canvas));
  color: var(--cor-categoria, var(--color-muted));
}

.item-extrato__info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.item-extrato__titulo {
  font-weight: var(--weight-medium);
  color: var(--color-ink);
  /* U16 — o nome do lançamento é o dado que a pessoa veio ver: ganha até duas linhas antes
     de truncar, em vez de uma só. A seta ao lado é enfeite e encolhe primeiro. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* U6/R6a: local é opcional (a maioria das compras existentes não tem). Uma linha só,
   truncada — não pode empurrar a altura de `.item-extrato__info` mesmo quando longo. */
.item-extrato__local {
  font-size: var(--text-label);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Busca textual (U4 do plano) — resultado cruza meses, sem cabeçalho de dia pra herdar a
   data; cada linha mostra a própria. Mesmo tom discreto de __local, acima do título. */
.item-extrato__data {
  font-size: var(--text-label);
  color: var(--color-muted);
}

/* R6 pede negrito explícito — <mark> sozinho só dá fundo do navegador, sem peso de fonte. */
.item-extrato__titulo mark,
.item-extrato__local mark {
  background: none;
  color: inherit;
  font-weight: var(--weight-bold);
}

/* Campo de busca: mesma largura das linhas de filtro abaixo dele, sem chip nem rótulo —
   é o único controle da barra que não é um radio/checkbox. */
.filtros-extrato__busca {
  margin-bottom: var(--space-3);
}

.filtros-extrato__busca input[type="search"] {
  width: 100%;
  max-width: 28rem;
}

/* U6 — `__meta` virou irmão de `__info` na ordem do DOM (entre `__info` e `__valor`,
   ver diagrama "High-Level Technical Design" do plano) — é o que permite ao cabeçalho
   de colunas alinhar em desktop. Nessa posição, um `flex-basis: 100%` sozinho empurraria
   o `__valor` para uma TERCEIRA linha (depois dos metadados) em vez de deixá-lo ao lado
   do título: por isso a quebra abaixo do piso é `order: 1` (reordenação visual, não
   movimento de DOM) + `flex-basis: 100%`, com ícone/info/valor implícitos em `order: 0`.
   Regra BASE (sem @media): é o comportamento mobile por padrão; o bloco de 900px abaixo
   cancela os dois (`order: 0`, `flex: none`) para virar coluna no grid. */
.item-extrato__meta {
  font-size: var(--text-label);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  order: 1;
  flex-basis: 100%;
}

/* Separador dos metadados: saiu do texto do ERB para o CSS quando os spans
   viraram irmãos (E4). Em mobile lê exatamente como antes:
   "Limpeza · paga 31/07 · 3x" — e sem `·` órfão quando só há categoria,
   porque o ::before só existe a partir do segundo span. */
.item-extrato__meta-item + .item-extrato__meta-item::before { content: "· "; }

.item-extrato__valor {
  margin-left: auto;
  flex-shrink: 0;
}

/* No celular, um badge de status ao lado do valor comia metade do nome do lançamento
   (149px caíam para 74px, e "Café e água para hóspede" virava "Café e água par…").
   O nome e o valor mandam na primeira linha; o status desce para a segunda. */
@media (max-width: 460px) {
  /* quebra de linha em vez de flex-basis no badge: com basis 100% ele esticaria os 263px
     inteiros e leria como faixa, não como etiqueta */
  .item-extrato:has(.badge-status)::after {
    content: "";
    flex-basis: 100%;
    order: 1;
  }
  .item-extrato .badge-status {
    order: 2;
    margin-left: calc(36px + var(--space-3)); /* alinha com o texto, não com o ícone */
  }
  .item-extrato > .btn { order: 2; }
}

/* Estornada: riscada + opacidade reduzida — é um registro anulado, não um erro do sistema,
   então nada de vermelho gritante aqui (esse fica pro badge de status quando precisar). */
.item-extrato--estornada .item-extrato__titulo,
.item-extrato--estornada .item-extrato__valor {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
}

.item-extrato--estornada {
  opacity: 0.6;
}

/* Link "Acertar ›" do item pendente: acento âmbar (mesma cor do badge pendente), não
   marinho — reserva o marinho pra ações primárias e mantém a leitura de "isso ainda
   precisa de atenção" consistente com o resto do app. */
/* Alvo de toque estendido pra linha inteira, mesmo padrão de .linha-lancamento. */
.item-extrato .link-linha::after {
  content: "";
  position: absolute;
  inset: 0;
}

.item-extrato form,
.item-extrato .btn:not(.link-linha),
.item-extrato .selo-plano {
  position: relative;
  z-index: 1;
}

/* ===== Bottom-nav: + central em destaque (FAB) ===== */
/* O botão de lançar é a ação mais frequente do app — precisa se destacar dos outros 3
   itens de navegação, não competir em igualdade de peso visual com eles. */

.bottom-nav__item--fab {
  position: relative;
}

.bottom-nav__item--fab svg {
  display: none;
}

.bottom-nav__item--fab::before {
  content: "";
  position: absolute;
  top: -18px;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--color-brand);
  box-shadow: var(--shadow-nav);
}

.bottom-nav__item--fab::after {
  content: "+";
  position: absolute;
  top: -18px;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.75rem;
  line-height: 1;
  font-weight: var(--weight-regular);
  color: var(--color-on-brand);
}

/* Some com o rótulo de texto: o círculo já comunica a ação primária sozinho. */
.bottom-nav__item--fab {
  color: transparent;
}

.bottom-nav__item--fab:focus-visible {
  outline: none;
}

.bottom-nav__item--fab:focus-visible::before {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Você já está nesta aba: o círculo ganha um anel e sobe um pouco, sinalizando
   "selecionado" sem depender do rótulo (que fica escondido no FAB). */
.bottom-nav__item--fab[aria-current="page"]::before {
  box-shadow: var(--shadow-nav), 0 0 0 4px var(--color-accent-subtle-bg);
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* 900px: a barra flutuante de polegar vira rail fixo à esquerda. Canvas + hairline,
   nunca marinho — marinho é reservado à faixa-herói e ao botão primário (DESIGN.md
   "Identidade do onboarding"). Uma elevação só: aqui a separação é a hairline, não
   --shadow-nav. */
@media (min-width: 900px) {
  .bottom-nav {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: auto;
    width: 240px;
    flex-direction: column;
    justify-content: flex-start;
    background: var(--color-canvas);
    box-shadow: none;
    border-radius: 0;
    border-inline-end: 1px solid var(--color-hairline);
    padding: var(--space-4) var(--space-2);
    gap: var(--space-1);
  }

  /* Assinatura de marca no topo do rail (DESIGN.md "Camada desktop") — mesmo
     corte reto do canto que o hero do onboarding usa como gesto de identidade. */
  .bottom-nav .wordmark {
    display: block;
    margin: 0 var(--space-2) var(--space-4);
    border-end-end-radius: 0;
  }

  /* Rodapé com a empresa corrente: fixo na base do rail, separado por hairline. */
  .bottom-nav__empresa {
    display: block;
    margin: auto var(--space-2) 0;
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-hairline);
    color: var(--color-muted);
    font-size: var(--text-caption);
  }

  /* O rail substitui o par de abas: Lançamentos ganhou destino próprio (KTD4). */
  .segmented {
    display: none;
  }

  .bottom-nav__item {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    min-height: var(--tap-min);
    /* Canto inferior direito reto: assinatura de marca que ecoa o gesto do
       onboarding (DESIGN.md) no elemento persistente da UI autenticada. */
    border-radius: 0;
  }

  /* O "+" some como pseudo-elemento circular flutuante e vira botão primário
     marinho em pílula no topo do rail — resolve D3 (nunca mais sobre o conteúdo). */
  .bottom-nav__item--fab {
    color: var(--color-on-brand);
    background: var(--color-brand);
    border-radius: var(--radius-full);
    justify-content: center;
    margin-bottom: var(--space-2);
  }

  .bottom-nav__item--fab::before,
  .bottom-nav__item--fab::after {
    content: none;
  }

  .bottom-nav__item--fab:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }

  .bottom-nav__item--fab[aria-current="page"] {
    box-shadow: 0 0 0 4px var(--color-accent-subtle-bg);
  }

  .page {
    margin-inline-start: 240px;
    /* O token já reserva a folga da barra flutuante; no rail não há barra
       flutuante nem safe-area inferior a compensar. */
    --bottom-nav-espaco: 0;
  }

  /* E2/U2 — o campo tem a largura do dado (D4). rem, não ch: ch varia por
     fonte e os testes asseguram px. 12rem = 192px. A ordem importa: a regra
     de type="number" vem DEPOIS para vencer o empate de especificidade nos
     dias de cartão (que têm number + inputmode). */
  .field input[inputmode="numeric"] { max-width: 12rem; }  /* valor, data DD/MM/AAAA */
  .field input[type="number"] { max-width: 8rem; }         /* parcelas, dias de cartão */
  .field input[type="email"],
  .field input[type="password"] { max-width: 28rem; }

  /* Item, "onde", nome: largura do grupo — e o grupo ganha teto de leitura.
     Vale para todos os forms do app (lançar, perfil, empresa, convite…). */
  .form { max-width: 40rem; }

  /* Botão de 690px não parece botão. fit-content + mínimo confortável.
     Dentro de .empty-state (align-items: center) ele centraliza sozinho.
     .btn--pilula fica de fora: a pílula já é largura de conteúdo por
     definição, e um min-width a engordaria (onboarding, salvar cartão). */
  .btn--primary:not(.btn--pilula),
  .btn--bloco { width: fit-content; min-width: 12rem; }

  /* E2/U3 — segmented à largura do conteúdo (D5). O padding-inline devolve
     respiro às pílulas, que com width: max-content ficariam coladas no rótulo. */
  .segmented { width: max-content; }
  .segmented__item { flex: none; padding-inline: var(--space-6); }

  /* E2/U3 — chips quebram linha em vez de rolar (D6). A máscara de fade da
     variante --rolavel deixa de fazer sentido sem rolagem. */
  .chips-filtro { flex-wrap: wrap; overflow-x: visible; }
  .chips-filtro--rolavel { mask-image: none; -webkit-mask-image: none; }

  /* E5 — ritmo vertical do form em desktop: 2rem entre grupos era respiro de
     rolagem de celular; sentado com mouse, 1.5rem mantém a separação sem
     esticar nove grupos além do necessário. min-height não muda (KTD3). */
  .grupo-campos { margin-bottom: var(--space-6); }

  /* Atalhos de data e campo na mesma linha: no celular empilham (polegar),
     no desktop a pergunta "quando?" se responde num relance só.
     O seletor é o div.field que carrega o controller data-atalho. */
  .field[data-controller~="data-atalho"] {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--space-3);
  }
  .field[data-controller~="data-atalho"] .erro-campo { flex-basis: 100%; }

  /* U8 — listas de navegação e afins com teto de leitura. Linha tocável de
     1100px é um braço de mouse; 40rem casa com o teto dos forms (E2). A
     tabela de fechamento (U12) entra na mesma regra — mesmo teto, mesma
     razão, sem duplicar a declaração. */
  .convite-telegram,
  .lista-config,
  .lista-editavel,
  .secao-arquivados,
  .rodape-legal,
  .tabela-relatorio-wrap { max-width: 40rem; }

  /* U10 — membros e convites: as regras de tela larga moram junto do bloco base
     de .linha-pessoa (mais adiante no arquivo). Mesma especificidade, e aqui
     perderiam a cascata por ordem de origem. */

  /* U11 — /cartoes vira grade; cartão aberto para edição toma a linha
     inteira (o form de 40rem não cabe em 230px). :has já é padrão do app.
     flex-direction da base fica inerte sozinho: só se aplica a flex container.
     Seletor com as duas classes (0,2,0): .cartoes-mini sozinho define
     display: flex mais adiante no arquivo, mesma especificidade de um
     seletor de uma classe só — perderia a cascata por ordem de origem. */
  .cartoes-mini.cartoes-mini--editaveis {
    display: grid;
    grid-template-columns: repeat(auto-fill, 230px);
    gap: var(--space-4);
    align-items: start;
  }
  .cartoes-mini--editaveis > li:has(.cartao-editavel[open]) { grid-column: 1 / -1; }

  /* .cartoes-mini .cartao-fisico (base, "dois por linha" do celular) fixa
     width: calc(50% - ...) com especificidade 0,2,0 e não é escopada a
     max-width — sem este override o cartão salvo fica com metade da coluna
     da grade (109px) em vez dos 230px do track. 0,3,0 vence. */
  .cartoes-mini.cartoes-mini--editaveis .cartao-fisico { width: 230px; }

  /* U12 — fechamento: totais lado a lado (teto de leitura da tabela já está
     no grupo U8). O overflow-x do wrapper segue como rede de segurança. */
  .fechamento-totais { display: flex; gap: var(--space-10); }

  /* E3 — descidas de 1180px pra 900px (KTD5 revisto): estas regras não dependem
     da coluna do .resumo-grid, a única do grupo que precisa mesmo de 1180px pra
     caber sem estourar (medido: em 980px cabe exato, em 900px já estoura ~23px
     de scroll horizontal). Ficavam presas ao mesmo teto por conveniência, não
     por necessidade — e isso deixava o "modo desktop" do Chrome (980px, teto
     forçado independente da largura real da tela) numa zona morta: rail visível,
     conteúdo do extrato ainda sem se adaptar. */

  /* Faixa de filtros: uma linha por dimensão (Status, Categoria, Cartão), cada uma em
     largura cheia com o rótulo numa calha fixa à esquerda e os chips ocupando o resto.
     Substituiu o grid de duas colunas (1fr para os quatro chips de status, 24rem para as
     N categorias): a coluna da esquerda ficava vazia por construção e o desequilíbrio
     crescia a cada categoria nova da empresa — 8 categorias já empilhavam 4 fileiras num
     terço da largura enquanto o outro terço ficava em branco. Em largura cheia as mesmas
     8 categorias cabem em uma ou duas fileiras.
     O <form> do filtro só carrega inputs hidden (os chips se associam a ele pelo atributo
     form=) e não pode ocupar linha nenhuma; display:none não afeta a submissão
     (requestSubmit e inputs hidden funcionam igual). */
  .filtros-extrato > form { display: none; }

  .filtros-extrato__linha {
    display: flex;
    align-items: baseline;
    gap: var(--space-6);
  }
  .filtros-extrato__linha + .filtros-extrato__linha,
  .filtros-extrato__grupo--refino { margin-top: var(--space-4); }

  /* Título de grupo só existe a partir daqui — ver regra base (display:none) junto de
     .filtros-refino, e comentário lá do porquê. Largura fixa: é a calha que alinha
     STATUS, CATEGORIA e CARTÃO no mesmo x e mantém o começo dos chips numa vertical só. */
  .filtros-extrato__titulo {
    display: block;
    flex: 0 0 6rem;
    font-size: var(--text-caption);
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--color-muted);
    font-weight: var(--weight-medium);
    margin-bottom: 0;
  }

  /* O respiro vertical entre linhas vem do margin-top acima; dentro da linha o
     margin-bottom do .chips-filtro (pensado para o empilhamento mobile) só desalinharia
     a calha. min-width:0 deixa a faixa de chips encolher em vez de estourar a linha. */
  .filtros-extrato__linha .chips-filtro {
    flex: 1 1 auto;
    min-width: 0;
    margin-bottom: 0;
  }

  /* Fim da linha de status: ancorado à direita, longe dos chips. */
  .filtros-extrato__limpar {
    display: inline-block;
    flex: 0 0 auto;
    font-size: var(--text-label);
    white-space: nowrap;
  }

  /* E4 — metadados em colunas (KTD4). O grid é NA META, não no item: toda
     meta começa no mesmo x (ícone de 36px + gap são constantes), então o
     mesmo template de larguras fixas alinha as colunas entre todas as linhas
     do dia — inclusive entre linhas heterogêneas, porque cada tipo de
     metadado é CRAVADO na sua coluna por grid-column (metadado ausente deixa
     o trilho vazio, não puxa o vizinho). Sem zebra, sem borda por linha:
     o hairline do .card-dia continua a única separação. */
  /* U6 — `__meta` virou irmão de `__info`, com colunas de largura fixa (528px de soma).
     Acima do piso, `icone + meta + valor + gaps` (~692px) chega perto da largura de
     conteúdo disponível (~693-725px, rail de 240px descontado) — sem margem pra
     `flex-wrap` (herdado da regra base, pensado pro empilhamento mobile) continuar
     ativo: o algoritmo de quebra decide por LINHA usando o tamanho hipotético (basis)
     de cada item, ANTES do encolhimento — então mesmo com `__meta` encolhível
     (`minmax(0, 11rem)` na coluna 1), o `flex-wrap` empurrava `__valor` pra uma
     segunda linha (medido em 980px) só por não caber no basis não-encolhido. Igual à
     regressão nomeada no passo 3 do plano pro mobile, só que em desktop. `nowrap`
     força o encolhimento de verdade a acontecer numa linha só — a coluna 1 cede
     primeiro (é a única flexível), o resto (colunas fixas + ícone + valor) não muda. */
  .item-extrato { flex-wrap: nowrap; }

  .item-extrato__meta {
    /* Cancela a regra base de mobile (U6: `order: 1; flex-basis: 100%`) — acima do
       piso `__meta` volta a ser mais uma coluna do flex, na ordem natural do DOM.
       `flex-basis: auto` (não `flex: none`/shorthand): o shorthand zera também
       `flex-shrink`, e essa coluna precisa continuar encolhível (ver comentário
       acima, em `.item-extrato`). `flex-shrink` continua no inicial (1), nunca
       tocado por nenhuma regra deste arquivo. */
    order: 0;
    flex-basis: auto;
    display: grid;
    /* U4: coluna 3 (parcelas) alargada de 3rem pra 6.5rem — mesma largura da coluna 2
       (pagamento) — pra caber "a cada 2 meses" (rótulo mais longo de Recorrencia#rotulo)
       sem cortar em reticências. A largura extra sai da coluna 1 (11rem → 7.5rem), a
       única de fato flexível (minmax(_, …)); o piso abaixo sobe na mesma medida.
       O mínimo da coluna 1 é 1.5rem, não 0: um mínimo de 0 combinado com o min-width
       abaixo (que soma só as colunas fixas) fazia a categoria sumir por inteiro —
       largura 0, sem nem reticências — sempre que __meta encolhia até o piso (medido
       ao vivo em 900px: rectWidth 0 nas 6 linhas do extrato). 1.5rem cabe pelo menos
       "M…" truncado — visível, mesmo que curto — em vez de invisível.
       Colunas 4 (quem, 8rem→6rem) e 5 (comprovante, 1.5rem→1rem) também cederam: um piso
       de categoria em 3rem não fechava a conta em 900px — sobrava overflow de linha
       inteira mesmo com __info já no próprio piso (0), medido ao vivo: 59px em TODA
       linha do extrato, não só nas de série. As duas colunas cedidas continuam com texto
       truncado por reticências como sempre ("Rafael Souza" ainda cabe inteiro em 6rem; o
       glifo de comprovante é 1 ícone, cabe em 1rem) — sem mudança visível de
       comportamento, só menos folga. Confirmado ao vivo (900px, linha comum e linha com
       comprovante anexado): overflow 0, "M…"/nome/glifo todos visíveis. Colunas 2 e 3
       (pagamento/parcelas) não cederam — guardam texto real, sem folga pra dar. */
    grid-template-columns: minmax(1.5rem, 7.5rem) 6.5rem 6.5rem 6rem 1rem;
    column-gap: var(--space-3);
    /* Piso do encolhimento de __meta: com a 1ª coluna no próprio mínimo (1.5rem), as 4
       colunas fixas + a categoria no piso + 4 gaps são o menor tamanho onde ainda sobra
       alguma coisa pra cada coluna (calc() em vez de um número solto: a soma acompanha
       sozinha se as colunas mudarem de novo). Sem este piso, um badge-status/botão
       (Reverter, Desfazer) que entra DEPOIS de __valor no flex força o shrink (padrão,
       sem flex-shrink aqui) a espremer a caixa de __meta além desse ponto; como as
       colunas fixas não encolhem, a coluna --pagamento vaza pra fora da caixa.
       min-width trava o piso e deixa o resto do aperto recair sobre __info (única
       coluna de fato flexível além da categoria). */
    min-width: calc(21.5rem + 4 * var(--space-3));
  }
  .item-extrato__meta-item {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Seletor idêntico ao da regra base (mesma especificidade: 2 classes + ::before)
     é o que faz esta anulação vencer por ordem de origem — um seletor mais fraco
     (ex.: só `.item-extrato__meta-item::before`) perde pra regra base e o "·"
     literal sobrevive dentro da coluna do grid. */
  .item-extrato__meta-item + .item-extrato__meta-item::before { content: none; } /* coluna substitui o · */
  .item-extrato__meta-item--categoria   { grid-column: 1; }
  .item-extrato__meta-item--pagamento   { grid-column: 2; }
  .item-extrato__meta-item--parcelas    { grid-column: 3; }
  .item-extrato__meta-item--quem        { grid-column: 4; }
  .item-extrato__meta-item--comprovante { grid-column: 5; }

  /* Valor em coluna de verdade: caixa de largura fixa alinhada à direita —
     sem isso o margin-left:auto alinha só a borda direita e o offsetLeft
     varia com o número de dígitos. 8rem cobre até R$ 999.999,99. */
  .item-extrato__valor { min-width: 8rem; text-align: right; }

  /* Badge de status genérico (Acertado/Estornado) some depois de __valor no
     flex, sem flex-shrink próprio: herda o encolhimento padrão e o texto
     quebra linha dentro do próprio badge. flex-shrink: 0 + nowrap tira ele da
     disputa por espaço — o aperto recai sobre __info, como já é o caso
     de __meta e __valor (ver comentário acima). */
  .item-extrato .badge-status { flex-shrink: 0; white-space: nowrap; }

  /* "Faltam R$ X" (e "Exclusão solicitada", mesma classe --parcial): pedido de
     design pra descer pra debaixo do valor total, não dividir a linha com ele.
     Sai do fluxo flex (senão reativa o wrap prematuro que o `nowrap` do
     `.item-extrato` existe pra evitar, ver comentário acima) e ancora no
     canto inferior direito — `right` na mesma distância do padding do `<li>`
     que o `text-align: right` de `__valor` já usa, então as duas bordas
     direitas alinham sem precisar medir a largura de nenhum dos dois. */
  .item-extrato:has(.badge-status--parcial) { padding-bottom: calc(var(--space-3) + 1.5rem); }
  .item-extrato .badge-status--parcial { position: absolute; right: var(--space-4); bottom: var(--space-2); }

  /* O chevron decorativo sai da linha em desktop: com ele (e com o gap do
     flex que ele arrasta mesmo vazio), o valor parava antes da borda e não
     alinhava com o subtotal do dia. O próprio <a> vira o overlay da linha —
     sai do fluxo flex (nenhum gap fantasma) e a linha inteira segue clicável
     e focável, com contorno de foco na linha inteira, melhor que no ícone
     minúsculo de antes. Os botões reais já têm z-index: 1 na regra base. */
  .item-extrato .link-linha { position: absolute; inset: 0; }
  .item-extrato .link-linha svg { display: none; }
  .item-extrato .link-linha:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: -2px;
  }

  /* Assinatura de marca: a faixa-herói perde o raio inferior direito em
     desktop (DESIGN.md, "Identidade do onboarding" / "Camada desktop"). */
  .hero { border-end-end-radius: 0; }

}

/* U2 — barra de página (Lançamentos): contexto+h1 à esquerda, mês e ação à
   direita, numa linha só, com hairline separando do conteúdo abaixo (DESIGN.md
   "Camada desktop"). Medido (KTD3), não presumido: a expectativa inicial era
   900px (mesmo piso do rail, sem coluna dupla envolvida), mas o pior caso real
   — mês não corrente ("Voltar ao mês atual" em vez de "Ver todos os meses") +
   nome de mês longo ("setembro de 2026") + "Baixar CSV" — só para de quebrar
   linha a partir de ~970px de viewport (teste descartável, técnica de
   docs/solutions/ui-bugs/breakpoint-zona-morta-980px-modo-desktop-chrome.md).
   980px é o próximo múltiplo redondo com folga, e não 1180px por conveniência:
   nenhum grid de duas colunas está envolvido aqui. Abaixo do piso a barra
   simplesmente quebra linha (flex-wrap) em vez de estourar — degrade seguro. */

/* U6 — o cabeçalho de colunas do extrato só existe acima do piso (regra base,
   fora do @media, pra vencer a cascata por especificidade igual); a regra que
   o mostra fica dentro do bloco de 980px abaixo. */
.cabecalho-extrato-colunas { display: none; }

@media (min-width: 980px) {
  .page__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4) var(--space-6);
    /* margin-top negativo cancela o padding-top do .page e padding-top devolve
       o respiro: sem isso, uma tira do padding do .page rola visível acima da
       barra sticky (o padding fica "atrás" da barra em vez de fazer parte dela). */
    margin-top: calc(-1 * var(--space-4));
    padding-top: var(--space-6);
    padding-bottom: var(--space-4);
    margin-bottom: var(--space-6);
    border-bottom: 1px solid var(--color-hairline);
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--color-surface);
  }

  .page__toolbar .page__header { margin-bottom: 0; }

  .page__toolbar__acoes {
    display: flex;
    align-items: center;
    gap: var(--space-3);
  }

  /* Busca textual (R8): `display: flex` acima empata em especificidade com a regra
     `[hidden]` do UA stylesheet e vence por vir depois — mesmo empate já documentado em
     `.field[hidden]`/`.btn[hidden]`/`img[hidden]`. Sem isto, esconder via JS/atributo
     não some o seletor de mês neste breakpoint. */
  .page__toolbar__acoes[hidden] {
    display: none;
  }

  /* O stepper perde o fundo marinho na barra de página: marinho fica reservado
     à faixa-herói do onboarding e ao botão primário (DESIGN.md "Identidade do
     onboarding"). Vira canvas + hairline, como qualquer outro controle da
     barra — a própria barra já assume o papel de "fixo no topo". */
  .page__toolbar .seletor-mes--sticky {
    position: static;
    background: var(--color-canvas);
    box-shadow: none;
    border: 1px solid var(--color-hairline);
    margin-bottom: 0;
  }
  .page__toolbar .seletor-mes--sticky .seletor-mes__seta { color: var(--color-ink-soft); }
  .page__toolbar .seletor-mes--sticky .seletor-mes__seta:hover,
  .page__toolbar .seletor-mes--sticky .seletor-mes__seta:active { background: var(--color-surface-2); }
  .page__toolbar .seletor-mes--sticky .seletor-mes__atual { color: var(--color-ink); }

  /* "Voltar ao mês atual" / "Ver todos os meses" vira mais um item da faixa de
     ações, não uma segunda linha embaixo do stepper — a margem negativa e a
     centralização da base (pensadas pro empilhamento mobile) não se aplicam aqui. */
  .page__toolbar .seletor-mes__acao {
    margin: 0;
    text-align: left;
  }

  /* U5 — inverso da regra base: acima do piso o botão da toolbar aparece e o link
     mobile do rodapé (mesmo destino) some. */
  .toolbar__fechamento { display: inline-flex; }
  .rodape-fechamento-mobile { display: none; }

  /* U6 — cabeçalho de colunas do extrato: nomeia as colunas que E4 já alinhou.
     Reusa as classes de `.item-extrato` (não reescreve larguras): `__icone`/`__info`
     ficam vazios só para empurrar `__meta` (que herda o grid de 900px acima) e
     `__valor` para o mesmo x das linhas reais — mesmo flex, mesmo padding
     horizontal, mesmo gap. Piso 980px, não 900: é o mesmo piso em que
     `.page__toolbar` vira sticky (acima); gatear o cabeçalho a 900px o deixaria
     sticky num intervalo (900-979px) onde a toolbar ainda não é sticky — ao rolar,
     a toolbar sai de vista e o cabeçalho, grudado a `top: 96px`, sobraria com um
     vão em branco no lugar dela. Medido (mesma receita do KTD3/zona morta): a
     `.page__toolbar` renderiza com 95.375px de altura em 1440×900 — 96px arredonda
     com folga de sub-pixel. */
  .cabecalho-extrato-colunas {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding-inline: var(--space-4);
    padding-block: 0 var(--space-2);
    font-size: var(--text-label);
    font-weight: var(--weight-medium);
    color: var(--color-muted);
    position: sticky;
    top: 96px;
    z-index: 4;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-hairline);
  }
  /* Placeholders vazios: só reservam a largura de `__icone`/`__info` pra empurrar
     `__meta`/`__valor` pro mesmo x das linhas reais. */
  .cabecalho-extrato-colunas .item-extrato__icone { visibility: hidden; }
}

/* U7 — rodapé de totais do extrato: bloco estático no fim da lista por padrão (regra
   base, fora do @media). Acima do piso vira sticky no fim da tela — mesmo piso de
   980px da toolbar/cabeçalho (U2/U6), medido para esta mesma página; abaixo dele o
   mobile não ganha barra fixa porque a bottom-nav já ocupa o rodapé (KTD2). */
.rodape-extrato {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-6);
  margin-top: var(--space-6);
  padding: var(--space-4);
  border-top: 1px solid var(--color-hairline);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  /* U7 — abaixo do piso o rodapé não é sticky (a bottom-nav já ocupa o rodapé da tela),
     mas é alto o bastante (3 totais + link, quebrando linha em 375px) para que o
     `padding-bottom` genérico de `.page` (pensado pra um último elemento curto) não
     baste sozinho: com página curta, o rodapé cai exatamente na faixa onde a nav fixa
     flutua. Reserva sua própria folga, com a mesma variável. */
  margin-bottom: var(--bottom-nav-espaco);
}

.rodape-extrato__totais {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  margin: 0;
}

.rodape-extrato__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rodape-extrato__item dt { font-size: var(--text-label); }
.rodape-extrato__item dd { margin: 0; }

@media (min-width: 980px) {
  .rodape-extrato {
    position: sticky;
    bottom: 0;
    z-index: 5;
    margin-inline: calc(-1 * var(--space-4));
    padding-inline: var(--space-4);
    border-radius: 0;
  }
}

@supports selector(::details-content) {
  @media (min-width: 900px) {
    /* Refino permanentemente aberto em desktop: o estado do filtro nunca fica
       escondido (D7). O summary some — quem mostra o estado ativo são os
       próprios chips marinho, então o texto do resumo (.filtros-refino__ativo)
       não fica duplicado na tela. */
    .filtros-refino::details-content { content-visibility: visible; }
    .filtros-refino > summary { display: none; }

    /* Conferência exige os dias todos visíveis: em desktop os .card-dia ficam
       permanentemente abertos (o servidor só manda `open` no primeiro). O
       summary continua visível — é o cabeçalho do dia. Clicar nele alterna o
       atributo sem efeito visual; inofensivo. (pointer-events:none foi
       cogitado aqui pra impedir o clique, mas só bloqueia mouse — teclado
       continua abrindo/fechando via Enter/Espaço no summary focado, o que
       trocaria uma dessincronia por mouse por uma dessincronia por teclado.
       Bloquear os dois exige JS, fora do escopo desta entrega.)
       :not(.card-dia--futuros) — o bucket de futuros NÃO entra nessa regra: R3
       (sem exceção) quebraria em desktop, o conteúdo real (e os botões
       Reverter/Desfazer acerto) ficaria visível mas ainda em opacidade
       reduzida (achado do ce-simplify-code). Os dias aninhados dentro do
       bucket continuam .card-dia puro — herdam a regra normalmente. */
    .card-dia:not(.card-dia--futuros)::details-content { content-visibility: visible; }
    .card-dia:not(.card-dia--futuros):not([open]) .card-dia__cabecalho {
      border-bottom: 1px solid var(--color-divisor); /* neutraliza a regra base "fechado sem divisor" */
    }
  }
}

/* Hover (D8): só superfície, nunca sombra nova (uma elevação só — DESIGN.md).
   Condicionado a hover:hover para não grudar estado em touch. */
@media (hover: hover) and (min-width: 900px) {
  .bottom-nav__item:not(.bottom-nav__item--fab):hover { background: var(--color-surface); }
  .bottom-nav__item--fab:hover { background: var(--color-brand-hover); }

  .linha-config:hover,
  .linha-lancamento:hover,
  .item-extrato:hover,
  .item-editavel__resumo:hover,
  .card-dia__cabecalho:hover,
  .seletor-mes__seta:hover,
  .cartao-editavel__resumo:hover { background: var(--color-surface); }

  /* Chip ativo é marinho e continua marinho — o hover só toca os inativos.
     Mesma lógica no segmented: a aba ativa (canvas + sombra) não muda. */
  .chip-filtro:not(:has(input:checked)):hover,
  .chip-categoria:not(:has(input:checked)):hover { background: var(--color-surface); }
  .segmented__item:not([aria-current="page"]):hover { background: var(--color-surface-2); }

}

/* Densidade de ponteiro fino (DESIGN.md, Acessibilidade): mouse/trackpad mira com mais
   precisão que dedo, então a linha de leitura pode encolher sem violar WCAG — 38px ainda
   folga sobre o mínimo AA de 2.5.8 (24px). Só linhas de leitura (--row-min); controles
   (chip, botão, seta do seletor de mês, item da bottom-nav) continuam em --tap-min (44px,
   AAA / 2.5.5) e não usam este token. */
@media (min-width: 900px) and (pointer: fine) {
  :root {
    --row-min: 38px;
  }
}

/* E3 — degrau de 1180px (KTD5): Resumo em duas colunas. Único ponto do grupo
   que precisa mesmo desse teto — medido: a coluna principal (7fr) só cabe sem
   estourar a partir de ~980px de viewport (main.page ~764px), e 900px estoura
   ~23px de scroll horizontal. As demais regras do antigo bloco de 1180px
   (filtros do extrato, metadados, refino sempre aberto) desceram pro degrau
   de 900px — não dependiam dessa largura, só estavam presas ao mesmo teto por
   conveniência (ver comentário no bloco de 900px). */
@media (min-width: 1180px) {
  /* U4 — duas colunas fixas, cada uma um wrapper único (`.resumo-grid__principal`,
     `.resumo-grid__lateral`), não mais um grid-column por painel individual: o
     esquema anterior (cada seção com sua própria classe de coluna + `grid-auto-flow:
     dense` pra "Por cartão" subir de linha quando o card de ação sumia) ficou
     desnecessário quando os dois painéis novos da lateral (reembolsos por pessoa,
     próximas faturas) entraram — mais itens condicionais por coluna, mais casos pro
     dense cobrir. Um wrapper por coluna resolve isso de graça: o que está dentro
     dele empilha em ordem de documento, existindo ou não. */
  .resumo-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    column-gap: var(--space-8);
    align-items: start;
  }
  .resumo-grid__principal,
  .resumo-grid__lateral {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
  }
  /* O `gap` do wrapper é o dono do ritmo vertical agora — sem isto, o margin-bottom
     que hero, painel do gráfico, card de ação e os `.painel` de lista carregam do
     fluxo mobile/tablet somaria com o gap. Prefixado com `.resumo-grid >` (3 classes)
     de propósito, não só `.resumo-grid__principal > .painel`: precisa bater a
     especificidade da regra de 900px logo abaixo no arquivo (2 classes) — do
     contrário essa regra, mais tardia na cascata, venceria e o margin-bottom nunca
     zeraria a partir daqui. Mesmo truque que o código já usava antes desta unidade
     (`.resumo-grid > .hero` batendo `.resumo-grid__categorias` solto). */
  .resumo-grid > .resumo-grid__principal > .hero,
  .resumo-grid > .resumo-grid__principal > .painel,
  .resumo-grid > .resumo-grid__lateral > .card-acao,
  .resumo-grid > .resumo-grid__lateral > .painel {
    margin-bottom: 0;
  }
}

/* ===== Item editável (cartões/categorias) ===== */
/* Texto + lápis por padrão; toque abre o form nativo via <details> — sem JS, sem
   segunda tela. Arquivados ficam numa seção <details> própria, colapsada. */

/* Um container só com divisórias finas, não um card com sombra por item: é o padrão que
   o DESIGN.md define para lista densa (.lista-config, .card-dia) e que esta lista
   contrariava — quatro categorias viravam quatro caixas empilhadas. */
.lista-editavel {
  background: var(--color-canvas);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.item-editavel + .item-editavel {
  border-top: 1px solid var(--color-divisor);
}

.item-editavel__details {
  padding: 0;
}

.item-editavel__resumo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--row-min);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  list-style: none;
}

.item-editavel__resumo::-webkit-details-marker {
  display: none;
}

.item-editavel__cor {
  width: 14px;
  height: 14px;
  border-radius: var(--radius-full);
  flex-shrink: 0;
}

.item-editavel__nome {
  flex: 1;
  font-size: var(--text-body);
  color: var(--color-ink);
}

.item-editavel__lapis {
  color: var(--color-muted);
  flex-shrink: 0;
}

.item-editavel__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: 0 var(--space-4) var(--space-4);
}

.item-editavel--arquivado + .item-editavel--arquivado {
  border-top: 1px solid var(--color-divisor);
}

.item-editavel--arquivado {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
}

.item-editavel--arquivado .item-editavel__nome {
  color: var(--color-muted);
}

/* Membros e convites: a linha tem nome, permissões e três ações. Não cabe no
   .linha-lancamento (uma linha só, valor à direita) — aqui empilha em blocos. */
.linha-pessoa {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-hairline);
}

.linha-pessoa:last-child {
  border-bottom: none;
}

.linha-pessoa__nome {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--color-ink);
}

/* Permissões: uma por linha, alvo de toque cheio. Lado a lado quebravam entre
   checkbox e rótulo no celular. */
.capacidades {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.capacidades label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  font-size: var(--text-body);
  color: var(--color-body);
}

.capacidades input[type="checkbox"] {
  flex-shrink: 0;
}

.capacidades .btn {
  align-self: flex-start;
  margin-top: var(--space-2);
}

.linha-pessoa__acoes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* A destrutiva sempre na própria linha: colada na ação neutra ela vira clique
   errado, e a posição mudava de linha pra linha conforme o texto do vizinho. */
.linha-pessoa__acoes form:has(.btn--destrutivo) {
  flex-basis: 100%;
}

/* Badge dentro do bloco do convite: encolhe até o texto, não vira faixa. */
.linha-pessoa .field .badge-status {
  align-self: flex-start;
}

/* U10 — em tela larga a linha volta a ser uma só, com o nome empurrando as
   ações para a direita. Empilhar é regra de celular: nome + 3 permissões + 3
   ações não cabem em 390px. O wrap FICA: com "Remover acesso de administrador"
   + "Desativar" na mesma linha, nowrap estoura a horizontal entre 900 e 1179px
   (botão tem white-space: nowrap); em 1440px cabe numa linha sozinho. */
@media (min-width: 900px) {
  .linha-pessoa {
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    padding: var(--space-3) var(--space-4);
  }

  .linha-pessoa__nome { flex: 1; min-width: 0; }
  .linha-pessoa .field { flex: 1 1 auto; }

  .capacidades {
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
  }

  .capacidades .btn { align-self: auto; margin-top: 0; }

  .linha-pessoa__acoes form:has(.btn--destrutivo) { flex-basis: auto; }
}

/* Seção colapsada de arquivados: some da leitura principal sem sumir de vez. */
.secao-arquivados {
  margin-top: var(--space-6);
}

.secao-arquivados > summary {
  cursor: pointer;
  font-size: var(--text-label);
  color: var(--color-muted);
  font-weight: var(--weight-medium);
  padding: var(--space-2) 0;
  list-style: none;
}

.secao-arquivados > summary::-webkit-details-marker {
  display: none;
}

.secao-arquivados .lista-editavel {
  margin-top: var(--space-2);
}

/* ===== Item de form: chip de categoria (U5) ===== */

/* Ponto de cor dentro do chip de categoria — a mesma cor que aparece no ícone do item de
   extrato, para a pessoa aprender a associar cor↔categoria em um lugar só. */
.chip-categoria__ponto {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--cor-categoria, var(--color-muted));
  margin-right: var(--space-2);
  flex-shrink: 0;
}

/* ===== Swatches de cor (categoria) + chips de pagamento (U5): mesmo corpo flex ===== */

.swatches,
.chips-pagamento {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.swatch {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  display: inline-flex;
  cursor: pointer;
  border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px var(--color-hairline-strong);
}

.swatch:has(input:checked) {
  border-color: var(--color-ink);
}

.swatch:has(input:focus-visible) {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* O select de cartão só existe depois de escolher a forma "cartão" — mesmo raciocínio do
   .campo-cartao original: um campo que parece opcional e some no fim é pior do que um
   campo que só aparece quando faz sentido preenchê-lo. */
.campo-revelado {
  display: none;
  padding-left: var(--space-1);
  margin-top: var(--space-2);
}

.grupo-pagamento:has(#forma_pagamento_cartao:checked) .campo-revelado--cartao {
  display: block;
}

.grupo-quem-pagou:has(#quem_pagou_novo:checked) .campo-revelado--nome-novo {
  display: block;
}

/* U3 (KTD8) — frequência e data de fim só aparecem com a repetição ligada. */
.grupo-repeticao:has(#repete_sim:checked) .campo-revelado--repete {
  display: block;
}

/* O nome da categoria nova só aparece com o chip "Nova categoria" marcado — mesma mecânica
   dos três acima. Vale nas duas telas de lançamento (new e edit), que renderizam o mesmo
   partial _chips_categoria. */
.grupo-categoria:has(#category_id_nova:checked) .campo-revelado--categoria-nova {
  display: block;
}

/* U3 (KTD2/KTD8) — recorrência e parcelamento são exclusivos: o campo de parcelas
   (fieldset irmão, não descendente de .grupo-repeticao) some por :has() no form inteiro
   assim que "Repete" liga. Nada de JavaScript regendo a mesma exclusividade que o
   servidor já força (num_parcelas = 1 em Recorrencia#gerar_ocorrencias!). */
.form--com-rodape:has(#repete_sim:checked) .grupo-parcelas {
  display: none;
}

/* U5 — mesma exclusividade na edição da ocorrência (purchases/edit.html.erb): não há
   rádio "repete" nesta tela (a série já existe ou não), então o marcador é um campo
   oculto (#ocorrencia_de_serie) em vez do :checked acima. */
.form:has(#ocorrencia_de_serie) .grupo-parcelas {
  display: none;
}

/* "1ª parcela em" liga/desliga via `hidden` no preview_controller (JS): o seletor CSS
   `[value="N"]` só enxerga o atributo HTML inicial, não o valor digitado depois — não
   dá para expressar "mais de uma parcela" em CSS puro aqui. */

/* Data: "Hoje"/"Ontem" escrevem no campo nativo via Stimulus; "Outra" só foca nele — o
   campo continua sempre visível e é sempre ele quem vale no submit. */
.chips-data {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

/* Card com borda de erro: o mesmo cartão que agrupa o campo, só que sinalizado — não move
   o layout dos outros cards, só acrescenta cor e a mensagem embaixo do campo errado. */
.grupo-campos--erro {
  border: 1px solid var(--color-value-negativo);
  border-radius: var(--radius-card);
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-value-negativo) 4%, var(--color-canvas));
}

.erro-campo {
  color: var(--color-value-negativo);
  font-size: var(--text-label);
  margin-top: var(--space-1);
}

.texto-fixo {
  padding: var(--space-2) 0;
  color: var(--color-ink-soft);
}

/* Rodapé do form: resumo vivo + Salvar. Fica no fim da página, no fluxo normal — a
   pessoa rola o formulário inteiro e o resumo/botão são a última coisa que ela vê. */
.rodape-lancamento {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-hairline);
}

.rodape-lancamento__resumo {
  margin-bottom: var(--space-3);
}

/* U8 — conteúdo do painel de conferência (preview.html.erb), válido nos dois mundos:
   aqui só a tipografia/espaçamento base; o visual de painel (fundo, sticky, cantos)
   é exclusivo do breakpoint 1300px, abaixo. */
.rodape-lancamento__topo {
  margin-bottom: var(--space-3);
}

.rodape-lancamento__rotulo {
  font-size: var(--text-label);
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}

.rodape-lancamento__valor {
  font-size: var(--text-display);
  font-weight: var(--weight-bold);
  letter-spacing: -0.02em;
}

.rodape-lancamento__parcelas {
  margin-bottom: var(--space-3);
}

.rodape-lancamento__parcelas-titulo {
  font-size: var(--text-label);
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}

.rodape-lancamento__parcela {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-label);
  min-height: 26px;
}

.rodape-lancamento__parcela span {
  color: var(--color-muted);
}

.rodape-lancamento__nota {
  margin-top: var(--space-2);
}

/* U8 — painel de conferência ao lado do form de lançar (DESIGN.md "Camada desktop").
   Medido (mesma receita do KTD3/zona morta), não o palpite de 1180px do plano: o
   container é `.form--com-rodape`, não `.form` genérico (que tem teto de 40rem em
   `.form { max-width: 40rem }`, 900px acima) — por isso a regra de grid é específica
   da classe composta, e o form ganha seu próprio teto (40rem + gap + 340px do painel)
   pra não fatiar o track 1 quando o track 2 é fixo. Com o rail de 240px + padding de
   `.page` (--space-4 nas duas bordas) + esse teto, o piso real fica em 1300px, não
   1180px: abaixo disso `minmax(0, 1fr)` encolhe os tracks e os testes de estouro
   nunca acusariam (o form nunca ultrapassa a viewport, só fica espremido). */
@media (min-width: 1300px) {
  .form--com-rodape {
    display: grid;
    /* As duas colunas internas não são iguais: medido (mesma receita do KTD3), não
       um palpite. `.grupo-parcelas` só carrega um campo numérico de 8rem (128px,
       `.field input[type="number"]` em 900px acima) + rótulo "Quantas parcelas"
       (~118px intrínseco) — 9rem (144px) sobra pros dois. "Quando" carrega 3 chips
       (~231px) + o campo de data mascarado de 12rem/192px na mesma linha
       (`.field[data-controller~="data-atalho"]`, também 900px acima; soma ~435px
       com o gap entre eles) — um split 1fr/1fr media (~304px cada) forçava essa
       linha a quebrar em duas, quebrando o teste pré-existente que exige chips e
       campo lado a lado. 9rem fixo pra Parcelas devolve a diferença (~464px) pro
       track de "Quando", folga de ~29px sobre o mínimo medido. */
    grid-template-columns: minmax(0, 1fr) 9rem 340px;
    grid-auto-flow: row dense;
    column-gap: var(--space-8);
    max-width: calc(40rem + var(--space-8) + 340px);
  }

  /* Todo fieldset ocupa as duas colunas do form (visual de coluna única, 40rem) por
     padrão — só os dois abaixo (R9) abrem mão de uma coluna cada pra dividir linha. */
  .form--com-rodape > fieldset { grid-column: 1 / 3; }

  /* R9: "Quando" e "Parcelas" (`.grupo-parcelas`) dividem a mesma linha. "Quando" não
     tem classe própria — `.chips-data` (só existe dentro dela) é o gancho estável,
     sobrevive a reordenar fieldsets, o que um `:nth-of-type` não faria. Nenhum wrapper
     novo em `purchases/new.html.erb`: os dois fieldsets não são irmãos adjacentes no
     DOM (mobile mantém a ordem atual — parceria, KTD2 — então não dá pra aproximar
     via reordenar), e um wrapper CSS-only não existe sem adjacência. `grid-auto-flow:
     dense` acima faz o "Parcelas" (que vem depois de "Como foi pago" e "Quem pagou"
     no DOM) voltar e preencher o buraco de coluna 2 que "Quando" deixa — sem
     `grid-row` fixo, então sobrevive a inserir/remover fieldsets antes dele.
     Trade-off aceito: a ordem de tabulação por teclado continua a do DOM (Quando →
     Como pago → Quem pagou → Parcelas), então quem navega por teclado passa por
     "Como pago"/"Quem pagou" antes de chegar em "Parcelas", mesmo ele aparecendo ao
     lado de "Quando" na tela. Sem reordenar o DOM (proibido pela paridade mobile)
     não tem como fechar esse gap; documentado aqui em vez de gasto tentando eliminar. */
  .form--com-rodape > fieldset:has(.chips-data) { grid-column: 1; }
  .form--com-rodape > .grupo-parcelas { grid-column: 2; }

  /* Painel de conferência: sticky na coluna 2, ocupando as linhas todas do grid.
     `align-self: start` é obrigatório — sem ele o item esticaria (`stretch`, o
     padrão de grid) e o sticky nunca teria uma borda de scroll pra grudar. Zera o
     rodapé-de-página herdado da regra base (margin-top/padding-top/border-top) —
     aqui ele é painel, não rodapé. Sem `.page__toolbar` nesta tela (só `new`, sem
     index), `top` usa a folga padrão da página, não os 96px do toolbar de U2/U6.
     `grid-row: 1 / -1` NÃO funciona aqui: sem `grid-template-rows` explícito o
     grid de linhas é implícito, e uma linha negativa conta a partir do fim do
     grid EXPLÍCITO (que tem 0 linhas) — `-1` resolve pra linha 1, e o painel
     vira `span 1` (só a altura do primeiro fieldset). `span <N>` com N maior que
     o número real de fieldsets (9 aqui, span 12 de folga) resolve: as linhas
     implícitas sobrando ficam com altura 0 (sem `row-gap` declarado), sem custo
     visual. Sem precedente disto no arquivo — `.resumo-grid`/`.filtros-extrato`
     só usam `grid-row: 1`, que não tem esse problema por não ser negativo. */
  .rodape-lancamento {
    grid-column: 3;
    grid-row: 1 / span 12;
    align-self: start;
    position: sticky;
    top: var(--space-4);
    margin-top: 0;
    padding: var(--space-4);
    border-top: none;
    background: var(--color-canvas);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    /* Canto inferior direito reto: mesma assinatura de marca do rail e do hero. */
    border-end-end-radius: 0;
  }

  /* U8 — o painel de conferência falando desde o topo: hairline separando o valor das
     parcelas (moc l. 288–303), e o rodapé (status + Lançar) com fundo distinto,
     sangrando até a borda do card — mesma receita de canto reto do card inteiro, só que
     no canto oposto (inferior ESQUERDO), pra não competir com a assinatura de marca.
     `:not(:last-child)`: com 1 parcela e sem cartão (sem bloco de parcelas, sem nota de
     fechamento) o topo é o único filho de [data-preview-valido] — sem isto, a hairline
     sobrava sozinha, separando o topo de nada, com um vão vazio até o rodapé. */
  .rodape-lancamento__topo:not(:last-child) {
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-divisor);
  }

  .rodape-lancamento__rodape {
    margin: var(--space-4) calc(var(--space-4) * -1) calc(var(--space-4) * -1);
    padding: var(--space-4);
    border-top: 1px solid var(--color-divisor);
    background: var(--color-surface);
    border-radius: 0 0 0 var(--radius-lg);
  }

  .rodape-lancamento__rodape .btn {
    width: 100%;
  }
}

/* ============================================================
   Dashboard (U4): hero marinho, card de ação, quebras clicáveis
   ============================================================ */

/* Hero: mesmo fundo marinho do seletor sticky, para o número mais importante da tela
   (gasto do mês) morar dentro da mesma faixa de cor, não abaixo dela. */
.hero {
  background: var(--color-brand);
  color: var(--color-on-brand);
  border-radius: var(--radius-lg);
  padding: 0 var(--space-4) var(--space-5);
  margin-bottom: var(--space-4);
  overflow: hidden;
}

.hero .seletor-mes {
  border-radius: 0;
  box-shadow: none;
  margin-bottom: var(--space-4);
}

.hero .seletor-mes__acao {
  color: var(--color-on-brand);
}

.hero .seletor-mes__acao a {
  color: inherit;
}

.hero__rotulo {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  opacity: 0.85;
}

.hero__valor {
  font-size: var(--text-display);
  font-weight: var(--weight-bold);
  overflow-wrap: anywhere;
}

.hero__comprometido {
  font-size: var(--text-label);
  opacity: 0.85;
  margin-top: var(--space-1);
}

/* U4 — faixa de métricas de 3 células (DESIGN.md "Camada desktop"). Célula 1 (rótulo +
   valor + delta) é a mesma sempre; a linha de delta só existe acima do piso (ver bloco de
   min-width abaixo) — abaixo dele, a frase "comprometidos nos próximos meses" (paridade
   mobile, KTD2) ocupa o lugar dela. Células 2 e 3 só existem como cartões de valor acima
   do piso; abaixo, a célula 1 sozinha reproduz o hero de antes desta unidade. */
.hero__delta {
  display: none;
  font-size: var(--text-label);
  opacity: 0.85;
  margin-top: var(--space-1);
  font-variant-numeric: tabular-nums;
}

.hero__celula--secundaria {
  display: none;
}

.hero__valor-celula {
  display: block;
  font-size: var(--text-value-md);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}

.hero__valor-celula--link {
  color: inherit;
  text-decoration: none;
}

.hero__valor-celula--link:hover {
  text-decoration: underline;
}

/* U4 — o gráfico sai do .hero (KTD2: DOM compartilhado com o mobile) e vira um painel
   próprio logo abaixo da faixa. Regra BASE (mobile): continua marinho, fundido
   visualmente com o hero (ver bloco `max-width: 899px` abaixo) — paridade exata com
   o hero único de antes desta unidade (KTD2). Acima do piso de 900px o painel vira
   canvas (ver bloco `min-width: 900px` acima, seção U5/R5/moc) — as barras usam
   --color-on-brand (branco) só fazem sentido sobre marinho; a versão desktop (12
   colunas, `--ampla`) troca de cor no mesmo bloco que troca o fundo. */
.grafico-painel {
  background: var(--color-brand);
  color: var(--color-on-brand);
  border-radius: var(--radius-lg);
  padding: var(--space-5) var(--space-4);
}

/* U2 (KTD7) — `.grafico-painel` virou filho de `.painel--grafico` (section que
   envolve o painel do gráfico com cabeçalho + legenda em desktop), então quem
   carrega o respiro pro próximo bloco no fluxo é o wrapper, não mais o `.grafico-
   painel` interno — senão o E3 (grid de 1180px) zeraria a margem no elemento
   errado (ele zera `.resumo-grid > .painel--grafico`, que agora é o filho direto
   do grid). */
.painel--grafico {
  margin-bottom: var(--space-4);
}

/* U2 (KTD7) — cabeçalho + legenda do painel do gráfico (moc l. 151–158/392–399) só
   existem pro desktop: o gráfico mobile de 5 colunas não tem título nem legenda hoje,
   e não ganha um agora (paridade mobile, R9). Toggle aceito no bloco BASE por KTD1 —
   só esconde o elemento; layout/estilo de verdade vive atrás do @media (min-width:
   900px) abaixo. */
.painel__cabecalho,
.legenda-serie {
  display: none;
}

/* U3/U4/R9 — "Por categoria", "Por cartão" e os dois painéis novos da lateral
   (reembolsos por pessoa, próximas faturas) reaproveitam `.painel__cabecalho` (U2),
   mas ao contrário do painel do gráfico (que não tem título no mobile), os h2 destes
   painéis de lista sempre existiram soltos ali — a regra geral acima (display:none)
   os esconderia junto se aplicada sem ressalva. `.resumo-grid__lateral .painel__cabecalho`
   cobre os dois painéis novos (conteúdo aditivo, R9: mesmo tratamento de "Por cartão",
   que já mora na lateral — sem CSS mobile especial escondendo-os) sem precisar de uma
   classe BEM só pra isto; casa de novo com `.resumo-grid__cartoes` (redundante, mesmas
   propriedades, sem efeito colateral). Só o mês (span.muted, novo, moc l. 406) é
   exclusivo de desktop; ele controla a própria visibilidade logo abaixo. */
.resumo-grid__categorias .painel__cabecalho,
.resumo-grid__lateral .painel__cabecalho {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.resumo-grid__categorias .painel__cabecalho .muted {
  display: none;
}

.grafico-painel .grafico-competencias {
  margin-top: 0;
}

/* Mini gráfico de 5 competências no hero: colunas verticais, altura proporcional.
   CSS puro (flex), sem lib. A coluna do mês de referência ganha opacidade cheia; as
   demais ficam esmaecidas — o destaque é o "onde estou", não um segundo dado. */
.grafico-competencias {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  height: 72px;
  margin-top: var(--space-5);
}

.grafico-competencias__coluna {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  gap: var(--space-1);
}

.grafico-competencias__barra {
  width: 100%;
  height: var(--proporcao, 0%);
  min-height: 2px;
  border-radius: var(--radius-sm);
  background: var(--color-on-brand);
  opacity: 0.4;
}

.grafico-competencias__coluna--referencia .grafico-competencias__barra {
  opacity: 1;
}

.grafico-competencias__rotulo {
  font-size: var(--text-caption);
  opacity: 0.7;
  text-transform: uppercase;
}

.grafico-competencias__coluna--referencia .grafico-competencias__rotulo {
  opacity: 1;
  font-weight: var(--weight-medium);
}

/* U5 — as 3 competências à frente (comprometido_por_mes) trocam de cor em vez de
   ganhar um segundo segmento: nelas não há realizado (a competência ainda não
   fechou), só comprometido. Token de status já existente (comprometido, o mesmo
   usado no badge de parcela futura), com opacidade — não introduz uma cor nova. */
.grafico-competencias__barra--comprometido {
  background: var(--color-status-comprometido-fg);
  opacity: 0.65;
}

/* U5 — a versão desktop (12 competências, `.grafico-competencias--ampla`) e a versão
   mobile (5 competências, `.grafico-competencias` puro) são DOIS markups, não um só
   alternado por CSS (ver comentário na view): cada uma normaliza a altura das barras
   contra o próprio máximo, então uma só marcação faria os dois tamanhos brigarem pela
   mesma variável de proporção. Mobile-first: a versão ampla começa escondida. */
.grafico-competencias--ampla {
  display: none;
}

/* U2 (KTD7) — linha de rótulos de mês do gráfico ampliado, irmã de `.grafico-
   competencias--ampla` (moc l. 177–180, `.serie__rotulos`): mesmo raciocínio
   mobile-first do bloco acima, começa escondida. */
.grafico-competencias__rotulos {
  display: none;
}

/* Card de ação de reembolsos: só existe no DOM quando há pendentes — a ausência do
   elemento É o estado "nada pendente", sem precisar de mensagem de "tudo certo". */
.card-acao {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  background: var(--color-status-pendente-bg, var(--color-surface));
  border: 1px solid var(--color-status-pendente-fg);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  color: var(--color-ink);
}

.card-acao__texto {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Lembrete de fatura: mesmo esqueleto do card de reembolsos, paleta de "comprometido"
   (a da legenda do gráfico) — vencimento de fatura é dinheiro comprometido, não
   reembolso pendente. */
.card-acao--fatura {
  background: var(--color-status-comprometido-bg, var(--color-surface));
  border-color: var(--color-status-comprometido-fg);
}

/* Item 6 do backlog — abaixo de 900px .painel não tinha NENHUM margin-bottom (a regra
   equivalente só existia dentro do @media (min-width: 900px) logo abaixo, pensada pro
   degrau tablet). Resultado visível só em screenshot real de celular: a última barra de
   "Por categoria" e o próximo .card-acao (reembolso pendente) ficavam colados — 0px de
   respiro — enquanto o h2 seguinte ("Por cartão") já tinha 32px por conta própria
   (margin-top do h2). Base mobile-first aqui, redundante mas inofensiva no degrau 900px
   (mesmo valor) e já zerada a partir de 1180px pela regra de maior especificidade
   `.resumo-grid > .resumo-grid__principal > .painel { margin-bottom: 0 }`. */
.resumo-grid__principal > .painel,
.resumo-grid__lateral > .painel {
  margin-bottom: var(--space-4);
}

/* U4 — faixa de métricas do Resumo em 3 células (DESIGN.md "Camada desktop"). Bloco
   próprio (não reaproveita o @media (min-width: 900px) do rail acima) de propósito: as
   regras precisam vir DEPOIS das declarações base de .hero/.hero__celula/.card-acao no
   arquivo — CSS resolve empate de especificidade por ordem de origem, e uma media query
   não muda isso; posicionado antes da base, o override perderia para ela mesmo casando
   a largura. Medido (KTD3), não presumido: mesmo com números de pior caso (7 dígitos em
   todas as 3 células) e sem wrap, a faixa cabe sem estourar já a partir de 900px — o
   mesmo piso do rail, não um degrau novo. Diferente do .resumo-grid de duas colunas (E3),
   que só cabe a partir de 1180px: aqui não há coluna dupla disputando espaço até esse
   ponto (a faixa some sozinha na coluna 7fr do .resumo-grid, com folga, mesmo a 1180px).
   Teste de sistema descartável apagado depois de medir. */
@media (min-width: 900px) {
  /* Sem align-items:center de propósito: a célula 1 é mais alta que as outras duas
     (rótulo + valor + delta vs. rótulo + valor), e centralizar o bloco inteiro de cada
     célula desalinharia os três valores entre si (cada um centralizado num bloco de
     altura diferente). Alinhamento padrão (topo) — mesmo rótulo, mesma fonte, nas três
     células — é o que garante que os três `.valor-monetario` caem na mesma linha. */
  .hero {
    display: grid;
    grid-template-columns: 1.6fr 1fr 1fr;
    padding: var(--space-5) var(--space-4);
  }

  .hero__comprometido { display: none; }
  .hero__delta { display: block; }
  .hero__celula--secundaria { display: block; }

  .hero__celula + .hero__celula {
    padding-inline-start: var(--space-4);
    border-inline-start: 1px solid rgb(255 255 255 / .16);
  }

  /* R4 — o card de reembolsos pendentes vira redundante com a célula 3 da faixa acima
     deste piso. Continua no DOM (o mobile depende dele) — só sai da tela. */
  .card-acao { display: none; }

  /* O lembrete de fatura NÃO é redundante com a faixa de métricas — nenhuma célula
     do hero mostra vencimento. Continua visível em desktop; a ordem dentro do bloco
     resolve o empate de especificidade a favor desta regra. */
  .card-acao--fatura { display: flex; }

  /* U5 — troca de gráfico no mesmo piso da faixa de métricas (900px, KTD3): sem
     coluna dupla disputando espaço aqui (mesmo raciocínio do `.hero` acima), 12
     colunas cabem sem o teto de 1180px que só o `.resumo-grid` de duas colunas
     precisa. Medido com `assert_sem_estouro_horizontal` em 900/980/1180/1440
     (test/system/desktop_layout_test.rb) antes de fixar este piso. */
  .grafico-competencias:not(.grafico-competencias--ampla) { display: none; }
  .grafico-competencias--ampla { display: flex; }
  .grafico-competencias__rotulos { display: flex; }

  /* R5/U2 (KTD7)/moc l. 151–158 (docs/mocks/desktop-v2.html, painel "Doze meses por
     competência", pino 3): o painel do gráfico ganha cabeçalho + legenda e sai do
     marinho pra canvas com identidade de cartão próprio — "sai de dentro do marinho"
     (R5) não é só sair do elemento `.hero`, é também deixar de ser navy. Sem isto,
     `.grafico-painel` (marinho, herdado da regra base pro mobile) fica colado embaixo
     do `.hero` (também marinho): marinho-sobre-marinho, contra a regra de identidade
     do CLAUDE.md/DESIGN.md. Abaixo do piso o bloco original (fusão visual com o hero,
     KTD2) continua intocado. */
  .painel {
    background: var(--color-canvas);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    padding: var(--space-5) var(--space-5) var(--space-6);
  }

  .painel__cabecalho {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
  }

  .painel h2 {
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    color: var(--color-ink);
  }

  .legenda-serie {
    display: flex;
    gap: var(--space-4);
    font-size: var(--text-caption);
    color: var(--color-muted);
  }

  .legenda-serie span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }

  .legenda-serie i {
    width: 10px;
    height: 10px;
    border-radius: 3px;
    display: block;
  }

  /* `.grafico-painel` agora vive DENTRO de `.painel` (KTD7) — o fundo/raio/sombra/
     padding que ele carregava aqui virou responsabilidade do wrapper; ele mesmo vira
     um contêiner neutro de conteúdo, senão o padding/sombra dobra. */
  .grafico-painel {
    background: transparent;
    color: var(--color-ink);
    box-shadow: none;
    padding: 0;
    border-radius: 0;
  }

  /* R1/moc l. 163–164 (renomeado `.serie` → `.grafico-competencias--ampla`, KTD7):
     altura de leitura fixa e um piso hairline no lugar do topo do card marinho de
     antes. */
  .grafico-competencias--ampla {
    position: relative;
    height: 220px;
    align-items: flex-end;
    border-bottom: 1px solid var(--color-hairline);
  }

  /* R2/moc l. 165–170 (renomeado `.serie__grade` → `.grafico-competencias__grade`):
     linhas de referência absolutas e decorativas (aria-hidden na view) — saem do
     fluxo (position: absolute) pra não disputar espaço com as colunas. */
  .grafico-competencias__grade {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    pointer-events: none;
  }

  .grafico-competencias__grade div {
    border-top: 1px dashed var(--color-hairline);
    font-size: var(--text-caption);
    color: var(--color-muted);
    transform: translateY(-50%);
  }

  .grafico-competencias__grade div span {
    background: var(--color-canvas);
    padding-right: 6px;
    display: inline-block;
    transform: translateY(-50%);
    color: var(--color-muted);
  }

  /* moc l. 171 (`.serie__col{position:relative;z-index:1}`) — sem isto, a grade
     (position: absolute, acima na ordem do DOM) pinta por cima das colunas em vez de
     atrás: cada "R$ Nk" carrega um fundo opaco (`--color-canvas`) que cobriria a(s)
     barra(s) mais próxima(s) de cada linha. */
  .grafico-competencias--ampla .grafico-competencias__coluna {
    position: relative;
    z-index: 1;
  }

  /* R1/moc l. 172–176 — duas séries por cor+opacidade, não duas cores plenas:
     realizado em marinho, comprometido no token de status já existente (mesmo do
     badge de parcela futura). A coluna de referência (mês atual) ganha opacidade
     cheia (realizado) ou reforçada (comprometido) pra guiar o olho pro "onde estou".
     As duas regras de `--comprometido` têm a mesma especificidade (3 classes) que as
     suas equivalentes sem o modificador — vêm depois no arquivo de propósito, pra
     vencer o empate em vez de depender de `!important`. */
  .grafico-competencias--ampla .grafico-competencias__barra {
    background: var(--color-brand);
    opacity: 0.35;
  }

  .grafico-competencias--ampla .grafico-competencias__barra--comprometido {
    background: var(--color-status-comprometido-fg);
    opacity: 0.28;
  }

  .grafico-competencias--ampla .grafico-competencias__coluna--referencia .grafico-competencias__barra {
    opacity: 1;
  }

  .grafico-competencias--ampla .grafico-competencias__coluna--referencia .grafico-competencias__barra--comprometido {
    opacity: 0.55;
  }

  /* moc l. 177–180 (`.serie__rotulos`) — linha de rótulos irmã da caixa de 220px, não
     mais dentro das colunas (ver comentário na view): cada mês ganha a mesma largura
     da coluna correspondente pra continuar alinhado com a barra de cima. */
  .grafico-competencias__rotulos {
    gap: var(--space-2);
    margin-top: var(--space-2);
  }

  .grafico-competencias__rotulos .grafico-competencias__rotulo {
    flex: 1;
    text-align: center;
    opacity: 1;
    color: var(--color-muted);
  }

  .grafico-competencias__rotulos .grafico-competencias__rotulo--referencia {
    color: var(--color-ink);
    font-weight: var(--weight-medium);
  }

  /* U3 — mês (span.muted) do cabeçalho de "Por categoria" só existe a partir daqui
     (moc l. 406); no mobile fica escondido pela regra base. */
  .resumo-grid__categorias .painel__cabecalho .muted {
    display: inline;
  }

  /* U3/R2/moc l. 184/191 (renomeada `.quebra__pct` → `.dashboard-quebra__pct`): 5ª
     coluna só em desktop — o mobile já lê a barra proporcional, reforçar com um número
     ali seria ruído numa tela pequena (R9). Escopado a `.resumo-grid__categorias`
     porque `.dashboard-quebra` é reaproveitável por outras listas (ver comentário na
     declaração base) que não têm essa coluna. */
  .resumo-grid__categorias .dashboard-quebra__linha {
    grid-template-columns: auto 1fr auto 3.5rem;
  }

  /* Escopado (não `.dashboard-quebra__pct` solto): a declaração base do mesmo nome
     de classe vem DEPOIS deste bloco no arquivo (junto das outras regras de
     `.dashboard-quebra`) e empataria em especificidade — perdendo a cascata para a
     base em qualquer largura, já que uma media query não desempata sozinha. */
  .resumo-grid__categorias .dashboard-quebra__pct {
    display: block;
    grid-row: 1;
    justify-self: end;
    font-size: var(--text-label);
    color: var(--color-muted);
  }

  /* margin-bottom de .resumo-grid__principal/lateral > .painel virou regra base
     (mobile-first, item 6 do backlog) — redundante aqui, removida. Continua zerada a
     partir de 1180px pela regra de maior especificidade logo acima no arquivo. */
}

/* U4 (KTD2) — abaixo de 900px, restaura a aparência mobile de antes desta unidade: o
   stepper marinho sticky agora é irmão do page__header (não mais filho do .hero), então
   volta a precisar da mesma anulação de raio/sombra/margem que `.hero .seletor-mes` fazia
   antes de o seletor sair de dentro do hero — só que escopada ao novo local (o wrapper
   .page__toolbar--resumo, exclusivo desta página, para não vazar pro stepper de
   Lançamentos). A largura do stepper passa a ser a da .page inteira, não mais a do hero
   por dentro (space-4 a mais de cada lado): consequência aceita da própria KTD2 ("largura
   cheia"), não uma tentativa de bater pixel a pixel nisso — a comparação de screenshot
   valida a composição (faixa marinho no topo, colada na faixa de métricas), não a largura
   exata do stepper.

   O .hero e o .grafico-painel (que herdou o gráfico que morava dentro do hero) se fundem
   numa só forma contínua: o hero perde o raio/padding/margem de baixo, o painel perde o
   de cima, e o padding interno do painel substitui a margem que o gráfico tinha quando
   morava dentro do hero — a costura vira as bordas arredondadas do conjunto (topo no
   stepper, base no painel), igual ao efeito visual de antes (uma faixa marinho contínua).
   Bloco posicionado depois das declarações base de .hero/.grafico-painel de propósito
   (mesmo motivo do bloco de 900px acima): empate de especificidade se resolve por ordem
   de origem, não por estar dentro de uma media query. */
@media (max-width: 899px) {
  .page__toolbar--resumo .seletor-mes--sticky {
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: none;
    margin-bottom: 0;
  }

  .hero {
    padding-top: var(--space-4);
    padding-bottom: 0;
    margin-bottom: 0;
    border-radius: 0;
  }

  .grafico-painel {
    border-start-start-radius: 0;
    border-start-end-radius: 0;
    margin-top: 0;
  }
}

/* Lista "Por categoria" / "Por responsável": dot de cor + rótulo + barra proporcional +
   valor, tudo numa linha só — a barra é a leitura rápida, o valor é a confirmação. */
.dashboard-quebra {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.dashboard-quebra__linha {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  min-height: var(--row-min);
  color: inherit;
  text-decoration: none;
}

a.dashboard-quebra__linha:hover .dashboard-quebra__rotulo {
  text-decoration: underline;
}

.dashboard-quebra__dot {
  grid-row: 1;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--cor-categoria, var(--color-muted));
  flex-shrink: 0;
}

.dashboard-quebra__rotulo {
  grid-row: 1;
  font-weight: var(--weight-medium);
}

.dashboard-quebra__linha .barra {
  grid-column: 1 / -1;
  grid-row: 2;
}

.dashboard-quebra__valor {
  grid-row: 1;
  justify-self: end;
  font-variant-numeric: tabular-nums;
}

/* U3/moc l. 191 (renomeada `.quebra__pct` → `.dashboard-quebra__pct`) — coluna de %
   é desktop-only (ver bloco 900px); a lista continua reaproveitável por outras quebras
   (ex.: "Por responsável") sem essa coluna. */
.dashboard-quebra__pct {
  display: none;
}

/* U3/moc l. 193–198 — "Por cartão" vira lista de linhas com divisor (não mais
   mini-cards em grid): mesmo componente que o moc reusa em "Reembolsos por pessoa"
   (l. 417). Bloco BASE de propósito (sem @media) — funciona nos dois mundos; no
   mobile ela substitui a antiga grade de mini-cards (única mudança visual mobile
   extra aceita nesta unidade, além da KTD3, R9). */
.lista-simples {
  list-style: none;
}

.lista-simples li + li {
  border-top: 1px solid var(--color-divisor);
}

.lista-simples > li > a,
.lista-simples > li > span {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--row-min);
  padding: var(--space-2) 0;
}

.lista-simples a:hover {
  background: var(--color-surface);
}

.lista-simples b {
  font-weight: var(--weight-medium);
}

/* ============================================================
   Onboarding: stepper, folga de rodapé e tela de conclusão
   ============================================================ */

/* O wizard não monta a bottom-nav (ver comentário no layout): a folga que .page reserva
   para ela sobrava como ~120px de canvas vazio no fim de cada etapa — parecia página que
   não terminou de carregar. Aqui o rodapé volta a ser só respiro + safe-area. */
.page--onboarding {
  padding-bottom: calc(var(--space-8) + env(safe-area-inset-bottom, 0px));
}

/* Nota: etapa curta (Empresa tem 2 campos) sobra tela num aparelho de 812px, e isso é
   normal — tentativas de "preencher" o vão (ancorar o rodapé embaixo, esticar o card)
   só trocaram vazio cinza por vazio branco e por um card com metade do corpo vazia.
   O DESIGN.md proíbe ilustração/textura de enfeite, então não há o que pôr ali: o
   conteúdo termina onde termina. */

/* Quatro segmentos de mesma largura, um por etapa. O estado mora na barra (hairline = a
   fazer, marinho = feita/atual) e é reforçado no rótulo (✓ + peso), porque cor sozinha
   não pode ser o único sinal. Em 375px sobra ~80px por coluna: "Categorias", o rótulo
   mais longo, ocupa ~62px em --text-caption. */
.stepper {
  margin-bottom: var(--space-6);
}

/* 5 colunas iguais: em 375px dá ~63px por coluna, onde "Categorias" cabe em uma linha
   agora que o rótulo não carrega mais o ✓. Se um rótulo futuro estourar, os itens alinham
   pelo topo e a linha acompanha a mais alta — mas o rótulo é que deve encurtar. */
.stepper__trilha {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-1);
  list-style: none;
  padding: 0;
  margin: 0;
}

.stepper__etapa {
  display: flex;
}

/* O link ocupa a coluna inteira e chega a --tap-min de altura: o alvo de toque é a
   coluna, não as ~12px do rótulo. */
/* Rótulo centrado sob a barra, que é a largura da coluna: centrado, rótulo curto e barra
   larga se lêem como o mesmo item. */
.stepper__link {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--tap-min);
  color: var(--color-muted);
  font-size: var(--text-caption);
  font-weight: var(--weight-medium);
  line-height: var(--leading-base);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-standard);
}

.stepper__link::before {
  content: "";
  display: block;
  /* width explícita: com align-items:center o ::before encolheria ao conteúdo (vazio). */
  width: 100%;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-hairline-strong);
  transition: background var(--duration-fast) var(--ease-standard);
}

/* Três estados de barra, nunca só dois. Cheia = feita ou atual; meia-tinta = etapa que
   ficou para trás sem preencher (pulada); hairline = ainda não alcançada. A trilha só
   anda para frente. Ver o comentário longo em _barra_progresso.html.erb. */
.stepper__link--feita::before,
.stepper__link--atual::before {
  background: var(--color-brand);
}

/* Tracejada, não só "marinho mais claro": a diferença entre pulada e não-alcançada
   precisa sobreviver a quem não distingue as duas tintas e à tela no sol ("cor nunca é o
   único sinal", DESIGN.md). Traço interrompido é diferença de forma, legível antes da
   cor — e continua dizendo "passei por aqui sem terminar". */
.stepper__link--pulada::before {
  background: repeating-linear-gradient(
    90deg,
    var(--color-brand) 0 5px,
    var(--color-canvas) 5px 9px
  );
}

/* Rótulo acompanha o estado: --color-muted (4.8:1) só na etapa ainda não alcançada; quem
   já passou fica em --color-body (7.7:1). Com tudo em muted, "Cartão" na etapa 3 ficava
   apagado a ponto de leitores não acharem o rótulo. */
.stepper__link--feita,
.stepper__link--pulada {
  color: var(--color-body);
}

.stepper__link--atual {
  color: var(--color-ink);
  font-weight: var(--weight-bold);
}

.stepper__link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Aviso de estado persistente (nome da empresa ainda no placeholder). Não é <dialog>:
   modal que abre sozinho no load tapa o formulário que a pessoa veio usar antes dela
   olhar a tela. Aqui o aviso fica na página, acima do form, e some quando o estado
   deixa de valer. Mesma faixa âmbar do status pendente — é pendência, não erro. */
.aviso-inline {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  margin-bottom: var(--space-6);
  border-radius: var(--radius-card);
  /* Superfície neutra, não o âmbar de --color-status-pendente: naquele token o app diz
     "dinheiro aguardando baixa". Emprestá-lo para um aviso sobre o NOME da empresa gasta
     um sinal financeiro num assunto que não é financeiro, e a cor deixa de ter um só
     ofício. O peso do aviso vem da posição (acima do form) e da ação embutida. */
  background: var(--color-surface-2);
  border: 1px solid var(--color-hairline-strong);
  color: var(--color-ink);
  font-size: var(--text-body);
}

.aviso-inline__corpo {
  line-height: var(--leading-loose);
}

.aviso-inline .btn {
  width: auto;
  background: transparent;
  border-color: var(--color-hairline-strong);
  color: var(--color-accent);
}

/* Tela de conclusão: era o único componente do wizard sem CSS nenhum — h1 colado no
   parágrafo, botão logo abaixo e meia tela de cinza. É o fim da jornada (peak-end):
   centraliza no viewport, sela com o ✓ e lista o que ficou pronto, para o marco parecer
   marco. Sem ilustração nem gradiente (anti-padrão do DESIGN.md): selo, texto e ação. */
.onboarding-concluido {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-4);
  /* dvh, não vh: no Safari iOS a barra de endereço come 100vh e empurra a ação para fora.
     62, não 78: a caixa de centralização não inclui o rodapé que vem depois dela, então
     um valor alto empurrava o bloco para cima e sobrava um vão maior embaixo do que em
     cima -- centralizado no próprio contêiner, torto na tela. */
  min-height: 62dvh;
}

.onboarding-concluido__selo {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--color-brand-subtle-bg);
  color: var(--color-brand);
}

.onboarding-concluido h1 {
  font-size: 1.75rem;
  letter-spacing: -0.02em;
  /* Nome de empresa não tem limite de tamanho e é interpolado aqui. */
  overflow-wrap: anywhere;
}

.onboarding-concluido__resumo {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-4);
  border-radius: var(--radius-card);
  /* canvas + a elevação única do app, não --color-surface: o body já é --color-surface,
     então o card sumia no fundo e a lista parecia solta na página. */
  background: var(--color-canvas);
  box-shadow: var(--shadow-card);
  list-style: none;
  margin: 0;
  text-align: left;
}

.onboarding-concluido__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-body);
  color: var(--color-body);
}

.onboarding-concluido__item svg {
  flex: 0 0 auto;
  color: var(--color-status-baixado-fg);
}

/* Pendência listada junto do que ficou pronto: quem pulou o cartão vê onde retomar em vez
   de descobrir depois que o app pede um cartão que ele nunca cadastrou. */
.onboarding-concluido__item--pendente {
  color: var(--color-muted);
}

.onboarding-concluido__item--pendente svg {
  color: var(--color-muted);
}

.onboarding-concluido__acoes {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
}

/* Único movimento da tela de conclusão, e o único do wizard fora do mini-cartão: o ✓ do
   selo se DESENHA (stroke-dashoffset), e o resumo do que ficou pronto aparece linha a
   linha logo atrás. É confirmação, não medalha — sem confete, sem pulso, sem escala
   passando de 1.0 (o DESIGN.md/PRODUCT.md barram gamificação, e este é o momento em que
   a tentação de comemorar é maior). Anima só opacidade, transform e um atributo de
   pintura; nada de propriedade de layout.
   O ✓ é `M20 6 9 17l-5-5` num viewBox de 24: ~20 unidades de traço. dasharray 24 cobre
   com folga -- se o path do selo mudar, conferir este número junto. */
.onboarding-concluido__selo svg path {
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  animation: selo-desenha var(--duration-slow) var(--ease-standard) 120ms forwards;
}

@keyframes selo-desenha {
  to { stroke-dashoffset: 0; }
}

/* Escada curta: o resumo entra depois do ✓ terminar de desenhar, uma linha por vez, para
   a lista ser LIDA em vez de aparecer inteira como bloco. 60ms por item -- passo menor
   vira cintilada, maior faz esperar pela ação primária que vem depois. */
.onboarding-concluido__item {
  animation: item-entra var(--duration-base) var(--ease-standard) both;
  animation-delay: calc(440ms + 60ms * var(--indice, 0));
}

.onboarding-concluido__item:nth-child(2) { --indice: 1; }
.onboarding-concluido__item:nth-child(3) { --indice: 2; }
.onboarding-concluido__item:nth-child(4) { --indice: 3; }
.onboarding-concluido__item:nth-child(5) { --indice: 4; }

@keyframes item-entra {
  from { opacity: 0; transform: translateY(4px); }
}

/* O reset global de reduced-motion zera DURAÇÃO, não DELAY: sem isto as linhas ainda
   apareceriam em sequência, só que cada uma de estalo -- pior que a animação. */
@media (prefers-reduced-motion: reduce) {
  .onboarding-concluido__selo svg path { stroke-dashoffset: 0; animation: none; }
  .onboarding-concluido__item { animation: none; animation-delay: 0s; }
}

/* Erro ao lado do campo. Vermelho semântico + o texto do próprio validador: cor sozinha
   não diz o que corrigir, e resumo só no topo obriga a rolar de volta caçando o campo. */
.erro-campo {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  color: var(--color-value-negativo);
}

.field input[aria-invalid="true"],
.field select[aria-invalid="true"] {
  border-color: var(--color-value-negativo);
}

/* Texto auxiliar persistente sob um campo. Placeholder some quando a pessoa digita, que
   é exatamente quando a explicação ainda faz falta. */
.ajuda-campo {
  font-size: var(--text-label);
  color: var(--color-muted);
  line-height: var(--leading-base);
}

.ajuda-campo--centro {
  text-align: center;
}

/* Assinatura de confiança do wizard: por que o Numo pede um dado e quem passa a vê-lo.
   Num app que mexe com dinheiro, a pergunta que trava o preenchimento não é "como
   preencho" e sim "por que você quer isso" — e ela precisa ser respondida no campo, não
   numa política linkada que ninguém abre. Distinta de .ajuda-campo (que explica COMO
   preencher) pela régua à esquerda e pelo texto menos apagado: é afirmação sobre o dado,
   não nota de rodapé. Régua em --color-brand, o mesmo marinho da ação primária, para o
   sinal de confiança e o sinal de "prosseguir" virem da mesma voz.
   Regra do conteúdo: finalidade concreta + quem enxerga. Nunca garantia de segurança
   ("dados protegidos", "100% seguro") — promessa não verificável no código custa mais
   confiança do que silêncio, e justamente com quem tem menos como checar. */
.motivo {
  border-left: 2px solid var(--color-brand);
  padding-left: var(--space-3);
  font-size: var(--text-label);
  line-height: var(--leading-base);
  color: var(--color-body);
}

/* Saída secundária no fim de uma etapa ("ainda não tenho cartão", "concluir onboarding"):
   separada do bloco de ação primária, para não competir com ele. */
.acao-secundaria {
  margin-top: var(--space-4);
}

/* Revelação progressiva com <details> nativo — sem JS, com teclado e leitor de tela de
   graça. Usado no lote de convites: uma linha à vista, as outras sob demanda. */
.revelar {
  border-top: 1px solid var(--color-hairline);
  padding-top: var(--space-4);
}

.revelar__gatilho {
  display: flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--color-accent);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  cursor: pointer;
  list-style: none;
}

/* O triângulo padrão do Safari/Chrome tem peso óptico próprio e destoa do resto; +/–
   acompanha a tipografia, mesmo padrão do glossário em <details> das Configurações. */
.revelar__gatilho::-webkit-details-marker {
  display: none;
}

.revelar__gatilho::after {
  content: "+";
  margin-left: var(--space-2);
  font-size: var(--text-title);
  line-height: 1;
}

.revelar[open] .revelar__gatilho::after {
  content: "–";
}

.revelar__gatilho:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.revelar__corpo {
  padding-top: var(--space-4);
}

/* Chips só de leitura (categorias que a empresa já tem). Mesma forma dos chips
   selecionáveis para se lerem como a mesma coisa, mas sem cursor nem estado de toque —
   não há o que tocar. Fundo de superfície, não marinho: já está pronto, não é seleção. */
.chips-categoria--estatico {
  list-style: none;
  padding: 0;
  margin: 0;
}

.chip-categoria--pronto {
  gap: var(--space-2);
  background: var(--color-surface);
  border-color: var(--color-hairline);
  color: var(--color-body);
  cursor: default;
}

.chip-categoria--pronto svg {
  width: 16px;
  height: 16px;
  color: var(--color-status-baixado-fg);
}

.acao-secundaria--centro {
  text-align: center;
}

/* Saída de etapa como link de texto: mais leve que .btn--ghost de propósito, para não
   virar uma terceira escolha com o mesmo peso da ação primária. Alvo de toque ainda
   cumpre --tap-min via padding vertical. */
.link-saida {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  color: var(--color-accent);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
}

/* Conteúdo de uma etapa do wizard. O resto do app é card branco sobre body cinza
   (.lista-config, .card-dia); as etapas eram o único lugar em que o formulário ficava
   solto direto no cinza, e o espaço que sobrava embaixo lia como página que não carregou
   em vez de fim de conteúdo. O card dá borda ao conteúdo e devolve o rodapé ao fundo. */
.etapa {
  background: var(--color-canvas);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-5) var(--space-4);
}

/* Só o último grupo DO FORMULÁRIO perde a margem (o gap do .form já separa do botão).
   Sem o `.form >`, o seletor casava também com a <section> "Já criadas" — que é o último
   elemento do seu próprio tipo — e as duas listas de chips colavam uma na outra. */
.etapa .form > .grupo-campos:last-of-type {
  margin-bottom: 0;
}

/* Saída do wizard: presente, mas subordinada — centralizada, discreta e depois de toda
   ação da etapa, para ser encontrável sem competir com o "Continuar". button_to gera um
   <form>, daí o reset de margem. */
.onboarding-rodape {
  display: flex;
  /* Mesmo eixo dos campos e do "Continuar" -- ver .acao-inline. */
  justify-content: flex-start;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-8);
}

.onboarding-rodape form {
  margin: 0;
}

.onboarding-rodape .link-saida {
  background: none;
  border: none;
  color: var(--color-muted);
  font-size: var(--text-label);
  cursor: pointer;
}

/* Chip de categoria já existente: sem borda de pílula e em superfície, para não se ler
   como um controle tocável ao lado dos chips do catálogo, que são idênticos em forma. */
.chip-categoria--pronto {
  border-color: transparent;
  background: var(--color-surface-2);
}

/* Campo travado (o "Nome" que vem do cadastro). Sem regra própria ele era pixel a pixel
   igual a um campo editável, e o primeiro campo da primeira tela do produto convidava a
   tocar em algo que não responde. Superfície em vez de canvas + cursor bloqueado dizem
   "só leitura" antes da linha de ajuda precisar explicar. */
.field input:disabled,
.field select:disabled,
.field textarea:disabled {
  background: var(--color-surface-2);
  border-color: var(--color-hairline);
  color: var(--color-body);
  cursor: not-allowed;
}


/* Itens já existentes exibidos como leitura (categorias da empresa). Sem pílula, sem
   borda: forma de texto, não de controle. */
.lista-pronta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  list-style: none;
  padding: 0;
  margin: 0;
}

.lista-pronta__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-body);
  color: var(--color-body);
}

.lista-pronta__item svg {
  width: 16px;
  height: 16px;
  color: var(--color-status-baixado-fg);
}

/* No escuro, --color-brand (#3f6bb8) sobre --color-brand-subtle-bg (#1a2740) dá ~3:1 —
   o ✓ some dentro do próprio selo. O acento claro (#6ea8ff) sobre o mesmo fundo dá ~6:1.
   A tabela de contraste do DESIGN.md audita marca contra canvas, não contra a tinta
   sutil da marca; este par ficou de fora e só aparece aqui. */
:root[data-theme="dark"] .onboarding-concluido__selo {
  color: var(--color-accent);
}

/* <legend> de um grupo de rádios que faz papel de rótulo de campo (Segmento). Casa com
   .field label; o reset do <fieldset> em si vem da regra global lá em cima. */
.field__legenda {
  padding: 0;
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  color: var(--color-ink-soft);
}

/* Separa as saídas globais (pausar / sair da conta) das ações da etapa logo acima: sem a
   linha, "pular esta etapa" e "sair da conta" viravam a mesma pilha de links parecidos e
   um toque errado custava a sessão. */
.onboarding-rodape {
  border-top: 1px solid var(--color-hairline);
  padding-top: var(--space-4);
}

/* Grupo de chips que falhou na validação. Paridade com `.field input[aria-invalid]`: o
   banner diz "confira os campos destacados", então o grupo precisa estar destacado — sem
   isto o Segmento errado era só uma linha vermelha embaixo de cinco pílulas iguais. */
.chips-categoria--invalido {
  border: 1px solid var(--color-value-negativo);
  border-radius: var(--radius-card);
  padding: var(--space-3);
}

/* No escuro, --color-hairline-strong (#383838) sobre o canvas quase preto some: o
   .btn--ghost ficava como texto solto, sem parecer botão. A borda sobe para um cinza que
   se lê contra o fundo escuro sem virar uma segunda ação de peso igual à primária. */
:root[data-theme="dark"] .btn--ghost {
  border-color: var(--color-muted);
}

/* Ação secundária que não deve imitar a silhueta do botão primário: largura automática,
   centrada, fora do fluxo de largura cheia do .form. */
.acao-inline {
  display: flex;
  /* Esquerda, não centro: com o "Continuar" em pílula alinhada aos campos, qualquer
     ação centralizada abaixo dele quebrava o eixo único da tela. Tudo no wizard
     alinha à esquerda. */
  justify-content: flex-start;
}

/* .nota-obrigatorio saiu junto com as três linhas "todos os campos são obrigatórios" que
   abriam Perfil, Empresa e Cartão. A convenção que ela anunciava continua valendo (campo
   sem marca é obrigatório, exceção leva "(opcional)" no rótulo) — só não precisa ser dita
   em prosa: os campos são `required`, o browser barra o submit e aponta qual faltou. Era
   uma linha de texto por tela para informar o caso que nunca acontece. */

/* === Identidade do onboarding (piloto: etapa Cartão) ============================== */

/* Fraunces só onde a marca fala: wordmark e título de etapa. Corpo continua na pilha
   de sistema — serifada em texto corrido de formulário atrapalha, e o objetivo é UMA
   voz de display, não redecorar o app. woff2 subset latin, 35KB, self-hosted (CSP e
   offline do PWA não permitem fonte de CDN). */
@font-face {
  font-family: "Fraunces";
  src: url("/assets/fraunces-600-173bdde1.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* Wordmark: o produto assina a tela com o logo em traço (SVG inline, `shared/_wordmark`).
   Minúsculo de propósito — assinatura, não banner. Altura em em para acompanhar o corpo;
   currentColor herda a tinta do contexto (marinho no claro, branco na faixa-herói). */
.wordmark {
  display: block;
  height: 1.4em;
  width: auto;
  color: var(--color-brand);
  margin-bottom: var(--space-1);
}

/* Título de etapa com voz. Fraunces 600 num corpo maior que --text-title: é o único
   elemento de display da tela, então pode ocupar esse espaço sozinho. */
/* h1.titulo-display: sem o h1, .page__header h1 (mais específico) vencia o font-size. */
h1.titulo-display {
  font-family: "Fraunces", Georgia, serif;
  /* Uma linha sempre: título quebrado ao meio perde a voz. Fluido com o viewport e
     teto no tamanho de desktop; o piso cobre 320px. Título novo tem que caber numa
     linha a 360px -- se não couber, encurta o texto, não a fonte. */
  white-space: nowrap;
  font-size: clamp(1.125rem, 6.4vw, 1.625rem);
  font-weight: 600;
  line-height: 1.2;
  color: var(--color-ink);
}

/* O dado vira objeto: cartão cadastrado renderiza como mini-cartão físico, não como
   linha de texto. Proporção real de cartão (1.586:1), fundo marinho da marca, apelido
   em cima, os dois dias no rodapé — a tela que pede um cartão mostra um cartão. */
.cartoes-mini {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  list-style: none;
  padding: 0;
  margin: 0;
}

.cartao-fisico {
  /* Canto superior direito reto: eco do gesto assimétrico da faixa-herói. */
  border-radius: var(--radius-md) 0 var(--radius-md) var(--radius-md);
  aspect-ratio: 1.586;
  width: min(230px, 100%);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: var(--space-4);
  background: linear-gradient(135deg, var(--color-brand), var(--color-brand-active));
  color: var(--color-on-brand);
  animation: cartao-entra var(--duration-base, 200ms) var(--ease-standard) both;
}

.cartao-fisico__nome {
  font-family: "Fraunces", Georgia, serif;
  font-size: var(--text-value-md);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.cartao-fisico__dias {
  font-size: var(--text-caption);
  opacity: 0.85;
  letter-spacing: 0.02em;
}

/* Único movimento da tela: o mini-cartão entra deslizando quando a página volta com o
   cartão salvo. Nada mais anima. */
@keyframes cartao-entra {
  from { opacity: 0; transform: translateY(6px); }
}

@media (prefers-reduced-motion: reduce) {
  .cartao-fisico { animation: none; }
}

/* Etapa sem moldura: mata o card cinza-admin que embrulhava o formulário. O conteúdo
   termina onde termina — hairline no lugar de caixa. */
.etapa--quieta {
  background: transparent;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
}

/* Botão-pílula: quebra a silhueta "retângulo azul full-width" do form genérico. Largura
   do conteúdo com respiro mínimo, cantos plenos, alinhado à esquerda com os campos. */
.btn--pilula {
  width: auto;
  min-width: 60%;
  align-self: flex-start;
  border-radius: var(--radius-full);
}

/* Faixa-herói: a tela ganha zonas em vez de ser um documento
   branco corrido — marca e navegação vivem numa faixa marinho, o conteúdo vem depois.
   Nasceu no wizard e passou a se chamar pelo que é, não por onde nasceu: as portas de
   entrada (login, cadastro, recuperar senha, aceite de convite) usam a mesma faixa.
   Continua exclusiva de wizard e portas de entrada — nenhuma tela do app logado
   ganha marinho novo.
   Margens negativas cancelam o padding do .page para a faixa sangrar até as bordas da
   coluna; o safe-area cobre o notch no PWA. */
.faixa-heroi {
  background: linear-gradient(160deg, var(--color-brand), var(--color-brand-active));
  color: var(--color-on-brand);
  margin: calc(-1 * var(--space-4)) calc(-1 * var(--space-4)) var(--space-6);
  padding: calc(var(--space-6) + env(safe-area-inset-top, 0px)) var(--space-4) var(--space-8);
  /* Canto direito reto, esquerdo redondo: o gesto assimétrico da identidade. O
     mini-cartão ecoa o mesmo canto -- repetido, lê como intenção; sozinho, leria
     como defeito. */
  border-radius: 0 0 0 var(--radius-lg);
}

.faixa-heroi .page__header {
  margin-bottom: var(--space-5);
}

/* Sem stepper embaixo (portas de entrada), o respiro do cabeçalho não separa nada —
   só alonga a faixa. O :has cobre isso sem precisar de classe modificadora na view. */
.faixa-heroi:not(:has(.stepper)) .page__header {
  margin-bottom: 0;
}

/* Linha de contexto dentro da faixa: o nome da empresa no aceite de convite é
   interpolação sem limite de tamanho e não cabe num h1 de uma linha só. */
.faixa-heroi__contexto {
  font-size: var(--text-label);
  color: rgba(255, 255, 255, 0.85);
  margin-top: var(--space-2);
  overflow-wrap: anywhere;
}

.faixa-heroi .wordmark,
.faixa-heroi .titulo-display {
  color: var(--color-on-brand);
}

.faixa-heroi .wordmark {
  opacity: 0.85;
}

/* Stepper sobre marinho: mesmos três estados por forma, tintas trocadas para a faixa.
   Alfa em vez de tons novos — herda qualquer ajuste futuro do marinho de graça. */
.faixa-heroi .stepper {
  margin-bottom: 0;
}

.faixa-heroi .stepper__link {
  color: rgba(255, 255, 255, 0.62);
}

.faixa-heroi .stepper__link--feita,
.faixa-heroi .stepper__link--pulada {
  color: rgba(255, 255, 255, 0.8);
}

.faixa-heroi .stepper__link--atual {
  color: #fff;
}

.faixa-heroi .stepper__link::before {
  background: rgba(255, 255, 255, 0.28);
}

.faixa-heroi .stepper__link--feita::before,
.faixa-heroi .stepper__link--atual::before {
  background: #fff;
}

.faixa-heroi .stepper__link--pulada::before {
  background: repeating-linear-gradient(90deg, #fff 0 5px, transparent 5px 9px);
}

.faixa-heroi .stepper__link:focus-visible {
  outline-color: #fff;
}

/* O cartão-preview vem logo depois da faixa, inteiro sobre o fundo. Já cavalgou a borda
   (metade no marinho, metade no fundo, estilo app de banco) — marinho sobre marinho lia
   como elemento desalinhado, não como sobreposição intencional. Sombra própria mantém o
   "objeto sobre a mesa". */
.cartao-fisico--destaque {
  box-shadow: 0 8px 22px rgba(17, 37, 74, 0.28);
}

/* Cartões já salvos: miniatura, dois por linha (metade da coluna menos o gap),
   proporção de cartão intacta. O preview é o objeto da vez; os salvos são histórico. */
.cartoes-mini .cartao-fisico {
  width: calc(50% - var(--space-3) / 2);
  padding: var(--space-3);
}

.cartoes-mini .cartao-fisico__nome {
  font-size: var(--text-label);
}

.cartoes-mini .cartao-fisico__dias {
  font-size: 0.6875rem;
}

/* Zona de desfazer (tela de edição). Um bloco à parte, depois de salvar: linha de corte no
   topo e fundo recuado marcam que dali para baixo a ação não é rotina. Fundo cinza, não
   vermelho — o vermelho fica no rótulo do botão, como o resto do app (DESIGN.md). */
.zona-perigo {
  margin-top: var(--space-10);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-hairline);
}

.zona-perigo__titulo {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}

/* Lado a lado enquanto couber: botões de largura automática deixam claro que são ações
   pontuais, não o próximo passo do formulário. */
.zona-perigo__acoes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

/* button_to gera um <form> por botão — sem isto cada um vira bloco de largura cheia e a
   fileira volta a ser a pilha que se queria desfazer. */
.zona-perigo__acoes form {
  display: contents;
}

/* Diálogo do produto. <dialog> nativo: foco preso, Escape e devolução de foco vêm de
   graça, sem uma linha de JS de acessibilidade. Canto direito reto no topo, o mesmo gesto
   assimétrico da faixa-herói — elemento novo de marca repete a assinatura. */
.dialogo {
  /* O reset global zera as margens e leva junto o `margin: auto` que o <dialog> nativo
     usa para se centrar — sem isto ele encosta no canto superior esquerdo e sangra pela
     borda, que lê como defeito de renderização, não como diálogo. */
  margin: auto;
  width: min(420px, calc(100vw - var(--space-8)));
  border: none;
  border-radius: var(--radius-lg) 0 var(--radius-lg) var(--radius-lg);
  padding: var(--space-6) var(--space-5);
  background: var(--color-canvas);
  color: var(--color-ink);
  box-shadow: var(--shadow-modal);
}

.dialogo::backdrop {
  background: rgba(17, 37, 74, 0.55);
}

/* Sem Fraunces: a serifada mora em exatamente dois lugares (título de etapa e nome do
   mini-cartão — o wordmark virou logo em traço) e um título de diálogo não é nenhum deles. O canto reto já carrega
   a assinatura aqui — o DESIGN.md autoriza o gesto, não a fonte. */
.dialogo__titulo {
  font-size: var(--text-title);
  font-weight: var(--weight-medium);
  margin-bottom: var(--space-2);
}

/* A consequência não é título: em corpo de texto ela se lê como frase, e o título volta a
   ser a pergunta curta que a pessoa responde. */
.dialogo__detalhe {
  color: var(--color-body);
  font-size: var(--text-body);
  line-height: 1.5;
}

/* Confirmar à direita, cancelar à esquerda — a ordem do DOM não muda: o primeiro botão do
   <form method="dialog"> é o default do Enter e tem que continuar sendo o cancelar. */
.dialogo__acoes {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

.dialogo__acoes .btn {
  width: auto;
}

/* `display: flex` acima empata em especificidade com a regra `[hidden]` do UA stylesheet e
   vence por vir depois — sem isto o diálogo em estado de carregamento reserva a faixa dos
   botões que escondeu. Mesmo empate já documentado em `.field[hidden]` e `.btn[hidden]`. */
.dialogo__acoes[hidden] {
  display: none;
}

/* Cartão editável (tela de Cartões, U5): o mini-cartão É o alvo de toque que abre a
   edição. Na lista do wizard os cartões são histórico; aqui são objetos que se corrigem.
   <details> nativo, sem JS — o mesmo padrão de disclosure já usado no app. */
.cartoes-mini--editaveis {
  flex-direction: column;
  flex-wrap: nowrap;
}

.cartao-editavel__resumo {
  list-style: none;
  cursor: pointer;
}

.cartao-editavel__resumo::-webkit-details-marker { display: none; }

.cartao-editavel__resumo:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Dentro do <summary> o mini-cartão é um <span>: precisa voltar a ser bloco para manter
   a proporção física. A largura vem de `.cartoes-mini .cartao-fisico` (metade da coluna):
   dar largura cheia aqui inflava o cartão a 320x200 com um vão morto no meio, e dois
   objetos grandes — o salvo e o preview do formulário — brigavam pela mesma tela. */
.cartao-editavel__resumo .cartao-fisico {
  display: flex;
}

/* Incompleto não é erro: sem os dias, o cartão perde a cor cheia da marca e fica com a
   silhueta pontilhada de "falta preencher". A cor sozinha não sinaliza — o aviso de texto
   abaixo diz o que falta e o que fazer. */
.cartao-fisico--incompleto {
  background: var(--color-surface-2);
  color: var(--color-body);
  border: 1px dashed var(--color-hairline-strong);
}

.cartao-editavel__aviso {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-label);
  color: var(--color-status-pendente-fg);
}

.cartao-editavel__form {
  padding: var(--space-4) 0 var(--space-6);
}

/* Campos quietos (wizard e portas de entrada): superfície preenchida em vez de caixa com
   borda — a moldura de todos os lados é o que mais grita "formulário". A borda volta no
   foco, onde ela informa em vez de decorar. `.form--quieta` é a mesma coisa aplicada
   direto num <form>, onde não há o contêiner de etapa do wizard. */
.etapa--quieta .field input,
.form--quieta .field input {
  background: var(--color-surface-2);
  border-color: transparent;
}

.etapa--quieta .field input:focus,
.form--quieta .field input:focus {
  background: var(--color-canvas);
  border-color: var(--color-accent);
  outline: none;
}

/* === Toggle de tema (sol/lua) ==================================================== */
/* Discreto, fixo no canto superior direito. Fora do <main> pra não empurrar layout.
   Ícone reflete o tema atual: sol no claro, lua no escuro. currentColor herda o ink,
   então lê bem sobre canvas claro e escuro; sobre a faixa-herói marinho o botão fica
   com fundo próprio pra não sumir. */
.tema-toggle {
  position: fixed;
  top: max(var(--space-3), env(safe-area-inset-top));
  right: max(var(--space-3), env(safe-area-inset-right));
  z-index: 50;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  color: var(--color-ink);
  background: color-mix(in srgb, var(--color-canvas) 70%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background var(--duration-fast) var(--ease-standard);
}

.tema-toggle:hover {
  background: var(--color-surface-pressed);
}

.tema-toggle svg {
  width: 20px;
  height: 20px;
}

/* Mostra só o ícone do tema vigente. */
.tema-toggle .tema-toggle__lua { display: none; }
:root[data-theme="dark"] .tema-toggle .tema-toggle__sol { display: none; }
:root[data-theme="dark"] .tema-toggle .tema-toggle__lua { display: block; }

/* ── Páginas públicas: termos, privacidade, ajuda (plano 003, U3/U5) ────────────── */

/* Texto corrido longo — o único lugar do app com prosa de verdade. Medida de linha
   limitada porque a coluna do app tem 480px mas o desktop esparrama. */
.prosa-legal {
  max-width: 62ch;
  color: var(--color-body);
  font-size: var(--text-body);
  line-height: var(--leading-loose);
}

.prosa-legal h2 {
  font-size: var(--text-title);
  color: var(--color-ink);
  margin: var(--space-8) 0 var(--space-3);
}

.prosa-legal h3 {
  font-size: var(--text-body);
  font-weight: var(--weight-bold);
  color: var(--color-ink-soft);
  margin: var(--space-5) 0 var(--space-2);
}

.prosa-legal p,
.prosa-legal ul {
  margin-bottom: var(--space-4);
}

.prosa-legal ul {
  padding-left: var(--space-5);
  list-style: disc;
}

.prosa-legal li + li {
  margin-top: var(--space-2);
}

.prosa-legal strong {
  color: var(--color-ink);
}


.rodape-publico {
  margin: var(--space-10) calc(-1 * var(--space-4)) calc(-1 * var(--space-4));
  padding: var(--space-5) var(--space-4) var(--space-8);
  /* Canvas sobre o surface do body — o mesmo par que separa card de fundo no resto do
     app. Surface aqui seria invisível: o body já é surface. */
  background: var(--color-canvas);
  font-size: var(--text-label);
  /* Canto ESQUERDO redondo, direito reto: o gesto da faixa-herói espelhado num elemento
     novo (DESIGN.md princípio 7) — a faixa abre a página com o corte embaixo, o rodapé a
     fecha com o mesmo corte em cima. Precisa de superfície própria: sem fundo, o raio não
     desenha nada e o comentário viraria uma afirmação sobre pixel que não existe. */
  border-radius: var(--radius-lg) 0 0 0;
}

.rodape-publico nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin-bottom: var(--space-3);
}

/* Alvo de toque de 44px sem inflar a altura visual da linha do rodapé. */
.rodape-publico nav a,
.rodape-legal a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
}

.rodape-legal {
  margin: var(--space-6) 0 var(--space-4);
  text-align: center;
  line-height: var(--leading-loose);
}

/* Frase de aceite dos termos, logo acima do "Criar conta". */
.aceite-termos {
  font-size: var(--text-label);
  line-height: var(--leading-base);
  margin: var(--space-4) 0 calc(-1 * var(--space-2));
}

/* Banner de consentimento (GA4, R2/R3). Barra fixa de ponta a ponta — sem canto para
   arredondar, a assinatura "canto direito reto" já é o formato natural do elemento. */
.consentimento-banner[hidden] {
  display: none;
}

.consentimento-banner {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--color-surface);
  border-top: 1px solid var(--color-hairline);
  box-shadow: var(--shadow-nav);
}

.consentimento-banner__texto {
  flex: 1 1 320px;
  font-size: var(--text-label);
  line-height: var(--leading-base);
  color: var(--color-body);
  margin: 0;
}

.consentimento-banner__acoes {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* ===== Impressão (fechamento do mês) ===== */
/* Ctrl+P em /fechamento precisa virar PDF legível: sem navegação, sem botão, sem o dark
   mode vazando pro papel. O dark mode em si já não casa em impressão (ver
   `@media not print` em "Dark mode" acima) — a cascata cai sozinha pro :root claro, sem
   precisar redeclarar nenhuma cor aqui. Só sobra resetar o que NÃO é inversão de tema:
   sombra ("remover sombras" é requisito à parte de U3). */
@media print {
  :root {
    --shadow-card: none; --shadow-nav: none; --shadow-modal: none;
  }

  body {
    background: #fff;
    color: #000;
  }

  /* Sem navegação no papel: bottom-nav, os dois tipos de nav de topo (stepper de mês e
     abas Resumo/Lançamentos), o toggle de tema, o flash flutuante e qualquer botão —
     incluindo o próprio botão "Imprimir" que a view acrescenta. */
  .bottom-nav,
  .seletor-mes,
  .segmented,
  .tema-toggle,
  .sino,
  #flash,
  button,
  .btn,
  input[type="submit"],
  input[type="button"] {
    display: none !important;
  }

  /* A coluna de 480/720px é a medida certa pra tela, mas encolhe a folha impressa sem
     necessidade — e a folga reservada pra bottom-nav não faz sentido sem ela no papel. */
  .page {
    max-width: none;
    margin-inline-start: 0;
    padding: 0;
    min-height: 0;
  }

  /* Na tela, overflow-x protege a página de vazar na horizontal (DESIGN.md L184); na
     impressão o navegador já pagina, e o overflow contido é o que cortaria coluna. */
  .tabela-relatorio-wrap {
    overflow-x: visible;
    border: none;
    box-shadow: none;
  }

  /* position: sticky some junto com o overflow acima — no motor de paginação do Chrome
     ele reintroduz cabeçalho sobreposto/deslocado entre páginas. thead já repete no topo
     de cada página impressa por padrão (display: table-header-group), sem precisar disto. */
  .tabela-relatorio thead th {
    position: static;
  }

  .tabela-relatorio tr {
    break-inside: avoid;
  }
}

/* ===== Tela de planos: a porta do plano pago ===== */
/* Exceção deliberada ao "faixa-herói só em wizard e portas de entrada": planos é a
   porta de entrada do plano pago, a única tela do app logado que vende — recebe o
   gesto de marca inteiro (marinho, Fraunces, canto direito reto), não um marinho novo. */

.faixa-heroi .page__header__contexto {
  color: rgba(255, 255, 255, 0.7);
}

.beneficios-plano {
  list-style: none;
  display: grid;
  gap: var(--space-3);
  padding: 0;
  margin-bottom: var(--space-8);
}

.beneficios-plano li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--color-body);
  font-size: var(--text-body);
  line-height: var(--leading-base);
}

.beneficios-plano svg {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--color-accent);
  margin-top: 1px;
}

/* Cartões de ciclo: um radio nativo por cartão (estado sem JS, teclado e leitor de
   tela de graça — mesmo padrão de .chip-categoria). A cor de seleção é o marinho da
   marca; nenhuma terceira cor entra na tela. */
.opcoes-ciclo {
  display: grid;
  gap: var(--space-3);
}

.opcao-ciclo {
  position: relative; /* âncora do input visually-hidden (absolute) */
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  border: 1px solid var(--color-hairline-strong);
  border-radius: var(--radius-card);
  background: var(--color-canvas);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-standard),
              background var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}

.opcao-ciclo:has(input:checked) {
  border-color: var(--color-brand);
  background: var(--color-brand-subtle-bg);
  box-shadow: var(--shadow-card);
}

.opcao-ciclo:has(input:focus-visible) {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Assinatura da marca no destaque: mesmo canto direito reto da faixa-herói. */
.opcao-ciclo--recomendada {
  border-end-end-radius: 0;
}

.opcao-ciclo__cabecalho {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.opcao-ciclo__rotulo {
  font-weight: var(--weight-medium);
  color: var(--color-ink);
}

.opcao-ciclo__preco {
  font-size: var(--text-title);
  font-weight: var(--weight-bold);
  color: var(--color-ink);
}

.opcao-ciclo__periodo {
  font-size: var(--text-label);
  color: var(--color-muted);
}

.opcao-ciclo__equivalente {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}

@media (min-width: 760px) {
  .beneficios-plano {
    grid-template-columns: 1fr 1fr;
    column-gap: var(--space-8);
  }

  .opcoes-ciclo {
    grid-template-columns: repeat(3, 1fr);
    align-items: start;
  }
}

/* Acima do rail o .form já tem teto de 40rem; os benefícios (fora do form) ganham o
   mesmo teto pra coluna de venda ler como uma coisa só, não como página esticada. */
@media (min-width: 900px) {
  .beneficios-plano { max-width: 40rem; }
}

/* A linha de economia pode quebrar entre as duas metades, nunca no meio de um valor. */
.opcao-ciclo__equivalente span {
  white-space: nowrap;
}

/* Bolder (registro product): amplificação em hierarquia, não em efeito. */

/* A porta de venda fala mais alto que uma etapa de wizard: mesmo Fraunces, corpo maior.
   Continua numa linha a 360px — regra de sempre: não couber, encurta o texto. */
.faixa-heroi--planos .titulo-display {
  font-size: clamp(1.375rem, 7.5vw, 2rem);
}

/* A única linha de preço da faixa não é nota de rodapé: corpo de texto, não label.
   Amplificação em hierarquia, não em efeito — mesma régua do resto do bloco. */
.faixa-heroi--planos .faixa-heroi__contexto {
  font-size: var(--text-body);
}

.faixa-heroi--planos .faixa-heroi__contexto strong {
  color: #fff;
  font-weight: var(--weight-bold);
}

/* O preço do ciclo recomendado é o herói da oferta: escala de display, não de título.
   Os vizinhos ficam menores de propósito — contraste vende a comparação. */
.opcao-ciclo--recomendada .opcao-ciclo__preco {
  font-size: var(--text-display);
  letter-spacing: -0.02em;
  line-height: var(--leading-tight);
}

/* ── Central de notificações (plano 008) ──────────────────────────────────────── */

/* Mora na linha do h1 de Resumo e Lançamentos (as duas telas onde a central faz sentido),
   travado no fluxo: rola junto com a página, nunca sobrepõe conteúdo. Mesmo corpo visual
   de .tema-toggle. */
.page__header--com-sino {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.sino {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  color: var(--color-ink);
  background: color-mix(in srgb, var(--color-canvas) 70%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background var(--duration-fast) var(--ease-standard);
}

.sino:hover {
  background: var(--color-surface-pressed);
}

.sino svg {
  width: 20px;
  height: 20px;
}

/* Sem novidade: traço vazado. */
.sino svg path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Com novidade: o glifo preenche. A mudança é de FORMA, não só de cor — sobrevive
   ao daltonismo e ao fundo marinho, onde recolorir de azul seria marinho sobre marinho
   (DESIGN.md, anti-padrão). O botão nunca fica sobre o marinho direto (KTD7): a pílula
   clara de fundo já resolve o contraste em qualquer tela, sem a variante --sobre-heroi. */
.sino--novo {
  color: var(--color-brand);
}

.sino--novo svg path.preenche {
  fill: currentColor;
}

/* ===== Convite de Telegram (Você + topo da Central de Notificações) ===== */
/* Faixa marinho: mesmo gesto de "porta de venda" que o DESIGN.md reserva à tela de planos
   — segunda exceção deliberada, registrada lá. Canto direito reto = assinatura de marca.
   ponytail: duas colunas fixas em vez de container query — o mesmo card cabe na coluna de
   "Você" e nos 380px do painel do sino sem uma segunda regra. */
.convite-telegram {
  display: grid;
  grid-template-columns: 38px 1fr;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  background: linear-gradient(160deg, var(--color-brand), var(--color-brand-active));
  border-radius: var(--radius-lg);
  border-end-end-radius: 0;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}
.convite-telegram__marca {
  width: 38px; height: 38px; border-radius: var(--radius-full);
  display: grid; place-items: center;
  background: rgb(255 255 255 / .16); color: var(--color-on-brand);
}
.convite-telegram__marca svg { width: 21px; height: 21px; fill: currentColor; }
.convite-telegram h2 { font-size: var(--text-value-md); font-weight: var(--weight-medium); color: var(--color-on-brand); line-height: var(--leading-tight); }
.convite-telegram__texto p { font-size: var(--text-label); color: var(--color-on-brand); line-height: var(--leading-base); }
.convite-telegram .btn { grid-column: 2; justify-self: start; }
/* hex fixo: a faixa é marinho nos dois temas, e --color-brand no escuro (#3f6bb8) sobre
   branco não bate 4.5:1 de contraste. */
.convite-telegram .btn--primary { background: #ffffff; color: #1e3a6e; }
.convite-telegram .btn--primary:hover { background: #eef2fb; }
.convite-telegram__como { grid-column: 2; font-size: var(--text-caption); color: rgb(255 255 255 / .78); }
.convite-telegram__como summary { cursor: pointer; }
/* A régua de .motivo é `2px solid var(--color-brand)` (linha 3867) — marinho sobre marinho
   some. Vira branco alfa; o gesto da régua fica, a cor acompanha o fundo. */
.convite-telegram .motivo { font-size: var(--text-caption); color: rgb(255 255 255 / .78); line-height: var(--leading-base); margin: var(--space-1) 0 0; border-inline-start-color: rgb(255 255 255 / .45); }

/* ===== Item de notificação ===== */
.notif-dia { background: var(--color-canvas); border-radius: var(--radius-card); box-shadow: var(--shadow-card); margin-bottom: var(--space-3); overflow: hidden; }
.notif-dia__titulo { font-size: var(--text-label); font-weight: var(--weight-medium); color: var(--color-muted); padding: var(--space-3) var(--space-4) var(--space-2); }
.notif { display: grid; grid-template-columns: 34px 1fr auto; gap: var(--space-3); align-items: start; width: 100%; text-align: left; padding: var(--space-3) var(--space-4); border-top: 1px solid var(--color-divisor); }
.notif:hover { background: var(--color-surface); }
.notif__icone { grid-row: span 2; width: 34px; height: 34px; border-radius: var(--radius-full); display: grid; place-items: center; }
.notif__icone svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.notif__icone--pendente { background: var(--color-status-pendente-bg); color: var(--color-status-pendente-fg); }
.notif__icone--baixado { background: var(--color-status-baixado-bg); color: var(--color-status-baixado-fg); }
.notif__icone--comprometido { background: var(--color-status-comprometido-bg); color: var(--color-status-comprometido-fg); }
.notif__icone--neutro { background: var(--color-status-neutro-bg); color: var(--color-status-neutro-fg); }
.notif__titulo { font-size: var(--text-body); color: var(--color-ink); line-height: var(--leading-base); }
.notif__meta { font-size: var(--text-label); color: var(--color-body); margin-top: 2px; font-variant-numeric: tabular-nums; }
.notif__quando { font-size: var(--text-caption); color: var(--color-muted); font-variant-numeric: tabular-nums; white-space: nowrap; padding-top: 2px; }
/* Não lida: título em peso médio + ponto. Peso é o sinal primário, o ponto é redundância. */
.notif--nova .notif__titulo { font-weight: var(--weight-medium); }
.notif--nova .notif__quando::after { content: ""; display: block; width: 7px; height: 7px; border-radius: var(--radius-full); background: var(--color-brand); margin: var(--space-2) 0 0 auto; }

/* ===== Vazio ===== */
.vazio { text-align: center; padding: var(--space-12) var(--space-6); color: var(--color-body); }
.vazio__glifo { width: 56px; height: 56px; margin: 0 auto var(--space-4); border-radius: var(--radius-full); display: grid; place-items: center; background: var(--color-surface-2); color: var(--color-muted); }
.vazio__glifo svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.vazio h2 { font-size: var(--text-title); color: var(--color-ink); font-weight: var(--weight-medium); margin-bottom: var(--space-2); }
.vazio p { font-size: var(--text-body); max-width: 34ch; margin: 0 auto; line-height: var(--leading-loose); }

/* ===== Cabeçalho da página mobile ===== */
.pagina-cabeca { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-4) var(--space-4) var(--space-3); background: var(--color-surface); }
.pagina-cabeca h1 { font-size: var(--text-title); font-weight: var(--weight-bold); letter-spacing: -.01em; }
.pagina-cabeca__voltar { width: var(--tap-min); height: var(--tap-min); margin-left: calc(var(--space-3) * -1); display: grid; place-items: center; color: var(--color-ink); }
.pagina-cabeca__voltar svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.pagina-cabeca__acao { margin-left: auto; font-size: var(--text-label); color: var(--color-accent); font-weight: var(--weight-medium); padding: var(--space-2); }
/* button_to gera um <form> em volta do botão — o auto migra pra ele, senão o botão não
   vai para a direita (mesma armadilha de .zona-perigo__acoes form). */
.pagina-cabeca form { margin-left: auto; }
.pagina-cabeca__acao { margin-left: 0; }

/* Painel lateral (desktop). Canto direito RETO — a assinatura assimétrica do Numo
   (DESIGN.md, princípio 7), e a borda direita é onde o gesto cai natural. É um
   <dialog> nativo: o reset global zera o `margin: auto` que o UA usa pra centrar,
   então reposicionamos à mão — mesma armadilha já documentada em .dialogo.
   Nome `.painel-notif`, não `.painel`: o plano usava `.painel`, mas essa classe já
   existe pro card do gráfico/cartões do Resumo (`.painel--grafico`, `.resumo-grid >
   .painel`) — colidir aplicava `inset-block/height:100dvh/margin:auto` neles também
   e estourava a tela inteira na horizontal (achado pelo próprio teste de sistema). */
.painel-notif {
  margin: 0 0 0 auto;
  inset-block: 0;
  height: 100dvh;
  max-height: 100dvh;
  width: min(380px, 100vw);
  padding: 0;
  border: none;
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
  background: var(--color-canvas);
  color: var(--color-ink);
  box-shadow: var(--shadow-modal);
  overflow: hidden;
}

/* `display: flex` só com `[open]`: sem isso o author CSS pisa no
   `dialog:not([open]) { display: none }` do UA (origem author ganha de UA
   independente de especificidade) e o painel fechado continua no layout,
   interceptando clique em qualquer página com empresa ativa. */
.painel-notif[open] {
  display: flex;
  flex-direction: column;
}

.painel-notif::backdrop { background: rgba(17, 37, 74, 0.4); }

/* Abaixo de 900px este diálogo nunca abre (o sino navega) — mas se abrir por engano,
   não pode virar uma tira estreita colada na direita. */
@media (max-width: 899px) {
  .painel-notif { width: 100vw; border-radius: 0; }
}

.painel-notif__cabeca { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-5) var(--space-5) var(--space-4); border-bottom: 1px solid var(--color-hairline); }
.painel-notif__cabeca h2 { font-size: var(--text-title); font-weight: var(--weight-bold); letter-spacing: -.01em; }
/* O ✕ vive num <form method="dialog">: o auto migra pro form, senão o botão não vai
   para a direita (mesma armadilha do button_to em .pagina-cabeca acima). */
.painel-notif__cabeca form { margin-left: auto; }
.painel-notif__fechar { margin-left: 0; width: 32px; height: 32px; display: grid; place-items: center; border-radius: var(--radius-full); color: var(--color-muted); }
.painel-notif__fechar:hover { background: var(--color-surface-2); color: var(--color-ink); }
.painel-notif__fechar svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
/* Em produção quem rola é o <turbo-frame> — .painel__lista do mock não existe aqui. */
.painel-notif turbo-frame { flex: 1; overflow: auto; display: block; }
.painel-notif__carregando { padding: var(--space-6); color: var(--color-muted); text-align: center; }
.painel-notif__acao { font-size: var(--text-label); color: var(--color-accent); font-weight: var(--weight-medium); }
/* Dentro do painel o item não é um card sobre card — vira lista com divisor. */
.painel-notif .notif-dia { background: none; box-shadow: none; border-radius: 0; margin: 0; }
.painel-notif .notif-dia__titulo { padding-left: var(--space-5); padding-right: var(--space-5); }
.painel-notif .notif { padding-left: var(--space-5); padding-right: var(--space-5); }
.painel-notif__rodape { padding: var(--space-3) var(--space-5); border-top: 1px solid var(--color-hairline); text-align: center; }
.painel-notif__rodape form { display: contents; }

/* Segmentado de tema em "Você". Mesma linguagem do .segmented das abas, em escala de
   controle de linha — a opção ativa ganha canvas + sombra, as outras ficam na trilha. */
.segmentado { margin-left: auto; display: flex; background: var(--color-surface-2); border-radius: var(--radius-full); padding: 3px; }
.segmentado button { padding: 5px var(--space-3); border-radius: var(--radius-full); font-size: var(--text-label); color: var(--color-body); display: flex; align-items: center; gap: 6px; }
.segmentado button svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.segmentado button[aria-pressed="true"] { background: var(--color-canvas); color: var(--color-ink); box-shadow: var(--shadow-card); font-weight: var(--weight-medium); }

/* Par de links de download lado a lado, com espaço entre eles (mesmo padrão de
   .zona-perigo__acoes: flex + gap, sem componente compartilhado no design system). */
.acoes-modelo {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* Ação destrutiva na lista de configurações: mesma linha, cor de alerta no rótulo.
   button_to gera <form><button>: sem width 100% o botão encolhe até o texto e a linha
   destoa da largura das outras (o teste de 1440px mede exatamente isso). */
.linha-config--perigo { color: var(--color-value-negativo); }
button.linha-config { width: 100%; }

/* Coach marks. Sem overlay escurecendo a tela: o tour aponta, não interrompe.
   position: fixed (não absolute) — ver tour_controller.js#posicionar para o motivo. */
.tour__balao {
  position: fixed;
  z-index: 60;
  max-width: min(320px, calc(100vw - 24px));
  padding: var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  background: var(--color-canvas);
  box-shadow: var(--shadow-modal);
}

.tour__titulo {
  margin: 0 0 var(--space-2);
  font-weight: var(--weight-medium);
}

.tour__texto {
  margin: 0 0 var(--space-3);
  font-size: var(--text-body);
  color: var(--color-body);
}

.tour__acoes {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
}

/* Convite de instalação do PWA. Flutua logo acima da bottom-nav, discreto: informa e oferece
   saída, sem bloquear a tela. Sem animação por decisão de produto.
   z-index 40 fica acima do conteúdo e da bottom-nav (que não declara z-index e só depende da
   ordem do DOM) e abaixo de .tema-toggle (50) e do #flash (100). */
.convite-instalacao {
  position: fixed;
  inset-inline: var(--bottom-nav-gap);
  bottom: calc(var(--bottom-nav-espaco) + var(--bottom-nav-gap) + env(safe-area-inset-bottom, 0px));
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-card);
  background: var(--color-canvas);
  box-shadow: var(--shadow-modal);
}

.convite-instalacao__texto {
  margin: 0;
  font-size: var(--text-body);
  color: var(--color-body);
}

.convite-instalacao__acoes {
  display: flex;
  gap: var(--space-2);
  justify-content: flex-end;
}

/* Destaque do elemento apontado: anel, sem mudar o layout dele. */
.tour-destaque {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.tour[hidden] { display: none; }

/* Menu radial do FAB (v2 — substitui a lista vertical da v1). O <details> assume o lugar
   que o link do FAB ocupava no flex da bottom-nav; o <summary> é que carrega o visual, via
   .bottom-nav__item--fab (KD3). As duas opções abrem em arco acima do círculo, não em lista
   (KD1/KD2). Sem animação nesta entrega (KD4). */
.fab-dial {
  flex: 1;
  display: flex;
  position: relative;
}

.fab-dial > summary {
  flex: 1;
  list-style: none;       /* tira o triângulo padrão (Firefox) */
  cursor: pointer;
}

/* Safari/Chrome usam um pseudo-elemento próprio para o marcador. */
.fab-dial > summary::-webkit-details-marker { display: none; }

/* Ponto-âncora: centro horizontal do item (left: 50%) e centro vertical do círculo do FAB
   (top: 8px = -18px do ::before + metade dos 52px de diâmetro, application.css:2003-2012).
   Sem tamanho próprio — é só a origem de onde as duas bolinhas se afastam via transform. */
.fab-dial__opcoes {
  position: absolute;
  top: 8px;
  left: 50%;
}

.fab-dial__opcao {
  position: absolute;
  top: 0;
  left: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-brand);
  color: var(--color-on-brand);
  box-shadow: var(--shadow-nav);
  font-size: var(--text-caption);
  line-height: 1.1;
  text-align: center;
  text-decoration: none;
  z-index: 30;
}

/* 60° (convenção matemática, 0°=direita, sentido anti-horário) a partir do centro do FAB —
   acima e um pouco à direita. Raio de 64px, bolinha de 44px: os -22px finais recentram a
   bolinha (metade do próprio tamanho) sobre o ponto do arco, não sobre o canto. KD2. */
.fab-dial__opcao--texto {
  transform: translate(calc(64px * 0.5 - 22px), calc(-64px * 0.866 - 22px));
}

/* 120°, espelhado — acima e um pouco à esquerda. */
.fab-dial__opcao--foto {
  transform: translate(calc(64px * -0.5 - 22px), calc(-64px * 0.866 - 22px));
}

/* No rail (900px+) o FAB é uma pílula no topo de uma coluna, não um círculo — um arco não
   faz sentido nessa forma. As opções abrem em pilha vertical abaixo dele, dentro da coluna
   (mesma solução que a v1 usava para o único caso onde ela ainda se aplica). */
@media (min-width: 900px) {
  .fab-dial { flex: none; flex-direction: column; }

  .fab-dial__opcoes {
    position: static;
    top: auto;
    left: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-block-start: var(--space-2);
  }

  .fab-dial__opcao {
    position: static;
    transform: none;
    width: auto;
    height: auto;
    min-height: var(--tap-min);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-full);
  }
}

.convite-instalacao[hidden] { display: none; }
