/* ==========================================================================
   Auth pages (login.html, register.html)
   Minimal standalone layout — no map, no filters, just a centred card.
   Reuses the same tokens so dark/light themes work out of the box.
   ========================================================================== */

.auth-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--sp-5);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
}
.auth-card {
  width: 100%;
  max-width: 380px;
  /* (Glass Terminal, 2026-07-11) tier-2 tinted card. Auth pages have NO globe
     behind them, so this is deliberately NOT heavy glass: a solid tinted surface
     (glass-2 fallback) + the glass trio — inset top highlight, hairline border,
     tier shadow — reads as premium depth without any backdrop-filter cost. */
  background: var(--glass-2-fallback);
  border: 1px solid var(--glass-hairline);
  border-radius: var(--r-lg);
  padding: var(--sp-6) var(--sp-6) var(--sp-5);
  box-shadow: var(--shadow-3), inset 0 1px 0 var(--glass-highlight);
}
.auth-card__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  margin-bottom: var(--sp-5);
  text-align: center;
}
.auth-card__brand-title {
  font-family: var(--font-wordmark);
  font-size: calc(var(--text-lg) * 1.45);
  font-weight: 700;
  letter-spacing: 0.005em;
  line-height: 1;
  color: var(--ink);
  margin: 0;
}
/* Wordmark on auth pages wraps in <a href="/"> so every brand click
   returns home. Neutralise default anchor chrome. */
a.auth-card__brand-title { text-decoration: none; cursor: pointer; }
a.auth-card__brand-title:hover,
a.auth-card__brand-title:focus-visible { color: var(--ink); opacity: 0.78; }
a.auth-card__brand-title:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}
.auth-card__brand-tagline {
  font-family: var(--font-wordmark);
  font-weight: 500;
  font-size: var(--text-xs);
  color: var(--ink-muted);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.auth-card h1 {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  margin: 0 0 var(--sp-5);
  text-align: center;
  color: var(--ink);
}
.auth-form { display: flex; flex-direction: column; gap: var(--sp-4); }
.auth-form__group { display: flex; flex-direction: column; gap: var(--sp-1); }
.auth-form__label {
  font-size: var(--text-xs);
  color: var(--ink-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
}
/* (Glass Terminal W#2, 2026-07-11) Inset "well" treatment. Critical audit
   finding: fields were invisible until focus because the input bg matched the
   page. Now the field is a clearly recessed, tinted surface AT REST on the card
   — light recedes to the warm elev-2 tint below the white card; the dark
   override (further down) recedes to --bg below the elevated card. Hairline
   border + inner shadow define the well; >=44px touch height. */
.auth-form__input {
  padding: var(--sp-2) var(--sp-3);
  min-height: 44px;
  font-size: var(--text-base);
  /* (W2) recessed-well surface via the --field-well alias (light = elev-2,
     dark = --bg) — one token replaces the old base-elev-2 + dark-override pair. */
  background: var(--field-well);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: inset 0 1px 2px var(--scrim-08);
  font-family: inherit;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.auth-form__input::placeholder { color: var(--ink-faint); }
.auth-form__input:hover { border-color: var(--border-strong); }
/* Mouse focus: quiet border brighten + soft link-tinted glow (the crisp accent
   RING is reserved for keyboard :focus-visible below). */
.auth-form__input:focus {
  outline: none;
  border-color: var(--link);
  box-shadow: inset 0 1px 2px var(--scrim-08),
              0 0 0 3px color-mix(in srgb, var(--link) 18%, transparent);
}
/* Keyboard focus (spec §1.6): 2px accent ring, offset 2px. Higher source order
   than :focus so it restores the ring the outline:none above removes. */
.auth-form__input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: var(--link);
  box-shadow: inset 0 1px 2px var(--scrim-08);
}
/* Error state — set by the form JS via aria-invalid. Danger border + subtle
   danger tint; the keyboard ring turns danger too. Placed after :focus so the
   danger border wins even while focused. */
.auth-form__input[aria-invalid="true"] {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--bg-elev-2));
}
.auth-form__input[aria-invalid="true"]:focus-visible {
  outline-color: var(--danger);
}
/* (W2) The plain dark recess override is now folded into --field-well (dark =
   --bg), so only the aria-invalid dark tint remains here. */
[data-theme="dark"] .auth-form__input[aria-invalid="true"] {
  background: color-mix(in srgb, var(--danger) 14%, var(--bg));
}
/* B8: standard select look for account-page selects (e.g. #digest-hour).
 * Mirrors .lang-select's recipe (~360): appearance:none + chevron data-URI
 * so the native OS dropdown arrow is replaced by our own. The account.html
 * agent applies this class to the element. */
.auth-form__select {
  appearance: none; -webkit-appearance: none;
  padding: 6px 28px 6px 12px;
  background: var(--bg-elev-1); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  font-size: var(--text-sm); cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%237a8694' stroke-width='1.5'><path d='M3 4.5l3 3 3-3'/></svg>");
  background-repeat: no-repeat; background-position: right 8px center; background-size: 10px 10px;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.auth-form__select:hover { border-color: var(--link); }
/* No outline:none here — the global :where(select):focus-visible baseline keeps
   the keyboard ring; mouse focus just brightens the border. */
.auth-form__select:focus { border-color: var(--link); }
.auth-form__hint {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}
/* (Glass Terminal, 2026-07-11) W#7 CTA unification. The accent FILL + hover
   brighten + active compress now come from `.btn .btn--primary` (10-base) on the
   submits that adopt those classes (login / register / account form submits).
   Because 70-aux loads AFTER 10-base at equal specificity, any fill declared on
   the bare `.auth-form__submit` here would defeat `.btn--primary` — so the fill
   moved to `.auth-form__submit:not(.btn)` below and this base rule keeps LAYOUT
   only. Legacy submits that stay bare (the account "Connect with Telegram"
   utility button, plus the out-of-scope admin / forgot-password / reset-password
   aux pages) match `:not(.btn)` and render EXACTLY as before — zero regression.
   `justify-content:center` centres the label for the full-width auth-card submit
   (a .btn flex box would otherwise left-align it); it's inert on the non-flex
   legacy path. Keyboard ring comes from the global :where(button):focus-visible
   baseline. */
.auth-form__submit {
  margin-top: var(--sp-1);
  padding: var(--sp-2);
  min-height: 44px;
  justify-content: center;
  font-size: var(--text-base);
  font-weight: 600;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
/* Legacy (non-.btn) fill: restrained neutral ink at rest → brand accent on
   hover. Buttons carrying `.btn .btn--primary` skip this and take their fill
   from .btn--primary instead. */
.auth-form__submit:not(.btn) {
  background: var(--ink);
  color: var(--bg-elev-1);
  border: 1px solid var(--ink);
}
.auth-form__submit:not(.btn):hover:not(:disabled) {
  background: var(--accent);
  border-color: var(--accent);
}
/* Lift / compress / disabled are fill-free, so they apply to EVERY submit —
   both the .btn primary path and the legacy path. */
.auth-form__submit:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: var(--shadow-2);
}
.auth-form__submit:active:not(:disabled) {
  transform: translateY(0.5px);
  box-shadow: none;
}
.auth-form__submit:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.auth-form__error {
  padding: var(--sp-2) var(--sp-3);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 30%, var(--border));
  border-radius: var(--r-sm);
  color: var(--danger);
  font-size: var(--text-sm);
  text-align: center;
}
.auth-form__error.is-hidden { display: none; }
/* Notifications (J2): checkbox toggle rows, indented sub-controls, and inline
   per-channel test buttons. Reuses the auth-form palette. */
.auth-form__check {
  display: flex; align-items: center; gap: 10px;
  cursor: pointer; font-size: var(--text-base); color: var(--ink);
}
.auth-form__check input[type="checkbox"] {
  width: 16px; height: 16px; flex: none; accent-color: var(--link); cursor: pointer;
}
.auth-form__subgroup {
  display: flex; flex-direction: column; gap: 6px;
  margin: 8px 0 0 26px;   /* indent under the channel toggle */
  padding-left: 12px;
  border-left: 2px solid var(--border);
}
.auth-form__subgroup.is-hidden { display: none; }
.auth-form__test-btn { align-self: flex-start; margin-top: 2px; }
.auth-form__group.is-disabled { opacity: 0.55; }
.auth-form__group.is-disabled .auth-form__check,
.auth-form__group.is-disabled input { cursor: not-allowed; }
.auth-card__footer {
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}
.auth-card__footer a {
  color: var(--link);
  text-decoration: none;
  font-weight: 500;
}
.auth-card__footer a:hover { text-decoration: underline; }
.auth-card__back {
  display: block;
  margin-top: var(--sp-3);
  font-size: var(--text-xs);
  color: var(--ink-dim);
  text-decoration: none;
  text-align: center;
}
.auth-card__back:hover { color: var(--ink-muted); }

/* ==========================================================================
   Settings / admin pages (account.html, admin.html)
   Cards + tables sharing the same tokens as the main app so theme
   switching Just Works.
   ========================================================================== */

.settings-page,
.admin-page {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.settings-card {
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  box-shadow: var(--shadow-1);
}
.settings-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
}
.settings-card__title {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  margin: 0 0 6px;
  color: var(--ink);
}
.settings-card__subtitle {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink-muted);
}
.auth-form__row {
  display: flex;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

/* Account page & admin use the shared auth-form styling, but the buttons
   there should stretch less than the centred auth-card version. */
.settings-card .auth-form__submit {
  align-self: flex-start;
  padding-left: var(--sp-5);
  padding-right: var(--sp-5);
}

/* Tiny sibling of .btn used inside the admin row action column. */
.btn--sm {
  padding: 4px 10px;
  font-size: var(--text-xs);
}
.btn--danger {
  color: #fff;
  background: var(--danger);
  border-color: var(--danger);
}
.btn--danger:hover { background: var(--danger-hover); border-color: var(--danger-hover); }

/* Admin toggle (checkbox + label stacked horizontally). */
.admin-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-sm);
  color: var(--ink);
  cursor: pointer;
}
.admin-toggle input[type="checkbox"] {
  width: 16px; height: 16px;
  accent-color: var(--link);
  cursor: pointer;
}

.admin-table-wrap {
  overflow-x: auto;
  margin: calc(-1 * var(--sp-2)) 0;
}
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.admin-table th,
.admin-table td {
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
  border-bottom: 1px solid var(--border);
}
.admin-table th {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-muted);
  font-weight: 600;
  background: var(--bg);
}
.admin-table tbody tr:hover { background: var(--bg); }
.admin-table__admin {
  color: var(--link);
  font-weight: 600;
}
.admin-table__empty {
  text-align: center;
  padding: var(--sp-5);
  color: var(--ink-dim);
}

.admin-feedback {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.admin-feedback__empty {
  padding: var(--sp-5);
  text-align: center;
  color: var(--ink-dim);
  background: var(--bg);
  border: 1px dashed var(--border);
  border-radius: var(--r-md);
}
.admin-feedback__row {
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.admin-feedback__head {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
  flex-wrap: wrap;
}
.admin-feedback__who  { font-weight: 600; color: var(--ink); }
.admin-feedback__meta { font-size: var(--text-xs); color: var(--ink-muted); }
.admin-feedback__msg  { white-space: pre-wrap; line-height: 1.55; }
.admin-feedback__tail {
  margin-top: var(--sp-2);
  display: flex;
  gap: var(--sp-3);
  font-size: var(--text-xs);
  color: var(--ink-dim);
  flex-wrap: wrap;
}

/* ==========================================================================
   Generic modal (admin user-edit dialog, potentially others).
   ========================================================================== */

.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--sp-5);
}
.modal.is-hidden { display: none; }
.modal__backdrop {
  position: absolute;
  inset: 0;
  background: var(--scrim-42);
  /* Soft blur behind modal — reinforces "the main page has paused" without
     making text illegible. Applied to all .modal__backdrop so the AI upsell
     and the anon login-prompt feel consistent. Safari needs -webkit- prefix. */
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.modal__panel {
  position: relative;
  width: 100%;
  max-width: 480px;
  /* (Glass Terminal, 2026-07-11) tier-4 glass (modal). Opaque fallback first;
     @supports below adds translucency + blur. */
  background: var(--glass-4-fallback);
  border: 1px solid var(--glass-hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-4), inset 0 1px 0 var(--glass-highlight);
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .modal__panel {
    background: var(--glass-4-bg);
    -webkit-backdrop-filter: blur(var(--glass-4-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-4-blur)) saturate(var(--glass-sat));
  }
}
.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}
.modal__title {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  margin: 0;
  color: var(--ink);
}
.modal__close {
  appearance: none;
  background: transparent;
  border: none;
  color: var(--ink-muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  display: inline-flex; align-items: center;   /* C2 — centre the SVG × */
}
.modal__close:hover { color: var(--ink); }
.modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}
.modal__foot {
  border-top: 1px solid var(--border);
  padding-top: var(--sp-3);
  display: flex;
  justify-content: flex-end;
}

/* #21 (2026-04-24) — Generic form elements for any modal. Moved out of the
 * old `.fb-dialog__*` scope so feedback, future settings/admin modals, or
 * any composite dialog can share the same vocabulary (design-critique §3.1).
 * Ported verbatim from the prior `.fb-dialog__*` rules — no visual change
 * intended. Scoped to `.modal` descendants via the element class alone so
 * `.modal` itself can pick them up without additional nesting.
 */
.modal__hint {
  margin: 0 0 var(--sp-2);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  line-height: 1.5;
}
.modal__form {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.modal__label {
  font-size: var(--text-xs);
  color: var(--ink-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  margin-top: 4px;
}
/* (Glass Terminal, 2026-07-11) Inset well — same recessed, clearly-bordered
   treatment as the auth fields so every text control in the app reads alike. */
.modal__input,
.modal__textarea {
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--text-sm);
  /* (W2) recessed-well surface via --field-well (light = elev-2, dark = --bg). */
  background: var(--field-well);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: inset 0 1px 2px var(--scrim-08);
  font-family: inherit;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.modal__input::placeholder,
.modal__textarea::placeholder { color: var(--ink-faint); }
.modal__textarea {
  resize: vertical;
  min-height: 80px;
}
.modal__input:hover,
.modal__textarea:hover { border-color: var(--border-strong); }
.modal__input:focus,
.modal__textarea:focus {
  outline: none;
  border-color: var(--link);
  box-shadow: inset 0 1px 2px var(--scrim-08),
              0 0 0 3px color-mix(in srgb, var(--link) 18%, transparent);
}
/* Keyboard ring restores the outline the :focus rule above removes (spec §1.6). */
.modal__input:focus-visible,
.modal__textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: var(--link);
  box-shadow: inset 0 1px 2px var(--scrim-08);
}
/* (W2) dark recess override folded into --field-well (dark = --bg). */
.modal__error,
.modal__ok {
  margin-top: var(--sp-2);
  padding: 6px var(--sp-2);
  border-radius: var(--r-sm);
  font-size: var(--text-xs);
  text-align: center;
}
.modal__error {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 30%, var(--border));
  color: var(--danger);
}
.modal__ok {
  background: color-mix(in srgb, var(--link) 12%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--link) 30%, var(--border));
  color: var(--link);
}
.modal__error.is-hidden,
.modal__ok.is-hidden { display: none; }
/* (Glass Terminal, 2026-07-11) W#7 CTA unification — #fb-submit now carries
   `.btn .btn--primary` (index.html / feed.html), so the accent fill + hover
   brighten + active compress come from 10-base. Loaded AFTER 10-base, this rule
   keeps LAYOUT only so it can't defeat `.btn--primary`; the lift/compress/
   disabled below are fill-free and apply cleanly. No bare `.modal__submit` uses
   remain, so (unlike .auth-form__submit) no `:not(.btn)` guard is needed. */
.modal__submit {
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-md);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.modal__submit:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: var(--shadow-2);
}
.modal__submit:active:not(:disabled) {
  transform: translateY(0.5px);
  box-shadow: none;
}
.modal__submit:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; }

/* #21 (2026-04-24) — `.modal--feedback` modifier: re-purposes the `.modal`
 * base into a bottom-right **anchored popover** (what `.fb-dialog` used to
 * be). Design-critique §3.1 asked for class-name consistency; user chose
 * to keep the popover UX (no backdrop, anchored to the FAB) so this
 * modifier reinstates those properties on top of `.modal`'s defaults.
 *
 * The `.modal--feedback` div serves as BOTH the modal container AND the
 * visual panel — there's no inner `.modal__panel` wrapper, unlike the
 * centred modals. That keeps the DOM tree identical to the old markup
 * and lets JS (bindFeedbackWidget) stay untouched.
 *
 * Sits below true modals (z 950 vs 1000) so if an ai-upsell pops while
 * the feedback popover is open, the modal takes precedence visually.
 */
.modal--feedback {
  /* Reset the centred-grid layout that `.modal` sets up. */
  inset: auto;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  z-index: var(--z-modal-under);
  /* Anchored position: above the feedback FAB (56 px tall + sp-5 bottom).
   * Bug fix (2026-06-12): Hotfix#6 moved the FAB right → LEFT (to clear
   * the globe panel's right-side floating area) but this anchor kept
   * pointing bottom-right, so the popover opened on the opposite side
   * of the screen from its own button. Mirrored to the left to match. */
  left: var(--sp-5);
  bottom: calc(var(--sp-5) + 56px);
  width: min(360px, calc(100vw - (var(--sp-5) * 2)));
  /* Take over the panel visual since there's no `.modal__panel` inside. */
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  padding: var(--sp-4);
}
/* Head spacing nudge so the first form row doesn't sit too close to the
 * title bar in the narrower popover width. */
.modal--feedback .modal__head {
  margin-bottom: var(--sp-1);
}
/* Close button in the popover variant uses a slightly smaller hit area
 * than the centred-modal version since the whole container is tighter. */
.modal--feedback .modal__close {
  font-size: 22px;
  padding: 2px 6px;
}
/* Actions row sits a touch further from the last form field in the
 * popover — mirrors the prior `.fb-dialog__actions` 12 px top margin. */
.modal--feedback .modal__actions {
  margin-top: var(--sp-3);
}

/* ==========================================================================
   AI admin (Phase G3) — provider + feature-routing tables, kind badges,
   provider modal widened to fit the longer form. All names prefixed with
   .admin- / .kind- so they don't clash with the user-edit styles above.
   ========================================================================== */

/* Monospace table cells — base_url, API-key last-4, feature keys. Adopts the
   Glass Terminal data face (JetBrains Mono) + tabular numerals. */
.admin-table__mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  color: var(--ink);
}

/* Inline warning badge for decrypt_ok === false. */
.admin-table__warn {
  color: var(--danger);
  font-weight: 600;
  margin-left: 6px;
}

/* Row action column — puts space between the 2-4 small buttons. */
.admin-table__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
}

/* Softer delete button — btn--danger is too loud for a secondary row
   action. Uses the danger colour only on the border + text. */
.btn--danger-outline {
  color: var(--danger);
  border-color: var(--danger);
  background: transparent;
}
.btn--danger-outline:hover {
  color: #fff;
  background: var(--danger);
  border-color: var(--danger);
}

/* Inline select used in feature routing rows. Forces a reasonable
   minimum width so the model id is readable, but lets the cell grow
   with the table. */
.admin-select {
  min-width: 220px;
  max-width: 100%;
  padding: 4px 8px;
  font-size: var(--text-sm);
}

/* AI usage (token ledger) — stat cards + breakdown tables. */
.settings-card__head-actions {
  margin-left: auto;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.ai-usage-auto {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-xs); color: var(--ink-muted); white-space: nowrap;
}
.btn--sm { padding: 4px 10px; font-size: var(--text-xs); }
.ai-usage-totals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  margin: 8px 0 18px;
}
.ai-usage-stat {
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 10px 12px;
}
.ai-usage-stat__label { font-size: var(--text-xs); color: var(--ink-muted); }
.ai-usage-stat__value {
  font-size: var(--text-lg, 20px); font-weight: 600; color: var(--ink);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; margin-top: 2px;
}
.ai-usage-block { margin-top: 18px; }
.ai-usage-block__title {
  font-size: var(--text-sm); font-weight: 600; color: var(--ink);
  margin: 0 0 6px;
}
.ai-usage-table td, .ai-usage-table th { font-variant-numeric: tabular-nums; }
.ai-usage-table td:not(.ai-usage-table__k):not(.admin-table__mono),
.ai-usage-table th:not(:first-child) { text-align: right; }
/* Numeric body cells get the mono data face so columns of figures align. */
.ai-usage-table td:not(.ai-usage-table__k):not(.admin-table__mono) { font-family: var(--font-mono); }
.ai-usage-table__k { font-weight: 500; }
.ai-usage-table__total { font-weight: 600; }
.ai-usage-table__cost { color: var(--ink-muted); }
.ai-usage-chart {
  width: 100%; height: 150px; display: block;
  background: var(--bg-elev-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.ai-usage-chart__bar { fill: var(--accent); transition: fill var(--dur-fast) var(--ease); }
.ai-usage-chart__bar:hover { fill: var(--ink); }

/* Kind badge — coloured pill so the admin can scan kind columns. Colours
   picked to be distinct in both light and dark themes (token based so
   tweaking the theme keeps them legible). */
.kind-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--ink);
}
/* Tinted chips — a subtle fill of the badge's own colour lifts each pill off
   the table without hurting the coloured border/label (color-mix re-resolves
   per theme automatically). */
.kind-badge--anthropic     { border-color: var(--badge-anthropic); color: var(--badge-anthropic); background: color-mix(in srgb, var(--badge-anthropic) 12%, transparent); }
.kind-badge--openai        { border-color: var(--badge-openai); color: var(--badge-openai); background: color-mix(in srgb, var(--badge-openai) 12%, transparent); }
.kind-badge--gemini        { border-color: var(--badge-gemini); color: var(--badge-gemini); background: color-mix(in srgb, var(--badge-gemini) 12%, transparent); }
.kind-badge--openai_compat { border-color: var(--ink-muted); color: var(--ink-muted); background: color-mix(in srgb, var(--ink-muted) 12%, transparent); }
/* Dark overrides are now redundant (the --badge-* tokens re-resolve under
   [data-theme="dark"]) but kept as explicit 1:1 no-ops. */
[data-theme="dark"] .kind-badge--anthropic     { border-color: var(--badge-anthropic); color: var(--badge-anthropic); }
[data-theme="dark"] .kind-badge--openai        { border-color: var(--badge-openai); color: var(--badge-openai); }
[data-theme="dark"] .kind-badge--gemini        { border-color: var(--badge-gemini); color: var(--badge-gemini); }

/* Model list modal — plain <ul> of <code> ids. */
.admin-models {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.admin-models li {
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg);
  font-size: var(--text-sm);
}
.admin-models code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink);
}
.admin-models__display {
  color: var(--ink-muted);
  font-size: var(--text-xs);
}

/* Widen the provider modal — the form is taller than the user-edit one
   so the fixed 480px default was cramped. Scoped to the provider modal
   only so the user-edit modal keeps its existing width. */
#provider-modal .modal__panel { max-width: 560px; }
#models-modal   .modal__panel { max-width: 560px; }

/* Utility visibility toggle used by the base_url row (hidden when kind
   isn't openai_compat). The generic .is-hidden above uses display:none
   which also works for form groups, but we add this alias so intent is
   clear in the HTML and JS. */
.auth-form__group.is-hidden { display: none; }

/* ==========================================================================
   Floating feedback widget (index.html).
   Pinned bottom-right so it doesn't compete with the pager, and tucked
   under --sp-5 of inset so it sits comfortably on small screens too.
   ========================================================================== */

.fb-button {
  position: fixed;
  /* Hotfix#6 (2026-04-28): moved right -> left to clear the
   * .globe-panel's right-side floating area. The headline panel now
   * lives at right:16/bottom:16 and a fixed feedback button used to
   * overlap its bottom-right corner. Bottom-left is unused real
   * estate and matches the user's mental model of "system controls
   * cluster on one side, content on the other". */
  left: var(--sp-5);
  bottom: var(--sp-5);
  z-index: var(--z-fab);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px 16px 10px 14px;
  /* (Glass Terminal, 2026-07-11) tier-1 glass FAB — IDENTICAL surface tokens to
     the globe control pills (.globe-reset / .globe-geo): a near-opaque tint with
     NO backdrop blur (the WebGL-hero perf guard, audit #25) + the glass trio:
     inset top highlight, hairline border, tier shadow. JS writes opacity /
     pointer-events / left / right inline here — none touched, no !important. */
  background: color-mix(in srgb, var(--bg-elev-1) 92%, transparent);
  color: var(--ink);
  border: 1px solid var(--glass-hairline);
  border-radius: 999px;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--glass-highlight);
  transition: transform var(--dur-base) var(--ease-out), background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out),
              left var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}
/* Scroll-position opacity fade (app.js sets .style.opacity): visible at the very
   top, fades out fast as scrolling starts, fades back in over 100%→150% of the
   button's own height. The opacity transition smooths the per-frame steps. */
.fb-button:hover {
  transform: translateY(-1px);
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
  box-shadow: var(--shadow-3), inset 0 1px 0 var(--glass-highlight);
}
.fb-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Home position = bottom-left (left: var(--sp-5) above). The right-shift that
   clears the hero's left profile panel is applied by app.js via inline `left`,
   but ONLY at the very top with a country panel open; once scrolled it returns
   home (so it never covers the filter panel at the shifted/centre spot). The
   `left` transition above just smooths that slide. */

/* A2 (2026-07-07): on the GLOBE page the new mini status stamp claims the
   bottom-LEFT corner, so the feedback FAB moves to bottom-RIGHT. app.js skips
   the left-shift dodge on the globe page (it's a right-anchored button there),
   and when a country panel opens on the right the FAB dodges LEFT instead. */
body[data-page="globe"] .fb-button {
  left: auto;
  right: var(--sp-5);
  transition: transform var(--dur-base) var(--ease-out), background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out),
              right var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}

.fb-button__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
@media (max-width: 520px) {
  .fb-button__label { display: none; }
  .fb-button { padding: 10px; }

  /* Page-split (2026-07-06): the 40vh phone hero shrink + its popover re-cap
   * were DELETED (see the 46vh block above) — the globe page hero is full
   * 100dvh at all widths now that the feed is a separate page. */
  .geo-panel__title { font-size: var(--text-md); }
  .geo-panel__header { gap: var(--sp-2); }
}

/* Phase D (2026-06-19) — touch targets. Coarse pointers (fingertips) get
 * >=44px on buttons + standalone icon controls per WCAG 2.5.8. Scoped to
 * pointer:coarse so the desktop mouse UI keeps its compact sizing. */
@media (pointer: coarse) {
  .btn, .icon-btn { min-height: 44px; }
  .globe-panel__close { min-width: 44px; min-height: 44px; }

  /* Globe overlay controls (audit #65). Fingertips need a >=44px hit area, but
   * the calm/dense look must survive — so where a control is a small visual pill
   * we EXPAND THE HIT AREA with a transparent ::after overlay rather than growing
   * the pixels. The 38px reset/geo pills keep their look; only the tap target
   * grows. Positioned controls (::after) already establish a positioning context
   * (position:absolute), so the overlay anchors correctly. */
  .globe-reset,
  .globe-geo {
    position: absolute;   /* already absolute; restated so ::after is anchored */
  }
  .globe-reset::after,
  .globe-geo::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 44px; height: 44px;   /* invisible tap target ≥44px around the 38px pill */
  }

  /* Base-imagery layer chips: pad the row up to a comfortable tap height. Radius
   * pill stays; only vertical padding grows so the chip bar reads the same. */
  .globe-layer-chip { padding-top: 11px; padding-bottom: 11px; min-height: 44px; }

  /* Layers popover rows — comfortable spacing + tap height per audit note. */
  .globe-data-pop__item { min-height: 44px; padding-top: 8px; padding-bottom: 8px; }

  /* Legend + popover period selects (stage-3 controls) and the profile period
   * select: give them a real tap height without changing the visible font. */
  .globe-legend__period,
  .globe-data-pop__period,
  .globe-profile__period { min-height: 36px; padding-top: 6px; padding-bottom: 6px; }

  /* Watchlist dock toggle pill + its row action buttons. */
  .watchlist-dock__toggle { min-height: 44px; }
  .watchlist-dock__go,
  .watchlist-dock__remove { width: 40px; height: 40px; }

  /* Mobile Watchlist menu button already inherits .btn min-height:44px above;
   * the overlay row buttons are covered by the dock rules. */

  /* (Glass Terminal, Wave 2) FAB to a comfortable tap height — icon-only at
   * <=520px (padding 10px) would otherwise land near ~36px. It's a .fb-button,
   * not a .btn, so it isn't covered by the 44px rule above. */
  .fb-button { min-height: 40px; }

  /* (Glass Terminal, Wave 2) iOS input-zoom guard (>=16px) for auth + modal +
   * admin fields. NB: --text-base is 15px, so the auth inputs need the bump too.
   * Desktop (pointer:fine) keeps the compact --text-* sizes. */
  .auth-form__input,
  .auth-form__select,
  .modal__input,
  .modal__textarea,
  .admin-select { font-size: 16px; }
}

/* (Glass Terminal, Wave 2) iOS home-bar safe area for the fixed bottom-anchored
   controls — lift them clear of the home indicator where the inset exists.
   @supports-guarded so non-iOS keeps the plain offsets. `bottom` is not
   JS-inline-owned on either element (JS writes left/right/opacity), so overriding
   it here can't fight runtime styles. Loaded after each base rule → wins by
   source order at equal specificity. */
@supports (padding: env(safe-area-inset-bottom)) {
  .fb-button { bottom: calc(var(--sp-5) + env(safe-area-inset-bottom, 0px)); }
  .modal--feedback { bottom: calc(var(--sp-5) + 56px + env(safe-area-inset-bottom, 0px)); }
}

/* #21 (2026-04-24) — `.fb-dialog*` block removed. See `.modal--feedback`
 * and `.modal__{hint,form,label,input,textarea,error,ok,submit}` above:
 * the feedback popover now reuses the unified modal vocabulary per
 * design-critique §3.1. Markup migrated in `frontend/index.html`;
 * bindFeedbackWidget() in app.js is untouched (IDs preserved). */

/* ==========================================================================
   (Glass Terminal, 2026-07-11) Skip-link — elevate to a tier-4 focus chip.
   --------------------------------------------------------------------------
   The canonical rule lives in 60-widgets.css (accent fill, #fff text, slide-in
   on focus, ink focus outline). This partition loads AFTER it, so these purely
   ADDITIVE declarations layer the glass trio — inset top highlight + hairline +
   tier-4 shadow — on top WITHOUT !important, via source order. The accent fill,
   slide-in transform and ink focus outline are deliberately preserved (an
   accent-on-accent ring would be invisible; a11y contrast is paramount here).
   ========================================================================== */
.skip-link {
  border: 1px solid var(--glass-hairline);
  box-shadow: var(--shadow-4), inset 0 1px 0 var(--glass-highlight);
}

/* ==========================================================================
   (Glass Terminal, 2026-07-11) prefers-reduced-transparency gate
   --------------------------------------------------------------------------
   Users who ask the OS for reduced transparency get the OPAQUE fallback on
   every glass surface — no translucency, no backdrop blur. This lives LAST in
   the cascade (final partition, end of file) so it overrides each surface's
   @supports enhancement WITHOUT !important — which matters because JS writes
   `background` inline on some elements and !important would fight it. Source
   order (this block is later) wins at equal specificity. backdrop-filter is
   never written inline by JS, so forcing it off is safe.
   The companion motion gate (prefers-reduced-motion) is already consolidated
   in 50-feed.css (`* { transition-duration:0; animation:none }`).

   Coverage (Wave 2 · W3): the base tier-1/3/4 surfaces PLUS every Wave-1/2 glass
   surface added across partitions — .map-tooltip / .map-toolbar__reset /
   .map-legend / .msel__panel / .globe-brief / .watchlist-dock__toggle /
   .watchlist-dock__panel (all 50-feed). .watchlist-dock__* + .globe-brief are
   opaque today (--bg-elev-1 == --glass-1-fallback / an unconditional blur being
   re-glassed in parallel) so gating them is zero-change now and correct once the
   feed pass lands. The globe-hero overlays (30-globe: .globe-layers/.globe-reset/
   .globe-geo/.globe-status-stamp/.globe-legend/.pw-detail) and .globe-panel
   (40-panel) keep their own LOCAL reduced-transparency gates in-file — NOT
   duplicated here.
   ========================================================================== */
@media (prefers-reduced-transparency: reduce) {
  /* tier-1 fallback — topbar + the Wave-1/2 map & watchlist mini-surfaces */
  .topbar-float,
  .map-toolbar__reset,
  .map-legend,
  .watchlist-dock__toggle,
  .watchlist-dock__panel { background: var(--glass-1-fallback); }
  /* tier-3 fallback — dropdowns / popovers / tooltips / since-last-visit brief */
  .user-menu__dropdown,
  .globe-data-pop,
  .msel__panel,
  .map-tooltip,
  .globe-brief { background: var(--glass-3-fallback); }
  /* tier-4 fallback — modal / command palette */
  .cmdk__panel,
  .modal__panel { background: var(--glass-4-fallback); }
  /* Force the blur off on every glass surface + the blurred backdrop scrims. */
  .topbar-float,
  .map-toolbar__reset,
  .map-legend,
  .watchlist-dock__toggle,
  .watchlist-dock__panel,
  .user-menu__dropdown,
  .globe-data-pop,
  .msel__panel,
  .map-tooltip,
  .globe-brief,
  .cmdk__panel,
  .modal__panel,
  .modal__backdrop,
  .cmdk__backdrop,
  /* Non-glass blur carriers: the legacy masthead (rgba .82 fill stays — it is
     near-opaque without the blur) and the two scrim overlays whose translucent
     dim must survive with the blur alone removed. */
  .masthead,
  .globe-ctrl-hint,
  .watchlist-overlay-backdrop {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
