/* ============================================================
   nfs.cx — Selo ultramar design system  (ADR-0013)
   Tokens → components. marca ≠ status: --brand is brand/interactive only;
   --ok / --warn / --crit are reserved for state.
   ============================================================ */

/* ---------- Self-hosted fonts (SIL OFL) ---------- */
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:500; font-display:swap; src:url('/fonts/space-grotesk-500.woff2') format('woff2'); }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:600; font-display:swap; src:url('/fonts/space-grotesk-600.woff2') format('woff2'); }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:700; font-display:swap; src:url('/fonts/space-grotesk-700.woff2') format('woff2'); }
@font-face { font-family:'Inter'; font-style:normal; font-weight:400; font-display:swap; src:url('/fonts/inter-400.woff2') format('woff2'); }
@font-face { font-family:'Inter'; font-style:normal; font-weight:500; font-display:swap; src:url('/fonts/inter-500.woff2') format('woff2'); }
@font-face { font-family:'Inter'; font-style:normal; font-weight:600; font-display:swap; src:url('/fonts/inter-600.woff2') format('woff2'); }
@font-face { font-family:'IBM Plex Mono'; font-style:normal; font-weight:400; font-display:swap; src:url('/fonts/ibm-plex-mono-400.woff2') format('woff2'); }
@font-face { font-family:'IBM Plex Mono'; font-style:normal; font-weight:500; font-display:swap; src:url('/fonts/ibm-plex-mono-500.woff2') format('woff2'); }

/* ---------- FOUT: metric-matched fallbacks ----------
   font-display:swap IS a flash of unstyled text by definition — it paints the fallback immediately,
   then swaps. That is the right trade (text is readable at once, never invisible), and the fix is
   NOT to change it to `optional`/`block`: `block` hides text for up to 3s, and `optional` means a
   slow first visit silently never gets the brand fonts at all.

   What actually hurt was the RE-LAYOUT on swap, which became obvious once the error-focus scroll
   started re-rendering the page (owner, 2026-08-14): the fallback has different metrics, so lines
   rewrap and the whole page shifts. These faces re-serve the local system font with its metrics
   scaled to match the webfont, so the fallback occupies the SAME space and the swap changes only
   the letterforms.

   ⚠ The numbers are MEASURED from the actual woff2 files in wwwroot/fonts against Segoe UI (the
   Windows system-ui), not copied from a generator — see the note above each. Re-measure if a font
   file is ever replaced; a stale size-adjust is worse than none, because it shifts text that used
   to be stable. Method: sum the advance widths of a sample string in both fonts at the same em
   size; size-adjust is webfont/fallback as a percentage. ascent/descent are hhea, as a % of upm.

   local() only — these must never trigger a download. A platform without the named font simply
   falls through to the next entry in the stack, exactly as before. */

/* Inter measured 27.774em vs Segoe UI 25.722em over a real pt-BR sentence ⇒ 107.98%.
   Inter hhea: asc 1984/2048 = 96.88%, desc 494/2048 = 24.12%, gap 0. */
@font-face {
  font-family:'Inter fallback';
  src:local('Segoe UI'), local('Helvetica Neue'), local('Arial');
  size-adjust:107.98%; ascent-override:96.88%; descent-override:24.12%; line-gap-override:0%;
}
/* Space Grotesk 700 measured 14.639em vs Segoe UI Bold 14.074em ⇒ 104.02%.
   Space Grotesk hhea: asc 984/1000, desc 292/1000, gap 0. */
@font-face {
  font-family:'Space Grotesk fallback';
  src:local('Segoe UI'), local('Helvetica Neue'), local('Arial');
  size-adjust:104.02%; ascent-override:98.40%; descent-override:29.20%; line-gap-override:0%;
}
/* IBM Plex Mono is monospace, so width follows the advance directly and no size-adjust is needed
   against another monospace; only the vertical metrics are overridden (asc 1025/1000, desc 275/1000
   — note the ascent exceeds the em, which is why leaving it to the fallback shifted baselines). */
@font-face {
  font-family:'IBM Plex Mono fallback';
  src:local('Consolas'), local('Menlo'), local('DejaVu Sans Mono');
  ascent-override:102.50%; descent-override:27.50%; line-gap-override:0%;
}

/* ---------- Tokens ---------- */
:root {
  --bg:#EEF0F7; --surface:#FFFFFF; --surface-2:#E7EAF4; --border:#DCDFEC;
  --text:#14162A; --text-dim:#565A72; --text-faint:#616680;
  --brand:#3A3FC4; --brand-ink:#2E33A8; --brand-soft:#E2E4F7; --on-brand:#FBFCFF;
  --ok:#15934F; --ok-ink:#0F7A40; --ok-soft:#DCF0E2;
  --warn:#B26A06; --warn-ink:#8A5205; --warn-soft:#F3E7CE;
  --crit:#C8203F; --crit-ink:#A2172F; --crit-soft:#F6DCE0;
  --stamp:rgba(58,63,196,.16);
  --shadow:0 1px 2px rgba(20,22,42,.05), 0 10px 30px -16px rgba(20,22,42,.25);

  /* Rail is a brand constant — deep ink in both themes */
  --rail-bg:#0A0D18; --rail-surface:#111634; --rail-text:#E8EAF3; --rail-dim:#8A90AE;
  --rail-border:#1E2440; --rail-active:#8A93FF; --rail-active-bg:#181E3E;

  /* The '… fallback' faces sit immediately after the webfont: they are the metric-matched
     re-serve of the local system font (see the @font-face block at the top), so the pre-swap
     paint occupies the same space as the post-swap one and the page does not rewrap. The plain
     system-ui/ui-monospace entries stay as the last resort for platforms that have none of the
     local() names. */
  --font-display:'Space Grotesk', 'Space Grotesk fallback', system-ui, sans-serif;
  --font-body:'Inter', 'Inter fallback', system-ui, -apple-system, sans-serif;
  --font-mono:'IBM Plex Mono', 'IBM Plex Mono fallback', ui-monospace, Menlo, Consolas, monospace;
  --radius:12px;
  /* Largura da coluna pública: o cabeçalho, o rodapé e a landing compartilham este valor para
     que as três faixas se alinhem. Constante de layout, não token de tema — não se redeclara
     nos outros três blocos. */
  --pub-max:1060px;
  /* Largura da shell autenticada (trilho + conteúdo). Maior que --pub-max porque inclui o trilho
     de 248px. Constante de layout como a de cima — não se redeclara nos blocos de tema. */
  --shell-max:1560px;

  /* The parts of a control the page cannot style: the <select> dropdown list, the date-picker
     popup, scrollbars, the file-input button, autofill. Without this they render light over a
     dark app (a white dropdown flashing open on the /notas filter bar). It must be declared in
     all four token blocks, exactly like a token. */
  color-scheme: light;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
  :root {
    --bg:#0B0E1A; --surface:#12162A; --surface-2:#171C33; --border:#242A46;
    --text:#E8EAF3; --text-dim:#9AA0BE; --text-faint:#8189A9;
    --brand:#8A93FF; --brand-ink:#AAB1FF; --brand-soft:#1B2145; --on-brand:#05060F;
    --ok:#37C98A; --ok-ink:#5FD9A2; --ok-soft:#123026;
    --warn:#F2B44E; --warn-ink:#F6C97A; --warn-soft:#372A10;
    --crit:#FB6A80; --crit-ink:#FF98A8; --crit-soft:#351320;
    --stamp:rgba(138,147,255,.15);
    --shadow:0 1px 2px rgba(0,0,0,.3), 0 18px 40px -22px rgba(0,0,0,.65);
  }
}
:root[data-theme="light"] {
  --bg:#EEF0F7; --surface:#FFFFFF; --surface-2:#E7EAF4; --border:#DCDFEC;
  --text:#14162A; --text-dim:#565A72; --text-faint:#616680;
  --brand:#3A3FC4; --brand-ink:#2E33A8; --brand-soft:#E2E4F7; --on-brand:#FBFCFF;
  --ok:#15934F; --ok-ink:#0F7A40; --ok-soft:#DCF0E2;
  --warn:#B26A06; --warn-ink:#8A5205; --warn-soft:#F3E7CE;
  --crit:#C8203F; --crit-ink:#A2172F; --crit-soft:#F6DCE0;
  --stamp:rgba(58,63,196,.16);
  --shadow:0 1px 2px rgba(20,22,42,.05), 0 10px 30px -16px rgba(20,22,42,.25);
  color-scheme: light;
}
:root[data-theme="dark"] {
  --bg:#0B0E1A; --surface:#12162A; --surface-2:#171C33; --border:#242A46;
  --text:#E8EAF3; --text-dim:#9AA0BE; --text-faint:#8189A9;
  --brand:#8A93FF; --brand-ink:#AAB1FF; --brand-soft:#1B2145; --on-brand:#05060F;
  --ok:#37C98A; --ok-ink:#5FD9A2; --ok-soft:#123026;
  --warn:#F2B44E; --warn-ink:#F6C97A; --warn-soft:#372A10;
  --crit:#FB6A80; --crit-ink:#FF98A8; --crit-soft:#351320;
  --stamp:rgba(138,147,255,.15);
  --shadow:0 1px 2px rgba(0,0,0,.3), 0 18px 40px -22px rgba(0,0,0,.65);
  color-scheme: dark;
}

/* ---------- Base ---------- */
* { box-sizing:border-box; }
/* Belt-and-braces: a decorative absolutely-positioned layer that escapes its containing block
   must never give the whole page a horizontal scrollbar. `clip` (not `hidden`) because `hidden`
   on <html>/<body> would break `position:sticky` for the rail and the mobile bar. */
html, body { margin:0; padding:0; overflow-x:clip; }
body {
  background:var(--bg); color:var(--text);
  font-family:var(--font-body); font-size:15px; line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
a { color:var(--brand-ink); text-decoration:none; }
a:hover { text-decoration:underline; }

/* ---------- Focus visibility (UX-REVIEW POL-1) ----------
   .btn and .theme-toggle carried their own :focus-visible; everything else fell back to the UA
   ring, which several browsers draw in a dark colour that is invisible against the rail. A
   keyboard user tabbing the nav simply lost track of where they were. Anything the rail or the
   drawer paints uses --rail-active for the same reason the wordmark re-maps there: the rail is a
   brand constant, so the page's --brand does not read against it. */
a:focus-visible, .nav-item:focus-visible, .tile-link:focus-visible, .row-link:focus-visible,
.logout-btn:focus-visible, .bundle-card:has(.bundle-radio:focus-visible), summary:focus-visible {
  outline:2px solid var(--brand); outline-offset:2px; border-radius:4px;
}
.rail a:focus-visible, .mobile-nav a:focus-visible, .mobile-bar a:focus-visible,
.rail .nav-item:focus-visible, .mobile-nav .nav-item:focus-visible,
.rail .logout-btn:focus-visible, .mobile-nav .logout-btn:focus-visible,
.auth-aside a:focus-visible {
  outline-color:var(--rail-active);
}

/* ---------- The hidden attribute means hidden (UX-REVIEW-2 ITEM-10) ----------
   `hidden` only maps to display:none in the UA stylesheet, so ANY author-origin `display` wins
   over it regardless of specificity — `.form-grid{display:grid}` displayed #boleto-fields as a
   grid while Pix was selected, and the JS setting el.hidden was a silent no-op. The codebase had
   dodged this per-component twice (.mobile-nav, .btn.copy-btn); this is the one mechanism instead.
   !important is correct here: it restores the attribute's own semantics against author display.
   Audited: every `hidden` in the app (boleto fields, #mobileNav, .copy-btn, the [data-passkey-*]
   blocks) wants display:none. */
[hidden] { display:none !important; }

/* ---------- Skip link (UX-REVIEW POL-2) ----------
   The rail is seven nav items plus a user block before the content starts, and the drawer puts
   the same list ahead of every page on a phone. Visually hidden until focused — the standard
   pattern; it must NOT be display:none, which would take it out of the tab order entirely. */
.skip-link {
  position:absolute; left:-9999px; top:0; z-index:100;
  background:var(--surface); color:var(--brand-ink); font-weight:600; font-size:.9rem;
  border:1px solid var(--brand); border-radius:0 0 var(--radius) 0;
  padding:12px 18px; text-decoration:none;
}
.skip-link:focus { left:0; outline:2px solid var(--brand); outline-offset:2px; }
.mono { font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
.eyebrow { font-size:.7rem; text-transform:uppercase; letter-spacing:.15em; font-weight:600; color:var(--text-faint); }

/* ---------- Shell ---------- */
/* 100vh on iOS Safari counts the collapsed URL bar, so a 100vh column is clipped behind the
   toolbar. dvh is the real viewport; the vh line stays first as the fallback. */
/* ⚠ A SHELL é o objeto centrado — trilho e conteúdo viajam juntos (decisão do dono, 2026-09-22).
   Sem o cap, o trilho fica colado na borda esquerda e a coluna de 1100px logo depois, então num
   monitor de 2560px toda a atividade acontece no terço esquerdo e sobram ~1.200px mortos à
   direita: o próprio dono tinha aprendido a NÃO maximizar o navegador e a arrastar a janela para
   o centro. Isso é o relatório de bug, não uma preferência estética.
   O --shell-max é maior que o da área pública (--pub-max) porque aqui o trilho de 248px entra na
   conta: 1560 - 248 = 1312 para o conteúdo, que ainda acomoda a coluna de 1100px com folga.
   ⚠ `position:sticky` do trilho continua valendo dentro do flex centrado — não troque por
   `fixed` "para consertar", que o descolaria da shell centrada e o jogaria na borda de novo. */
.shell { display:flex; min-height:100vh; min-height:100dvh; max-width:var(--shell-max); margin:0 auto; }
.rail {
  width:248px; flex-shrink:0; background:var(--rail-bg); color:var(--rail-text);
  border-right:1px solid var(--rail-border); display:flex; flex-direction:column;
  padding:20px 14px; gap:20px; position:sticky; top:0;
  height:100vh; height:100dvh; overflow:hidden;
}
.brand { padding:6px 10px; }

/* ---------- Wordmark (BrandWordmark.razor) ----------
   Typographic, never an image: it recolors with the tokens, so the same component works on the
   rail, the mobile bar, the auth aside and the white public header. See the component's header
   comment for why the SVG wordmark had to go. */
.brand-wordmark { display:inline-flex; align-items:center; gap:10px; text-decoration:none; min-width:0; }
.brand-wordmark:hover { text-decoration:none; }
.brand-wordmark .brand-icon { width:34px; height:34px; flex-shrink:0; }
.brand-wordmark .brand-text { display:flex; flex-direction:column; min-width:0; }
.brand-wordmark .logo { font-family:var(--font-display); font-weight:700; font-size:1.35rem; letter-spacing:-.02em; color:var(--text); line-height:1; }
.brand-wordmark .logo b { color:var(--brand-ink); }
.brand-wordmark .brand-sub { display:block; font-size:.62rem; text-transform:uppercase; letter-spacing:.14em; color:var(--text-dim); margin-top:3px; }
.brand-wordmark.brand-lg .brand-icon { width:40px; height:40px; }
.brand-wordmark.brand-lg .logo { font-size:1.6rem; }
.brand-wordmark.brand-lg .brand-sub { font-size:.66rem; }
/* Dark-surface contexts re-map to the rail tokens — the rail is a brand constant in both themes. */
.rail .brand-wordmark .logo, .mobile-bar .brand-wordmark .logo,
.auth-aside .brand-wordmark .logo { color:var(--rail-text); }
.rail .brand-wordmark .logo b, .mobile-bar .brand-wordmark .logo b,
.auth-aside .brand-wordmark .logo b { color:var(--rail-active); }
.rail .brand-wordmark .brand-sub, .mobile-bar .brand-wordmark .brand-sub,
.auth-aside .brand-wordmark .brand-sub { color:var(--rail-dim); }

.nav { display:flex; flex-direction:column; gap:3px; }
.nav-item {
  display:flex; align-items:center; gap:10px; padding:9px 12px; border-radius:9px;
  color:var(--rail-dim); font-weight:500; font-size:.92rem; text-decoration:none;
  border:1px solid transparent; transition:background .15s, color .15s;
}
.nav-item:hover { background:var(--rail-surface); color:var(--rail-text); text-decoration:none; }
.nav-item.active { background:var(--rail-active-bg); color:var(--rail-text); border-color:color-mix(in srgb, var(--rail-active) 32%, transparent); }
.nav-item.active::before { content:""; width:3px; height:16px; border-radius:3px; background:var(--rail-active); margin-left:-4px; }

/* Oversized brand symbol bleeding off the bottom-left corner, behind the footer.
   icon.svg's baked #8a93ff == --rail-active, so opacity alone tints it. */
.rail-ornament { position:absolute; left:-12%; bottom:-9%; width:127%; z-index:0; pointer-events:none; }
.rail-ornament img { display:block; width:100%; height:auto; opacity:.16; transform: rotate(-21deg); }
.brand, .nav, .rail-foot { position:relative; z-index:1; }

.rail-foot { margin-top:auto; border-top:1px solid var(--rail-border); padding-top:14px; }
.user { display:flex; align-items:center; gap:10px; padding:4px 8px; }
.avatar { min-width:32px; height:32px; border-radius:50%; background:var(--rail-active); color:#05060F; display:grid; place-items:center; font-weight:600; font-size:.78rem; }
.user-meta { display:flex; flex-direction:column; line-height:1.25; }
.user-meta b { font-size:.85rem; color:var(--rail-text); }
.user-meta small { font-size:.72rem; color:var(--rail-dim); }

/* `position:relative` é a âncora do botão de tema flutuante (.topbar-inner), que é absoluto. */
.main { flex:1; min-width:0; display:flex; flex-direction:column; position:relative; }
/* ⚠ A .topbar NÃO tem mais fundo nem borda, e não ocupa altura (decisão do dono, 2026-09-22).
   Ela existia só para segurar o botão de tema: uma faixa branca de 60px atravessando a tela
   inteira para justificar um controle de 36px. O botão agora flutua no canto e a faixa devolve
   os 60px ao conteúdo.
   ⚠ `position:sticky` (não `fixed`): fixed o tiraria do fluxo da shell centrada e o colaria na
   borda da JANELA, que é exatamente o defeito que esta sessão veio corrigir. Sticky mantém o
   botão preso à coluna centrada e ainda acompanha a rolagem.
   ⚠ `height:0` + `align-items:flex-start` faz a barra não reservar espaço; o botão é posicionado
   para fora dela. Sem isso volta a sobrar uma faixa vazia, só que transparente.
   ⚠ NADA DE `sticky` AQUI — foi tentado e NÃO funciona, em qualquer um dos dois elementos: um
   sticky nunca sai da caixa do pai, e o pai (.topbar) tem `height:0`, ou seja, curso zero. Medido:
   o botão rolava para fora da tela (`top:-486` a 500px de rolagem). O botão é `absolute` contra a
   .main (que é `relative`), que é o que realmente o mantém no canto. */
.topbar { height:0; flex-shrink:0; }
/* ⚠ O botão ocupa a folga à direita da coluna de conteúdo (2026-09-22). Alinhado à coluna, ele
   caía exatamente na linha dos chips da .section-nav (/configuracoes, /suporte) e ficava
   encavalado neles mesmo estando por cima. A folga existe porque .content é centrada dentro do
   painel — é o "room to spare" que o dono apontou.
   ⚠ É `fixed`, e as duas alternativas JÁ FORAM TENTADAS E MEDIDAS, ambas falham:
     · `sticky` — um sticky nunca sai da caixa do pai, e o pai (.topbar) tem `height:0`: curso
       zero. O botão rolava para fora (`top:-486` a 500px de rolagem).
     · `absolute` na .main — ancora na PÁGINA, não na janela; idem, rolava para fora.
   O botão precisa acompanhar a janela numa página longa (/configuracoes, /suporte), então `fixed`
   é a única que serve. O `right` abaixo é o que impede o defeito clássico do fixed — colar na
   borda do monitor em vez da coluna.
   ⚠ O recuo NÃO pode ser um número fixo: a folga à direita da shell é (100vw - --shell-max)/2
   enquanto a janela é mais larga que o cap, e ZERO abaixo dele. `max(0px, ...)` cobre os dois
   casos; com um número fixo, num 1280px o botão sairia da tela.
   ⚠ z-index ACIMA da .section-nav (/configuracoes, /suporte), que é sticky com fundo OPACO
   sangrando até a borda do container: empatados em z-index:5 a ordem do DOM decidia e os chips
   pintavam por cima da metade de baixo do botão — ele aparecia CORTADO AO MEIO. Abaixo da
   .mobile-bar (20) e do drawer (19), que precisam cobrir tudo no celular. */
.topbar-inner {
  position:fixed; top:14px; z-index:10;
  right:max(clamp(20px,3vw,40px), calc((100vw - var(--shell-max)) / 2 + clamp(20px,3vw,40px)));
  display:flex; align-items:center; gap:14px;
}
.topbar-inner > * { pointer-events:auto; }
.status { display:inline-flex; align-items:center; gap:8px; font-size:.82rem; color:var(--text-dim); }
.status-dot { width:8px; height:8px; border-radius:50%; background:var(--ok); box-shadow:0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent); }
.theme-toggle {
  font:inherit; font-size:1rem; cursor:pointer; width:36px; height:36px; border-radius:9px;
  background:var(--surface-2); color:var(--text-dim); border:1px solid var(--border);
}
.theme-toggle:hover { color:var(--text); border-color:var(--brand); }
.theme-toggle:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }

/* Centrada no painel: com a .shell limitada a --shell-max sobram 212px entre a coluna de 1100px
   e a borda direita da shell, e encostada à esquerda essa sobra vira uma faixa morta só de um
   lado. `margin:0 auto` divide a folga nos dois lados. Abaixo de --shell-max a shell ocupa a
   janela inteira, o painel encolhe e o auto vira zero sozinho — nada muda em telas menores.
   ⚠ O padding do topo é maior que os demais: com a .topbar sem altura, o botão de tema flutua
   sobre esta área (14px + 36px de botão), e um padding uniforme faria o <h1> passar por baixo
   dele. Não iguale os quatro lados "para simplificar". */
.content { padding:60px clamp(20px,3vw,40px) clamp(20px,3vw,40px); max-width:1100px; width:100%; margin:0 auto; }

/* ---------- Page header ---------- */
.page-head { margin-bottom:26px; position:relative; }
.page-title { font-family:var(--font-display); font-weight:700; font-size:clamp(1.9rem,4vw,2.6rem); letter-spacing:-.02em; line-height:1.05; margin:10px 0 8px; text-wrap:balance; }
/* One <h1> per page is the accessibility contract, and FocusOnNavigate (Routes.razor) focuses the
   FIRST one after every navigation — Configurações had four, so the announce was always "Sua
   empresa" no matter where you arrived. Its section headers are h2 now; the smaller size is also
   a better hierarchy than four identical mega-titles stacked down one page. */
h2.page-title { font-size:clamp(1.5rem,3vw,2rem); }
.page-title .accent { color:var(--ok-ink); }       /* a state word — semantic green, not brand */
/* FocusOnNavigate (Routes.razor) gives the <h1> tabindex=-1 and focuses it after every nav —
   including the save PRG redirect — which draws the browser's default outline. Keep the focus
   move (a11y) but drop the visual ring; it read as a stray white box around the title. */
.page-title:focus { outline:none; }
.page-sub { color:var(--text-dim); font-size:1rem; }
.page-sub .num { font-family:var(--font-mono); color:var(--text); font-weight:500; }

/* carimbo — brand signature, used sparingly */
.stamp {
  position:absolute; right:4px; top:6px; transform:rotate(-8deg);
  font-family:var(--font-body); font-weight:800; font-size:clamp(1.4rem,3.4vw,2.2rem);
  letter-spacing:.06em; color:var(--stamp); border:.16em solid var(--stamp);
  border-radius:12px; padding:.05em .18em; pointer-events:none; user-select:none;
}
@media (max-width:680px){ .stamp{ display:none; } }

/* ---------- Cards / tiles ---------- */
.grid-tiles { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:14px; margin-bottom:22px; }
.card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow); }
.tile { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow); padding:16px 18px; }
.tile .lab { font-size:.76rem; color:var(--text-dim); margin-bottom:8px; }
.tile .val { font-family:var(--font-mono); font-variant-numeric:tabular-nums; font-size:1.7rem; font-weight:500; letter-spacing:-.01em; }
.tile .val small { font-size:.85rem; color:var(--text-faint); }
.tile .val.brand { color:var(--brand-ink); }
/* A .val that holds text (an e-mail, a bank holder, a GUID) rather than a number — the mono
   display size is wrong for it, and it must be allowed to break. Replaces the inline font-sizes
   that used to do this job. */
.tile .val.val-text { font-size:1rem; font-family:var(--font-body); line-height:1.4; }
.tile .val.val-md { font-size:1.5rem; }

/* ---------- Overflow hygiene (UX-REVIEW OVF-1) ----------
   A grid/flex item's automatic minimum size is its min-content width, so one unbreakable token —
   payer1784041116@estudio.com.br, a 36-char GUID, a 64-hex thumbprint, a boleto linha digitável —
   widens its track past the container and paints outside the card. min-width:0 lets the track
   shrink; overflow-wrap:anywhere lets the string itself break. Both halves are needed. */
.tile, .field, .panel-head, .row > *, .order-row > * { min-width:0; }
.tile .val, .tile .tile-cta, .panel-head .mono, .pay-code, .field .mono,
.mono-break, .row .file, .page-title .mono { overflow-wrap:anywhere; }
.panel-head { flex-wrap:wrap; gap:6px 14px; }

/* ---------- Panel with header ---------- */
.panel { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden; }
.panel-head { display:flex; align-items:center; justify-content:space-between; padding:16px 18px; border-bottom:1px solid var(--border); }
.panel-head h2 { font-family:var(--font-display); font-weight:600; font-size:1.05rem; margin:0; }
.panel-head a { font-size:.85rem; }

/* Repeated layout values, as classes rather than inline styles. Not pedantry: a media query
   cannot reach an inline style, so every responsive rule below would be unable to override one. */
.panel-body { padding:14px 18px; }
.panel-gap { margin-bottom:18px; }
.panel-gap-sm { margin-bottom:14px; }
.section-gap { margin-top:38px; }

/* ---------- In-page section navigation (UX-REVIEW-2 ITEM-11) ----------
   Anchor chips, deliberately NOT tabs: each section is its own named EditForm that POSTs and
   re-renders the whole page, so a validation error in a hidden tab would be invisible, and the
   deep links other pages already use would need tab-state plumbing. Sticky so it stays reachable
   down a long page; the background is opaque so content does not ghost through it. */
/* ⚠ The backdrop BLEEDS into .content's padding on purpose. .content pads by
   clamp(20px,3vw,40px) and the panels fill that inner width exactly, so a backdrop only as wide
   as the content leaves the panel's own 1px left/right borders visible in the gutter as it
   scrolls underneath — two thin lines flanking the chips, which reads as a patch taped over the
   page. The negative margin pulls the background out to the container edge; the matching padding
   keeps the chips on the content grid, so nothing moves visually. */
.section-nav {
  display:flex; flex-wrap:wrap; gap:8px;
  position:sticky; top:0; z-index:5;
  margin:0 calc(-1 * clamp(20px,3vw,40px)) 22px;
  padding:10px clamp(20px,3vw,40px);
  background:var(--bg);
}
.section-nav a {
  display:inline-block; padding:6px 12px; border-radius:999px;
  border:1px solid var(--border); background:var(--surface);
  color:var(--text-dim); text-decoration:none;
  font-size:.85rem; font-weight:600; white-space:nowrap;
}
.section-nav a:hover { color:var(--text); border-color:var(--brand); }
/* Anchored jumps must clear the sticky chips (and, on mobile, the 56px bar above them).
   /configuracoes sections first, then /suporte's. ⚠ Both breakpoints hardcode these ids — a new
   section must be added HERE and in the mobile rule below, or the jump lands under the bars on
   phones only. */
#fiscal, #empresa, #certificado, #email, #conta,
#pausa, #reembolso, #ajuste, #fator, #estacionadas, #historico { scroll-margin-top:72px; }
.tight { margin-top:0; }
.flush { margin:0; }
.stack-sm { margin-top:12px; }
.stack { margin-top:14px; }
.stack-lg { margin-top:22px; }
.stack-y { margin:14px 0; }
.panel-foot-pad { padding:0 18px 14px; }
.nowrap { white-space:nowrap; }
.upper { text-transform:uppercase; }
.field-grow { flex:1 1 320px; }
.row-link { text-decoration:none; color:inherit; }
.row-link:hover { text-decoration:none; }

.rows { display:flex; flex-direction:column; }
/* Each .row is its OWN grid — tracks are not shared down the list. So the trailing status column
   is a FIXED length on purpose: an `auto` pill (whose width follows its label — "Emitida" vs
   "Concluído com erros") would leave a different amount of space for the fr columns in every row,
   and every column would drift. Fixed pill + fr elsewhere ⇒ identical geometry in every row. */
.row { display:grid; grid-template-columns:1.6fr .8fr .8fr 11rem; align-items:center; gap:14px; padding:13px 18px; border-top:1px solid var(--border); }
.row .pill { justify-self:end; }
.row:first-child { border-top:0; }
.row .file { font-family:var(--font-mono); font-size:.86rem; font-weight:500; }
.row .cnt { font-family:var(--font-mono); font-size:.82rem; color:var(--text-dim); font-variant-numeric:tabular-nums; }
.bar { height:6px; border-radius:100px; background:var(--surface-2); overflow:hidden; min-width:80px; }
.bar > i { display:block; height:100%; border-radius:100px; background:var(--brand); }
.bar.ok > i { background:var(--ok); }
.bar.crit > i { background:var(--crit); }
/* Painel "Lotes recentes": arquivo · progresso · barra · situação.
   The file track has a FLOOR and the bar YIELDS (UX-REVIEW-2 ITEM-13). Non-fr tracks size first in
   grid, so `minmax(120px,18rem)` on the bar took up to 288px + 80 + 176 + gaps + padding ≈ 622px
   before the 1fr file column got a single pixel: at ~670px the file track collapsed to near zero
   and `.row .file{overflow-wrap:anywhere}` wrapped the name one letter per line. */
.lote-row { grid-template-columns:minmax(9rem,1.6fr) 5rem minmax(110px,1fr) 11rem; }
.lote-row .cnt { text-align:right; }
.lote-row .bar { width:100%; }
/* Processamento "Lotes enviados": arquivo · enviado em · resumo · situação. The date column is
   sized to "31/12/2026 23:59" and the summary takes the remaining flex so it stops wrapping. */
.batch-row { grid-template-columns:1.4fr 9.5rem 1fr 11rem; }
/* 760px, not 640 (UX-REVIEW-2 ITEM-13): between 641 and 760 the fixed tracks still crowded the file
   name into a vertical strip of letters, which is why the SMALLEST viewport looked better than the
   middle one. Above 760 the tracks (144+80+110+176+gaps+padding ≈ 588px) fit comfortably. */
@media (max-width:760px){ .row{ grid-template-columns:1fr auto; } .row .cnt, .row .bar{ display:none; }
  .lote-row, .batch-row{ grid-template-columns:1fr auto; } }

/* ---------- Status pills (semantic only) ---------- */
.pill { display:inline-flex; align-items:center; gap:7px; font-size:.78rem; font-weight:600; padding:4px 11px 4px 9px; border-radius:100px; border:1px solid transparent; white-space:nowrap; }
.pill .dot { width:7px; height:7px; border-radius:50%; }
.pill.ok   { color:var(--ok-ink);   background:var(--ok-soft);   border-color:color-mix(in srgb,var(--ok) 30%,transparent); }   .pill.ok .dot{ background:var(--ok); }
.pill.warn { color:var(--warn-ink); background:var(--warn-soft); border-color:color-mix(in srgb,var(--warn) 30%,transparent); } .pill.warn .dot{ background:var(--warn); }
.pill.crit { color:var(--crit-ink); background:var(--crit-soft); border-color:color-mix(in srgb,var(--crit) 30%,transparent); } .pill.crit .dot{ background:var(--crit); }
.pill.idle { color:var(--text-dim); background:var(--surface-2);  border-color:var(--border); }                                  .pill.idle .dot{ background:var(--text-faint); }

/* ---------- Buttons ---------- */
.btn { font:inherit; font-weight:600; font-size:.92rem; cursor:pointer; border:1px solid transparent; border-radius:10px; padding:10px 18px; transition:filter .15s, transform .15s, border-color .15s; }
.btn.primary { background:var(--brand); color:var(--on-brand); }
.btn.primary:hover { filter:brightness(1.06); transform:translateY(-1px); }
.btn.ghost { background:transparent; color:var(--text); border-color:var(--border); }
.btn.ghost:hover { border-color:var(--brand); color:var(--brand-ink); }
.btn:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.actions { display:flex; flex-wrap:wrap; gap:12px; margin-top:20px; }
/* For a row mixing a <form>-wrapped button with a bare <a class="btn">: the row is stretch by
   default, so the form grows to the row's height and the two controls end up different sizes.
   (Pair with the existing .flush when the row is the panel body's own last block.) */
.actions.centered { align-items:center; }

/* ---------- Notices (inline status banners) ----------
   align-items:flex-start, not center (UX-REVIEW POL-10): the pill is one line and the text beside
   it is often three or four — on /suporte and the read-only account banner, centering floated the
   pill against the middle of a paragraph and broke the reading line. The small margin puts it on
   the text's first baseline instead. */
.notice {
  display:flex; align-items:flex-start; gap:12px; flex-wrap:wrap;
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  box-shadow:var(--shadow); padding:14px 16px; margin-bottom:18px;
  font-size:.9rem; color:var(--text-dim); align-items: center;
}
.notice > .pill { margin-top:2px; }
/* Same reasoning as .page-title:focus above: site.js focuses the post-redirect error notice so the
   user lands AT the failure, but the user never tabbed to it, so the default outline reads as a
   stray box around a paragraph. The focus move is the a11y win; the ring is not. Note this is
   :focus and not :focus-visible — Chrome does not match :focus-visible on a programmatic focus, so
   a :focus-visible rule here would never apply and the UA outline would still be drawn. */
.notice[data-focus-error]:focus { outline:none; }
.notice.ok { border-color:color-mix(in srgb,var(--ok) 34%,transparent); background:var(--ok-soft); color:var(--ok-ink); }
.notice.warn { border-color:color-mix(in srgb,var(--warn) 34%,transparent); background:var(--warn-soft); color:var(--warn-ink); }

/* ---------- Forms ---------- */
.form { padding:22px clamp(18px,3vw,26px) 20px; }
.form-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:18px 22px; }
.field { display:flex; flex-direction:column; gap:6px; min-width:0; }
.field-wide { grid-column:1 / -1; }
.field-lab { font-size:.82rem; font-weight:600; color:var(--text); }
.field-hint { font-size:.76rem; color:var(--text-faint); }
.field-opt { font-weight:400; color:var(--text-faint); }
.nbs-ok { color:var(--ok); }
.nbs-unknown { color:var(--warn); }
.input {
  font:inherit; font-size:.92rem; color:var(--text); background:var(--surface);
  border:1px solid var(--border); border-radius:9px; padding:9px 12px; width:100%;
  transition:border-color .15s, box-shadow .15s;
}
.input:focus { outline:none; border-color:var(--brand); box-shadow:0 0 0 3px color-mix(in srgb,var(--brand) 22%,transparent); }
.input.mono { font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
select.input { cursor:pointer; }
/* The file picker is a real control on two pages that matter (the certificate upload and Novo
   lote), and the UA button ignores every token — it rendered as grey system chrome inside an
   otherwise styled field, and read as broken in dark theme. ::file-selector-button is the
   standard hook; padding stays generous enough for the 44px touch floor with the field around it. */
.input[type="file"] { padding:7px 10px; cursor:pointer; }
.input::file-selector-button {
  font:inherit; font-weight:600; font-size:.86rem; cursor:pointer;
  border:0; border-radius:7px; padding:7px 13px; margin-right:11px;
  background:var(--brand-soft); color:var(--brand-ink);
  transition:filter .15s;
}
.input::file-selector-button:hover { filter:brightness(.96); }

/* Disabled controls. There was no :disabled rule at all, so a control the page had switched off
   looked exactly like a live one — the customer's only feedback was that clicking did nothing.
   /suporte already shipped a disabled button in that state; the read-only forms (ADR-0025
   decision 12) made it general.

   Opacity alone would be the easy answer and is the wrong one: it dims the label too and can push
   text under the 4.5:1 floor the UX-REVIEW sweep measured. Muted tokens keep the text readable —
   a paused customer must still be able to READ their fiscal settings — while the cursor and the
   flat surface say the control is not available. */
.form-fieldset { border:0; margin:0; padding:0; min-width:0; }
.input:disabled, .input[disabled] {
  background:var(--surface-2); color:var(--text-dim); cursor:not-allowed; border-color:var(--border);
}
.input:disabled::file-selector-button { background:var(--surface-2); color:var(--text-dim); cursor:not-allowed; }
.btn:disabled, .btn[disabled] { cursor:not-allowed; filter:grayscale(.55) opacity(.75); }
.btn:disabled:hover, .btn[disabled]:hover { transform:none; filter:grayscale(.55) opacity(.75); }
.btn.ghost:disabled:hover { border-color:var(--border); color:var(--text); }
/* A disabled fieldset greys its own legend and check labels too, so they are muted deliberately
   rather than left at full strength beside dimmed inputs. */
fieldset:disabled .field-lab, fieldset:disabled .check span, fieldset:disabled legend { color:var(--text-dim); }
fieldset:disabled .check { cursor:not-allowed; }
/* The at-the-button marker for a paused form. Warn-coloured so it reads as state rather than as a
   hint, but it is one clause on one line: /configuracoes has four guarded forms, and a full amber
   notice above each printed the same paragraph five times down the page (owner's review). */
.form-note-paused { color:var(--warn-ink); font-weight:600; }
.form-note-paused a { color:inherit; }

.field-checks { grid-column:1 / -1; flex-direction:row; flex-wrap:wrap; gap:10px 26px; margin-top:2px; }
/* The label takes a full row of its own so the options always travel together (UX-REVIEW-2 ITEM-9):
   label and options share one wrapping flex row, so at narrow widths the wrap fell BETWEEN the two
   options — "Forma de pagamento · Pix" on one line and a stranded "Boleto" on the next. Inert where
   a .field-checks has no label of its own (the fiscal checkboxes in Configurações). */
.field-checks > .field-lab { flex-basis:100%; }
.check { display:inline-flex; align-items:center; gap:9px; font-size:.9rem; color:var(--text); cursor:pointer; }
.check input { width:16px; height:16px; accent-color:var(--brand); }

.form-foot { display:flex; align-items:center; gap:16px; flex-wrap:wrap; margin-top:22px; padding-top:18px; border-top:1px solid var(--border); }
/* A .form-foot used as a panel's last strip rather than a form's footer: it needs the panel's own
   padding and no leading gap. Defined after .form-foot so it actually wins. */
.form-foot.foot-flush { margin-top:0; padding:14px 18px; }
.form-note { font-size:.8rem; color:var(--text-faint); }

/* An in-form section divider: one panel, two groups of fields (the Emissor Nacional block on
   /configuracoes — ADR-0026). A second panel would read as a second form with a second save
   button, which it is not: one submit saves both groups. The rule above it is what says "new
   group" without promising a separate action. */
.form-subhead { margin-top:26px; padding-top:20px; border-top:1px solid var(--border); margin-bottom:16px; }
.form-subtitle { font-size:.95rem; font-weight:650; color:var(--text); margin:0 0 6px; }
.form-subhead .form-note { max-width:62ch; }

.validation-message { font-size:.78rem; color:var(--crit-ink); }
.input.invalid { border-color:var(--crit); }
.input.invalid:focus { box-shadow:0 0 0 3px color-mix(in srgb,var(--crit) 22%,transparent); }

/* ---------- Sequence warning (ADR-0026 / ADR-0005) ----------
   The série or the channel changed, and nfs.cx does NOT reset the RPS/DPS counter. This is the
   only thing on /configuracoes where ignoring the message damages data at the *Prefeitura* — a
   repeated number is not an error there, it silently SUBSTITUTES an already-issued nota — so it
   is deliberately not a fourth `.notice`.

   Three departures from the house notice, each earning its place:

   1. **--crit, not --warn.** Amber is already spent on this page by three page-state notices
      (Conflito / Salvo / Pendente) and the paused-form marker. A fourth amber block reads as
      equal weight to "we reloaded your values", which this is not.
   2. **It sits AT the field, not at the top of the page.** The other notices describe the page's
      state; this one describes one number the user is looking at.
   3. **The number is the message.** The counter is rendered in the mono face the rest of the app
      uses for figures — abstract advice about "the sequence" is ignorable, `777` is not. It is
      the one datum that makes the consequence concrete, and showing it is what lets the user act
      without leaving to go look it up.

   No dismiss control on purpose: it is not an alert about something that happened, it is a
   standing condition of the edit being made. It disappears when the cause does. */
.seq-warn {
  display:grid; grid-template-columns:auto 1fr; gap:0 18px; align-items:start;
  grid-column:1 / -1; margin:2px 0 4px;
  padding:15px 17px 15px 15px;
  background:var(--crit-soft); color:var(--text);
  border:1px solid color-mix(in srgb,var(--crit) 34%,transparent);
  border-left:3px solid var(--crit);
  border-radius:var(--radius);
}
/* The counter block: the app's numeric voice (tabular mono) at display size. Its own surface so it
   reads as a value read off the system rather than a number written into the sentence. */
.seq-warn-num {
  display:flex; flex-direction:column; align-items:center; gap:2px;
  padding:9px 16px; border-radius:9px;
  background:var(--surface); border:1px solid color-mix(in srgb,var(--crit) 26%,transparent);
}
.seq-warn-num b {
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:1.5rem; font-weight:500; line-height:1.1; color:var(--crit-ink);
}
.seq-warn-num span {
  font-size:.63rem; text-transform:uppercase; letter-spacing:.09em;
  font-weight:600; color:var(--text-faint); white-space:nowrap;
}
.seq-warn-body { min-width:0; }
.seq-warn-title { font-size:.9rem; font-weight:650; color:var(--crit-ink); margin:0 0 5px; }
.seq-warn-body p { margin:0 0 7px; font-size:.84rem; line-height:1.5; color:var(--text-dim); max-width:60ch; }
.seq-warn-body p:last-child { margin-bottom:0; }
.seq-warn-body strong { color:var(--text); font-weight:600; }

/* Stacked on a phone: 2-up puts the numeral beside a 4-line paragraph in ~150px of text column.
   The numeral leads because it is the subject of the sentence that follows. */
@media (max-width:560px){
  .seq-warn { grid-template-columns:1fr; gap:12px; justify-items:start; }
}

@media (max-width:640px){ .form-grid{ grid-template-columns:1fr; } }

/* ---------- Data tables (Novo lote help, relatórios de lote) ---------- */
.table-wrap { overflow-x:auto; }
.table { width:100%; border-collapse:collapse; font-size:.87rem; }
.table th { text-align:left; font-size:.74rem; text-transform:uppercase; letter-spacing:.07em; color:var(--text-dim); font-weight:600; padding:10px 16px; border-bottom:1px solid var(--border); white-space:nowrap; }
.table td { padding:10px 16px; border-top:1px solid var(--border); vertical-align:top; }
.table tr:first-child td { border-top:0; }
.table .aliases { font-size:.78rem; color:var(--text-faint); }
.table .num, .table td.num, .table th.num { text-align:right; }
/* A money value is ONE unit. Auto table layout was free to break "R$ 1.180,50" at the space after
   the currency symbol, so /notas rendered two-line amounts in a table that was not even full width
   — the column had simply been handed less than its natural size, because a breakable cell reports
   a smaller minimum than it wants. nowrap makes that minimum honest, so the column reserves the
   width it actually needs. If the row's columns then genuinely exceed the panel, .table-wrap's
   overflow-x:auto scrolls — the same trade the action buttons make. */
.table td.num { white-space:nowrap; }
/* Actions cell (/notas). It used to be a plain `.num`, so the buttons were inline and the longest
   label broke across two lines — "Ver na" / "Prefeitura" — which then pushed Cancelar out of line
   with the row below it (sandbox finding B3, 2026-08-23). A button label is one unit, so it
   nowraps; the actions then stack in a right-aligned column, and a second action sits UNDER the
   first at the same edge instead of being shoved around by however wide the first turned out.
   ⚠ The flex box is an INNER wrapper, not the <td> itself: `display:flex` on a cell takes it out
   of the table's column-sizing algorithm, so the column would stop reserving width for these
   buttons and crowd them again. With nowrap labels the column now asks for its real width and, if
   the viewport cannot give it, .table-wrap's horizontal scroll absorbs the difference — which is
   the intended behaviour, unlike chopping a label in half. */
.table td.row-actions { text-align:right; }
/* ⚠ `align-items:flex-end`, NOT stretch: stretch sizes every button to the widest one in its own
   row, so a row offering "Consultar no portal nacional" blew "Cancelar" up to match it while the
   row next to it kept a normal-width one — trading the wrap for a worse misalignment. Each button
   sizes to its own label and they share a right edge. */
.table td.row-actions .actions-stack { display:inline-flex; flex-direction:column; align-items:flex-end; gap:6px; }
.table td.row-actions .btn { white-space:nowrap; }
/* Column priority (UX-REVIEW TBL-1). Horizontal scroll inside the panel is the correct baseline,
   but it was the ONLY affordance: at 360px the /notas table showed three columns with no hint
   that Valor, Situação and Cancelar existed off to the right. These columns are the ones whose
   data is also in the CSV export, so hiding them costs nothing the accountant needs. */
@media (max-width:640px) {
  .table .hide-sm { display:none; }
  .table th, .table td { padding:10px 12px; }
}
/* Below 480px even the priority columns do not fit — six columns of real data are wider than a
   phone no matter how the padding is tuned. So a second tier drops one more column (`.hide-xs`,
   applied to Emissão on /notas: the competência filter already scopes the period and the exact
   date is in the CSV). What is trimmed beyond that is chrome, never meaning — the status pill
   keeps its word, because a bare coloured dot is not a status to a screen reader or a colourblind
   reader, and that would trade an overflow defect for an accessibility one. */
@media (max-width:480px) {
  .table .hide-xs { display:none; }
  .table th, .table td { padding:9px 8px; font-size:.84rem; }
  .table th { font-size:.68rem; letter-spacing:.05em; }
  .table .pill { padding:3px 8px 3px 7px; font-size:.72rem; gap:5px; }
  .table td .aliases { font-size:.72rem; }
  .table .btn.small { padding:8px 10px; font-size:.8rem; }
}
/* Stacked rows for the /novo-lote "Colunas aceitas" help table (UX-REVIEW-2 ITEM-14). Column
   shedding does not apply here — all three columns ARE the content — and the nowrap pill column
   held its width while "Como funciona" fell off the right edge behind a scroll box nobody finds.
   Stacking removes the scroll and the wasted whitespace at once. The pill keeps its label: the
   colour is not the status (UX-REVIEW TBL-1), it just sits beside the field name instead of in a
   column of its own. `@media screen` on purpose — the print block below restores full tables,
   because paper is not a viewport (POL-8), and must not inherit this. The visually-hidden thead
   keeps the table semantics coherent; the cells still read in their natural order. */
@media screen and (max-width:560px) {
  .table--stack thead { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }
  .table--stack, .table--stack tbody { display:block; }
  .table--stack tr {
    display:grid; grid-template-columns:1fr auto; gap:4px 10px;
    padding:12px 4px; border-top:1px solid var(--border);
  }
  .table--stack tbody tr:first-child { border-top:0; }
  .table--stack td { display:block; padding:0; border:0; }
  .table--stack td:nth-child(2) { justify-self:end; }   /* the pill, beside the field name */
  .table--stack td:nth-child(3) { grid-column:1 / -1; color:var(--text-dim); }
}
.err-list { margin:0; padding-left:18px; display:flex; flex-direction:column; gap:3px; color:var(--crit-ink); font-size:.82rem; }
.err-list.muted { color:var(--text-dim); } /* skipped duplicates: informational, not an error */
.rule-list { margin:0; padding-left:20px; color:var(--text-dim); font-size:.9rem; display:flex; flex-direction:column; gap:7px; }

/* ---------- Empty state (stub pages) ---------- */
.empty-state { display:grid; place-items:center; text-align:center; padding:60px 20px; color:var(--text-dim); }
.empty-state .badge { font-family:var(--font-mono); font-size:.72rem; text-transform:uppercase; letter-spacing:.12em; color:var(--brand-ink); background:var(--brand-soft); padding:4px 12px; border-radius:100px; margin-bottom:14px; }
/* h2 where the empty state sits inside a page that already has its own h1 (the usual case); h1
   where the empty state IS the page (NotFound, "lote não encontrado"). Both styled the same. */
.empty-state h1, .empty-state h2 { font-family:var(--font-display); font-weight:600; font-size:1.6rem; color:var(--text); margin:0 0 8px; }
.empty-state p { max-width:44ch; }

/* ---------- Small-viewport shell: top bar + drawer (UX-REVIEW SHELL-1) ----------
   The rail is desktop-only. The old fallback rotated it 90° into a horizontal strip, which is not
   a pattern any shipped product uses and produced four separate defects at once: an unsized 686px
   brand image pushing every nav item off-screen, the rail ornament escaping its containing block
   (position:static removed it) and painting a purple swirl over the whole page, no logout and no
   legal links on a phone, and — because the only condition was max-width — landscape phones wider
   than 820px getting a 100vh sticky rail inside a 390px-tall viewport with no way to scroll it.

   Both shells are in the markup; CSS picks. The height condition is what fixes landscape. */
.mobile-bar, .mobile-nav { display:none; }
.nav-toggle {
  font:inherit; font-size:1.15rem; line-height:1; cursor:pointer; width:44px; height:44px;
  border-radius:9px; background:transparent; color:var(--rail-text);
  border:1px solid var(--rail-border);
}
.nav-toggle:hover { background:var(--rail-surface); border-color:var(--rail-active); }
.nav-toggle:focus-visible { outline:2px solid var(--rail-active); outline-offset:2px; }

@media (max-width:820px), (max-height:520px) {
  /* Hide the rail outright — the ornament, the status chip and the desktop toggle go with it. */
  .rail, .topbar { display:none; }
  .shell { flex-direction:column; }
  /* Sem a .topbar não há botão flutuando sobre o conteúdo (o toggle daqui mora na .mobile-bar),
     então o respiro extra do topo não tem função e viraria uma faixa vazia sob a barra. */
  .content { padding-top:clamp(20px,3vw,40px); }

  /* A fixed height, not min-height: the drawer below is pinned at exactly `top:56px`, so the bar's
     height has to be a number both rules agree on. The tallest child is the 44px toggle + 16px
     padding = 56px, so nothing is squeezed. */
  .mobile-bar {
    display:flex; align-items:center; gap:10px; position:sticky; top:0; z-index:20;
    height:56px; padding:8px 12px;
    background:var(--rail-bg); color:var(--rail-text);
    border-bottom:1px solid var(--rail-border);
  }
  .mobile-bar .brand-wordmark { margin-right:auto; }
  .mobile-bar .theme-toggle {
    background:transparent; color:var(--rail-dim); border-color:var(--rail-border);
  }
  .mobile-bar .theme-toggle:hover { color:var(--rail-text); border-color:var(--rail-active); }

  /* An overlay pinned under the bar, NOT a block in normal flow (UX-REVIEW-2 ITEM-7). In flow it
     sat at the top of the document, so opening it while scrolled down inserted it *above* the
     viewport: technically open, invisible, and the tap read as "☰ does nothing". z-index stays
     under the bar's 20 so the bar (and its close affordance) is never covered. max-height + scroll
     is what keeps every item — Sair included — reachable on a short landscape viewport, which is
     the exact failure the old rotated rail had. */
  .mobile-nav {
    display:flex; flex-direction:column; gap:3px; padding:10px 12px 16px;
    position:fixed; top:56px; left:0; right:0; z-index:19;
    max-height:calc(100dvh - 56px); overflow:auto; overscroll-behavior:contain;
    background:var(--rail-bg); color:var(--rail-text);
    border-bottom:1px solid var(--rail-border);
  }
  /* The mobile bar is sticky at top:0 and 56px tall, so the section chips stick directly under it
     and anchored jumps must clear both (UX-REVIEW-2 ITEM-11). */
  .section-nav { top:56px; }
  #fiscal, #empresa, #certificado, #email, #conta,
  #pausa, #reembolso, #ajuste, #fator, #estacionadas, #historico { scroll-margin-top:124px; }

  .mobile-nav .nav-item { padding:12px; font-size:.98rem; }
  .mobile-nav-foot { margin-top:12px; border-top:1px solid var(--rail-border); padding-top:12px; }
  .mobile-nav-foot .rail-legal { justify-content:flex-start; gap:6px 18px; margin-top:12px; font-size:.85rem; }
  .mobile-nav-foot .logout-btn { padding:11px 12px; }

  /* iOS Safari zooms the page when focusing an input under 16px — .input is .92rem ≈ 13.8px, so
     tapping a filter field zoomed and shifted the layout, and the user had to pinch back out. */
  .input, select.input { font-size:16px; }

  /* 44×44 is the touch floor. */
  .btn { padding:12px 18px; }
  .btn.small { padding:10px 14px; }
  .theme-toggle, .nav-toggle { width:44px; height:44px; }
}

/* ---------- Auth (conta) — split screen: brand aside + form ---------- */
/* Mesma decisão da .shell: o par (painel da marca + cartão) é o objeto centrado. Antes, o painel
   parava em 460px e o painel direito crescia sem limite, então num monitor de 2000px+ a divisão
   42/58 virava 23/77 e o cartão de login flutuava sozinho bem à direita do centro da janela. */
.auth-shell { display:flex; min-height:100vh; min-height:100dvh; max-width:var(--shell-max); margin:0 auto; }
.auth-aside {
  width:42%; max-width:460px; flex-shrink:0; background:var(--rail-bg); color:var(--rail-text);
  border-right:1px solid var(--rail-border);
  display:flex; flex-direction:column; justify-content:center; gap:clamp(30px,5vh,52px);
  padding:clamp(28px,4vw,56px); position:relative; overflow:hidden;
}
/* pitch + seal, part of the one centered left-aligned brand block */
.auth-body { display:flex; flex-direction:column; align-items:flex-start; gap:clamp(28px,6vh,63px); }
.auth-pitch { font-family:var(--font-display); font-weight:600; font-size:clamp(1.5rem,2.6vw,2.1rem); line-height:1.15; letter-spacing:-.02em; text-wrap:balance; margin:0; max-width:16ch; }
.auth-pitch .accent { color:var(--rail-active); }

/* Signature: the EMITIDA carimbo stamped over the comet mark, left-anchored */
.auth-seal { position:relative; align-self:flex-start; display:inline-grid; place-items:center; }
.auth-seal-mark { position:absolute; width:190px; opacity:.10; transform:rotate(11deg); pointer-events:none; user-select:none; }
.auth-stamp {
  position:relative; font-family:var(--font-display); font-weight:700; text-transform:uppercase;
  font-size:clamp(1.8rem,3.4vw,2.6rem); letter-spacing:.14em; text-indent:.14em; line-height:1;
  color:var(--rail-active); padding:.26em .42em .3em;
  border:3px solid color-mix(in srgb,var(--rail-active) 55%,transparent); border-radius:12px;
  box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--rail-active) 22%,transparent);
  transform:rotate(-6deg); user-select:none;
}

.auth-main { flex:1; min-width:0; display:flex; flex-direction:column; background:var(--bg); }
/* ⚠ O botão e o rodapé ancoram na BORDA DO PAINEL, não no cartão (2026-09-22, decidido sobre um
   par antes/depois do dono com o overlay do DevTools).
   O cartão é estreito (400px) e centrado num painel largo, então qualquer controle alinhado a ele
   vira uma segunda coluna a meia distância da borda: sobra um vão morto à direita e o conjunto lê
   como "alinhado à direita". Levados à borda do painel, botão, limite do painel e fim do rodapé
   caem na MESMA vertical, e o cartão volta a ser lido como o que é — centrado.
   ⚠ Portanto NÃO reintroduza um wrapper com `max-width`/`margin:0 auto` aqui para "alinhar com o
   cartão": já foi tentado e é justamente o efeito que o dono marcou em vermelho.
   ⚠ Neutralizado na media query pequena, onde a barra flutua na faixa da marca. */
.auth-topbar {
  height:60px; flex-shrink:0; display:flex; align-items:center; justify-content:flex-end;
  padding:0 clamp(16px,3vw,40px);
}
.auth-content { flex:1; display:grid; place-items:center; padding:clamp(16px,3vw,40px); }
/* Alinhado à direita, na mesma vertical do botão de tema — ver a nota em .auth-topbar. O padding
   lateral acompanha o da barra para que as duas pontas coincidam de fato. */
.auth-foot { flex-shrink:0; display:flex; justify-content:flex-end; gap:18px; padding:0 clamp(16px,3vw,40px) 22px; font-size:.82rem; }
.auth-foot a { color:var(--text-faint); text-decoration:none; }
.auth-foot a:hover { color:var(--text-dim); text-decoration:underline; }

.auth-card { width:100%; max-width:400px; }
.auth-title { font-family:var(--font-display); font-weight:700; font-size:clamp(1.6rem,3vw,2rem); letter-spacing:-.02em; line-height:1.1; margin:8px 0 6px; }
.auth-title .accent { color:var(--brand-ink); }   /* brand word on brand action surface */
.auth-sub { color:var(--text-dim); font-size:.95rem; margin:0 0 20px; }
.auth-form { display:flex; flex-direction:column; gap:15px; }
.auth-form .check { margin-top:-2px; }
.auth-submit { width:100%; margin-top:6px; text-align:center; }
.auth-alt { margin-top:18px; font-size:.9rem; color:var(--text-dim); }
.auth-alt + .auth-alt { margin-top:6px; }                 /* stacked links stay one group */
.auth-resend { margin-top:10px; }                          /* secondary action under the code form */

/* Passkey sign-in, offered under the password form (ADR-0023). */
.auth-passkey { margin-top:18px; }
.auth-or { display:flex; align-items:center; gap:12px; margin:2px 0 12px; color:var(--text-faint); font-size:.8rem; }
.auth-or::before, .auth-or::after { content:""; flex:1; height:1px; background:var(--border); }

/* The quiet "you could use an app instead" note on the e-mail-code screen. Deliberately a note,
   not a call to action: ADR-0023 dec. 4 offers the upgrade, it does not nag for it. */
.upsell { display:flex; gap:11px; margin-top:18px; padding:13px 15px; border:1px solid var(--border); border-radius:var(--radius); background:var(--surface-2); font-size:.87rem; color:var(--text-dim); line-height:1.5; }
.upsell b { color:var(--text); }
.upsell a { display:inline-block; margin-top:4px; font-weight:600; }
.upsell-icon { font-size:1.15rem; line-height:1.2; }
.auth-errors { margin:2px 0 0; padding-left:18px; display:flex; flex-direction:column; gap:2px; }
.auth-devlink { display:inline-block; margin-top:6px; font-weight:600; }

/* Same condition as the app shell, so a landscape phone gets the stacked auth layout too. */
@media (max-width:820px), (max-height:520px) {
  .auth-shell { flex-direction:column; position:relative; }
  .auth-aside { width:auto; max-width:none; flex-direction:row; align-items:center; justify-content:space-between; padding:14px 20px; gap:12px; }
  .auth-body { display:none; }
  /* Float the theme toggle up into the wordmark strip, centered against it. The strip's height is
     now the wordmark's own (padding + a 40px mark), not a magic number copied from an image. */
  .auth-topbar { position:absolute; top:0; right:0; z-index:2; height:auto; padding:14px 20px; }
  /* Empilhado, o rodapé volta ao centro: à direita ele ficaria sob o polegar e longe do texto. */
  .auth-foot { justify-content:center; }
}

/* Logout control in the rail foot and the drawer.
   ⚠ It is an <a> to /conta/sair, not a submit button (UX-REVIEW-2 ITEM-12) — only that page's form
   posts to /conta/logout. Hence display:block + text-decoration:none + the margin-top that used to
   come from the wrapping .logout-form, which is gone: nothing wraps it any more, and the
   confirmation page's own form sits in an .actions row that owns its spacing. */
.logout-btn {
  display:block; text-decoration:none; margin-top:10px;
  font:inherit; font-size:.82rem; font-weight:500; cursor:pointer; width:100%; text-align:left;
  color:var(--rail-dim); background:transparent; border:1px solid var(--rail-border);
  border-radius:8px; padding:7px 12px; transition:color .15s, border-color .15s, background .15s;
}
.logout-btn:hover { color:var(--rail-text); background:var(--rail-surface); border-color:var(--rail-active); }

/* Quiet legal links under the rail foot — reachable everywhere, never competing with the nav. */
.rail-legal { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:12px; font-size:.78rem; justify-content:space-between; }
.rail-legal a { color:var(--rail-dim); text-decoration:none; }
.rail-legal a:hover { color:var(--rail-text); text-decoration:underline; }

/* Deployed revision (operator only). Mono and dim: a diagnostic, not part of the interface —
   `marca ≠ status`, so it borrows no brand or state colour. */
.build-stamp { margin-top:8px; font-family:var(--font-mono); font-size:.72rem; letter-spacing:.04em; }
.build-stamp a, .build-stamp span { color:var(--rail-dim); text-decoration:none; }
.build-stamp a:hover { color:var(--rail-text); text-decoration:underline; }

/* ---------- Pacotes (bundle purchase) ---------- */
.tile-link { display:block; text-decoration:none; color:inherit; transition:border-color .15s, transform .15s; }
.tile-link:hover { border-color:var(--brand); transform:translateY(-1px); }
.tile-cta { margin-top:10px; font-size:.8rem; font-weight:600; color:var(--brand-ink); }

/* Notas filter bar: fields flex to fill, actions sit on the baseline; wraps on narrow viewports. */
.filters { display:flex; flex-wrap:wrap; align-items:flex-end; gap:14px 18px; margin-bottom:22px; }
.filters .field { flex:1 1 220px; }
.filters-actions { display:flex; gap:10px; align-items:center; }
@media (max-width:640px){ .filters .field, .filters-actions{ flex:1 1 100%; } }

.bundle-choices { border:0; padding:0 clamp(18px,3vw,26px); margin:0 0 4px; }
.bundle-choices > .field-lab { display:block; margin-bottom:12px; padding-top:4px; }
.bundle-choices .validation-message { padding-top:8px; }
/* the three offers, laid out as selectable cards */
/* An explicit class on our own wrapper (UX-REVIEW-2 ITEM-8), not `> div:has(.bundle-card)`: that
   selector matched whatever element InputRadioGroup happened to emit, and when it emitted none the
   cards stacked flush against each other while the landing page's identical pricing row was spaced. */
.bundle-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:14px; }
.bundle-card {
  position:relative; display:block; cursor:pointer;
  border:1px solid var(--border); border-radius:var(--radius); background:var(--surface);
  padding:16px 18px; transition:border-color .15s, box-shadow .15s, transform .15s;
}
.bundle-card:hover { border-color:var(--brand); transform:translateY(-1px); }
.bundle-card.featured { border-color:color-mix(in srgb,var(--brand) 40%,transparent); }
.bundle-card:has(.bundle-radio:checked) {
  border-color:var(--brand); box-shadow:0 0 0 3px color-mix(in srgb,var(--brand) 22%,transparent);
}
/* keep the native radio in flow for a11y/keyboard, but visually minimal in the corner */
.bundle-radio { position:absolute; top:14px; right:14px; width:16px; height:16px; accent-color:var(--brand); }
.bundle-body { display:flex; flex-direction:column; gap:3px; }
.bundle-top { display:flex; align-items:center; gap:8px; }
.bundle-qty { font-family:var(--font-display); font-weight:700; font-size:1.5rem; letter-spacing:-.01em; }
.bundle-qty small { font-family:var(--font-body); font-weight:500; font-size:.8rem; color:var(--text-dim); }
.bundle-tag { font-size:.64rem; text-transform:uppercase; letter-spacing:.08em; font-weight:700;
  color:var(--brand-ink); background:var(--brand-soft); padding:3px 8px; border-radius:100px; }
.bundle-name { font-size:.82rem; color:var(--text-dim); }
.bundle-price { font-family:var(--font-mono); font-size:1.15rem; font-weight:500; margin-top:6px; }
.bundle-unit { font-size:.76rem; color:var(--text-faint); }

/* boleto/pix payment instructions */
.pay .pay-body { padding:18px clamp(18px,3vw,22px) 20px; display:flex; flex-direction:column; gap:16px; }
.pay-field { display:flex; flex-direction:column; gap:6px; }
.pay-code {
  display:block; background:var(--surface-2); border:1px solid var(--border); border-radius:9px;
  padding:12px 14px; font-size:.86rem; word-break:break-all; line-height:1.5; user-select:all;
}
.pay-note { font-size:.85rem; color:var(--text-dim); margin:0; }

/* ---------- Copy affordance (UX-REVIEW POL-5) ----------
   user-select:all on .pay-code helps, but "Copiar" is what people look for on a linha digitável or
   a pix code — and mis-copying one digit means a payment that never arrives.

   The button ships `hidden` and is unhidden by the delegated init in App.razor, so a page rendered
   without JS never offers a control that cannot work. That is the same honesty rule static SSR
   buys everywhere else in this app: nothing on screen that the server did not actually enable. */
.copy-wrap { display:flex; align-items:flex-start; gap:10px; }
.copy-wrap > .pay-code, .copy-wrap > .mono { flex:1; min-width:0; }
.btn.copy-btn { flex-shrink:0; padding:7px 13px; font-size:.82rem; }
/* The confirmation is the button's own label for a moment — no toast, no live region to manage. */
.btn.copy-btn.copied { color:var(--ok-ink); border-color:color-mix(in srgb,var(--ok) 45%,transparent); }

/* recent orders */
.order-item { border-top:1px solid var(--border); }
.order-item:first-child { border-top:0; }
/* The trailing status track is a FIXED length, for exactly the reason spelled out on `.row` above:
   each .order-row is its own grid, so an `auto` pill sized itself to its own label and every row
   ended up with different tracks — "Aguardando pagamento" and "Expirado" right-aligned to
   different edges, and the money/date columns drifted with them (sandbox finding B2, 2026-08-23).
   The track is 11.5rem, not `.row`'s 11rem: the widest StatusLabel here is "Aguardando pagamento",
   MEASURED at 182px (11.375rem) — wider than anything on `.row`, so 11rem really would clip. Every
   px beyond that is taken from the money column, which starts wrapping "R$ 375,00" in the 640–760
   band, so this is deliberately a ~2px margin rather than a round number.
   `justify-self:end` keeps every pill on the same right edge whatever its own width. */
.order-row { display:grid; grid-template-columns:1.3fr .7fr .8fr .9fr 11.5rem; align-items:center;
  gap:14px; padding:13px 18px; }
.order-row .file { font-size:.9rem; font-weight:500; }
.order-row .pill { justify-self:end; }
@media (max-width:640px){ .order-row{ grid-template-columns:1fr auto; } .order-row .cnt{ display:none; } }

/* dev-only payment simulation (Local mode) */
.sim-wrap { margin:16px 0 4px; }
.sim { display:flex; align-items:center; flex-wrap:wrap; gap:10px; }
.order-sim { padding:0 18px 14px; }
.sim-lab { font-family:var(--font-mono); font-size:.66rem; text-transform:uppercase; letter-spacing:.1em;
  font-weight:700; color:var(--warn-ink); background:var(--warn-soft); padding:3px 9px; border-radius:100px;
  border:1px solid color-mix(in srgb,var(--warn) 30%,transparent); }
.sim-btn { padding:7px 13px; font-size:.82rem; }

/* ---------- Blazor's unhandled-error bar ----------
   ⚠ These rules MUST stay global. They used to live in MainLayout.razor.css, which is *scoped*:
   Blazor rewrites it to `#blazor-error-ui[b-xxxxx]`, and only MainLayout's own markup carries that
   attribute. So the moment a second layout rendered the same div (PublicLayout), it had no
   `display:none` and the bar showed permanently, on every public page, with no error to report.
   The element is framework infrastructure shared by every layout — its styling is not any one
   layout's business. */
#blazor-error-ui {
  display:none; position:fixed; bottom:0; left:0; width:100%; z-index:1000; box-sizing:border-box;
  padding:11px 44px 12px 20px; font-size:.9rem;
  background:var(--crit-soft); color:var(--text);
  border-top:1px solid color-mix(in srgb,var(--crit) 40%,transparent);
  box-shadow:0 -1px 12px rgba(0,0,0,.14);
}
#blazor-error-ui .reload { color:var(--crit-ink); font-weight:600; text-decoration:underline; }
#blazor-error-ui .dismiss { cursor:pointer; position:absolute; right:14px; top:10px; }

/* ---------- Pagination (UX-REVIEW POL-9) ----------
   Query-string paging with plain GET links — no JS, no interactivity, and every page is a real URL
   that can be bookmarked, shared or opened in a new tab. The current page is a <span>, not a link:
   a link to where you already are is noise to a screen reader and a dead target for a pointer. */
.pager { display:flex; align-items:center; justify-content:space-between; gap:12px 16px; flex-wrap:wrap; padding:14px 18px; border-top:1px solid var(--border); }
.pager-info { font-size:.84rem; color:var(--text-dim); }
.pager-info b { color:var(--text); font-family:var(--font-mono); font-variant-numeric:tabular-nums; font-weight:500; }
.pager-links { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
.pager-link {
  display:inline-flex; align-items:center; justify-content:center; min-width:38px; height:38px;
  padding:0 11px; border:1px solid var(--border); border-radius:9px;
  font-size:.86rem; font-weight:500; color:var(--text-dim); text-decoration:none; background:var(--surface);
}
.pager-link:hover { border-color:var(--brand); color:var(--brand-ink); text-decoration:none; }
.pager-link.current { background:var(--brand-soft); border-color:color-mix(in srgb,var(--brand) 40%,transparent); color:var(--brand-ink); font-weight:600; }
.pager-gap { color:var(--text-faint); padding:0 2px; }
@media (max-width:820px), (max-height:520px) {
  .pager-link { min-width:44px; height:44px; }
}

/* ---------- Print (UX-REVIEW POL-8) ----------
   /termos and /privacidade get printed or saved to PDF by exactly the kind of buyer who reads
   them, and a batch report is what someone takes to their accountant. Everything the reader cannot
   act on from paper is dropped — nav, toggles, drawers, buttons, ornaments — and the page reverts
   to black on white so it does not consume a cartridge rendering the dark theme. */
@media print {
  .rail, .topbar, .mobile-bar, .mobile-nav, .theme-toggle, .nav-toggle, .skip-link,
  .pub-actions, .rail-legal, .logout-btn, .build-stamp, .actions, .filters, .sim-wrap,
  .order-sim, .copy-btn, .pager, .rail-ornament, .stamp, .lp-seal-mark, .section-nav,
  #blazor-error-ui {
    display:none !important;
  }
  html, body, .shell, .pub-shell, .main, .content, .pub-content, .panel, .tile, .card {
    background:#fff !important; color:#000 !important; box-shadow:none !important;
  }
  .content, .pub-content { max-width:none; padding:0; }
  .panel, .tile, .card, .notice { border:1px solid #999 !important; break-inside:avoid; }
  a { color:#000 !important; text-decoration:underline; }
  /* A printed link's target is invisible otherwise — but only for real destinations: in-page
     fragments and javascript-less anchors would just add noise. */
  .legal a[href^="http"]::after, .art-body a[href^="http"]::after { content:" (" attr(href) ")"; font-size:.85em; }
  /* Tables must show every column on paper — hide-sm/hide-xs are viewport concessions, and the
     .table-wrap scroll box would otherwise clip whatever did not fit the sheet. */
  .table-wrap { overflow:visible !important; }
  .table .hide-sm, .table .hide-xs { display:table-cell !important; }
  .table { font-size:.8rem; }
  .table th, .table td { border-color:#999 !important; }
  h1, h2, h3 { break-after:avoid; }
  .page-title { font-size:1.6rem; }
}

/* ---------- Motion ---------- */
@media (prefers-reduced-motion: reduce) { * { transition:none !important; animation:none !important; } }

/* keep Blazor's default validation helpers */
.valid.modified:not([type=checkbox]) { outline:1px solid var(--ok); }
.invalid { outline:1px solid var(--crit); }
.validation-message { color:var(--crit); }

/* ── Segurança da conta (ADR-0023) ──────────────────────────────────────────────────────── */
.seg-card { margin-bottom:18px; }
.seg-title { font-family:var(--font-display); font-weight:600; font-size:1.05rem; margin:0 0 6px; display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.seg-sub { color:var(--text-dim); font-size:.92rem; line-height:1.55; margin:0; }
.seg-row { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; }
.seg-form { display:flex; gap:10px; flex-wrap:wrap; margin-top:16px; }
.seg-form-inline { display:flex; align-items:flex-end; gap:12px; flex-wrap:wrap; margin-top:16px; }
.seg-form-inline .field { flex:1; min-width:180px; }
.seg-hint { font-size:.82rem; color:var(--text-faint); margin:10px 0 0; }
.seg-qr { display:block; margin:16px 0; border-radius:10px; background:#fff; padding:10px; }
.seg-meta { display:block; font-size:.8rem; color:var(--text-faint); }
.seg-list { list-style:none; margin:16px 0 0; padding:0; display:flex; flex-direction:column; gap:10px; }
.seg-list li { display:flex; align-items:center; justify-content:space-between; gap:14px; padding:12px 14px; border:1px solid var(--border); border-radius:10px; background:var(--surface-2); }

/* The honest case for the app: mechanism, sources, and the downside — all three (ADR-0023 dec. 5). */
.seg-facts { margin:14px 0 0; padding-left:18px; display:flex; flex-direction:column; gap:9px; font-size:.88rem; color:var(--text-dim); line-height:1.55; }
.seg-facts b { color:var(--text); }

.seg-recovery { border-color:color-mix(in srgb,var(--warn) 40%,transparent); }
.recovery-codes { list-style:none; margin:16px 0 0; padding:0; display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:8px; }
.recovery-codes li { padding:9px 11px; border:1px solid var(--border); border-radius:8px; background:var(--surface-2); text-align:center; letter-spacing:.04em; }

.btn.small { padding:6px 13px; font-size:.85rem; }
.page-foot { margin-top:24px; font-size:.9rem; }

/* ── Páginas de texto corrido (privacidade) ─────────────────────────────────────────────── */
/* Reuses the seg-* card vocabulary; only prose rhythm is added here. */
.legal { max-width:74ch; }
.legal .seg-sub + .seg-sub { margin-top:11px; }
.legal .seg-facts + .seg-sub { margin-top:14px; }
.legal-sub { font-family:var(--font-display); font-weight:600; font-size:.95rem; color:var(--text); margin:20px 0 0; }
.legal-sub:first-of-type { margin-top:14px; }
.legal .seg-facts { margin-top:9px; }

@media (max-width:620px) {
  .seg-list li { flex-direction:column; align-items:flex-start; }
  .seg-form .btn, .seg-form form { width:100%; }
  .seg-form .btn { width:100%; }
}

/* ── Área pública (/artigos) ────────────────────────────────────────────────────────────────
   Shell próprio (PublicLayout): o rail do app só tem links que exigem sessão, então um leitor
   anônimo bateria em seis redirecionamentos para o login. Aqui a única ação é criar conta. */
.pub-shell { min-height:100vh; min-height:100dvh; display:flex; flex-direction:column; background:var(--bg); }
/* A barra sangra de ponta a ponta (fundo + borda); o conteúdo dela para na mesma largura da
   coluna de baixo. Sem o wrapper, num monitor largo a marca e as ações iam para as bordas do
   vidro com um vão morto no meio, e nada se alinhava com a página. --pub-max é o mesmo valor
   que a landing usa em .pub-content:has(.lp-hero). */
.pub-top {
  padding:16px clamp(16px,4vw,40px); border-bottom:1px solid var(--border); background:var(--surface);
}
.pub-top-inner {
  display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap;
  width:100%; max-width:var(--pub-max); margin:0 auto;
}
/* The public header sits on --surface in both themes, so the wordmark uses the page tokens (no
   dark-surface re-map). That is the whole point of it being type: one component, every surface. */
.pub-actions { display:flex; align-items:center; gap:8px 16px; flex-wrap:wrap; }
.pub-link { color:var(--text-dim); font-size:.9rem; font-weight:500; text-decoration:none; }
.pub-link:hover { color:var(--text); text-decoration:underline; }
/* "Entrar" (quem volta) e "Criar conta" (quem chega) encostados se confundem numa varrida rápida;
   o respiro extra separa os dois caminhos. Margem, não gap, para não afastar também os "Artigos". */
.pub-link-sep { margin-right:6px; }
.pub-content { flex:1; width:100%; max-width:800px; margin:0 auto; padding:clamp(24px,4vw,48px) clamp(16px,4vw,40px); }
/* Mesmo alinhamento do cabeçalho: a borda de cima sangra de ponta a ponta, o conteúdo para em
   --pub-max. O .pub-foot é o elemento que sangra; .pub-foot-inner é o que alinha. */
.pub-foot {
  flex-shrink:0; border-top:1px solid var(--border);
  font-size:.82rem; color:var(--text-faint);
  padding:20px clamp(16px,4vw,40px);
}
.pub-foot-inner {
  display:flex; align-items:center; justify-content:space-between; gap:12px 24px;
  flex-wrap:wrap; width:100%; max-width:var(--pub-max); margin:0 auto;
}
.pub-foot-links { display:flex; gap:18px; flex-wrap:wrap; }
.pub-foot a { color:var(--text-faint); text-decoration:none; }
.pub-foot a:hover { color:var(--text-dim); text-decoration:underline; }
.pub-foot-contact a { color:var(--text-dim); }
.pub-foot-contact a:hover { color:var(--text); text-decoration:underline; }

/* Índice */
.art-list { display:flex; flex-direction:column; }
.art-card { padding:20px 0; border-top:1px solid var(--border); }
.art-card:last-child { border-bottom:1px solid var(--border); }
.art-card-title { display:block; font-family:var(--font-display); font-weight:600; font-size:1.25rem; line-height:1.25; letter-spacing:-.01em; color:var(--text); text-decoration:none; text-wrap:balance; }
.art-card-title:hover { color:var(--brand-ink); text-decoration:underline; }
.art-card-sub { color:var(--text-dim); font-size:.95rem; line-height:1.55; margin:7px 0 9px; max-width:70ch; }
.art-card-date { font-family:var(--font-mono); font-size:.75rem; letter-spacing:.05em; color:var(--text-faint); text-transform:uppercase; }

/* Artigo */
.art-back { display:inline-block; font-size:.82rem; color:var(--text-faint); text-decoration:none; margin-bottom:12px; }
.art-back:hover { color:var(--text-dim); text-decoration:underline; }
.art-body { max-width:70ch; font-size:1.03rem; line-height:1.75; color:var(--text); }
.art-body > p:first-of-type { font-size:1.14rem; color:var(--text); }
.art-body p { margin:0 0 1.15rem; }
.art-body h2 { font-family:var(--font-display); font-weight:600; font-size:1.3rem; line-height:1.25; letter-spacing:-.01em; margin:2.1rem 0 .7rem; }
.art-body h3 { font-family:var(--font-display); font-weight:600; font-size:1.05rem; margin:1.6rem 0 .5rem; }
.art-body a { color:var(--brand-ink); text-decoration:underline; text-underline-offset:2px; }
.art-body strong { font-weight:600; }
.art-body ul, .art-body ol { margin:0 0 1.15rem; padding-left:1.35rem; }
.art-body li { margin-bottom:.5rem; }
.art-body ul li::marker { color:var(--brand); }
.art-body ol li::marker { font-family:var(--font-mono); color:var(--brand-ink); }
.art-body code { font-family:var(--font-mono); font-size:.86em; background:var(--surface-2); border:1px solid var(--border); border-radius:6px; padding:.06em .36em; }
.art-body blockquote { margin:1.5rem 0; padding:2px 0 2px 16px; border-left:3px solid var(--brand); color:var(--text-dim); }
.art-body blockquote p:last-child { margin-bottom:0; }
.art-body table { width:100%; border-collapse:collapse; margin:0 0 1.3rem; font-size:.92rem; }
.art-body th, .art-body td { text-align:left; padding:9px 12px; border-bottom:1px solid var(--border); }
.art-body th { font-weight:600; color:var(--text-dim); font-size:.82rem; text-transform:uppercase; letter-spacing:.05em; }
.art-body hr { border:0; border-top:1px solid var(--border); margin:2rem 0; }
/* :::atencao … ::: — Markdig's custom container. The honest-scope callout: what the article does
   NOT cover, which city we actually serve, when to ask an accountant. Warn colour, not brand:
   marca ≠ status (ADR-0013). */
.art-body .atencao {
  margin:1.7rem 0; padding:14px 16px; border-radius:var(--radius);
  border:1px solid color-mix(in srgb,var(--warn) 34%,transparent); background:var(--warn-soft);
  font-size:.95rem; line-height:1.6;
}
.art-body .atencao::before { content:"Atenção"; display:block; font-family:var(--font-display); font-weight:600; font-size:.72rem; text-transform:uppercase; letter-spacing:.12em; color:var(--warn-ink); margin-bottom:6px; }
.art-body .atencao > :last-child { margin-bottom:0; }

.art-cta { margin-top:34px; }

/* An article may ship a table wider than a phone; scrolling it beats breaking the page. */
.art-body table { display:block; overflow-x:auto; max-width:100%; }

@media (max-width:620px) {
  .pub-top { padding:12px 16px; }
  /* O flex agora é o -inner: empilhar o .pub-foot (que só sangra) não faria nada. */
  .pub-foot-inner { flex-direction:column; align-items:flex-start; }
}
@media (max-width:480px) { .hide-xs { display:none; } }

/* ── Landing page (/) ───────────────────────────────────────────────────────────────────────
   A porta de entrada do funil. Mesmo sistema de tokens do app (ADR-0013) — nenhuma cor nova,
   nenhuma fonte nova, nenhum CDN. `marca ≠ status`: --brand é marca/ação, o carimbo EMITIDA é o
   mesmo motivo da tela de login, e os avisos de escopo usam .notice warn, que já é estado. */
/* A landing respira mais que um artigo. Feito com :has() para o PublicLayout não precisar saber
   qual página está renderizando; onde :has() não existir, ela simplesmente fica com a largura de
   leitura padrão — mais estreita, nunca quebrada. */
.pub-content:has(.lp-hero) { max-width:var(--pub-max); }

.lp-hero { display:flex; align-items:center; gap:clamp(24px,5vw,64px); padding:clamp(8px,3vw,26px) 0 clamp(30px,5vw,52px); }
.lp-hero-copy { flex:1 1 460px; min-width:0; }
.lp-title {
  font-family:var(--font-display); font-weight:700; letter-spacing:-.025em; line-height:1.03;
  font-size:clamp(2.2rem,5.2vw,3.5rem); text-wrap:balance; margin:12px 0 14px;
}
.lp-title .accent { color:var(--ok-ink); }   /* palavra de estado — verde semântico, não marca */
.lp-lead { font-size:clamp(1.02rem,1.6vw,1.16rem); line-height:1.6; color:var(--text-dim); max-width:52ch; margin:0 0 24px; }
.lp-cta { display:flex; flex-wrap:wrap; gap:12px; }
.lp-cta .btn { padding:11px 22px; font-size:.98rem; }
.lp-fineprint { margin:16px 0 0; font-size:.85rem; color:var(--text-faint); max-width:60ch; }

/* O carimbo sobre o cometa — a assinatura visual, a mesma do aside de login */
.lp-hero-seal { flex:0 0 auto; display:inline-grid; place-items:center; position:relative; }
.lp-seal-mark { position:absolute; width:270px; max-width:46vw; opacity:.13; transform:rotate(11deg); pointer-events:none; user-select:none; }
.lp-stamp {
  position:relative; font-family:var(--font-display); font-weight:700; text-transform:uppercase;
  font-size:clamp(1.5rem,3.4vw,2.5rem); letter-spacing:.14em; text-indent:.14em; line-height:1;
  color:var(--brand); padding:.26em .42em .3em;
  border:3px solid color-mix(in srgb,var(--brand) 52%,transparent); border-radius:12px;
  box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--brand) 20%,transparent);
  transform:rotate(-6deg); user-select:none;
}

.lp-section { padding:clamp(30px,5vw,52px) 0; border-top:1px solid var(--border); }
.lp-h2 { font-family:var(--font-display); font-weight:600; font-size:clamp(1.35rem,2.6vw,1.75rem); letter-spacing:-.015em; line-height:1.2; margin:0 0 10px; text-wrap:balance; }
.lp-section-sub { color:var(--text-dim); font-size:1rem; line-height:1.6; max-width:66ch; margin:0 0 22px; }
.lp-section-sub b, .lp-pain b, .lp-fact b, .lp-steps b { color:var(--text); }

/* Dores e diferenciais: mesma grade fluida, sem cartão pesado — texto é o conteúdo */
.lp-pains, .lp-facts { display:grid; gap:18px 26px; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); margin-top:20px; }
.lp-pain, .lp-fact { color:var(--text-dim); font-size:.95rem; line-height:1.62; padding-left:15px; border-left:2px solid var(--border); }
.lp-fact { border-left-color:color-mix(in srgb,var(--brand) 45%,transparent); }
.lp-pain b, .lp-fact b { display:block; margin-bottom:4px; font-weight:600; }

/* Passos numerados — o mesmo tratamento dos artigos, mas com o disco de marca */
.lp-steps { list-style:none; counter-reset:lp-step; margin:22px 0 0; padding:0; display:grid; gap:16px; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); }
.lp-steps > li { counter-increment:lp-step; position:relative; padding-left:46px; color:var(--text-dim); font-size:.95rem; line-height:1.62; }
.lp-steps > li b { display:block; margin-bottom:4px; font-weight:600; }
.lp-steps > li::before {
  content:counter(lp-step); position:absolute; left:0; top:-1px;
  width:30px; height:30px; display:grid; place-items:center; border-radius:50%;
  font-family:var(--font-display); font-size:.9rem; font-weight:700;
  background:var(--brand-soft); color:var(--brand-ink);
  border:1px solid color-mix(in srgb,var(--brand) 30%,transparent);
}

/* Preços — vindos do IBundleCatalog, nunca escritos à mão */
.lp-prices { display:grid; gap:16px; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); margin-bottom:18px; }
.lp-price { position:relative; padding:22px 20px; border:1px solid var(--border); border-radius:var(--radius); background:var(--surface); }
.lp-price.featured { border-color:var(--brand); box-shadow:var(--shadow); }
.lp-price-tag {
  position:absolute; top:-10px; left:20px; font-size:.68rem; font-weight:600; letter-spacing:.1em;
  text-transform:uppercase; color:var(--on-brand); background:var(--brand); padding:3px 10px; border-radius:100px;
}
.lp-price-name { font-family:var(--font-display); font-weight:600; font-size:.95rem; color:var(--text-dim); }
.lp-price-value { font-family:var(--font-display); font-weight:700; font-size:2rem; letter-spacing:-.02em; margin:6px 0 2px; font-variant-numeric:tabular-nums; }
.lp-price-value small { font-size:.95rem; font-weight:600; color:var(--text-dim); }
.lp-price-qty { font-size:.92rem; color:var(--text); }
.lp-price-unit { font-family:var(--font-mono); font-size:.78rem; color:var(--text-faint); margin-top:6px; }

.lp-more { margin:18px 0 0; font-size:.92rem; font-weight:600; }
.lp-close { margin-top:34px; }

@media (max-width:860px) {
  /* O selo é ornamento: some antes de espremer o texto, que é o que converte. */
  .lp-hero { flex-direction:column; align-items:flex-start; gap:28px; }
  /* .lp-seal-mark é absoluto dentro de um inline-grid, então o cometa não ocupa altura: entre
     561 e 860px o herói vira coluna e o selo fica centrado, e a metade de baixo do cometa
     invadia a borda da seção seguinte. A altura mínima reserva o espaço que o absoluto não
     reserva sozinho (UX-REVIEW POL-12).

     260px é medido, não estimado: o cometa tem 270px de lado e está rotacionado 11°, o que dá
     317px de caixa, e ele começa 58px acima do topo do selo — logo passa 258px abaixo dele.
     200px (o valor sugerido na revisão) deixava ~23px de invasão. Se a largura ou o ângulo de
     .lp-seal-mark mudar, meça de novo. */
  .lp-hero-seal { align-self:center; min-height:260px; }
}
@media (max-width:560px) {
  .lp-hero-seal { display:none; }
  .lp-cta { flex-direction:column; align-items:stretch; }
  .lp-cta .btn { text-align:center; }
}
