/* ───────────────────────── Sanolith, clinical-editorial ─────────────────────────
   Three type registers (serif / sans / mono), bone background, single teal accent,
   redaction as visual motif. Hairline borders, dense layout.

   ─── Breakpoints (canonical, grep-friendly) ───
       600px   --bp-sm   (phone)
       900px   --bp-md   (tablet / drawer point)
      1200px   --bp-lg   (desktop)
   Validator W13 fails the build if any @media uses a different max-width
   outside @media print. CSS custom-props aren't valid inside media queries
   so these stay as literals; comment is the source of truth. */

@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url("/static/fonts/InterVariable.woff2") format("woff2");
}
@font-face {
  font-family: "Source Serif 4"; font-style: normal; font-weight: 200 900;
  font-display: swap; src: url("/static/fonts/SourceSerif4-Variable.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 100 800;
  font-display: swap; src: url("/static/fonts/JetBrainsMono-Variable.woff2") format("woff2");
}

/* :root tokens moved to tokens.css */

/* ── Reset / body ── */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { background: var(--app-backdrop-base, var(--bone)); }
body {
  font: 15px/1.45 var(--sans);
  font-feature-settings: "cv11", "ss01";
  letter-spacing: -0.005em;
  color: var(--ink);
  background:
    linear-gradient(125deg, transparent 0 48%, var(--app-backdrop-glow-right) 100%),
    linear-gradient(235deg, var(--app-backdrop-glow-left) 0, transparent 22rem),
    linear-gradient(180deg, transparent 0, transparent 42%, var(--app-backdrop-vignette) 100%),
    linear-gradient(180deg, var(--app-backdrop-top) 0, var(--app-backdrop-base, var(--bone)) 23rem),
    var(--app-backdrop-base, var(--bone));
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
}
button, input, textarea, select { font: inherit; color: inherit; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4, h5 {
  font-family: var(--serif); font-weight: 500;
  letter-spacing: -0.015em; color: var(--ink);
  margin: 0;
}
.mono, time, .timestamp, code, kbd, samp, pre {
  font-family: var(--mono);
  font-feature-settings: "calt" 1;
}

/* ── Topbar ── */
.topbar {
  position: sticky; top: 0; z-index: 30;
  background: color-mix(in srgb, var(--bone) 88%, transparent);
  backdrop-filter: blur(14px) saturate(1.05);
  -webkit-backdrop-filter: blur(14px) saturate(1.05);
  /* 0.53.0 v4 — quieter divider. var(--rule) was reading as a hard
     dark line in dark mode (near-black rule on near-black paper);
     rule-soft gives the same visual separation in light mode with
     a barely-there hint in dark. */
  border-bottom: 1px solid var(--rule-soft);
}
.topbar-inner {
  max-width: 1200px; margin: 0 auto;
  min-height: 58px;
  padding: 0.55rem 1.5rem;
  display: flex; align-items: center; gap: 1.05rem;
}
.brand-cluster { display: flex; align-items: center; gap: 0.55rem; min-width: 0; }
.brand {
  font-family: var(--sans);
  font-weight: 720; font-size: 1.02rem; letter-spacing: 0;
  color: var(--ink); text-decoration: none;
  display: inline-flex; align-items: center; gap: 0.42rem;
  min-width: 0;
}
.brand:hover { text-decoration: none; color: var(--accent); }
.brand svg { color: var(--accent); width: 17px; height: 17px; }
.brand-logo { max-height: 24px; width: auto; }
.workspace-pill {
  display: inline-flex; align-items: center; gap: 0.52rem;
  /* 0.61.0, the pill IS now the brand affordance, so it carries the
     wordmark's typography (weight 720, font-size 1.02rem, no letter
     spacing). The .brand rules above are retained verbatim so any
     future page that wants the older standalone wordmark still gets
     the canonical look — but on the main topbar this pill replaces
     them. Larger height + padding accommodates the bigger text. */
  max-width: 260px; min-width: 0; height: 34px;
  overflow: hidden; white-space: nowrap;
  padding: 0 0.78rem 0 0.68rem;
  border: 1px solid var(--rule-soft);
  border-radius: 999px;
  color: var(--ink);
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--paper) 82%, transparent), color-mix(in srgb, var(--accent-tint) 18%, var(--paper) 82%));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--paper) 86%, transparent),
    0 10px 26px color-mix(in srgb, var(--ink) 7%, transparent);
  /* Typography inherited from .brand (lines 75-84): sans, weight 720,
     1.02rem, no letter-spacing. Line-height 1 keeps the glyph
     vertically centered inside the 34px pill. */
  font-family: var(--sans); font-size: 1.02rem; font-weight: 720;
  letter-spacing: 0; line-height: 1;
}
.workspace-pill svg { color: var(--accent); width: 19px; height: 19px; flex: 0 0 auto; }

/* 0.61.0, when the pill is the canonical home link (anchor-element
   variant), it needs the link-affordance treatment: hover lifts the
   border to --accent and the label to --accent-deep, focus shows a
   visible ring for keyboard users. We deliberately keep the height
   + paddings identical to the plain-span variant so the topbar
   geometry doesn't shift between auth states. */
a.workspace-pill,
.workspace-pill-home {
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  transition: border-color 120ms ease, color 120ms ease, background 120ms ease;
}
a.workspace-pill:hover,
.workspace-pill-home:hover {
  border-color: var(--accent);
  color: var(--accent-deep);
  background: color-mix(in srgb, var(--accent-tint) 50%, var(--paper) 50%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--paper) 86%, transparent),
    0 12px 28px color-mix(in srgb, var(--accent) 14%, transparent);
}
a.workspace-pill:focus-visible,
.workspace-pill-home:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.workspace-pill-home .brand-logo {
  /* Logo (if any) replaces the shield glyph; size matches the
     canonical brand-logo height (24px) from the original .brand
     rules so a tenant's custom logo lands at the same scale it
     would have on the standalone wordmark. */
  max-height: 24px;
  width: auto;
}
.workspace-name {
  min-width: 0;
  /* Don't truncate by default — `SANOLITH PLATFORM` at 1.02rem +
     uppercase + 0.06em letter-spacing was rendering as
     `SANOLITH PLATFO...` because the inherited pill font was sized
     for mixed-case "Sanolith". Drop the size + tracking on the
     workspace-name specifically so the wordmark fits inside the
     existing 286px pill without ellipsis. */
  overflow: hidden;
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 720;
  font-size: 0.84rem;
}
.workspace-lockup {
  display: inline-flex;
  align-items: baseline;
  gap: 0.38rem;
  min-width: 0;
}
.workspace-lockup .workspace-name {
  font-size: 1.06rem;
  font-weight: 780;
}
.workspace-suffix {
  color: var(--muted);
  font-size: 0.76rem;
  font-weight: 680;
  letter-spacing: 0.08em;
  line-height: 1;
  text-transform: uppercase;
}

.topnav { margin-left: auto; display: flex; gap: 0.2rem; align-items: center; min-width: 0; }
.topnav a,
.topnav .nav-item {
  color: var(--ink-soft); padding: 0.4rem 0.75rem;
  border-radius: var(--radius); font-weight: 500; font-size: 0.875rem;
  transition: background 100ms;
  white-space: nowrap;
}
.app-nav .nav-item {
  display: inline-flex; align-items: center; gap: 0.38rem;
  border: 1px solid transparent;
  background: transparent;
  text-decoration: none;
  min-height: 34px;
}
.topnav a:hover,
.topnav .nav-item:hover {
  background: var(--bone-soft); color: var(--ink); text-decoration: none;
}
.app-nav .nav-item.active {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--rule);
  box-shadow: var(--shadow-1);
}
.app-nav .nav-item.active svg { color: var(--accent); }
.nav-command {
  width: auto;
  font: inherit;
  cursor: pointer;
}
.nav-command kbd {
  margin-left: 0.25rem;
  padding: 0.06rem 0.28rem;
  border-radius: 4px;
  background: var(--bone-soft);
  color: var(--muted);
  border: 1px solid var(--rule);
  font-size: 0.68rem;
}
.topnav a.muted { color: var(--muted); }
.topnav a.primary { background: var(--ink); color: var(--bone); }
.topnav a.primary:hover { background: var(--accent-deep); color: var(--paper); }

/* Platform-admin "pop-out" nav. Visually distinct so a cross-tenant
 * operator immediately sees that they're in privileged mode — the icon
 * gets the accent color permanently (vs only-when-active for normal
 * nav items), and a hairline accent border outlines the chip. Hover
 * deepens the accent without flipping to a filled background, so the
 * topbar rhythm stays even. */
.nav-item-platform {
  border-color: color-mix(in srgb, var(--accent) 38%, transparent) !important;
  color: var(--ink);
}
.nav-item-platform svg { color: var(--accent); }
.nav-item-platform:hover {
  border-color: var(--accent) !important;
  background: color-mix(in srgb, var(--accent) 8%, var(--bone-soft)) !important;
}
.app-nav .nav-item-platform.active {
  background: color-mix(in srgb, var(--accent) 12%, var(--paper));
  border-color: var(--accent);
}

/* Wave B Δ5 2026-05-20: topbar prune on /chat — minimal nav with
   Workspace popover + Search + initials avatar. Active only when
   base.html renders the chat-mode branch; other routes use the
   standard 8-item nav above. */
.topnav-workspace-menu { position: relative; display: inline-flex; }
.topnav-workspace-menu > summary {
  list-style: none;
  cursor: pointer;
}
.topnav-workspace-menu > summary::-webkit-details-marker { display: none; }
.topnav-workspace-menu > summary::marker { content: ""; }
.topnav-workspace-pop {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 200px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: var(--shadow-2);
  padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  z-index: 50;
}
.topnav-workspace-pop .menu-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px;
  border-radius: 6px;
  color: var(--ink);
  text-decoration: none;
  font-size: 0.85rem;
  white-space: nowrap;
}
.topnav-workspace-pop .menu-item:hover { background: var(--bone-soft); }
.topnav-workspace-pop .menu-item.active {
  background: var(--bone-soft);
  color: var(--ink);
}
.topnav-workspace-pop .menu-item-signout { color: var(--muted); }
.topnav-workspace-pop .menu-sep {
  height: 1px;
  background: var(--rule-soft);
  margin: 4px 0;
}

.account-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border-radius: 999px;
  background: var(--accent-tint);
  color: var(--accent-deep);
  font-family: var(--sans);
  font-size: 0.8rem; font-weight: 600;
  border: 1px solid var(--accent-soft-border);
  text-decoration: none;
}
.account-avatar:hover { background: var(--accent-soft); }
.account-avatar:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.account-avatar .avatar-initials { line-height: 1; }

main { max-width: 1200px; margin: 0 auto; padding: 1.5rem; }
.muted { color: var(--muted); font-size: 0.875rem; }
.container { max-width: 720px; margin: 0 auto; }
.spacer { flex: 1; }

/* ── Buttons ──
   Hierarchy flip (audit #5): default is secondary (paper + rule); .primary
   is the single CTA per region (dark, accent on hover); .danger is
   destructive. Was the other way round, which meant Save/Sign-out/Trash
   all looked identical on dense admin/account pages. Promote ONE CTA
   per region with .btn.primary. */
button, .btn {
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule);
  padding: 0.5rem 0.95rem; border-radius: var(--radius);
  cursor: pointer; font-weight: 500; font-size: 0.875rem;
  display: inline-flex; align-items: center; gap: 0.4rem;
  transition: background 100ms, border-color 100ms, transform 80ms;
}
button:hover, .btn:hover { background: var(--bone-soft); border-color: var(--ink-soft); color: var(--ink); }
button:active, .btn:active { transform: translateY(1px); }
button:focus-visible, .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button.primary, .btn.primary {
  background: var(--ink); color: var(--paper);
  border-color: var(--ink);
}
button.primary:hover, .btn.primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--paper); }
/* .secondary kept as explicit alias, renders identical to default. */
button.secondary, .btn.secondary {
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule);
}
button.secondary:hover, .btn.secondary:hover { background: var(--bone-soft); border-color: var(--ink-soft); color: var(--ink); }
button.ghost, .btn.ghost {
  background: transparent; color: var(--muted);
  border: 1px solid transparent; padding: 0.35rem 0.5rem;
}
button.ghost:hover { background: var(--bone-soft); color: var(--ink); border-color: transparent; }
button.icon-only, .btn.icon-only { padding: 0.4rem; }
button.danger, .btn.danger { background: var(--danger); border-color: var(--danger); color: var(--on-danger); }
button.danger:hover, .btn.danger:hover { background: var(--danger-deep); border-color: var(--danger-deep); color: var(--on-danger); }
button:disabled { opacity: 0.45; cursor: not-allowed; }
/* 0.67.2, a <span class="btn"> rendered as a disabled empty-state needs
   the same visual treatment as a real disabled <button>. Used by the
   curation page Export button when accepted_count is 0. */
.btn[aria-disabled="true"], .btn.disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }

input, textarea, select {
  background: var(--surface-subtle); color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0.5rem 0.7rem;
  width: 100%; outline: none;
  transition: border-color 100ms, box-shadow 100ms;
}
input:focus, textarea:focus, select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
}
textarea { resize: vertical; min-height: 2.4rem; }
::placeholder { color: var(--muted); }

/* ── Surface tiers ──
   1. page (bone), body bg
   2. surface (paper), primary cards, no border, shadow
   3. data (paper inset), datasets/audit, monoline border, mono type */
.surface {
  background: var(--surface-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  padding: 1.1rem 1.25rem;
  box-shadow: var(--shadow-1);
}
.card { /* shared raised surface; palette comes from tokens.css */
  background: var(--surface-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  padding: 1.1rem 1.25rem;
  box-shadow: var(--shadow-1);
}
.card h3 { font-size: 0.95rem; font-weight: 600; margin-bottom: 0.6rem; font-family: var(--sans); }
.card form { display: flex; flex-direction: column; gap: 0.55rem; }

/* 0.53.0 v2 — disclosure / "heads up" card variant. Quiet enough not
   to look like an error, prominent enough that users actually read it.
   Used on the personal-model eval pages to flag the base-model-only
   inference limitation while LoRA serving is still being built. */
.ps-disclosure-card {
  background: var(--warn-bg);
  border-left: 3px solid var(--warn);
  color: var(--warn-text-deep);
  margin-bottom: 1rem;
}
.ps-disclosure-card p { line-height: 1.55; font-size: 0.9rem; }
.ps-disclosure-card code {
  background: color-mix(in srgb, var(--warn-bg) 70%, transparent);
  border: 1px solid var(--warn-border);
}

/* Eval drill-down pre blocks (prompt + answer in the per-row expand
   panel). Inherits the markdown <pre> palette so dark mode just works. */
.ps-eval-pre {
  background: var(--bone-soft);
  border: 1px solid var(--rule-soft);
  border-radius: 6px;
  padding: 0.65em 0.85em;
  font-family: var(--mono);
  font-size: 0.82em;
  line-height: 1.5;
  margin: 0.4em 0 0.9em;
  white-space: pre-wrap;
  overflow-x: auto;
  max-height: 320px;
}
.data {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--ink-soft);
}

/* ── Status pills (meaning-only colors) ── */
.pill {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.1rem 0.5rem; border-radius: 9999px;
  font-family: var(--mono); font-size: 0.7rem; font-weight: 500;
  border: 1px solid transparent; letter-spacing: 0.02em;
  /* Compactness pass 2026-05-19: pills must never wrap mid-token.
     The Models Hub status column was rendering `needs_preload` on
     two lines when the column was narrow. Long snake_case status
     values stay on one line; the column has to widen instead. */
  white-space: nowrap;
}
.pill-ready    { background: var(--ok-bg);     color: var(--ok);     border-color: var(--ok-border); }
.pill-indexing { background: var(--info-bg);   color: var(--info);   border-color: var(--info-border); }
.pill-failed   { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-border); }
.pill-empty    { background: var(--bone-soft); color: var(--muted);  border-color: var(--rule); }
.pill-active   { background: var(--accent-tint); color: var(--accent-deep); border-color: var(--accent-soft-border); }
/* portal-htmx 0.71.4, Deferred badge. Muted tone so users
   instantly recognise "intentional placeholder" vs the danger reds that
   signal a regression. Rendered by the deferred_badge macro at
   templates/_macros/deferred_badge.html. */
.pill-deferred {
  background: var(--bone-soft);
  color: var(--muted);
  border-color: var(--rule);
  font-variant: small-caps;
  letter-spacing: 0.04em;
}

/* ── Redaction motif ── */
.redact {
  background: var(--redact); color: var(--redact);
  border-radius: 2px; padding: 0 0.35em;
  user-select: none; letter-spacing: -0.02em;
}
.redact-block {
  display: inline-block; background: var(--redact);
  width: 0.9em; height: 0.9em;
  margin: 0 0.05em; border-radius: 1px;
  vertical-align: -0.1em;
}

/* ── Home / hero (editorial) ── */
.hero {
  display: grid; grid-template-columns: 1.15fr 1fr;
  gap: 4rem; align-items: center;
  padding: 4.5rem 0 3rem;
  max-width: 1100px; margin: 0 auto;
}
.hero-text h1 {
  font-family: var(--serif); font-weight: 500;
  font-size: 3.2rem; line-height: 1.05; letter-spacing: -0.025em;
  color: var(--ink); margin: 0;
}
.hero-text h1 em { font-style: normal; color: var(--accent); }
.hero-text .lead {
  font-size: 1.05rem; max-width: 50ch; margin: 1.25rem 0 1.75rem;
  color: var(--ink-soft); line-height: 1.55;
}
.hero-text .cta { display: flex; gap: 0.6rem; }
.hero-illust {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: 1.5rem;
  box-shadow: var(--shadow-2);
}
.trust-strip {
  max-width: 1100px; margin: 0 auto;
  padding: 1rem 1.5rem;
  display: flex; gap: 2rem; flex-wrap: wrap;
  font-family: var(--mono); font-size: 0.72rem;
  color: var(--muted); letter-spacing: 0.04em; text-transform: uppercase;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  justify-content: center;
}
.trust-strip span { display: inline-flex; align-items: center; gap: 0.4rem; }
.trust-strip svg { color: var(--accent); }

.flow {
  max-width: 1100px; margin: 4rem auto;
  padding: 0 1.5rem;
}
.flow-title {
  text-align: center; font-family: var(--serif); font-weight: 500;
  font-size: 1.75rem; letter-spacing: -0.015em;
  margin-bottom: 2.5rem;
}
.flow-steps {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--radius-lg);
  overflow: hidden;
}
.flow-step {
  background: var(--paper); padding: 1.5rem 1.25rem; text-align: left;
}
.flow-step .step-num {
  font-family: var(--mono); font-size: 0.7rem;
  color: var(--muted); letter-spacing: 0.05em;
  margin-bottom: 0.6rem;
}
.flow-step h4 { font-family: var(--serif); font-weight: 500; font-size: 1.1rem; margin: 0 0 0.4rem; letter-spacing: -0.01em; }
.flow-step p { margin: 0; font-size: 0.85rem; color: var(--ink-soft); line-height: 1.5; }

.features {
  max-width: 1100px; margin: 4rem auto;
  display: flex; flex-direction: column; gap: 3rem;
  padding: 0 1.5rem;
}
.feature-row {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 3rem; align-items: center;
}
.feature-row.reverse > :first-child { order: 2; }
.feature-row h3 {
  font-family: var(--serif); font-weight: 500;
  font-size: 1.55rem; letter-spacing: -0.015em;
  margin-bottom: 0.7rem;
}
.feature-row p { margin: 0; color: var(--ink-soft); line-height: 1.6; max-width: 48ch; }
.feature-row .feature-illust {
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 1.1rem 1.25rem;
  font-family: var(--mono); font-size: 0.78rem;
  color: var(--ink-soft); line-height: 1.7;
}

footer.site {
  max-width: 1100px; margin: 4rem auto 2rem;
  padding: 1.5rem 1.5rem 0;
  display: flex; justify-content: space-between;
  border-top: 1px solid var(--rule);
  font-family: var(--mono); font-size: 0.75rem; color: var(--muted);
  letter-spacing: 0.02em;
}
footer.site nav { display: flex; gap: 1.25rem; }
footer.site a { color: var(--muted); }

.empty-state {
  padding: 3rem 1.5rem; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
  color: var(--ink-soft);
}
.empty-state svg { color: var(--accent); margin-bottom: 0.4rem; opacity: 0.6; }
.empty-state h3 { font-family: var(--serif); color: var(--ink); font-size: 1.25rem; font-weight: 500; }
.empty-state p { margin: 0 0 0.8rem; max-width: 50ch; }

/* Tighter friendly variant for in-card empty states (members, tools,
   webhooks, models). Less vertical real estate than the dataset-page
   empty hero, but warmer than the old single-line ``panel-empty``
   strip. Uses the existing --accent / --muted tokens so it adapts to
   dark mode. */
.empty-state-friendly {
  padding: 1.5rem 1rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
}
.empty-state-friendly .empty-state-icon {
  display: inline-flex;
  width: 56px;
  height: 56px;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--accent-tint, rgba(15, 118, 110, 0.08));
  color: var(--accent-deep);
  margin-bottom: 0.35rem;
}
.empty-state-friendly .empty-state-icon svg { color: currentColor; opacity: 1; margin: 0; }
.empty-state-friendly .empty-state-title {
  margin: 0;
  font-family: var(--serif, "Source Serif 4", serif);
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--ink);
}
.empty-state-friendly .empty-state-body {
  margin: 0;
  max-width: 44ch;
  line-height: 1.5;
}
.empty-state-friendly .empty-state-hint {
  margin: 0.35rem 0 0;
  max-width: 44ch;
  opacity: 0.75;
}

/* ── Account / Admin ── */
.account-grid, .admin-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  /* Compactness pass 2026-05-19: 1rem gap × 6-12 cards on /admin
     stacks ~6rem of whitespace. Tightened to 0.7rem — same visual
     rhythm as the Models Hub panel shell. */
  gap: 0.7rem; max-width: 920px;
}

/* ── Section titles inside .account-grid / .admin-grid ──
   Span the full grid row so the cards under them line up cleanly
   below. Subtle (sans, small caps look via letter-spacing + size,
   muted color) so they read as dividers, not screaming headings.
   Each title sits in a tight margin-top so it visually anchors the
   tier of cards beneath it. */
.account-section-title,
.admin-section-title {
  grid-column: 1 / -1;
  margin: 0.5rem 0 -0.25rem;
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
/* The very first section title sits flush with the grid top, no
   extra margin. */
.account-grid > .account-section-title:first-child,
.admin-grid > .admin-section-title:first-child {
  margin-top: 0;
}

/* ── Card flex-column variant ──
   Lets us push the primary action / fine-print to the BOTTOM of the
   card so 3 cards in the same grid row have their action buttons
   aligned at the same y, even when content above is different height.
   Apply `.account-card-flex` on the <article> and `.account-card-action`
   on the link/p that should pin to the bottom. */
.account-card-flex {
  display: flex;
  flex-direction: column;
}
.account-card-flex > .account-card-action {
  margin-top: auto;
  padding-top: 0.65rem;
}

/* ── Footer tip strip (was the orphaned Keyboard shortcuts card) ──
   A thin row at the bottom of /account with the keyboard-shortcuts
   hint. Looks like a hint, not a card -- less visual weight than the
   primary settings above. Hides on very narrow screens because the
   shortcuts are reference info, not a tap-target. */
.account-footer-tip {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 1.25rem 0 0;
  padding: 0.65rem 0.85rem;
  border-radius: var(--radius);
  background: var(--surface-subtle);
  border: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 0.825rem;
  max-width: 920px;
}
.account-footer-tip-icon {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--accent);
}
.account-footer-tip-icon svg { width: 14px; height: 14px; }
.account-footer-tip-body { flex: 1; line-height: 1.45; }
.account-footer-tip kbd {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.72rem;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: var(--paper);
  color: var(--ink);
}
@media (max-width: 540px) {
  .account-footer-tip { display: none; }
}
.kpi-row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.75rem; margin-bottom: 1rem;
}
.kpi {
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 0.85rem 1rem;
}
.kpi .label { font-family: var(--mono); font-size: 0.65rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.07em; }
.kpi .value { font-family: var(--serif); font-size: 1.5rem; font-weight: 500; margin-top: 0.25rem; }

.identity-card {
  display: flex; align-items: center; gap: 1rem;
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: var(--radius-lg); padding: 1.1rem 1.25rem;
}
.avatar {
  width: 48px; height: 48px;
  border-radius: 50%;
  background: var(--ink); color: var(--bone);
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-size: 1.25rem; font-weight: 500;
}
.identity-card .name { font-family: var(--serif); font-size: 1.15rem; font-weight: 500; letter-spacing: -0.01em; }
.identity-card .meta { color: var(--muted); font-family: var(--mono); font-size: 0.78rem; margin-top: 0.2rem; }
.role-pill {
  display: inline-block; padding: 0.1rem 0.45rem;
  background: var(--bone-soft); color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-radius: 4px; font-family: var(--mono); font-size: 0.68rem;
  margin-right: 0.25rem; letter-spacing: 0.02em;
}

/* ── Mobile ── */
@media (max-width: 900px) {
  .chat-layout, .datasets-layout, .hero, .feature-row { grid-template-columns: 1fr; gap: 1.25rem; }
  .feature-row.reverse > :first-child { order: 0; }
  .chat-layout { height: auto; }
  .thread-sidebar { max-height: 240px; }
  .flow-steps { grid-template-columns: 1fr; }
  .ds-side { position: static; }
}

/* utility */
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }

/* ── Toasts ── */
.toasts {
  position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 50;
  display: flex; flex-direction: column; gap: 0.5rem; max-width: 360px;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 0.65rem 0.85rem; font-size: 0.85rem;
  box-shadow: var(--shadow-2);
  display: flex; align-items: flex-start; gap: 0.55rem;
  animation: toastIn 200ms ease-out;
}
.toast.ok    { border-left-color: var(--ok); }
.toast.warn  { border-left-color: var(--warn); }
.toast.error { border-left-color: var(--danger); background: var(--danger-bg); }
.toast .toast-msg { flex: 1; line-height: 1.4; }
.toast button {
  background: transparent; border: none; color: var(--muted);
  padding: 0.1rem 0.3rem; font-size: 1rem; line-height: 1;
}
.toast button:hover { color: var(--ink); background: transparent; }
@keyframes toastIn {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
.toast.exit { animation: toastOut 180ms ease-in forwards; }
@keyframes toastOut { to { opacity: 0; transform: translateY(-4px); } }

/* ── Keyboard shortcut hint pills ── */
kbd {
  font-family: var(--mono); font-size: 0.7rem;
  background: var(--bone-soft); color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-bottom-width: 2px;
  border-radius: 4px; padding: 0.05rem 0.35rem;
}

/* ── Command palette (Cmd+K), native <dialog> ──
   <dialog> is hidden by default unless it has the [open] attribute (set by
   showModal()). We override `display: block` (the UA default for an open
   dialog) to flex so the panel stays anchored at the top. The ::backdrop
   pseudo replaces the previous .open class' overlay tint. */
.cmd-overlay {
  border: none; padding: 0; background: transparent;
  position: fixed; inset: 0; z-index: 40;
  width: 100%; max-width: none; height: 100dvh; max-height: none;
  margin: 0;
  display: none;
}
.cmd-overlay[open] {
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 12vh;
}
.cmd-overlay::backdrop {
  background: rgba(10, 14, 13, 0.35);
  backdrop-filter: blur(2px);
}
.cmd-panel {
  width: min(540px, 92vw);
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-2);
  overflow: hidden;
}
.cmd-input {
  width: 100%; border: none; padding: 0.95rem 1.1rem;
  font-size: 0.95rem; border-bottom: 1px solid var(--rule);
  border-radius: 0;
}
.cmd-input:focus { box-shadow: none; border-bottom-color: var(--accent); }
.cmd-list { list-style: none; padding: 0.4rem; margin: 0; max-height: 50vh; overflow-y: auto; }
.cmd-item {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.5rem 0.7rem; border-radius: var(--radius);
  cursor: pointer; font-size: 0.875rem;
}
.cmd-item:hover, .cmd-item.active { background: var(--bone-soft); }
.cmd-item.active { color: var(--accent-deep); }
.cmd-item svg { color: var(--muted); }
.cmd-item.active svg { color: var(--accent); }
.cmd-item .cmd-shortcut { margin-left: auto; }

/* ── Skeleton loaders ── */
.skel {
  background: linear-gradient(90deg, var(--bone-soft), var(--rule-soft), var(--bone-soft));
  background-size: 200% 100%;
  animation: skelShimmer 1.4s linear infinite;
  border-radius: var(--radius);
}
@keyframes skelShimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.skel-line { height: 0.85rem; margin: 0.4rem 0; }
.skel-line.short { width: 40%; }
.skel-line.med   { width: 70%; }
.skel-line.long  { width: 95%; }

/* ── Inline editable thread title ── */
.chat-header h2[contenteditable="true"] {
  outline: none; border-bottom: 1px dashed var(--rule);
  padding-bottom: 1px;
}
.chat-header h2[contenteditable="true"]:focus { border-bottom-color: var(--accent); }

/* ─────────────────── 0.8.0 wave ─────────────────── */

/* Page header (used on /admin, /admin/runs, /account) */
.page-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 1rem; flex-wrap: wrap; margin: 0 0 1.25rem;
}
.page-title {
  font-family: var(--serif); font-weight: 600; letter-spacing: -0.01em;
  margin: 0; font-size: 1.6rem;
}
.page-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.muted-h { color: var(--muted); font-size: 0.78rem; text-transform: uppercase;
           letter-spacing: 0.04em; font-weight: 500; margin: 1rem 0 0.4rem; }

/* Top-bar Search button */
.topnav-btn {
  background: transparent; border: 1px solid transparent; border-radius: 6px;
  padding: 0.3rem 0.6rem; cursor: pointer; color: inherit;
  display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.85rem;
}
.topnav-btn:hover { background: var(--bone-soft); border-color: var(--rule); }
@media (max-width: 640px) { .hide-sm { display: none; } }

/* Modal overlay (shortcuts, etc.), native <dialog> */
.modal-overlay {
  border: none; padding: 1rem; background: transparent;
  position: fixed; inset: 0; z-index: 70;
  width: 100%; max-width: none; height: 100dvh; max-height: none;
  margin: 0;
  display: none;
}
.modal-overlay[open] {
  display: flex; justify-content: center; align-items: center;
  animation: fade-in 0.15s ease;
}
.modal-overlay::backdrop { background: rgba(15, 23, 42, 0.55); }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.modal-panel {
  background: var(--paper); border: 1px solid var(--rule); border-radius: 12px;
  width: 100%; max-width: 560px; max-height: 90vh; overflow: auto;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.3);
}
.modal-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 0.7rem 1rem; border-bottom: 1px solid var(--rule-soft);
}
.modal-body { padding: 0.8rem 1rem 1rem; }

/* Shortcuts cheat sheet */
.shortcuts-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.shortcuts-table td { padding: 0.45rem 0.6rem; border-top: 1px solid var(--rule-soft); }
.shortcuts-table td:first-child { white-space: nowrap; width: 1%; color: var(--muted); }
.shortcuts-table tr:first-child td { border-top: 0; }
kbd {
  display: inline-block; padding: 0.05rem 0.4rem; border: 1px solid var(--rule);
  border-bottom-width: 2px; border-radius: 4px; font-family: var(--mono);
  font-size: 0.78em; background: var(--bone-soft);
}

/* Cmd palette: kind badges + footer */
.cmd-kind {
  display: inline-block; padding: 0.05rem 0.4rem; margin-right: 0.5rem;
  font-size: 0.65rem; font-family: var(--mono); text-transform: uppercase;
  border-radius: 4px; background: var(--bone-soft); color: var(--muted);
  border: 1px solid var(--rule);
}
.cmd-foot { padding: 0.4rem 0.8rem; border-top: 1px solid var(--rule-soft); font-size: 0.74rem; }
.cmd-empty { color: var(--muted); font-size: 0.85rem; padding: 0.6rem 0.8rem; }
.cmd-label { flex: 1; }

/* Code copy buttons (#8) */
pre { position: relative; }
.code-copy-btn {
  position: absolute; top: 0.4rem; right: 0.4rem;
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.15rem 0.45rem; font-size: 0.72rem; font-family: var(--mono);
  border: 1px solid var(--rule); border-radius: 5px; background: var(--paper);
  color: var(--muted); cursor: pointer; opacity: 0; transition: opacity 0.15s;
}
pre:hover .code-copy-btn { opacity: 1; }
.code-copy-btn:hover { color: var(--accent); border-color: var(--accent); }
.code-copy-btn.copied { color: var(--ok-deep); border-color: var(--ok-strong-border); opacity: 1; }

/* Thread menu */
.thread-menu { position: relative; }
.thread-menu summary::-webkit-details-marker { display: none; }
.thread-menu summary { list-style: none; cursor: pointer; }
.thread-menu-pop {
  position: absolute;
  right: 0;
  top: 100%;
  /* 2026-05-26 — width pinned to .model-picker-popover (chat.css:4996).
     Was 420px (visibly too wide per operator screenshots). Falls back
     to viewport-width on narrow screens with the same -1rem margin
     the picker uses. */
  width: min(176px, calc(100vw - 1rem));
  max-height: min(72vh, 560px);
  margin-top: 0.42rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: 0 14px 38px rgb(10 14 13 / 0.22);
  /* Padding tightened to match picker (was 0.55rem). */
  padding: 0.24rem;
  z-index: 80;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--muted) 45%, transparent) transparent;
}

/* 2026-05-26 — When any inline editor (<details>) inside the menu is
   open, expand the popover wider so the form (System prompt textarea,
   Model parameters sliders/select, Text size radio group) gets room.
   `:has()` is broadly supported in 2026. The narrow 176px state is
   only ideal for the collapsed list; once a form opens we need ~280px
   to render the textarea / sliders comfortably. */
.thread-menu-pop:has(.menu-item-group[open]) {
  width: min(300px, calc(100vw - 1rem));
}
.thread-menu-pop::-webkit-scrollbar {
  width: 8px;
}
.thread-menu-pop::-webkit-scrollbar-track {
  background: transparent;
}
.thread-menu-pop::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--muted) 36%, transparent);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.thread-menu-pop .menu-item-group {
  border: 1px solid transparent;
  border-radius: 10px;
}
.thread-menu-pop .menu-item-group[open] {
  border-color: var(--rule-soft);
  background: color-mix(in srgb, var(--bone-soft) 58%, transparent);
}
/* 2026-05-26 — picker-style sections. Each group is a vertical
   stack of items with a muted small-label heading. Adjacent groups
   are separated by a hairline border-top + matching padding-top,
   exactly mirroring .model-picker-group-block + .model-picker-
   local-section from chat.css:4992-5018. */
.thread-menu-pop .mp-group { display: grid; gap: 1px; }
.thread-menu-pop .mp-group + .mp-group {
  margin-top: 0.24rem;
  padding-top: 0.24rem;
  border-top: 1px solid var(--rule);
}
.thread-menu-pop .mp-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.42rem;
  padding: 0.2rem 0.35rem 0.16rem;
  color: var(--muted);
  font-size: 0.71rem;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: 0.02em;
}

/* The shared row block. Applies to both <summary> (collapsible
   System prompt / Model parameters / Text size) and direct <a> /
   <button> .menu-item children (Share link / Markdown / PDF /
   Regenerate). The previous 0.76.x compact pass used 1.85rem
   min-height + 0.32rem/0.55rem padding; picker style is slightly
   tighter (no min-height + 0.32rem/0.42rem padding) per
   chat.css:4992-5018. */
.thread-menu-pop .menu-item-group + .menu-item-group,
.thread-menu-pop .menu-item-group + .menu-item,
.thread-menu-pop .menu-item + .menu-item {
  margin-top: 0;
}
.thread-menu-pop .menu-item-group > summary,
.thread-menu-pop .menu-item,
.thread-menu-pop .mp-item {
  display: flex;
  align-items: center;
  gap: 0.42rem;
  padding: 0.32rem 0.42rem;
  border-radius: 5px;
  color: var(--ink);
  font-size: 0.84rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
}
.thread-menu-pop .menu-item-group > summary:hover,
.thread-menu-pop .menu-item:hover,
.thread-menu-pop .mp-item:hover {
  background: var(--bone-soft);
}
.thread-menu-pop .menu-item-group[open] > summary {
  background: var(--bone-soft);
  color: var(--ink);
}
/* Icon slot + label inside .mp-item; the label truncates with
   ellipsis if too long, mirroring the picker's behavior on long
   model names like "Qwen2.5 7B Instruct" → "Qwen2.5 7B…". */
.thread-menu-pop .mp-icon {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
}
.thread-menu-pop .mp-label {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
/* Trailing right-aligned value (e.g. "Compact" / "Default" /
   "Large" on the Text size row). Matches the picker's mp-trailing
   number convention. */
.thread-menu-pop .mp-trailing {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.74rem;
  font-weight: 500;
  flex: 0 0 auto;
}
.thread-menu-pop .menu-item-group > summary:hover,
.thread-menu-pop .menu-item-group[open] > summary {
  background: var(--bone-soft);
  color: var(--ink);
}
.thread-menu-pop form {
  display: grid;
  gap: 0.72rem;
  padding: 0 0.62rem 0.62rem;
}
.thread-menu-pop textarea {
  width: 100%;
  min-height: 6.5rem;
  resize: vertical;
  border-radius: 8px;
}
.thread-menu-pop .param-row {
  display: grid;
  gap: 0.36rem;
  color: var(--ink-soft);
  font-size: 0.84rem;
}
.thread-menu-pop .param-row > span:first-child {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}
.thread-menu-pop .param-row input[type="range"],
.thread-menu-pop .param-row select {
  width: 100%;
}
.thread-menu-pop .param-row select {
  min-height: 2.35rem;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  padding: 0.45rem 0.58rem;
}
.thread-menu-pop .param-row:has(input[type="checkbox"]) {
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 0.58rem;
  line-height: 1.35;
}
.thread-menu-pop .param-row input[type="checkbox"] {
  margin-top: 0.12rem;
}
.settings-save-row {
  display: flex;
  align-items: center;
  gap: 0.62rem;
  flex-wrap: wrap;
}
.settings-save-state {
  min-height: 1rem;
  color: var(--muted);
  font-size: 0.76rem;
  line-height: 1.25;
}
.settings-save-state.is-ok {
  color: var(--accent-deep);
}
.settings-save-state.is-error {
  color: var(--danger);
}
.menu-item {
  display: flex; align-items: center; gap: 0.4rem; width: 100%;
  text-align: left; padding: 0.45rem 0.6rem; border-radius: 6px;
  background: transparent; border: none; cursor: pointer; color: inherit;
  font: inherit; text-decoration: none; font-size: 0.85rem;
}
.menu-item:hover { background: var(--bone-soft); color: var(--accent); }
.menu-item:disabled,
.menu-item[aria-busy="true"] {
  opacity: 0.68;
  cursor: progress;
}

/* Share link result */
.share-mount:empty { display: none; }
.share-result {
  margin: 0.6rem 1rem 0; padding: 0.6rem 0.8rem;
  background: color-mix(in oklab, var(--accent) 6%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 30%, transparent);
  border-radius: 8px; max-width: 700px;
}
.share-row { display: flex; gap: 0.4rem; align-items: center; }
.share-input {
  flex: 1; font-family: var(--mono); font-size: 0.78rem;
  padding: 0.35rem 0.5rem; border: 1px solid var(--rule);
  border-radius: 5px; background: var(--paper);
}

/* Phase mini stepper (above messages during a stream, #4) */
.phase-stepper {
  display: flex; align-items: center; gap: 0.4rem; padding: 0.4rem 1rem;
  border-bottom: 1px solid var(--rule-soft); background: var(--bone-soft);
  font-size: 0.78rem; color: var(--muted); flex-wrap: wrap;
}
.phase-mini { display: inline-flex; align-items: center; gap: 0.3rem; }
.phase-mini .phase-mini-icon {
  width: 8px; height: 8px; border-radius: 999px; background: var(--rule);
  display: inline-block;
}
.phase-mini.active .phase-mini-icon { background: var(--accent); animation: pulse 1.4s ease-in-out infinite; }
.phase-mini.done   .phase-mini-icon { background: var(--ok-deep); animation: none; }
.phase-mini.failed .phase-mini-icon { background: var(--danger); }
.phase-mini-arrow { color: var(--rule); }

/* Usage list */
.usage-list { list-style: none; padding: 0; margin: 0.4rem 0 0; }
.usage-list li { display: flex; justify-content: space-between; padding: 0.35rem 0;
                  border-top: 1px solid var(--rule-soft); font-size: 0.88rem; }
.usage-list li:first-child { border-top: 0; }

/* Misc helpers */
.text-ok { color: var(--ok-deep); }
.text-error { color: var(--danger); }
.muted-link { color: var(--muted); text-decoration: none; }
.muted-link:hover { color: var(--accent); }
.btn.small { font-size: 0.78rem; padding: 0.2rem 0.55rem; }
.rotate-180 { transform: rotate(180deg); }

/* Empty state polish (#6), improve if not yet present */
.empty-state .empty-icon { display: inline-flex; opacity: 0.6; margin-bottom: 0.4rem; color: var(--accent); }

/* ── Accessibility, Wave 1 polish ────────────────────────────────────── */

/* Skip-to-content link. Visually hidden until focused via keyboard. */
.skip-link {
  position: absolute; top: -100px; left: 0;
  background: var(--ink); color: var(--paper);
  padding: 0.5rem 1rem; border-radius: 0 0 6px 0;
  z-index: 1000; text-decoration: none;
  transition: top 0.15s;
}
.skip-link:focus,
.skip-link:focus-visible {
  top: 0; outline: 2px solid var(--accent); outline-offset: 2px;
}

/* Focus-ring fallback (a11y audit wave 2). Earlier rules already cover
   .btn/button (line 264), .workspace-pill (line 134), .status-filter
   (line 1499), .ops-nav-pill (line 1582), .surface-card (line 3323),
   and form fields via border+box-shadow (line 297). Plain anchors
   (<a> outside the workspace pill), [role="button"] divs, and any
   element using [tabindex] inherited the browser default — invisible
   on dark themes and inconsistent across browsers. This fallback paints
   the same accent outline on those un-covered selectors. Form fields
   are included so keyboard focus shows the outline in addition to the
   box-shadow ring (which is visually subtler and was easy to miss). */
a:focus-visible,
[role="button"]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* prefers-reduced-motion. Respects the OS-level "reduce motion" pref so
   spinners, mic pulse, tool-pulse, and skeleton shimmers go nearly still
   for users with vestibular sensitivities. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Dark mode (auto, OS-pref) ─────────────────────────────────────────
   Clinical apps get used on night shifts. With the existing token system
   this is a single media query. Light mode is unchanged; dark mode just
   re-binds the surface + ink + rule tokens. */
@media (prefers-color-scheme: dark) {
  /* dark-mode :root rebind moved to tokens.css */
  /* Hairline inputs lose contrast on dark; nudge them up. */
  input, textarea, select { border-color: var(--rule); }
  /* Keep the shell translucent in dark mode without turning it into a heavy bar. */
  .topbar { background: color-mix(in srgb, var(--bone) 88%, transparent); }
  .workspace-pill { background: color-mix(in srgb, var(--paper) 62%, transparent); }
}

/* ── Print stylesheet ──────────────────────────────────────────────────
   Clinicians print conversations. Hide chrome (topbar/sidebar/composer),
   force serif body, expand redaction blocks to a clearly-readable
   `[REDACTED]` so a printed page can't accidentally leak unredacted PHI. */
@media print {
  .topbar, .skip-link, .topnav, .toasts,
  .app-sidebar, .mobile-topbar, .sb-scrim,
  .thread-sidebar, .composer, .sidebar-header,
  .cmd-overlay, .modal-overlay,
  .img-thumb-wrap, button { display: none !important; }
  body, main, .chat-layout, .messages {
    background: #fff !important; color: #000 !important;
    box-shadow: none !important; border: none !important;
    height: auto !important; overflow: visible !important;
    grid-template-columns: 1fr !important;
  }
  body { font-family: var(--serif); font-size: 11pt; }
  .redact, .redact * {
    background: transparent !important; color: #000 !important;
  }
  .redact::after { content: " [REDACTED]"; font-weight: 600; }
  a { color: #000; text-decoration: underline; }
  a[href]::after { content: " (" attr(href) ")"; font-size: 0.85em; }
}

/* ── Utility classes (Wave 5) ──────────────────────────────────────────
   Replaces the most-frequent inline `style="..."` patterns in account.html
   and admin.html. Templates can opt-in incrementally; existing inline
   styles still work. Naming follows the small handful already present
   (.flex, .gap-2). */
.row         { display: flex; align-items: center; }
.row.gap-2   { gap: 0.5rem; }
.row.gap-1   { gap: 0.25rem; }
.row.wrap    { flex-wrap: wrap; }
.col-span-full { grid-column: 1 / -1; }
.flex-1      { flex: 1; }
.muted-text  { color: var(--muted); font-size: 0.78rem; }
.py-2 { padding-top: 0.4rem; padding-bottom: 0.4rem; }
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mb-1 { margin-bottom: 0.5rem; }
.mb-2 { margin-bottom: 1rem; }
.mb-0 { margin-bottom: 0 !important; }
.text-error { color: var(--danger); }
.text-ok    { color: var(--ok); }
.btn.small  { font-size: 0.78rem; padding: 0.3rem 0.5rem; }

/* ── Onboarding checklist polish ── */
.onboarding-step.done { color: var(--muted); text-decoration: line-through; }
.onboarding-step.done svg { color: var(--ok); }

/* ── Wave 5 finish, additional utility classes for inline-style migration ── */
.fz-78        { font-size: 0.78rem; }
.fz-85        { font-size: 0.85rem; }
.text-right   { text-align: right; }
.text-left    { text-align: left; }
.self-start   { align-self: flex-start; }
.rule-top     { border-top: 1px solid var(--rule-soft); }
.mt-q         { margin-top: 0.25rem; }   /* quarter-rem */
.mt-08        { margin-top: 0.8rem; }
.mb-08        { margin-bottom: 0.8rem; }
.mb-06        { margin-bottom: 0.6rem; }
.muted-th     { color: var(--muted); font-weight: 500; }   /* table headers */
.full-table   { width: 100%; border-collapse: collapse; }
.checkbox-stub {
  display: inline-block; width: 14px; height: 14px;
  border: 1.5px solid var(--rule); border-radius: 3px;
}

/* ── Wave 6 finish, purpose-named classes for multi-property mixes ────
   These cover the inline-style patterns that the Wave 5 utility-class
   pass deliberately skipped. Naming by purpose, not by property bag, so
   the markup stays scannable ("oh that's the audit search input") even
   though it's three lines of CSS underneath. */

/* container widths (one rule per page, named) */
.page-account     { max-width: 1100px; }
.page-admin       { max-width: 1200px; }

/* tables, both account API keys + admin members + admin tools etc. */
.table-data       { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.table-mono-sm    { width: 100%; border-collapse: collapse; font-size: 0.78rem; font-family: var(--mono); }
.th-muted-row     { text-align: left; color: var(--muted); }
.th-muted-row-bold{ text-align: left; color: var(--muted); font-weight: 500; }
.th-actions       { text-align: right; width: 1%; }
.row-tr-rule      { border-top: 1px solid var(--rule); }
.cell-pad-y       { padding: 0.5rem 0; }
.cell-actions     { text-align: right; white-space: nowrap; }
.cell-th-pad      { padding: 0.4rem 0.5rem; }
.cell-th-pad-l    { padding: 0.4rem 0.5rem 0.4rem 0; }
.cell-td-pad      { padding: 0.35rem 0.5rem; }
.cell-td-pad-l    { padding: 0.35rem 0.5rem 0.35rem 0; white-space: nowrap; }
.cell-resource    { padding: 0.35rem 0.5rem; word-break: break-all; }
.cell-subject     { padding: 0.35rem 0.5rem; color: var(--muted); }

/* form rows (used in invite/api-key/saved-prompt forms) */
.form-row-stack   { margin-top: 0.6rem; display: flex; flex-direction: column; gap: 0.5rem; }
.form-row-stack-tight { margin-top: 0.6rem; display: flex; flex-direction: column; gap: 0.4rem; }
.form-row-inline  { margin-top: 0.6rem; display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; }
.form-row-wrap    { margin-top: 0.6rem; display: flex; gap: 0.4rem; flex-wrap: wrap; }
.col-stack        { display: flex; flex-direction: column; gap: 0.5rem; }
.col-stack-tight  { display: flex; flex-direction: column; gap: 0.4rem; }
.row-flex-wrap    { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }
.row-narrow       { display: flex; gap: 0.3rem; align-items: center; }

/* inputs that need exact sizing */
.input-named      { flex: 1; min-width: 220px; }
.input-audit-search { font-size: 0.85rem; padding: 0.35rem 0.6rem; width: 300px; }
.input-narrow     { font-size: 0.78rem; padding: 0.2rem 0.4rem; }
.input-tight      { font-size: 0.78rem; padding: 0.15rem 0.3rem; }
.fz-85-input      { font-size: 0.85rem; }
.textarea-mono    { font-family: var(--mono); font-size: 0.85rem; }

/* lists */
.list-plain       { list-style: none; padding: 0; margin: 0; }
.list-plain-mt    { list-style: none; padding: 0; margin: 0.6rem 0 0; }
.list-plain-stack { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.shortcut-grid    { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.4rem 1rem; font-size: 0.85rem; }

/* misc inline-y bits */
.code-sm          { font-size: 0.8em; }
.muted-sm-mt      { margin: 0.5rem 0 0; font-size: 0.78rem; }
.muted-tiny-mt    { margin: 0.6rem 0 0; font-size: 0.75rem; }
.fz-75            { font-size: 0.75rem; }
.preview-line     { margin: 0; font-size: 0.85rem; white-space: pre-wrap; }
.summary-pill     { display: inline-flex; gap: 0.4rem; align-items: center; }
.summary-pill-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.3rem; }
.section-head-flex{ display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; flex-wrap: wrap; }

/* onboarding */
.onboarding-card-hero { margin-bottom: 1rem; border-left: 3px solid var(--accent); }
.onboarding-step-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0; border-bottom: 1px solid var(--rule-soft); }
.onboarding-step-row.last { border-bottom: 0; }

/* api-key list cards */
.key-card         { border: 1px solid var(--rule); border-radius: 8px; padding: 0.6rem 0.8rem; }

/* small icon delete button reused in members + others */
.btn-icon-trash {
  margin-left: 0.3rem; padding: 0.15rem 0.35rem; font-size: 0.8rem;
  border: 1px solid var(--rule); border-radius: 4px;
  background: transparent; cursor: pointer;
}

/* identity card spacing */
.identity-card-mb { margin-bottom: 1rem; }

/* anchor with margin-top inside cards */
.btn-mt           { margin-top: 0.6rem; }
.mt-half          { margin-top: 0.5rem; }
.muted-quota-note { font-size: 0.78rem; margin-top: 0.6rem; }
.row-flex-tight   { font-size: 0.85rem; display: flex; align-items: center; gap: 0.4rem; }
.overflow-x-auto  { overflow-x: auto; }

/* ── Wave 7, finish mobile polish plan ────────────────────────────────
   Plan called for: (A) drawer breakpoint at 900px, (E) icon-led short
   labels, (F) bottom-sheet popovers, (G) compact icon Send button, (H)
   guided empty state with starter prompt chips, (I) dataset-empty CTA. */

/* (A) Promote the mobile breakpoint from 760px → 900px so the drawer
   triggers earlier (matches the plan's "below 900px is mobile"). The
   smaller-phone (760px) optimizations cascade in addition. */
@media (max-width: 900px) {
  .chat-drawer-trigger { display: inline-flex; }
  .chat-layout {
    grid-template-columns: 1fr !important;
    height: calc(100dvh - 64px) !important;
  }
  .thread-sidebar {
    position: fixed; top: 56px; left: 0;
    width: min(86vw, 320px); height: calc(100dvh - 56px);
    z-index: 40;
    background: var(--paper);
    border-right: 1px solid var(--rule);
    box-shadow: 4px 0 24px rgba(10, 14, 13, 0.18);
    transform: translateX(-105%);
    transition: transform 200ms ease-out;
    overflow-y: auto;
  }
  .thread-sidebar.open { transform: translateX(0); }
  /* Drawer unification (UI fix waves): the chat-pane dim used to live in a
     separate `@media (max-width: 800px)` swipe-only block keyed on
     `.thread-sidebar.mobile-open` / `body.sidebar-open`, which created an
     801–900px dead-zone (swipe toggled classes with no visual effect).
     Both the button and the swipe path now use `.thread-sidebar.open` +
     `body.sidebar-open`, so the dim is in this single 900px block. */
  body.sidebar-open .chat-pane {
    pointer-events: none;
    opacity: 0.5;
  }
  /* (E) Icon-led short labels, hide the verbose count text on mobile,
     keep the icon + a 4-letter label visible. The desktop spans stay
     clipped via .hide-sm so screen-readers still read full text. */
  .composer-toolbar #rag-label,
  .composer-toolbar #tools-label,
  .composer-toolbar #img-label,
  .composer-toolbar #prompts-trigger > span:not(.sr) { font-size: 0.75rem; }
  .composer-toolbar .rag-trigger { padding: 0.4rem 0.55rem; }
  /* D1, show the short label below 900px (was full text mid-truncating).
     Default (≥900px): full visible, short hidden, see rule below. */
  .composer-toolbar .label-full  { display: none; }
  .composer-toolbar .label-short { display: inline; }
  /* Hide the verbose count suffix when narrow, keep just the leading
     word ("RAG", "Tools", "Voice", "Image", "Prompts"). The labels
     update via JS; on mobile we trim them client-side too. */
  .composer-toolbar .rag-trigger { white-space: nowrap; }

  /* (F) Bottom-sheet treatment for popovers below the composer.
     The dataset-popover and tools-popover float as desktop popovers
     above; on mobile they slide up from the bottom edge so a thumb
     can reach them. */
  .dataset-popover.open {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    width: 100%; max-height: 60dvh; overflow-y: auto;
    border-radius: 12px 12px 0 0;
    box-shadow: 0 -8px 30px rgba(10, 14, 13, 0.22);
    padding: 1rem 1rem max(1rem, env(safe-area-inset-bottom));
    z-index: 50;
    animation: sheet-up 200ms ease-out;
  }
  @keyframes sheet-up {
    from { transform: translateY(105%); }
    to   { transform: translateY(0); }
  }
  /* Add a top "grabber" handle to hint draggability (visual only). */
  .dataset-popover.open::before {
    content: ''; display: block;
    width: 36px; height: 4px; border-radius: 2px;
    background: var(--rule); margin: 0 auto 0.6rem;
  }
  /* Backdrop dimmer when a sheet is open (a sibling div toggled by JS). */
  .sheet-scrim {
    position: fixed; inset: 0; z-index: 49;
    background: rgba(10, 14, 13, 0.4);
    opacity: 0; pointer-events: none;
    transition: opacity 200ms;
  }
  .sheet-scrim.open { opacity: 1; pointer-events: auto; }

  /* (G) Compact send button, text → icon. Plan said "compact
     icon/text"; keep "Send" for screen-readers via aria-label, hide
     visually below 600px. */
  #send-btn { padding: 0.55rem 0.7rem; }
}
@media (max-width: 600px) {
  body { font-size: 14.5px; }
  /* C1, send button collapses to icon-only on phones; SVG stays visible */
  #send-btn .send-text { display: none; }
  #send-btn { padding: 0.55rem 0.7rem; }
}

/* (H) Guided empty-state, starter prompt chips + dataset CTA. */
.guided-start-form {
  display: flex; flex-direction: column; gap: 0.75rem;
  margin: 1rem auto 0; max-width: 520px;
}
.guided-start-row {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.85rem;
}
.guided-start-row select { flex: 1; }
.starter-chips {
  display: flex; flex-wrap: wrap; gap: 0.5rem;
  justify-content: center;
}
.starter-chip {
  display: inline-flex; align-items: center; gap: 0.5rem;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule);
  padding: 0.55rem 0.85rem; border-radius: 14px;
  font-size: 0.85rem; cursor: pointer;
  text-align: left;
  transition: background 100ms, border-color 100ms;
}
.starter-chip:hover {
  background: var(--accent-soft); border-color: var(--accent);
  color: var(--accent-deep);
}
/* C2, chip body shows title + meta line. Chip is now a 2-row
   inline block instead of the old single-line pill. */
.starter-chip .chip-body {
  display: inline-flex; flex-direction: column; gap: 2px;
  line-height: 1.2;
}
.starter-chip .chip-title { font-weight: 500; }
.starter-chip .chip-meta {
  font-size: 0.72rem; color: var(--muted); font-family: var(--mono);
}
.starter-chip:hover .chip-meta { color: var(--accent-deep); }

/* (I) "Create dataset" CTA strip when no datasets exist. */
.empty-cta-row {
  display: flex; align-items: center; gap: 0.5rem;
  background: var(--accent-soft); border: 1px solid var(--accent-tint);
  border-radius: 8px; padding: 0.6rem 0.8rem;
  font-size: 0.85rem; color: var(--accent-deep);
}
.empty-cta-row svg { color: var(--accent); flex-shrink: 0; }

/* ── Premium SaaS modernization layer ─────────────────────────────── */
.page-head-lede { margin: 0.25rem 0 0; max-width: 78ch; }
.page-admin-runs { max-width: 1200px; }

.console-hero {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  margin: 0 0 1rem;
  padding: 1rem 1.15rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--paper);
  box-shadow: var(--shadow-1);
}
.console-hero h1 {
  font-family: var(--serif);
  font-size: clamp(1.45rem, 2.2vw, 2rem);
  line-height: 1.05;
}
.console-hero p { margin: 0.25rem 0 0; max-width: 66ch; }
.console-metrics,
.account-command-strip,
.trust-command-strip {
  display: flex; flex-wrap: wrap; gap: 0.45rem;
  align-items: center;
}
.console-metrics span,
.account-command-strip span,
.trust-command-strip span,
.status-filter,
.panel-kicker {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.32rem 0.55rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--bone-soft);
  color: var(--ink-soft);
  font-family: var(--mono);
  font-size: 0.72rem;
}
.console-metrics strong { color: var(--ink); }

/* .sidebar-title is now an h2 styled in the 0.18.0 sidebar-header
   block above. The old two-line strong+span pattern was retired with
   the static "Recent clinical work" subtitle. */
.premium-sidebar { box-shadow: var(--shadow-1); }
.chat-identity { min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.chat-subtitle {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.68rem;
}
.chat-subtitle span { display: inline-flex; align-items: center; gap: 0.25rem; }
.message-canvas { background: var(--surface-raised); }
.premium-composer {
  gap: 0.65rem;
  background: var(--surface-raised);
}
/* 0.19.0, composer overflow group. Single "···" trigger that reveals
   Voice / Image / Prompts inline. Default state hides them so the
   composer reads as textarea + RAG + Tools + Send. */
.toolrail-overflow { justify-content: flex-end; min-width: 0; flex: 0 0 auto; }
.composer-overflow { position: relative; }
.composer-overflow > summary {
  list-style: none;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.composer-overflow > summary::-webkit-details-marker { display: none; }
.composer-overflow[open] > summary { background: var(--bone-soft); border-color: var(--accent); color: var(--accent); }
.composer-overflow-pop {
  position: absolute; bottom: calc(100% + 6px); right: 0;
  display: flex; align-items: stretch; gap: 0.3rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: 0.4rem;
  box-shadow: var(--shadow-2);
  z-index: 10;
  white-space: nowrap;
}
.composer-overflow-pop > .rag-trigger { flex: 0 0 auto; }
/* The prompts popover is anchored under the prompts-trigger button.
   Keep its existing absolute positioning (defined elsewhere) but make
   sure it can escape the overflow popover bounds. */
.composer-overflow-pop #prompts-popover { z-index: 11; }
@media (max-width: 600px) {
  .composer-overflow-pop {
    flex-wrap: wrap;
    max-width: calc(100vw - 1rem);
    right: 0;
  }
}
.composer-input textarea {
  min-height: 3.1rem;
  border-radius: var(--radius-lg);
  background: var(--paper);
}
.composer-hint {
  display: flex; align-items: center; gap: 0.35rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.68rem;
}

.status-filter-row {
  display: flex; flex-wrap: wrap; gap: 0.35rem;
  padding: 0.65rem 0 0.3rem;
}
.status-filter {
  /* 0.43.x, chips are clickable filters; the shared base style at
     ~1733 doesn't include cursor + focus affordances because other
     consumers (.console-metrics span, .panel-kicker) are pure labels. */
  cursor: pointer;
  user-select: none;
}
.status-filter:hover {
  border-color: var(--accent-soft-border);
  color: var(--ink);
}
.status-filter:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.status-filter.active {
  color: var(--accent-deep);
  background: var(--surface-accent);
  border-color: var(--accent-soft-border);
}
.next-action-panel,
.panel-empty {
  display: flex; align-items: center; gap: 0.55rem;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--surface-subtle);
  color: var(--ink-soft);
  font-size: 0.86rem;
}
.next-action-panel { margin: 0.75rem 0; }
.next-action-panel svg,
.panel-empty svg { color: var(--accent); flex-shrink: 0; }
.next-action-panel .btn { margin-left: auto; }
.premium-empty {
  border: 1px dashed var(--rule);
  border-radius: var(--radius-lg);
  background: var(--paper);
}

.ops-head {
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--paper);
  box-shadow: var(--shadow-1);
}
.ops-nav {
  display: flex; flex-wrap: wrap; gap: 0.35rem;
  margin: 0 0 1rem;
}
.ops-nav a {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.4rem 0.65rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  font-family: var(--mono);
  font-size: 0.72rem;
  text-decoration: none;
}
.ops-nav a:hover {
  color: var(--accent-deep);
  background: var(--accent-soft);
  border-color: var(--accent-soft-border);
}

/* 0.54.0: tier pills as real tabs. Buttons now share .ops-nav-pill
 * styling with the legacy <a> rules; the data-active-filter dance lives
 * on .admin-grid below. Active pill gets the accent-tinted background
 * so the current filter is visually obvious. */
.ops-nav-pill {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  font-family: var(--mono);
  font-size: 0.72rem;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.ops-nav-pill:hover {
  color: var(--accent-deep);
  background: var(--accent-soft);
  border-color: var(--accent-soft-border);
}
.ops-nav-pill.active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-deep);
}
.ops-nav-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Tier-filter behavior. /admin now defaults to Access so the page doesn't
 * dump every operational card at once. Every panel + section title whose
 * data-tier doesn't match the active pill is hidden by these selectors. */
.admin-grid[data-active-filter="access"] [data-tier]:not([data-tier="access"]),
.admin-grid[data-active-filter="usage"]  [data-tier]:not([data-tier="usage"]),
.admin-grid[data-active-filter="tools"]  [data-tier]:not([data-tier="tools"]),
.admin-grid[data-active-filter="audit"]  [data-tier]:not([data-tier="audit"]) {
  display: none;
}

/* 0.54.0 — Members card role filter. Pills above the table flip
 * data-active-role on the table; rows whose data-roles list doesn't
 * include the active value are hidden. `[data-roles~="X"]` is a
 * whitespace-token match, so `data-roles="tenant-admin tenant-member"`
 * matches both filters correctly. */
#members-table[data-active-role="tenant-admin"]    tbody tr:not([data-roles~="tenant-admin"]),
#members-table[data-active-role="tenant-member"]   tbody tr:not([data-roles~="tenant-member"]),
#members-table[data-active-role="tenant-readonly"] tbody tr:not([data-roles~="tenant-readonly"]) {
  display: none;
}

.ops-panel {
  border: 1px solid var(--rule-soft);
  box-shadow: var(--shadow-1);
}
.account-command-strip {
  margin: 0 0 1rem;
  padding: 0.75rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--paper);
}

.platform-preview-grid {
  display: grid;
  grid-template-columns: 1.2fr 0.9fr 0.9fr;
  gap: 0.85rem;
  margin: 1rem 0 2rem;
}
.preview-panel {
  padding: 1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--paper);
  box-shadow: var(--shadow-1);
}
.preview-panel-chat {
  background: linear-gradient(135deg, var(--accent-soft), var(--paper) 58%);
}
.preview-panel h2 {
  font-size: 1.25rem;
  margin: 0.55rem 0 0.4rem;
}
.preview-list {
  list-style: none; padding: 0; margin: 0.75rem 0 0;
  display: grid; gap: 0.45rem;
}
.preview-list li {
  display: flex; justify-content: space-between; gap: 0.75rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--rule-soft);
}
.preview-list li:last-child { border-bottom: 0; }
.preview-list span { color: var(--muted); font-family: var(--mono); font-size: 0.72rem; }
.trust-command-strip {
  margin: 0 0 0.85rem;
  padding: 0.65rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--paper);
}

@media (max-width: 900px) {
  .topbar-inner { flex-wrap: wrap; gap: 0.55rem; }
  .brand-cluster { width: 100%; justify-content: space-between; }
  /* The lockup stays prominent on tablets, but sheds a little width
     so the nav rail can scroll without the brand stealing the row. */
  .workspace-pill {
    max-width: min(68vw, 260px);
    height: 34px;
    font-size: 0.92rem;
    padding: 0 0.7rem;
  }
  .workspace-pill svg { width: 16px; height: 16px; }
  .workspace-lockup .workspace-name { font-size: 0.96rem; }
  .workspace-suffix { font-size: 0.66rem; }
  .workspace-pill-home .brand-logo { max-height: 20px; }
  .app-nav {
    width: 100%;
    overflow-x: auto;
    padding-bottom: 0.1rem;
    scrollbar-width: thin;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  .app-nav .nav-item {
    flex: 0 0 auto;
    scroll-snap-align: start;
  }
  /* Below iPad portrait the topbar gets crowded; the standalone
     "Sign out" chip is the first to drift offscreen on iPad split
     view. Hide it on the rail — the Account chip remains visible,
     and the Account page itself ships a Sign out button so the
     fallback path is one tap deeper. DOM is preserved for tests
     and keyboard users on wider screens. */
  .app-nav .nav-signout { display: none; }
  .nav-command kbd { display: none; }
  .console-hero { align-items: flex-start; flex-direction: column; }
  .platform-preview-grid { grid-template-columns: 1fr; }
  .composer-hint { display: none; }
}

@media (max-width: 600px) {
  main { padding: 1rem; }
  .topbar-inner { padding: 0.5rem 1rem; }
  .app-nav .nav-item { padding-inline: 0.65rem; }
}

/* Topbar mobile-safe scaffolding — audit finding P9 (folded back from the
   former `_topbar.html` inline <style id="topbar-mobile-safe"> block during
   the UI fix waves; rules are verbatim). Soft fix that prevents the nav
   wrap/overflow below 640px without restructuring the nav (the full mobile
   drawer is still a future wave). */
@media (max-width: 480px) {
  .topbar-inner .app-nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .topbar-inner .app-nav::-webkit-scrollbar {
    display: none;
  }
  .topbar-inner .app-nav .nav-item {
    scroll-snap-align: start;
    flex-shrink: 0;
  }
}
@media (max-width: 379px) {
  .topbar-inner .brand-cluster .workspace-pill-home .workspace-name {
    display: none;
  }
}

/* ── Wave 8, additional utility classes for cross-template inline-style sweep ── */
.muted-color   { color: var(--muted); }   /* style="color:var(--muted)" pattern */
.danger-color  { color: var(--danger); }
.text-center   { text-align: center; }
.hidden        { display: none; }
.opacity-0     { opacity: 0; }
.fz-74-cap     { font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.2rem; }
.preview-pre   { font-size: 0.85rem; white-space: pre-wrap; }
.row-mb        { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.row-mb-1rem   { margin-bottom: 1rem; display: flex; gap: 0.5rem; align-items: center; }
.popover-error { padding: 0.5rem; font-size: 0.78rem; }
.popover-error.danger { color: var(--danger); }
.page-shared   { max-width: 760px; }
.page-legal    { max-width: 720px; padding: 2rem 0; }
.serif-h2      { font-family: var(--serif); }
.fw-500-85     { font-weight: 500; font-size: 0.85rem; }
.list-stack-08 { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.8rem; }
.muted-mt-15   { margin-top: 1.5rem; font-size: 0.78rem; }
.popover-form-row { padding: 0.4rem 0.6rem; display: flex; gap: 0.3rem; }

/* ── Wave 9: In-thread search (Phase 5.4) ──────────────────────────────
   Cmd/Ctrl-F over an open chat pane reveals a search bar that
   highlights matching messages without hiding non-matches. Pure
   client-side over the rendered DOM. */
.thread-search {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.7rem;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 5;
  animation: slide-down 150ms ease-out;
}
.thread-search.hidden { display: none; }
.thread-search input {
  flex: 1; padding: 0.35rem 0.5rem; font-size: 0.85rem;
  border: 1px solid var(--rule); border-radius: 4px;
}
.thread-search .count {
  font-size: 0.78rem; color: var(--muted);
  min-width: 4ch; text-align: right;
}
.thread-search button {
  padding: 0.25rem 0.45rem; font-size: 0.78rem;
}
@keyframes slide-down {
  from { transform: translateY(-100%); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}
.search-match {
  background: var(--warn-bg);
  outline: 2px solid var(--warn);
  outline-offset: 2px;
  border-radius: 4px;
}
.search-match.search-current {
  outline-width: 3px;
  outline-color: var(--accent-deep);
  background: var(--accent-soft);
}

/* Phase 5.7, per-message metadata strip below assistant bubbles. */
.msg-meta {
  display: flex; gap: 0.4rem; align-items: center;
  font-size: 0.72rem; font-family: var(--mono);
  margin: 0.2rem 0 0 0.6rem;
  letter-spacing: 0.02em;
}
.msg-meta-model { color: var(--accent-deep); font-weight: 500; }
.msg-meta-lat,
.msg-meta-cost { color: var(--muted); }


/* Reevaluation hardening polish */
.share-note { margin: 0 0 0.4rem; font-size: 0.85rem; }
.shared-token-note { font-size: 0.78rem; margin-top: 1.5rem; }

/* Phase C1, stream-fail Retry button. */
.stream-fail { color: var(--danger); }
.stream-fail button {
  margin-left: 0.4rem;
  font-size: 0.78rem;
  padding: 0.15rem 0.45rem;
}

/* 0.50.x v9: hide the messages container during a chat-pane swap so
   the user doesn't see a scroll-top frame between innerHTML
   replacement and the auto-scroll JS. Body class is toggled by
   chat/index.js's beforeSwap/afterSwap pair. The hide is brief
   (microtask between events) and only applies to the messages list,
   so the composer + thread sidebar stay visible. */
body.chat-pane-swapping #messages {
  opacity: 0;
}

/* Phase D, drag/drop visual feedback. */
.chat-pane { transition: background 120ms; position: relative; }
.chat-pane.drag-active {
  background: var(--accent-soft);
  outline: 2px dashed var(--accent);
  outline-offset: -8px;
}
.chat-pane.drag-active::after {
  content: "Drop image to attach";
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 0.85rem; color: var(--accent-deep);
  pointer-events: none;
  background: rgba(204, 251, 241, 0.6);
}
.ds-row { transition: background 100ms; }
.ds-row.drag-target {
  background: var(--accent-tint);
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
}

/* Brand logo (was inline style; moved out for CSP-friendly script-src). */
.brand-logo { height: 20px; width: auto; }

/* UI experience polish, chat IA, product preview, legal trust copy, and fragments */
.hero-product-preview {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2);
  overflow: hidden;
}
.preview-topbar,
.preview-ledger {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 0.75rem 0.9rem;
  font-family: var(--mono); font-size: 0.68rem; color: var(--muted);
  letter-spacing: 0.04em; text-transform: uppercase;
  background: var(--bone-soft);
}
.preview-topbar { border-bottom: 1px solid var(--rule); }
.preview-ledger { border-top: 1px solid var(--rule); flex-wrap: wrap; }
.preview-topbar > span:first-child { display: inline-flex; align-items: center; gap: 0.35rem; color: var(--accent-deep); }
.preview-thread { padding: 1rem; display: flex; flex-direction: column; gap: 0.75rem; }
.preview-bubble {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.8rem 0.9rem;
  background: var(--paper);
}
.preview-bubble p { margin: 0.25rem 0 0; color: var(--ink-soft); font-size: 0.9rem; line-height: 1.45; }
.user-preview { align-self: flex-end; max-width: 92%; background: var(--accent-soft); border-color: var(--accent-tint); }
.assistant-preview { align-self: flex-start; max-width: 96%; }
.preview-label {
  display: block; font-family: var(--mono); font-size: 0.65rem;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
.preview-redactor {
  align-self: center;
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-family: var(--mono); font-size: 0.7rem; color: var(--accent-deep);
  background: var(--bone-soft); border: 1px solid var(--rule);
  border-radius: 9999px; padding: 0.32rem 0.65rem;
}
.text-accent { color: var(--accent); }

.sidebar-empty-note { padding: 0.5rem 0.6rem; }
.sidebar-search {
  display: flex; align-items: center; gap: 0.35rem;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bone-soft);
}
.sidebar-search svg { color: var(--muted); flex-shrink: 0; }
.sidebar-search input {
  border: 0; background: transparent; padding: 0.1rem 0; font-size: 0.78rem;
  box-shadow: none;
}
.sidebar-search input:focus { box-shadow: none; border-color: transparent; }
.thread-actions { display: inline-block; position: relative; }
.thread-folder-options {
  display: flex; flex-direction: column; gap: 0.2rem; padding-left: 0.5rem;
}
.thread-folder-input { flex: 1; font-size: 0.85rem; }
.popover-help { padding: 0.4rem; font-size: 0.75rem; }
.prompt-menu-item {
  flex-direction: column; align-items: flex-start; gap: 0.1rem;
  width: 100%; text-align: left; padding: 0.4rem 0.6rem;
  border: 0; background: transparent; color: var(--ink);
}
.prompt-menu-item:hover { background: var(--bone-soft); color: var(--ink); }
.prompt-menu-name { font-size: 0.85rem; }
.prompt-menu-preview {
  font-size: 0.72rem; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.secret-created-note { margin: 0 0 0.4rem; }
.secret-help { margin-top: 0.4rem; font-size: 0.78rem; }
.shortcuts-panel { max-width: 540px; }
.modal-title { margin: 0; }

.legal-head h1 { font-family: var(--serif); margin-bottom: 0.5rem; }
.legal-head .muted { font-size: 0.95rem; margin-bottom: 1.5rem; line-height: 1.6; }
.legal-card { border-left: 3px solid var(--accent); }
.legal-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 0.75rem;
}
.legal-list li { display: flex; align-items: flex-start; gap: 0.55rem; color: var(--ink-soft); }
.legal-list svg { color: var(--accent); flex-shrink: 0; margin-top: 0.15rem; }
.legal-contact { margin: 1.25rem 0 0; }

.ds-search-input { max-width: 220px; padding: 0.35rem 0.6rem; font-size: 0.85rem; }
.ingest-steps {
  list-style: none; padding: 0; margin: 0 0 0.9rem;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.35rem;
}
.ingest-steps li {
  display: inline-flex; align-items: center; gap: 0.3rem;
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 0.35rem 0.45rem;
  font-family: var(--mono); font-size: 0.68rem; color: var(--muted);
}
.ingest-steps li.done { color: var(--ok); background: var(--ok-bg); border-color: var(--ok-border); }
.ingest-steps li.active { color: var(--info); background: var(--info-bg); border-color: var(--info-border); }
.chunk-summary { cursor: pointer; font-size: 0.8rem; padding: 0.4rem 0; }
.chunk-stack {
  display: flex; flex-direction: column; gap: 0.5rem;
  max-height: 280px; overflow-y: auto; margin-top: 0.5rem;
}
.onboarding-progress {
  height: 6px; background: var(--bone-soft); border: 1px solid var(--rule);
  border-radius: 999px; overflow: hidden; margin-top: 0.75rem;
}
.onboarding-progress-fill {
  display: block; height: 100%; background: var(--accent);
  transition: width 180ms ease-out;
}
.onboarding-progress-0 { width: 0; }
.onboarding-progress-1 { width: 25%; }
.onboarding-progress-2 { width: 50%; }
.onboarding-progress-3 { width: 75%; }
.onboarding-progress-4 { width: 100%; }

@media (max-width: 600px) {
  .hero { padding-top: 2.75rem; }
  .hero-text h1 { font-size: 2.35rem; }
  .hero-text .cta { flex-direction: column; align-items: stretch; }
  .preview-ledger { justify-content: flex-start; }
  .ingest-steps { grid-template-columns: 1fr; }
}

/* Audit #2, jump-to-latest pill. Floats above the composer (anchored
   to .chat-pane which is position:relative) so it doesn't fall to the
   end of the document layout when the messages list grows. chat.js
   toggles `hidden` based on a passive scroll listener on #messages.
   Shown only when the user has scrolled away from the bottom. */
.jump-pill {
  position: absolute;
  bottom: calc(var(--composer-clearance, 11rem));
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  cursor: pointer;
  box-shadow: var(--shadow-2);
}
/* Default text-pill style (kept for any future caller that wants
   the full "Jump to latest ↓" label). */
.jump-pill:not(.jump-pill-compact) {
  padding: 0.4rem 0.95rem;
  border-radius: 999px;
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--ink);
  font-size: 0.78rem;
}
/* 0.47.0 compact chevron-only variant. A small icon button with just
   the down-chevron, less visually noisy than the text pill. The
   button is icon-only by markup; ARIA label + title preserve
   accessibility.

   0.50.x: round (radius:999px) + slightly smaller (32px) so it visually
   echoes the mic-chevron in the composer toolrail. Same chevron-down
   icon appears in both spots, so the user reads them as members of the
   same family. The accent-colored hover treatment matches the toolrail
   icon-only triggers (Knowledge / Assist / "+"). */
.jump-pill-compact {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  color: var(--muted);
  border: 1px solid var(--rule);
  transition: border-color 120ms, color 120ms, background 120ms;
}
.jump-pill-compact:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.jump-pill:not(.jump-pill-compact):hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
}
@media (max-width: 600px) {
  .jump-pill { bottom: calc(var(--composer-clearance, 13rem)); }
}

/* Audit #11/#12/#13/#14, chat polish styles */
.phase-mini-hint {
  margin-left: 0.5rem;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--muted);
}

.thread-totalizer {
  font-size: 0.78rem;
  margin-left: 0.6rem;
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
}

.reasoning-label { font-weight: 500; }
.reasoning-preview {
  color: var(--muted);
  font-size: 0.82rem;
  margin-left: 0.25rem;
}

.coachmark {
  margin: 0.5rem 0;
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  background: var(--bone-soft);
  border: 1px solid var(--rule);
  font-size: 0.85rem;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  justify-content: space-between;
}
.coachmark strong { color: var(--accent-deep); }
.coachmark button { flex-shrink: 0; }

.redaction-coachmark {
  border-left: 3px solid var(--accent);
}

/* ────────────── 0.13.0: Model Lab + private-model story ─────────────── */
.model-lab           { max-width: 1200px; }
.model-lab-lede      { max-width: 70ch; margin-top: 4px; }
.model-lab-grid      {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  align-items: start;
  margin-top: var(--space-3);
}
.model-lab-form-card { grid-column: 1 / 2; grid-row: 1 / 3; }
.model-lab-status-card { grid-column: 2 / 3; grid-row: 1 / 2; }
.model-lab-models-card { grid-column: 2 / 3; grid-row: 2 / 3; }

@media (max-width: 900px) {
  .model-lab-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .model-lab-form-card,
  .model-lab-status-card,
  .model-lab-models-card { grid-column: 1; grid-row: auto; }
}

.model-lab-form .form-row {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: var(--space-2);
  align-items: center;
  margin-bottom: var(--space-2);
}
.model-lab-form .form-row > span {
  font-weight: 500;
  color: var(--text-strong);
  font-size: 0.92em;
}
.model-lab-form .form-row-half {
  display: inline-grid;
  grid-template-columns: 120px minmax(120px, 1fr);
  width: 48%;
  margin-right: 2%;
}
@media (max-width: 700px) {
  .model-lab-form .form-row,
  .model-lab-form .form-row-half {
    grid-template-columns: minmax(0, 1fr);
    width: 100%;
    margin-right: 0;
  }
}
.model-lab-form select,
.model-lab-form textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
}
.model-lab-form textarea { font-family: var(--mono); font-size: 0.88em; }
.model-lab-form .form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  flex-wrap: wrap;
}
.model-lab-form .form-help { font-size: 0.85em; }
.run-result-mount { margin-top: var(--space-2); min-height: 0; }
.run-result-ok { padding: 8px 10px; background: var(--ok-bg); border-radius: 6px; }
.model-lab-run-card { display: grid; gap: var(--space-4); }
.model-lab-run-grid,
.model-lab-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
}
.model-lab-run-grid > div,
.model-lab-metrics > div {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.model-lab-run-grid strong,
.model-lab-metrics strong { overflow-wrap: anywhere; }
.section-kicker {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
  text-transform: uppercase;
  font-weight: 700;
}
.model-lab-artifact {
  display: grid;
  gap: var(--space-2);
  margin: 0;
}
.model-lab-artifact div {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: var(--space-2);
}
.model-lab-artifact dt { color: var(--muted); }
.model-lab-artifact dd { margin: 0; overflow-wrap: anywhere; }
.model-lab-error {
  margin: 0;
  max-height: 260px;
  overflow: auto;
}
@media (max-width: 720px) {
  .model-lab-run-grid,
  .model-lab-metrics { grid-template-columns: 1fr; }
  .model-lab-artifact div { grid-template-columns: 1fr; }
}

.run-list, .model-list { list-style: none; padding: 0; margin: 0; }
.run-list .run-item,
.model-list .model-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}
.run-list .run-item:last-child,
.model-list .model-item:last-child { border-bottom: 0; }
.run-list .run-name { font-weight: 500; }
.run-list .run-mode { font-size: 0.85em; }
.run-list .run-time { margin-left: auto; font-size: 0.85em; }
.deploy-form { margin-left: auto; }
.active-runs-foot { margin-top: var(--space-2); font-size: 0.9em; }

/* Private-Local-Model story (homepage hero + admin panel) */
.private-model-story {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: linear-gradient(135deg, var(--paper), var(--bone-soft));
}
.private-model-story h3 {
  margin: 0 0 4px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.private-model-story .pms-row {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: var(--space-2);
  align-items: start;
}
.private-model-story .pms-row .icon-wrap {
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--accent-tint);
  color: var(--accent-deep);
  flex-shrink: 0;
}
.private-model-story .pms-row strong { display: block; margin-bottom: 2px; }

/* Datasets training-readiness pills (item #3) */
.dataset-readiness {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.dataset-readiness .pill { font-size: 0.78em; }
.pill-rag-ready  { background: var(--ok-bg);     color: var(--ok);          border: 1px solid var(--rule); }
.pill-ft-ready   { background: var(--accent-tint); color: var(--accent-deep); border: 1px solid var(--rule); }
.pill-needs-work { background: var(--warn-bg);   color: var(--warn);        border: 1px solid var(--rule); }
.dataset-readiness .stat {
  font-size: 0.78em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.dataset-readiness-panel {
  display: grid;
  gap: 8px;
  padding: 10px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  margin: 12px 0;
}
.readiness-metrics {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 0.86rem;
  color: var(--muted);
}
.readiness-metrics strong {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Chat model picker grouped (item #4), labels above optgroup-style headings */
.model-picker-group {
  font-weight: 600;
  font-size: 0.78em;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--bone-soft);
}

/* Homepage product preview (item #5), 4-step strip */
.product-preview {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-2);
  margin: var(--space-4) 0;
}
@media (max-width: 800px) {
  .product-preview { grid-template-columns: minmax(0, 1fr); }
}
.product-preview .pp-step {
  padding: var(--space-3);
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--paper);
  position: relative;
}
.product-preview .pp-step .pp-num {
  position: absolute;
  top: 8px; right: 10px;
  font-size: 0.78em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.product-preview .pp-step strong { display: block; margin: 4px 0 4px; }
.product-preview .pp-step p { margin: 0; font-size: 0.9em; color: var(--muted); }

/* ────────── 0.14.0 Wave C, deferred audit items ────────── */

/* #6 system-prompt chip */
.system-prompt-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.78em;
  cursor: pointer;
  padding: 2px 8px;
  border-radius: 999px;
}
.system-prompt-chip-label { font-weight: 500; }

/* 0.76.x — RAG bound chip. Mirrors the system-prompt-chip pattern;
   surfaces the per-thread Knowledge binding count where the user
   can see it. The Wave B Δ8 redesign CSS-hid the original Knowledge
   trigger; this chip reuses the same data (#rag-label state in
   popovers.js) but renders visibly in the chat header. The
   [hidden] attribute is the load-bearing toggle — popovers.js
   sets/clears it based on the bound count. Server-rendered hidden
   so SSR doesn't flash an empty RAG pill before JS hydrates. */
.rag-bound-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.78em;
  cursor: pointer;
  padding: 2px 8px;
  border-radius: 999px;
}
.rag-bound-chip[hidden] { display: none; }
.rag-bound-chip-label { font-weight: 500; }

/* 0.76.x — Toolrail popover footer. Holds the explicit Done button
   for the Knowledge + Assist popovers. The outside-click handler
   in popovers.js still works as a fallback; this is the
   discoverable affordance the multi-select popovers were missing.
   Border-top separates it from the popover body without competing
   with the existing toolrail-panel-header.sep-top divider style. */
.toolrail-panel-footer {
  display: flex;
  justify-content: flex-end;
  padding: 0.35rem 0.55rem;
  border-top: 1px solid var(--rule);
}

/* #7 citation hover preview */
.cite-hover-tip {
  position: absolute;
  z-index: 200;
  max-width: 360px;
  padding: 8px 10px;
  background: var(--ink);
  color: var(--paper);
  border-radius: 6px;
  box-shadow: var(--shadow-2);
  font-size: 0.82em;
  line-height: 1.35;
  pointer-events: none;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.cite-hover-tip.visible { opacity: 1; transform: translateY(0); }
.cite-hover-tip strong { display: block; font-size: 0.78em; opacity: 0.78; margin-bottom: 2px; }
.cite-hover-tip p { margin: 0; }

/* #9 quick reply chips */
.quick-reply-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.quick-reply-chip {
  font-size: var(--chat-chip-size, 0.8rem);
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--rule);
  background: var(--paper);
  cursor: pointer;
  color: var(--ink-soft);
}
.quick-reply-chip:hover {
  background: var(--accent-tint);
  color: var(--accent-deep);
  border-color: var(--accent-deep);
}

/* 0.53.0 v8 — Continue button. Appears below a possibly-truncated
   assistant response. Sits inside the bubble (peer of [data-md])
   so it's clearly associated with the answer it would extend. */
.continue-row {
  margin-top: 0.65em;
  display: flex;
}
.continue-btn {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.82rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  background: var(--bone-soft);
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  cursor: pointer;
  transition: background 120ms, border-color 120ms, color 120ms;
}
.continue-btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-deep);
}
.continue-btn svg { flex-shrink: 0; }

/* 0.53.0 v8 — paste-to-attachment chip. Sits ABOVE the textarea
   (composer toolbar height area) so the user sees "this paste was
   saved as a chip" rather than "where did my paste go". */
.paste-attachment-host {
  display: flex;
  margin: 0 0 0.4rem;
}
.paste-attachment-chip {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.4rem 0.75rem;
  font-size: 0.82rem;
  background: var(--accent-soft);
  color: var(--accent-deep);
  border: 1px solid var(--accent-tint);
  border-radius: 999px;
  cursor: pointer;
  transition: background 120ms, border-color 120ms;
}
.paste-attachment-chip:hover {
  background: color-mix(in srgb, var(--accent-tint) 60%, var(--paper));
  border-color: var(--accent);
}
.paste-attachment-chip svg { flex-shrink: 0; color: var(--accent); }
.paste-attachment-label {
  font-variant-numeric: tabular-nums;
}
.paste-attachment-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent-deep);
  font-size: 0.95rem;
  line-height: 1;
  margin-left: 0.15rem;
}
.paste-attachment-chip:hover .paste-attachment-remove {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* #16 admin onboarding hero */
.onboarding-hero {
  background: linear-gradient(135deg, var(--accent-tint), var(--paper));
  border: 1px solid var(--rule);
  margin-bottom: var(--space-3);
}
.onboarding-hero-text h3 { margin: 0 0 4px; }
.onboarding-hero-steps {
  margin: 12px 0 0;
  padding-left: 1.4em;
  display: grid;
  gap: 8px;
}
.onboarding-hero-steps li > strong { color: var(--ink); }
.onboarding-hero-cta { margin-left: 8px; vertical-align: middle; }

/* #18 curation example card */
.curation-example-card {
  margin-bottom: var(--space-3);
  background: var(--bone-soft);
  border: 1px solid var(--rule);
}
.curation-example-card > summary { cursor: pointer; padding: 4px 0; }
.curation-example-body { margin-top: 10px; }
.curation-example-body ol { padding-left: 1.4em; display: grid; gap: 6px; }
.curation-example-foot { margin-top: 10px; }

/* #20 mobile sidebar swipe — unified into the 900px drawer block above
   (UI fix waves). The swipe path now shares `.thread-sidebar.open` +
   `body.sidebar-open` with the burger button, so the old 800px
   `.mobile-open` rules (which produced an 801–900px dead-zone) are gone.
   The pane-dim (`body.sidebar-open .chat-pane`) lives in the
   `@media (max-width: 900px)` block next to `.thread-sidebar.open`. */

/* ── #B1 / #B2, Responsive table card-stacks ─────────────────────────────
   Below the breakpoint, rows render as stacked cards. Each <td> has
   data-label="…" matching its column header; a ::before pseudo-element
   surfaces that label inline with the value. Pure CSS, no JS. */

/* B1, Admin members table → cards below 900px (tablet down) */
@media (max-width: 900px) {
  .members-table thead { display: none; }
  .members-table, .members-table tbody { display: block; width: 100%; }
  .members-table tr {
    display: block;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--rule);
    border-radius: 8px;
    margin-bottom: 0.6rem;
    background: var(--paper);
  }
  .members-table td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.35rem 0;
    border-top: 1px solid var(--rule-soft);
    text-align: left;
  }
  .members-table td:first-child { border-top: none; }
  .members-table td::before {
    content: attr(data-label);
    font-weight: 500;
    color: var(--muted);
    font-size: 0.78rem;
    margin-right: 0.75rem;
    flex-shrink: 0;
  }
  .members-table td.cell-actions {
    flex-wrap: wrap;
    gap: 0.4rem;
    justify-content: flex-end;
  }
  .members-table td.text-right { text-align: left; }
}

/* B2, Admin audit log table → cards below 600px (phone) */
@media (max-width: 600px) {
  .table-mono-sm thead { display: none; }
  .table-mono-sm, .table-mono-sm tbody { display: block; width: 100%; }
  .table-mono-sm tr {
    display: block;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--rule);
    border-radius: 6px;
    margin-bottom: 0.5rem;
    background: var(--paper);
  }
  .table-mono-sm td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 0.25rem 0;
    border-top: 1px solid var(--rule-soft);
    word-break: break-all;
  }
  .table-mono-sm td:first-child { border-top: none; }
  .table-mono-sm td::before {
    content: attr(data-label);
    font-family: var(--sans);
    font-weight: 500;
    color: var(--muted);
    font-size: 0.72rem;
    margin-right: 0.6rem;
    flex-shrink: 0;
  }
}

/* Reasoning-chain disclosure. Lives inside the assistant bubble after
   the prose and translates internal step types into a compact audit trail
   the user can scan without seeing raw orchestrator names. */
/* 2026-05-20 — tighten the "How I got this answer" disclosure to
   match Claude rhythm. Was margin 0.45/0.6, font 0.86rem — felt like
   a chunky bar sitting between the activity card and the assistant
   prose. Drop margins + font so it reads as a quiet inline link. */
.reasoning-chain {
  margin: 0.18rem 0 0.28rem;
  padding: 0;
  border: 0;
  color: var(--ink-soft);
  font-size: 0.76rem;
}
.reasoning-chain[hidden] { display: none; }
.reasoning-chain > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  list-style: none;
  cursor: pointer;
  color: var(--muted);
  line-height: 1.2;
}
.reasoning-chain > summary::-webkit-details-marker { display: none; }
.reasoning-chain > summary::before {
  content: "";
  width: 0.44rem;
  height: 0.44rem;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 140ms ease;
}
.reasoning-chain[open] > summary::before {
  transform: rotate(45deg) translate(-0.05rem, -0.05rem);
}
.reasoning-chain-label { color: var(--ink-soft); font-weight: 500; }
.reasoning-chain-count {
  color: var(--muted);
  font-style: italic;
  font-weight: 420;
}
.reasoning-chain-steps {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.38rem;
}
.reasoning-chain-step {
  display: grid;
  grid-template-columns: 1.55rem minmax(0, 1fr);
  gap: 0.6rem;
  align-items: start;
  min-width: 0;
  padding: 0.48rem 0.58rem;
  border: 1px solid color-mix(in srgb, var(--rule) 68%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--bone-soft) 42%, transparent);
}
.reasoning-chain-icon {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 1.35rem;
  height: 1.35rem;
  margin-top: 0.05rem;
  border-radius: 999px;
  color: var(--accent-deep);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--rule));
}
.reasoning-chain-icon::before {
  content: "";
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 999px;
  background: currentColor;
  opacity: 0.88;
}
.reasoning-chain-icon[data-icon="redact"]::before,
.reasoning-chain-icon[data-icon="guardrail"]::before,
.reasoning-chain-icon[data-icon="safety"]::before {
  width: 0.44rem;
  height: 0.58rem;
  border: 1.6px solid currentColor;
  border-top: 0;
  border-left: 0;
  border-radius: 0;
  background: transparent;
  transform: rotate(42deg) translateY(-0.05rem);
}
.reasoning-chain-icon[data-icon="retrieve"]::before,
.reasoning-chain-icon[data-icon="rag"]::before,
.reasoning-chain-icon[data-icon="rag_search"]::before {
  width: 0.7rem;
  height: 0.5rem;
  border: 1.4px solid currentColor;
  border-radius: 0.16rem;
  background: transparent;
  box-shadow: 0 0.24rem 0 -0.08rem currentColor;
}
.reasoning-chain-icon[data-icon="web_search"]::before,
.reasoning-chain-icon[data-icon="web_fetch"]::before,
.reasoning-chain-icon[data-icon="pubmed_search"]::before,
.reasoning-chain-icon[data-icon="dailymed_lookup"]::before,
.reasoning-chain-icon[data-icon="openfda_adverse_events"]::before,
.reasoning-chain-icon[data-icon="rxnav_interactions"]::before,
.reasoning-chain-icon[data-icon="tool"]::before,
.reasoning-chain-icon[data-icon="tool_use"]::before {
  width: 0.68rem;
  height: 0.68rem;
  border: 1.4px solid currentColor;
  border-radius: 0.18rem;
  background: transparent;
}
.reasoning-chain-icon[data-icon="compose"]::before,
.reasoning-chain-icon[data-icon="citation"]::before {
  width: 0.58rem;
  height: 0.58rem;
  border-radius: 0.14rem;
  background: transparent;
  border: 1.4px solid currentColor;
  box-shadow: inset 0 -0.22rem 0 color-mix(in srgb, currentColor 22%, transparent);
}
.reasoning-chain-copy {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, max-content) auto;
  align-items: baseline;
  gap: 0.14rem 0.42rem;
}
.reasoning-chain-step-type {
  color: var(--ink);
  font-size: 0.82rem;
  font-weight: 620;
  letter-spacing: 0;
}
.reasoning-chain-step-desc {
  grid-column: 1 / -1;
  color: var(--muted);
  line-height: 1.38;
  overflow-wrap: anywhere;
}
.reasoning-chain-latency {
  justify-self: start;
  color: var(--muted);
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  border: 1px solid color-mix(in srgb, var(--rule) 76%, transparent);
  border-radius: 999px;
  padding: 0.05rem 0.34rem;
}
.reasoning-chain-why {
  grid-column: 1 / -1;
  margin: 0.2rem 0 0;
  color: var(--ink-soft);
  font-size: 0.78rem;
  line-height: 1.42;
}
@media (max-width: 600px) {
  .reasoning-chain-step {
    grid-template-columns: 1.35rem minmax(0, 1fr);
    padding: 0.42rem 0.48rem;
  }
  .reasoning-chain-copy { grid-template-columns: minmax(0, 1fr); }
  .reasoning-chain-latency { margin-top: 0.05rem; }
}

/* StepInfo activity log card. Sits above the assistant bubble; shows
   the persisted agent.agent_steps rows so a thread reload renders the
   same activity that streamed live. */
.steps-card {
  margin: 0.18rem 0 0.5rem;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-size: var(--chat-activity-size, 0.86rem);
  max-width: min(640px, 100%);
}
.steps-card[open] { background: transparent; }
.steps-card-summary {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.25rem 0;
  cursor: pointer;
  list-style: none;
  color: var(--muted);
}
.steps-card-summary::-webkit-details-marker { display: none; }
.steps-summary-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  color: currentColor;
}
.steps-summary-icon svg,
.steps-card-chevron svg {
  display: block;
}
.steps-card-label { font-weight: 500; color: currentColor; }
.steps-card-count { color: var(--muted); }
.steps-card-chevron {
  margin-left: auto;
  color: color-mix(in srgb, currentColor 72%, transparent);
  transition: transform 130ms ease;
}
.steps-card[open] .steps-card-chevron {
  transform: rotate(90deg);
}
.steps-card-list {
  list-style: none;
  margin: 0;
  padding: 0.42rem 0 0.18rem;
  display: flex;
  flex-direction: column;
  gap: 0.28rem;
  border-top: 1px solid color-mix(in srgb, var(--rule) 74%, transparent);
}
.steps-card-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.12rem 0;
  border-radius: 0;
  color: var(--muted);
  animation: thinking-row-in 170ms ease-out both;
}
.steps-card-row.is-error { background: var(--danger-bg); color: var(--danger); }
.steps-row-icon {
  position: relative;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  background: transparent;
  -webkit-mask: none;
          mask: none;
  opacity: 0.72;
}
/* CSS-drawn fallback icons so live activity rows never degrade into
   solid color squares when the icon mask is not available. */
.steps-row-icon::before {
  content: '';
  position: absolute;
  inset: 6px 2px 3px;
  border: 1.6px solid currentColor;
  border-radius: 3px;
}
.steps-row-icon::after {
  content: '';
  position: absolute;
  left: 3px;
  top: 3px;
  width: 7px;
  height: 4px;
  border: 1.6px solid currentColor;
  border-bottom: 0;
  border-radius: 3px 3px 0 0;
}
.steps-row-icon[data-icon="globe"]::before,
.steps-row-icon[data-icon="web_search"]::before {
  inset: 2px;
  border-radius: 999px;
}
.steps-row-icon[data-icon="globe"]::after,
.steps-row-icon[data-icon="web_search"]::after {
  left: 6px;
  top: 2px;
  width: 6px;
  height: 14px;
  border: 0;
  border-left: 1.4px solid currentColor;
  border-right: 1.4px solid currentColor;
  border-radius: 999px;
}
.steps-row-name { font-weight: 500; color: var(--ink-soft); }
.steps-row-msg { color: var(--muted); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.steps-row-dur { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 0.7rem; margin-left: auto; flex-shrink: 0; }
.steps-card-row--rag { color: var(--accent); }
.steps-card-row--web_search { color: var(--accent-deep); }
.steps-card-row--thinking { color: var(--ink-soft); }
.steps-card-row--tool_use { color: var(--ink); }
/* Loop-guard fires (doom-loop, malformed-JSON, continuation guard, …).
   Distinct from a regular activity row so operators can spot when the
   orchestrator self-corrected mid-turn vs. when it just used a tool.
   `.steps-card-row--approval_timeout` shares this warning treatment —
   a HITL fail-closed timeout is also an agent-level self-correction
   signal and should read at the same urgency tier. */
.steps-card-row--guard_fired,
.steps-card-row--approval_timeout,
.guard-fired-row {
  color: var(--warning, var(--accent-deep));
  background: color-mix(in srgb, var(--warning, var(--accent-deep)) 9%, transparent);
  border-left: 2px solid color-mix(in srgb, var(--warning, var(--accent-deep)) 70%, transparent);
  padding-left: 0.4rem;
}
.guard-fired-row .steps-row-name,
.steps-card-row--approval_timeout .steps-row-name {
  color: var(--warning, var(--accent-deep));
  font-weight: 600;
}

/* Context-compaction notices — informational, not warning. Softer accent
   than guard_fired so operators can tell at a glance that the gateway
   summarized older messages to fit the model window (vs. the orchestrator
   self-correcting mid-turn). See gateway/context_compaction.py. */
.steps-card-row--compacted {
  color: var(--accent, var(--ink-soft));
  background: color-mix(in srgb, var(--accent, var(--ink-soft)) 6%, transparent);
  border-left: 2px solid color-mix(in srgb, var(--accent, var(--ink-soft)) 50%, transparent);
  padding-left: 0.4rem;
}
.steps-card-row--compacted .steps-row-name { color: var(--accent, var(--ink-soft)); font-weight: 500; }

/* Thread-row last-message preview line. Sits between the title and the
   meta strip (model · age). Single-line, ellipsized so the sidebar row
   keeps its fixed height, no reflow on long messages. */
.thread-row-preview {
  display: block;
  font-size: 0.74rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 0.1rem;
  line-height: 1.25;
}
.thread-row-preview-prefix { color: var(--ink-soft); font-weight: 500; }
.thread-row.active .thread-row-preview { color: var(--ink-soft); }

/* 0.21.0, composer toolrail popup-style. Three uniform <details>
   triggers (Knowledge / Assist / Inputs). Click opens an absolutely-
   positioned panel beneath the trigger; chat.js closes any open one
   on outside-click. */
.composer-toolrail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.46rem;
  padding: 0.45rem 0 0;
  align-items: center;
  position: relative;
}
.toolrail-pop {
  position: relative;
  min-width: 0;
}
.toolrail-pop[data-toolrail-group="inputs"],
.toolrail-pop[data-toolrail-group="plus"] { margin-left: auto; }
.toolrail-pop > summary {
  list-style: none;
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 34px;
  max-width: min(330px, 100%);
  padding: 0.42rem 0.72rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  color: var(--ink);
  font-size: 0.78rem;
  line-height: 1.1;
  user-select: none;
  white-space: nowrap;
  transition: border-color 120ms, background 120ms, color 120ms, box-shadow 120ms;
}
.toolrail-pop > summary::marker { content: ""; display: none; }
.toolrail-pop > summary::-webkit-details-marker { display: none; }
.toolrail-pop > summary:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--paper);
}
.toolrail-pop[open] > summary,
.toolrail-pop.is-active > summary {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-deep);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-tint) 48%, transparent);
}
.toolrail-trigger-icon { display: inline-flex; align-items: center; flex: 0 0 auto; }
.toolrail-trigger-name { font-weight: 600; }
.toolrail-trigger-status {
  color: var(--muted);
  border-left: 1px solid var(--rule);
  padding-left: 0.5rem;
  margin-left: 0.1rem;
  min-width: 0;
  max-width: 170px;
  display: inline-flex;
  align-items: center;
}
.toolrail-trigger-status .label-full,
.toolrail-trigger-status .label-short,
#img-label .label-full,
#img-label .label-short {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.toolrail-pop[open] > summary .toolrail-trigger-status { color: var(--accent-deep); border-left-color: var(--accent); }

.toolrail-panel {
  position: absolute;
  /* Anchor at the summary's top edge so the panel grows UPWARD into the
     messages-list space instead of DOWN into the textarea below. The
     toolrail sits directly above the composer input; dropping down was
     hiding what the user is typing.
     Explicit top: auto + !important on bottom: deployed prior versions
     had a competing `.dataset-popover { top: calc(100% + 6px) }` rule
     that the cascade resolved against this drop-up intent in some
     browsers; pinning top: auto + bottom !important forces the
     drop-up direction even when stale CSS is mixed in via cache. */
  top: auto;
  bottom: calc(100% + 6px) !important;
  left: 0;
  z-index: 40;
  width: min(420px, calc(100vw - 2rem));
  min-width: 320px;
  max-width: min(420px, calc(100vw - 2rem));
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: 0 -20px 54px -32px rgb(10 14 13 / 0.42), var(--shadow-2);
  padding: 0.72rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  overflow: hidden;
  max-height: min(60vh, 520px);
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--muted) 48%, transparent) transparent;
}
.toolrail-panel::-webkit-scrollbar,
.toolrail-inline-popover.dataset-popover::-webkit-scrollbar { width: 8px; }
.toolrail-panel::-webkit-scrollbar-track,
.toolrail-inline-popover.dataset-popover::-webkit-scrollbar-track { background: transparent; }
.toolrail-panel::-webkit-scrollbar-thumb,
.toolrail-inline-popover.dataset-popover::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--muted) 46%, transparent);
  border: 2px solid transparent;
  border-radius: 999px;
  background-clip: padding-box;
}
.toolrail-panel::-webkit-scrollbar-thumb:hover,
.toolrail-inline-popover.dataset-popover::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--accent) 52%, var(--muted));
}
.toolrail-pop[data-toolrail-group="inputs"] .toolrail-panel,
.toolrail-pop[data-toolrail-group="plus"] .toolrail-panel {
  /* Anchor to the RIGHT edge of the trigger so the panel doesn't slide
     off-screen — the "+" sits at the right end of the toolrail
     (margin-left: auto) and the default panel `left: 0` would push the
     panel past the viewport edge. The pre-v2 "inputs" group had this;
     the new "plus" group missed inheriting it. */
  left: auto;
  right: 0;
  min-width: 300px;
}
.toolrail-pop[data-toolrail-group="assist"] .toolrail-panel {
  width: min(460px, calc(100vw - 2rem));
  min-width: min(360px, calc(100vw - 2rem));
  max-width: min(460px, calc(100vw - 2rem));
}
.toolrail-pop[data-toolrail-group="knowledge"] .toolrail-panel {
  width: min(400px, calc(100vw - 2rem));
  min-width: min(340px, calc(100vw - 2rem));
  max-width: min(400px, calc(100vw - 2rem));
}
.toolrail-pop[data-toolrail-group="knowledge"] .toolrail-panel,
.toolrail-pop[data-toolrail-group="assist"] .toolrail-panel {
  max-height: min(440px, 64vh);
}
/* 2026-05-20 Claude-compact pass — drop the tutorial subtitle ("RAG
   runs against everything you select", "Let the assistant call
   helpers mid-response") inside each toolrail panel header. The
   strong title alone is enough context; the subtitle was tutorial
   copy that bloated every popover open. Strong title shrunk to
   match the dropdown section headings. */
.toolrail-panel-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.55rem 0.18rem;
}
.toolrail-panel-header.sep-top {
  border-top: 1px solid var(--rule);
  padding-top: 0.4rem;
  margin-top: 0.35rem;
}
.toolrail-panel-header strong { font-size: 0.74rem; color: var(--muted); font-weight: 500; line-height: 1.2; }
.toolrail-panel-header .muted { display: none; }
.toolrail-section-action {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  color: var(--muted);
  font-size: 0.7rem;
  cursor: pointer;
}
.toolrail-section-action:hover { border-color: var(--accent); color: var(--accent); }

/* Inline popovers, same content as before but always visible inside
   their parent panel. Override the absolute-positioned default. */
.toolrail-inline-popover.dataset-popover {
  position: static;
  display: block;
  border: none;
  box-shadow: none;
  padding: 0.05rem 0.18rem 0.05rem 0;
  width: 100%;
  max-height: 250px;
  overflow: auto;
  background: transparent;
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--muted) 48%, transparent) transparent;
}
.toolrail-pop[data-toolrail-group="assist"] .toolrail-inline-popover.dataset-popover {
  max-height: min(232px, 42vh);
}
.toolrail-pop[data-toolrail-group="knowledge"] .toolrail-inline-popover.dataset-popover {
  max-height: min(282px, 48vh);
}
.toolrail-pop[data-toolrail-group="assist"] #prompts-popover {
  max-height: min(112px, 20vh);
}
.toolrail-inline-popover.dataset-popover label {
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr);
  align-items: start;
  gap: 0.65rem;
  min-height: 2.45rem;
  padding: 0.55rem 0.62rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--ink);
  font-size: 0.92rem;
  line-height: 1.25;
  transition:
    background-color 140ms ease,
    border-color 140ms ease,
    color 140ms ease,
    transform 140ms ease;
}
.toolrail-inline-popover.dataset-popover label:hover {
  background: color-mix(in srgb, var(--bone-soft) 72%, transparent);
  border-color: color-mix(in srgb, var(--rule) 72%, transparent);
}
.toolrail-inline-popover.dataset-popover label:has(input:checked) {
  background: color-mix(in srgb, var(--accent) 11%, var(--paper));
  border-color: color-mix(in srgb, var(--accent) 38%, var(--rule));
}
.toolrail-inline-popover.dataset-popover label:focus-within {
  outline: 2px solid color-mix(in srgb, var(--accent) 38%, transparent);
  outline-offset: 2px;
}
.toolrail-inline-popover.dataset-popover input[type="checkbox"] {
  appearance: none;
  inline-size: 1.05rem;
  block-size: 1.05rem;
  margin: 0.08rem 0 0;
  border: 1.5px solid color-mix(in srgb, var(--muted) 58%, var(--rule));
  border-radius: 0.28rem;
  background: color-mix(in srgb, var(--paper) 90%, var(--bone-soft));
  box-shadow: inset 0 1px 1px rgb(10 14 13 / 0.08);
  display: grid;
  place-content: center;
  flex: 0 0 auto;
}
.toolrail-inline-popover.dataset-popover input[type="checkbox"]::after {
  content: "";
  inline-size: 0.28rem;
  block-size: 0.55rem;
  border: solid var(--paper);
  border-width: 0 2px 2px 0;
  opacity: 0;
  transform: translateY(-0.04rem) rotate(42deg) scale(0.72);
  transition: opacity 120ms ease, transform 120ms ease;
}
.toolrail-inline-popover.dataset-popover input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.toolrail-inline-popover.dataset-popover input[type="checkbox"]:checked::after {
  opacity: 1;
  transform: translateY(-0.04rem) rotate(42deg) scale(1);
}
.tool-option-copy,
.dataset-option-copy {
  display: grid;
  min-width: 0;
  gap: 0.14rem;
}
.tool-option-name,
.dataset-option-name {
  overflow-wrap: anywhere;
  color: var(--ink);
  font-weight: 560;
}
.tool-option-desc {
  color: var(--muted);
  font-size: 0.76rem;
  font-weight: 420;
  line-height: 1.32;
}
.popover-help,
#prompts-popover .popover-error {
  margin: 0.25rem 0 0;
  padding: 0.58rem 0.65rem;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--bone-soft) 52%, transparent);
  font-size: 0.76rem;
  line-height: 1.35;
}

.toolrail-input-grid {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.toolrail-input-btn { flex: 1 1 auto; min-width: 110px; justify-content: center; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   APP LANDING (app.sanolith.com logged-out home)

   Audience is mostly returning workspace members trying to sign in,
   not net-new prospects (those land on www.sanolith.com). The page
   should be conversion-focused, not pitch-focused. Reuses existing
   .hero-product-preview / .trust-strip / .flow-* / .redact / .preview-*
   classes; adds only the surfaces grid and the live-pulse strip.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */

.app-hero {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: 3.5rem;
  align-items: center;
  padding: 3.5rem 0 2.5rem;
  max-width: 1180px;
  margin: 0 auto;
  padding-left: max(1.5rem, env(safe-area-inset-left));
  padding-right: max(1.5rem, env(safe-area-inset-right));
}

.app-hero-kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 10%, transparent);
  padding: 0.3rem 0.65rem;
  border-radius: 999px;
  margin-bottom: 1.1rem;
}

.app-hero-text h1 {
  font-family: var(--serif);
  font-size: 2.75rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.08;
  margin: 0 0 1rem;
  color: var(--ink);
}

.app-hero-text h1 em { font-style: normal; color: var(--accent); }

.app-hero-text .lead {
  font-size: 1.05rem;
  color: var(--ink-soft);
  line-height: 1.55;
  margin: 0 0 1.6rem;
  max-width: 36rem;
}

.cta-cluster {
  display: flex;
  gap: 0.65rem;
  flex-wrap: wrap;
  margin-bottom: 1.2rem;
}

.btn-lg {
  font-size: 0.98rem;
  padding: 0.7rem 1.2rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.app-live-pulse {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.82rem;
  color: var(--ink-soft);
  padding: 0.45rem 0.85rem;
  background: color-mix(in oklab, var(--paper-deep) 60%, transparent);
  border: 1px solid var(--rule);
  border-radius: 999px;
  margin-bottom: 1.1rem;
}

.app-live-pulse a { color: var(--ink-soft); }
.app-live-pulse a:hover { color: var(--accent); }
.app-live-pulse .pulse-sep { color: var(--rule); }

.pulse-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 0 rgba(22, 163, 74, 0.55);
  animation: app-pulse 2.4s ease-out infinite;
}

/* Status-driven pulse: the dot colour + animation reflect the live
   /readyz result rather than always reading "healthy". "checking" is
   a neutral grey with no pulse; "degraded" is amber/red and static. */
.app-live-pulse[data-status="checking"] .pulse-dot {
  background: var(--muted);
  box-shadow: none;
  animation: none;
}
.app-live-pulse[data-status="degraded"] .pulse-dot {
  background: var(--danger);
  box-shadow: none;
  animation: none;
}

@keyframes app-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0.55); }
  70%  { box-shadow: 0 0 0 8px rgba(22, 163, 74, 0); }
  100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0); }
}

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

.app-hero-aside {
  font-size: 0.85rem;
  margin: 0;
  line-height: 1.5;
}

.app-hero-aside a {
  color: var(--accent);
  text-decoration: none;
  font-weight: 500;
}

.app-hero-aside a:hover { text-decoration: underline; }

.app-surfaces {
  max-width: 1180px;
  margin: 4rem auto 0;
  padding-left: max(1.5rem, env(safe-area-inset-left));
  padding-right: max(1.5rem, env(safe-area-inset-right));
}

.app-surfaces-title {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 1.6rem;
  letter-spacing: -0.015em;
  text-align: center;
  margin: 0 0 2rem;
  color: var(--ink);
}

.app-surfaces-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
}

.surface-card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.4rem 1.2rem 1.25rem;
  background: var(--surface-raised);
  border: 1px solid var(--line);
  border-radius: 14px;
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.surface-card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 6px 20px -10px color-mix(in oklab, var(--accent) 35%, transparent);
}

.surface-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.surface-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  color: var(--accent);
  margin-bottom: 0.3rem;
}

.surface-card h3 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  margin: 0;
}

.surface-card p {
  font-size: 0.85rem;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 0;
  flex: 1;
}

.surface-link {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--accent);
  margin-top: 0.4rem;
}

.app-flow {
  margin-top: 3.5rem;
  max-width: 1180px;
  margin-left: auto;
  margin-right: auto;
  padding-left: max(1.5rem, env(safe-area-inset-left));
  padding-right: max(1.5rem, env(safe-area-inset-right));
}

.app-footer {
  margin-top: 4rem;
}

@media (max-width: 900px) {
  .app-hero {
    grid-template-columns: 1fr;
    gap: 2rem;
    padding-top: 2.5rem;
  }
  .app-hero-text h1 { font-size: 2.15rem; }
  .app-surfaces-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 540px) {
  .app-hero-text h1 { font-size: 1.85rem; }
  .app-hero-text .lead { font-size: 0.98rem; }
  .cta-cluster { flex-direction: column; align-items: stretch; }
  .btn-lg { justify-content: center; }
  .app-surfaces-grid { grid-template-columns: 1fr; }
}

/* ── Sidebar skeleton loader (perf wave, A5) ───────────────────────────
   Shown inside the thread sidebar while A1's HTMX request lazy-loads the
   real thread list. Keeps the column from looking broken / collapsed
   during the round-trip, matches the real thread-row metrics so the
   layout doesn't shift when the response swaps in.

   Expected HTML structure (rendered by A1 inside the sidebar placeholder
   slot, e.g. hx-get target with hx-trigger="load"):

     <ul class="sidebar-skeleton-list" aria-hidden="true">
       <li class="sidebar-skeleton-row">
         <span class="sidebar-skeleton-line sidebar-skeleton-line-title"></span>
         <span class="sidebar-skeleton-line sidebar-skeleton-line-meta"></span>
       </li>
       ... 5 to 7 rows total, width variation handled in CSS by :nth-child ...
     </ul>

   Notes for A1:
     1. aria-hidden="true" on the <ul> so SR users don't hear placeholder
        rows announced; the real list will be announced when it swaps in.
     2. Row count is fixed at 6 in markup, CSS varies widths per row.
     3. No inline styles, no hex values, all colors via tokens. */

.sidebar-skeleton-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}

.sidebar-skeleton-row {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.4rem 0.55rem;
  border-radius: var(--radius);
  background: var(--bone-soft);
}

.sidebar-skeleton-line {
  display: block;
  height: 0.6rem;
  border-radius: var(--radius);
  background: var(--rule-soft);
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    var(--rule) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  background-position: -100% 0;
  animation: sidebar-skeleton-shimmer 1.4s ease-in-out infinite;
}

.sidebar-skeleton-line-title {
  height: 0.65rem;
  width: 78%;
}

.sidebar-skeleton-line-meta {
  height: 0.5rem;
  width: 48%;
}

/* Width variation per row keeps the placeholder from looking like a
   uniform stamp. Six rows, three width buckets, alternating. */
.sidebar-skeleton-row:nth-child(1) .sidebar-skeleton-line-title { width: 82%; }
.sidebar-skeleton-row:nth-child(1) .sidebar-skeleton-line-meta  { width: 52%; }
.sidebar-skeleton-row:nth-child(2) .sidebar-skeleton-line-title { width: 64%; }
.sidebar-skeleton-row:nth-child(2) .sidebar-skeleton-line-meta  { width: 38%; }
.sidebar-skeleton-row:nth-child(3) .sidebar-skeleton-line-title { width: 74%; }
.sidebar-skeleton-row:nth-child(3) .sidebar-skeleton-line-meta  { width: 46%; }
.sidebar-skeleton-row:nth-child(4) .sidebar-skeleton-line-title { width: 58%; }
.sidebar-skeleton-row:nth-child(4) .sidebar-skeleton-line-meta  { width: 34%; }
.sidebar-skeleton-row:nth-child(5) .sidebar-skeleton-line-title { width: 70%; }
.sidebar-skeleton-row:nth-child(5) .sidebar-skeleton-line-meta  { width: 42%; }
.sidebar-skeleton-row:nth-child(6) .sidebar-skeleton-line-title { width: 50%; }
.sidebar-skeleton-row:nth-child(6) .sidebar-skeleton-line-meta  { width: 30%; }
.sidebar-skeleton-row:nth-child(7) .sidebar-skeleton-line-title { width: 68%; }
.sidebar-skeleton-row:nth-child(7) .sidebar-skeleton-line-meta  { width: 40%; }

/* Stagger animation start across rows so the shimmer doesn't pulse in
   lockstep, gives the column a more organic loading rhythm. */
.sidebar-skeleton-row:nth-child(2) .sidebar-skeleton-line { animation-delay: 0.1s; }
.sidebar-skeleton-row:nth-child(3) .sidebar-skeleton-line { animation-delay: 0.2s; }
.sidebar-skeleton-row:nth-child(4) .sidebar-skeleton-line { animation-delay: 0.3s; }
.sidebar-skeleton-row:nth-child(5) .sidebar-skeleton-line { animation-delay: 0.4s; }
.sidebar-skeleton-row:nth-child(6) .sidebar-skeleton-line { animation-delay: 0.5s; }
.sidebar-skeleton-row:nth-child(7) .sidebar-skeleton-line { animation-delay: 0.6s; }

@keyframes sidebar-skeleton-shimmer {
  0%   { background-position: -100% 0; }
  100% { background-position:  200% 0; }
}

/* prefers-reduced-motion: hold the static base color, no shimmer pass.
   The placeholder rows still convey "content is loading" via their
   shape and bone-soft background. */
@media (prefers-reduced-motion: reduce) {
  .sidebar-skeleton-line {
    animation: none;
    background-image: none;
    background: var(--rule-soft);
  }
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   UPLOAD PROGRESS — Claude-Code-style monospace bar

   Drives any form with hx-encoding="multipart/form-data" that calls
   window.sanoUploadProgress(event, this) on htmx::xhr:progress. The
   bar uses a real <progress> element under the hood (a11y); the visual
   layer is custom-styled to look like the terminal progress aesthetic.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */

.upload-progress {
  font-family: var(--mono, "JetBrains Mono", ui-monospace, monospace);
  font-size: 0.82rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.75rem 0.9rem;
  margin-top: 0.6rem;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: color-mix(in oklab, var(--paper-deep) 60%, transparent);
  transition: border-color 0.2s ease, background 0.2s ease;
}

.upload-progress[hidden] {
  display: none;
}

.upload-progress-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
}

.upload-progress-label {
  font-weight: 500;
  color: var(--ink);
  /* Truncate long filenames gracefully */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 60%;
}

.upload-progress-meta {
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
}

.upload-progress-bar-wrap {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* The native <progress> element, styled to look like a terminal
   progress bar. Cross-browser styling needs both ::-webkit-progress-*
   pseudos and ::-moz-progress-bar. */
.upload-progress-bar {
  flex: 1;
  appearance: none;
  -webkit-appearance: none;
  height: 10px;
  border: none;
  border-radius: 2px;
  background: var(--rule-soft);
  overflow: hidden;
}

.upload-progress-bar::-webkit-progress-bar {
  background: var(--rule-soft);
  border-radius: 2px;
}

.upload-progress-bar::-webkit-progress-value {
  background: linear-gradient(90deg,
    var(--accent) 0%,
    color-mix(in oklab, var(--accent) 80%, white) 100%);
  border-radius: 2px;
  transition: width 0.15s linear;
}

.upload-progress-bar::-moz-progress-bar {
  background: var(--accent);
  border-radius: 2px;
}

.upload-progress-percent {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--accent);
  min-width: 3.2em;
  text-align: right;
}

.upload-progress-status {
  font-size: 0.74rem;
  color: var(--ink-soft);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Done state — green flash before auto-dismiss */
.upload-progress.done {
  border-color: color-mix(in oklab, var(--ok) 60%, var(--rule));
  background: color-mix(in oklab, var(--ok) 8%, transparent);
}
.upload-progress.done .upload-progress-bar::-webkit-progress-value {
  background: var(--ok);
}
.upload-progress.done .upload-progress-bar::-moz-progress-bar {
  background: var(--ok);
}
.upload-progress.done .upload-progress-percent {
  color: var(--ok);
}
.upload-progress.done .upload-progress-status {
  color: var(--ok);
}

/* Processing state — after upload finishes, server is still ingesting */
.upload-progress.processing .upload-progress-bar::-webkit-progress-value {
  /* Subtle indeterminate stripe so the user knows server work is ongoing */
  background:
    repeating-linear-gradient(
      45deg,
      var(--accent),
      var(--accent) 8px,
      color-mix(in oklab, var(--accent) 70%, white) 8px,
      color-mix(in oklab, var(--accent) 70%, white) 16px
    );
  animation: upload-stripes 1s linear infinite;
}
@keyframes upload-stripes {
  from { background-position: 0 0; }
  to { background-position: 32px 0; }
}

/* Failed state — red bar, stays visible until next submit */
.upload-progress.failed {
  border-color: color-mix(in oklab, var(--danger) 60%, var(--rule));
  background: color-mix(in oklab, var(--danger) 6%, transparent);
}
.upload-progress.failed .upload-progress-bar::-webkit-progress-value,
.upload-progress.failed .upload-progress-bar::-moz-progress-bar {
  background: var(--danger);
}
.upload-progress.failed .upload-progress-percent,
.upload-progress.failed .upload-progress-status {
  color: var(--danger);
}

/* a11y — preserve motion preference */
@media (prefers-reduced-motion: reduce) {
  .upload-progress-bar::-webkit-progress-value {
    transition: none;
  }
  .upload-progress.processing .upload-progress-bar::-webkit-progress-value {
    animation: none;
    background: var(--accent);
  }
}

/* ───────────────────────────────────────────────────────────────
   FLOATING UPLOAD HOST — for JS-driven uploads (chat folder-picker,
   future SDK uploads). Stacks bottom-right; each upload is its own
   pinned widget. Auto-dismisses on done.
   ─────────────────────────────────────────────────────────────── */
.upload-progress-host {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 9000;
  display: flex;
  flex-direction: column-reverse;  /* newest at the bottom */
  gap: 0.5rem;
  pointer-events: none;            /* host transparent; children are interactive */
  max-width: min(420px, calc(100vw - 2rem));
}

.upload-progress.upload-progress-floating {
  pointer-events: auto;
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.18),
              0 2px 6px rgba(0, 0, 0, 0.06);
  background: var(--paper);
  min-width: 280px;
  animation: upload-slide-in 0.2s ease-out;
}

@keyframes upload-slide-in {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .upload-progress.upload-progress-floating {
    animation: none;
  }
}

@media (max-width: 540px) {
  .upload-progress-host {
    right: 0.5rem;
    left: 0.5rem;
    bottom: 0.5rem;
    max-width: none;
  }
}

/* ── Crisis-escalation banner (HARVEST #21, Wave 6 encounter-scribe) ──
   Renders above the SOAP sections on /notes/<id> when the gateway
   detects a clinical-safety regex match on the transcript. Tone maps to
   the CrisisSignal.severity field: "immediate" -> red (danger tokens),
   "urgent" -> orange (warn-orange tokens). Note generation itself is
   never blocked; the banner is advisory. */
.crisis-banner {
  margin: 1rem 0;
  padding: 1rem 1.25rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--rule);
  background: var(--paper);
}
.crisis-banner-immediate {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  color: var(--danger-deep);
}
.crisis-banner-urgent {
  background: var(--warn-orange-bg);
  border-color: var(--warn-orange-border);
  color: var(--warn-orange);
}
.crisis-banner-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}
.crisis-banner-icon {
  display: inline-flex;
  flex: 0 0 auto;
}
.crisis-banner-title {
  flex: 1 1 auto;
  font-size: 0.95rem;
}
.crisis-banner-severity {
  flex: 0 0 auto;
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius);
  border: 1px solid currentColor;
  font-weight: 700;
}
.crisis-banner-protocol {
  margin: 0.5rem 0 0.75rem;
  font-size: 0.875rem;
  line-height: 1.45;
  color: inherit;
}
.crisis-banner-phrases {
  margin: 0.5rem 0 0.75rem;
  font-size: 0.825rem;
}
.crisis-banner-phrases summary {
  cursor: pointer;
  font-weight: 500;
  opacity: 0.85;
}
.crisis-banner-phrases ul {
  margin: 0.5rem 0 0;
  padding-left: 1.25rem;
}
.crisis-banner-phrases code {
  background: rgb(0 0 0 / 0.06);
  padding: 0.1rem 0.35rem;
  border-radius: 3px;
  font-size: 0.825rem;
}
.crisis-banner-note {
  margin: 0;
  color: inherit;
  opacity: 0.85;
}
@media (prefers-reduced-motion: no-preference) {
  .crisis-banner-immediate {
    animation: crisis-banner-attention 1.4s ease-in-out 1;
  }
}
@keyframes crisis-banner-attention {
  0%   { box-shadow: 0 0 0 0 rgb(185 28 28 / 0.35); }
  60%  { box-shadow: 0 0 0 8px rgb(185 28 28 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(185 28 28 / 0); }
}

/* ── EHR write-back posted chip (Wave 7 — Epic/Cerner/Athena DocumentReference) ──
   Renders inline next to the status pill on /notes/<id> when the
   gateway sign-handler successfully posted DocumentReference + Composition
   + Provenance to the tenant's configured EHR. Uses the existing semantic
   ok-* tokens (green) so it visually reinforces "this went out" without
   competing with the status pill. */
.pill-ehr-posted {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: 0.5rem;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  background: var(--ok-bg);
  color: var(--ok);
  border: 1px solid var(--ok-border);
  font-size: 0.72rem;
  font-weight: 500;
  line-height: 1.2;
}
.pill-ehr-posted .pill-ehr-icon {
  display: inline-flex;
  flex: 0 0 auto;
}
.pill-ehr-posted .pill-ehr-label {
  white-space: nowrap;
}
.pill-ehr-posted .pill-ehr-time {
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .pill-ehr-posted {
    margin-left: 0;
    margin-top: 0.35rem;
  }
  .pill-ehr-posted .pill-ehr-time {
    display: none;
  }
}

/* ── Pulsing-asterisk thinking indicator (shared component) ─────────────
   Inline-block indicator with an animated mark + live elapsed timer.
   Driven by /static/thinking_indicator.js which auto-attaches the
   per-second timer on DOMContentLoaded + htmx:afterSwap. Uses the
   canonical --accent token so it adapts to dark mode automatically.
   Pulse cadence (1.4s) is tuned to feel calm rather than urgent --
   this indicator appears during model-thinking which is normal, not
   an error state. */
.thinking-asterisk {
  display: inline-flex;
  align-items: baseline;
  gap: 0.45rem;
  font-size: 0.875rem;
  color: var(--muted);
  line-height: 1.2;
}
/* v3 mark: cycles through 4 distinct animations every 4 seconds so
   long thinking durations feel alive instead of looping one motion
   forever. All 4 phases render in the DOM; only one is opacity:1
   at a time. Pure CSS — no JS overhead. 16s total cycle, then loops.
   .thinking-phase-cycle is the parent keyframe that controls which
   phase is visible; .phase-N-* are per-phase keyframes that drive
   the actual motion within each phase. */
.thinking-asterisk-mark {
  display: inline-block;
  position: relative;
  width: 32px;
  height: 26px;
  flex: 0 0 auto;
  color: var(--accent);
  transform: translateZ(0);
}
.thinking-phase {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  animation: thinking-phase-cycle 16s linear infinite;
}
.thinking-phase-1 { animation-delay: 0s; }
.thinking-phase-2 { animation-delay: -12s; }
.thinking-phase-3 { animation-delay: -8s; }
.thinking-phase-4 { animation-delay: -4s; }
/* Phase opacity envelope: each phase sits in its 4s window with a
   ~0.4s crossfade at the start and end. Outside its window it's
   opacity 0 (and the underlying motion animation continues at zero
   GPU cost since opacity:0 elements aren't composited). */
@keyframes thinking-phase-cycle {
  0%   { opacity: 0; }
  2.5% { opacity: 1; }
  22%  { opacity: 1; }
  25%  { opacity: 0; }
  100% { opacity: 0; }
}

/* ── Phase 1: orbital sparkle ────────────────────────────────────── */
.thinking-mark-sparkle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  color: inherit;
  transform-origin: 50% 50%;
  animation: thinking-sparkle-pulse 1.4s ease-in-out infinite;
}
.thinking-mark-orbit {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  animation: thinking-orbit-spin 2.4s linear infinite;
  transform-origin: 0 0;
}
.thinking-mark-orbit-1 { animation-duration: 2.4s; }
.thinking-mark-orbit-2 {
  animation-duration: 3.2s;
  animation-direction: reverse;
  animation-delay: -1.6s;
}
.thinking-mark-dot {
  display: block;
  position: absolute;
  width: 3.5px;
  height: 3.5px;
  border-radius: 50%;
  background: currentColor;
}
.thinking-mark-orbit-1 .thinking-mark-dot {
  top: -12px;
  left: -1.75px;
  opacity: 0.65;
}
.thinking-mark-orbit-2 .thinking-mark-dot {
  top: -10px;
  left: -1.75px;
  opacity: 0.45;
}
@keyframes thinking-sparkle-pulse {
  0%, 100% { opacity: 0.7; transform: scale(0.88) rotate(0deg); }
  50%      { opacity: 1.0; transform: scale(1.04) rotate(45deg); }
}
@keyframes thinking-orbit-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ── Phase 2: typing dots ────────────────────────────────────────── */
.thinking-phase-2 { gap: 4px; }
.thinking-typing-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  animation: thinking-typing-bounce 1.4s ease-in-out infinite;
}
.thinking-typing-dot:nth-child(2) { animation-delay: 0.15s; }
.thinking-typing-dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes thinking-typing-bounce {
  0%, 70%, 100% { opacity: 0.3; transform: translateY(0); }
  35%           { opacity: 1.0; transform: translateY(-3px); }
}

/* ── Phase 3: breathing orb ──────────────────────────────────────── */
.thinking-orb-core {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: currentColor;
  animation: thinking-orb-breathe 2s ease-in-out infinite;
}
.thinking-orb-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  opacity: 0;
  animation: thinking-orb-ring-expand 2s ease-out infinite;
}
@keyframes thinking-orb-breathe {
  0%, 100% { transform: scale(0.85); opacity: 0.75; }
  50%      { transform: scale(1.15); opacity: 1.0; }
}
@keyframes thinking-orb-ring-expand {
  0%   { transform: scale(0.5); opacity: 0.6; }
  100% { transform: scale(2.2); opacity: 0; }
}

/* ── Phase 4: equalizer bars ─────────────────────────────────────── */
.thinking-phase-4 {
  align-items: flex-end;
  gap: 3px;
  padding-bottom: 4px;
}
.thinking-eq-bar {
  width: 3px;
  background: currentColor;
  border-radius: 2px;
  animation: thinking-eq-bounce 1.2s ease-in-out infinite;
  transform-origin: bottom center;
}
.thinking-eq-bar:nth-child(1) { height: 11px; animation-delay: -0.4s; }
.thinking-eq-bar:nth-child(2) { height: 17px; animation-delay: -0.2s; }
.thinking-eq-bar:nth-child(3) { height: 13px; animation-delay: 0s; }
.thinking-eq-bar:nth-child(4) { height: 9px;  animation-delay: -0.6s; }
@keyframes thinking-eq-bounce {
  0%, 100% { transform: scaleY(0.45); opacity: 0.65; }
  50%      { transform: scaleY(1.0);  opacity: 1.0; }
}

/* ── label + timer (unchanged from v2) ───────────────────────────── */
.thinking-asterisk-label {
  font-weight: 500;
}
.thinking-asterisk-timer {
  font-variant-numeric: tabular-nums;
  opacity: 0.7;
  min-width: 2.5ch;
  display: inline-block;
}

/* ── prefers-reduced-motion: lock to phase 1 + disable motion ────── */
@media (prefers-reduced-motion: reduce) {
  .thinking-phase,
  .thinking-mark-sparkle,
  .thinking-mark-orbit,
  .thinking-typing-dot,
  .thinking-orb-core,
  .thinking-orb-ring,
  .thinking-eq-bar {
    animation: none;
  }
  /* Show only phase 1 (the sparkle), static, at reduced opacity so
     the indicator is still visible without movement. */
  .thinking-phase { opacity: 0; }
  .thinking-phase-1 { opacity: 0.85; }
  .thinking-mark-sparkle { opacity: 1; }
}

/* Generic indicator slot. Pages that include `_thinking_asterisk.html`
   wrap it in `.thinking-slot.htmx-indicator` and point their form at
   it via `hx-indicator="#that-id"`. HTMX adds the `htmx-request`
   class to the slot during an in-flight request; we flip the slot
   from display:none -> inline-flex so it's invisible AND
   layout-collapsed when idle, and only takes vertical space while
   the request is actually in flight. Removing it from layout
   (vs opacity:0) is the fix to the "it shows when I scroll down
   even though I never clicked" bug — opacity:0 reserved space and
   bled through. */
.thinking-slot {
  margin: 0.75rem 0;
}
.thinking-slot.htmx-indicator {
  display: none;
}
.thinking-slot.htmx-indicator.htmx-request,
.htmx-request .thinking-slot.htmx-indicator,
.thinking-slot.htmx-indicator.htmx-request > .thinking-asterisk {
  display: inline-flex;
}
/* Fallback for any other htmx-indicator wrapping our partial (e.g.
   inline buttons that don't use the .thinking-slot class). Default
   the inner asterisk to display:none and flip on the request class. */
.htmx-indicator > .thinking-asterisk {
  display: none;
}
.htmx-indicator.htmx-request > .thinking-asterisk,
.htmx-request .htmx-indicator > .thinking-asterisk {
  display: inline-flex;
}

/* Inline variant for indicators that sit next to a button in a row
   (note-section header, table action cell, etc). No vertical margin
   so it lines up with the button row. Tap-target friendly on mobile. */
.thinking-inline {
  display: inline-flex;
  align-items: baseline;
  margin-left: 0.5rem;
  vertical-align: middle;
}

/* ── 404 page polish (Sanolith brand-friendly) ────────────────────────
   Replaces the prior bare `<h2>Not found</h2>` shape with a card-
   centered block: large accent-tinted icon, serif heading, body
   copy, and a row of common-destination buttons. Same friendly tone
   as `empty-state-friendly` so first-impression failure modes look
   intentional rather than broken. */
.page-404 {
  min-height: calc(100vh - 12rem);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem 1.5rem;
}
.page-404-card {
  max-width: 560px;
  width: 100%;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  padding: 2.5rem 2rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
}
.page-404-icon {
  display: inline-flex;
  width: 64px;
  height: 64px;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--accent-tint);
  color: var(--accent-deep);
  margin-bottom: 0.25rem;
}
.page-404-icon svg { width: 32px; height: 32px; color: currentColor; }
.page-404-title {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 500;
  color: var(--ink);
}
.page-404-body {
  margin: 0;
  max-width: 44ch;
  line-height: 1.5;
}
.page-404-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  margin: 0.5rem 0 0.25rem;
}
.page-404-hint {
  margin: 0.5rem 0 0;
  opacity: 0.85;
}



/* ── Wave 12 long-tail Modern finish, misc pages: error pages
   (2026-06-11, operator-approved renders at
   /tmp/portal_revamp_renders.html section 20). CSS-only: the shared
   error card (404/500/503 reuse .page-404-card) joins the elevated
   family under the signed-in shell. Logged-out renders (no
   .app-frame) keep the current look byte-identical, the sign-in hero
   and legal per-page dates are untouched. */
.app-frame .page-404-card {
  border-color: var(--hairline);
  box-shadow: var(--el-1);
}

/* ─────────────────────────────────────────────────────────────────
   Task 235 css-split fixup: shared utility classes that were extracted
   to chat.css but are referenced by non-chat templates (admin, datasets,
   notes, account, secops). Re-published here so every page that loads
   app.css gets them. chat.css still carries identical rules; the
   cascade order makes both copies resolve the same way.
   ───────────────────────────────────────────────────────────────── */

/* Visually-hidden label for screen readers. Used everywhere. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

/* Button styled as an inline text link — for controls that toggle
   content but must not be an <a href="#"> (avoids the jump-to-top,
   history-dirtying, and "announced as link" problems). */
.linklike {
  background: none; border: 0; padding: 0;
  font: inherit; color: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}

/* Generic section header — datasets-css originated but reused by
   secops CVE inbox and other admin cards. */
.ds-header {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--rule);
  /* 2026-05-20 audit P0-2 (secops): allow header pills + filter chips
     to wrap onto a second row instead of overflowing. The single-row
     layout was unreachable at <450px because the H2 and the 4-pill
     filter cluster competed for the same row. */
  flex-wrap: wrap;
  row-gap: 0.5rem;
}
.ds-header h2 { font-family: var(--serif); font-weight: 500; font-size: 1.2rem; letter-spacing: -0.015em; flex: 1; }

/* 2026-05-20 audit P0-3: `.flex-1-right` was referenced in 3 page
   headers (platform.html, account/ehr.html, secops/detail.html) but
   had no CSS rule anywhere — a no-op. Push the trailing element to
   the right edge of its flex row. */
.flex-1-right { flex: 1; margin-left: auto; text-align: right; }

/* Generic banner — notes-css originated but referenced by ehr, secops,
   and other surfaces. Republished so a non-notes page that uses
   `class="banner banner-info"` gets the expected card chrome. */
.banner {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 1rem;
  border-radius: 8px;
  margin: 1rem 0;
  border: 1px solid var(--rule);
  background: var(--bone-soft);
}
.banner-info { background: var(--info-bg); color: var(--info); border-color: var(--info-border); }
.banner-err { background: var(--danger-soft-bg); color: var(--danger); border-color: var(--danger-soft-border); }
.banner-ok { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-border); }
.banner-danger {
  background: var(--danger-soft-bg);
  color: var(--danger);
  border-color: var(--danger-soft-border);
}
.banner p { margin: 0; }

/* Generic status pill — notes-originated but used by SSO/EHR/note
   templates admin surfaces. Token-only colors. */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.1em 0.5em;
  border-radius: 999px;
  font-size: 0.78em;
  font-family: var(--mono);
  background: var(--bone-soft);
  color: var(--muted);
  border: 1px solid var(--rule);
}
.status-pill.slate   { color: var(--slate);   background: var(--slate-bg);   border-color: var(--slate-border); }
.status-pill.indigo  { color: var(--indigo);  background: var(--indigo-bg);  border-color: var(--indigo-border); }
.status-pill.queued  { color: var(--queued);  background: var(--queued-bg);  border-color: var(--queued-border); }
.status-pill.ok      { color: var(--ok-deep); background: var(--ok-deep-bg); border-color: var(--ok-deep-border); }
.status-pill.warn    { color: var(--warn);    background: var(--warn-bg);    border-color: var(--warn-border); }
.status-pill.danger  { color: var(--danger);  background: var(--danger-bg);  border-color: var(--danger-border); }

/* Banners — reused on admin model lab / catalog / RAG eval pages. */
.warn-banner {
  background: var(--warn-bg);
  color: var(--warn);
  border: 1px solid var(--warn-border);
  border-radius: 0.4rem;
  padding: 0.5rem 0.7rem;
  margin: 0.35rem 0;
  font-size: 0.85rem;
}
.info-banner {
  background: var(--info-bg);
  color: var(--info);
  border: 1px solid var(--info-border);
  border-radius: 0.4rem;
  padding: 0.5rem 0.7rem;
  margin: 0.35rem 0;
  font-size: 0.85rem;
}

/* ── Admin sub-page cross-navigation breadcrumb ──────────────────
   Hoisted from admin.css to app.css 2026-05-19 because the partial
   `_admin_breadcrumb.html` is included on /account (gated to
   tenant-admin role) but /account does NOT load admin.css. With the
   styles only living in admin.css, the breadcrumb rendered as a
   default bulleted <ul> on /account. Moving the rules here ensures
   every page that includes the partial gets the pill row regardless
   of which per-page stylesheet is loaded. */
.admin-breadcrumb {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0.5rem 0;
  font-size: 0.825rem;
}
.admin-breadcrumb-home {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--muted);
  text-decoration: none;
  font-weight: 500;
}
.admin-breadcrumb-home:hover {
  border-color: var(--accent-soft-border, var(--rule));
  color: var(--accent-deep);
}
.admin-breadcrumb-sep {
  color: var(--muted);
  opacity: 0.5;
  user-select: none;
}
/* Defensive: also-tag the nav ancestor so the rule wins against any
   default `ul { list-style: disc }` from agent or third-party CSS. */
nav.admin-breadcrumb ul.admin-breadcrumb-list,
.admin-breadcrumb-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  list-style: none;
  list-style-type: none;
  padding: 0;
  margin: 0;
}
nav.admin-breadcrumb ul.admin-breadcrumb-list > li,
.admin-breadcrumb-list > li {
  list-style: none;
  margin: 0;
  padding: 0;
}
.admin-breadcrumb-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--muted);
  text-decoration: none;
  font-weight: 500;
  transition: border-color 120ms ease, color 120ms ease;
  white-space: nowrap;
}
.admin-breadcrumb-pill:hover {
  border-color: var(--accent);
  color: var(--accent-deep);
}
.admin-breadcrumb-active {
  background: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent-deep);
  cursor: default;
}
@media (max-width: 640px) {
  .admin-breadcrumb {
    gap: 0.35rem;
  }
  .admin-breadcrumb-pill,
  .admin-breadcrumb-home {
    padding: 0.25rem 0.55rem;
    font-size: 0.78rem;
  }
}

/* 0.74.0 — Client-side display redactor (round 7).
   `.redacted` is the pill that replaces a detected PHI run when a user
   has opted into the client-side redactor on /account.
   `.redacted-pending` is the placeholder shown while the NER pipeline
   is still processing a bubble; we never paint the unredacted text
   while inference is in flight (HIPAA-fail-closed for the UX layer).
   NOTE: This is NOT a HIPAA control. The server-side redactor at
   services/redactor/ remains the authoritative one. Palette uses
   tokens only (tokens.css owns the actual colors) so this opts in to
   the no-hex-in-app.css invariant. */
.redacted {
  display: inline-block;
  padding: 0 0.3rem;
  border-radius: var(--radius-sm);
  background: var(--surface-subtle);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.85em;
  border: 1px dashed var(--line-soft);
}
.redacted-pending {
  display: inline-block;
  padding: 0 0.3rem;
  border-radius: var(--radius-sm);
  background: var(--surface-subtle);
  color: var(--muted);
  font-style: italic;
  font-size: 0.85em;
}

/* Toggle status text on /account #client-redactor card. */
#client-redactor-status[data-status-kind="error"] { color: var(--danger); }
#client-redactor-status[data-status-kind="loading"] { color: var(--accent); }
#client-redactor-status[data-status-kind="ok"] { color: var(--ok); }

/* ─────────────────────────────────────────────────────────────────────────
   Re-ingest progress indicator (2026-05-25).
   Returned by POST /datasets/{id}/reingest as the success-state HTML
   fragment. Claude-compacting style: thin track with a sliding accent
   stripe that loops while the work is in flight. The user refreshes
   the page (per the inline hint) to see ingest completion; the bar
   doesn't auto-complete — it indicates "work has started, look back
   later." A determinate bar driven by the ingest_jobs job-status
   endpoint is a planned follow-up.

   Lives inside a ``<span class="ingest-progress">`` so it can swap
   into the inline ``<span id="reingest-result-{id}">`` swap target in
   datasets.html without triggering the block-in-span layout collapse
   that the previous ``<p>``-wrapped response had.
   ───────────────────────────────────────────────────────────────────── */

.ingest-progress {
  display: inline-flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 220px;
  max-width: 340px;
  margin-left: 0.5rem;
  /* Inline-flex inside the .dataset-readiness inline-flex parent keeps
     the surrounding pills + button on the same baseline. */
  vertical-align: middle;
}

.ingest-progress-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.78rem;
  color: var(--accent-deep);
  font-weight: 600;
}

.ingest-progress-eta {
  color: var(--muted);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.ingest-progress-foot {
  font-size: 0.72rem;
  color: var(--muted);
}

.ingest-progress-bar {
  position: relative;
  height: 4px;
  background: var(--rule-soft, var(--rule));
  border-radius: 999px;
  overflow: hidden;
}

.ingest-progress-bar.indeterminate::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  border-radius: 999px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--accent) 50%,
    transparent 100%
  );
  animation: ingest-progress-stripe-slide 1.6s ease-in-out infinite;
}

@keyframes ingest-progress-stripe-slide {
  0%   { left: -45%; }
  100% { left: 105%; }
}

/* WCAG 2.3.3 (Animation from Interactions, AAA): users who opted into
   prefers-reduced-motion get a static, non-pulsing fill at half-width
   instead of the sliding stripe. The bar still reads as "in flight"
   thanks to its accent color, but nothing animates. */
@media (prefers-reduced-motion: reduce) {
  .ingest-progress-bar.indeterminate::before {
    animation: none;
    left: 0;
    width: 50%;
    opacity: 0.7;
  }
}


/* ─────────────────────────────────────────────────────────────────
   2026-05-25 PR D3 — Determinate reingest progress bar + telemetry

   Extends the indeterminate-bar shell that shipped in PR A (398).
   Adds:
     * Determinate fill (.ingest-progress-fill) with shimmer overlay
     * Heartbeat indicator color states (live / stalled / dead / unknown)
     * Mini steps strip (8 segments: done / active / pending)
     * Inline info-grid block surfacing Step / Attempt / Elapsed / Worker / Job
     * is-dead modifier — fill bar reads as red when the worker has
       gone past the reap threshold

   All elements are inline (`<span>`) so the fragment stays valid
   inside the dataset row's inline flex container (the PR 400 bug
   surface). ───────────────────────────────────────────────────── */

.ingest-progress-bar.determinate {
  position: relative;
  display: block;
  width: 100%;
  height: 4px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  border-radius: 2px;
  overflow: hidden;
}

.ingest-progress-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  background: var(--accent);
  border-radius: 2px;
  transition: width 0.4s ease-out;
  overflow: hidden;
}

/* Shimmer overlay on the fill so an actively-running job reads as
   "alive" even when the step doesn't change for ~5 seconds. */
.ingest-progress-fill::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.35) 50%,
    transparent 100%
  );
  animation: ingest-progress-shimmer 1.4s linear infinite;
}

.ingest-progress-bar.is-dead .ingest-progress-fill {
  background: var(--danger);
}

.ingest-progress-bar.is-dead .ingest-progress-fill::after {
  animation: none;
}

@keyframes ingest-progress-shimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
  .ingest-progress-fill::after { animation: none; opacity: 0.3; }
}

/* Heartbeat indicator — small inline pill on the foot row. Color
   carries the freshness state (live=green, stalled=warn, dead=red,
   unknown=muted). */
.ingest-progress-heartbeat {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: 0.55rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  border-radius: 4px;
  background: color-mix(in srgb, var(--ink) 4%, transparent);
}

.ingest-progress-heartbeat.heartbeat-live    { color: var(--ok); }
.ingest-progress-heartbeat.heartbeat-stalled { color: var(--warn); }
.ingest-progress-heartbeat.heartbeat-dead    { color: var(--danger); }
.ingest-progress-heartbeat.heartbeat-unknown { color: var(--muted); }

/* Mini steps strip — 8 inline segments below the bar, each 3px tall.
   Tiny so it doesn't dominate the row but visible enough to read
   how many steps are done at a glance. */
.ingest-steps-strip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  width: 100%;
  margin-top: 0.3rem;
}

.ingest-step-seg {
  flex: 1 1 auto;
  height: 3px;
  border-radius: 1px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  transition: background 0.3s ease-out;
}

.ingest-step-seg.done    { background: var(--accent); }
.ingest-step-seg.pending { background: color-mix(in srgb, var(--ink) 8%, transparent); }

.ingest-step-seg.active {
  background: linear-gradient(
    90deg,
    var(--accent) 0%,
    color-mix(in srgb, var(--accent) 35%, transparent) 100%
  );
  background-size: 200% 100%;
  animation: ingest-step-active-slide 1.6s ease-in-out infinite;
}

.ingest-step-seg.active.is-dead {
  animation: none;
  background: var(--danger);
}

@keyframes ingest-step-active-slide {
  0%   { background-position: 100% 0; }
  100% { background-position: 0   0; }
}

@media (prefers-reduced-motion: reduce) {
  .ingest-step-seg.active { animation: none; }
}

/* Inline info-grid block — surfaces Step / Attempt / Elapsed /
   Worker / Job below the steps strip. Wraps the side-panel content
   from the render inline since /datasets is a single-column layout.
   Six pairs across; flex-wraps on narrow widths. */
.ingest-progress-info {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.45rem;
  font-size: 0.72rem;
  color: var(--muted);
}

.ingest-info-pair {
  display: inline-flex;
  gap: 0.25rem;
  align-items: baseline;
}

.ingest-info-pair .k {
  color: var(--muted);
  font-weight: 500;
}

.ingest-info-pair .v {
  color: var(--ink);
  font-weight: 600;
}

.ingest-info-pair .v.mono {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.7rem;
}

/* ═══════════════════════════════════════════════════════════════════
   2026-05-26 — /admin/data-upsampler
   ═══════════════════════════════════════════════════════════════════
   Top-level admin surface for the data-upsampler service (LLM fan-out
   synthetic-data generator for SFT/STA training). Mirrors the Variant
   C render approved by the operator. All color hex literals live in
   tokens.css (--worker-* / --stream-* / --status-succeeded-*); app.css
   only references them through var(). */

/* Cost-meter strip — top of /admin/data-upsampler list page. */
.cost-strip {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 0.8rem;
  padding: 0.7rem 0.9rem;
  margin-bottom: 0.9rem;
  background: linear-gradient(90deg,
    var(--accent-soft) 0%,
    color-mix(in srgb, var(--accent-soft) 70%, var(--paper)) 100%);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--rule));
  border-radius: 8px;
  font-size: 0.78rem;
}
.cost-strip-label { display: flex; flex-direction: column; gap: 0.18rem; }
.cost-strip-l {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-weight: 700;
}
.cost-strip-v {
  font-size: 0.82rem;
  color: var(--ink);
  font-weight: 600;
  font-family: var(--mono);
}
.cost-strip-bar {
  height: 8px;
  background: color-mix(in srgb, var(--accent) 18%, var(--rule));
  border-radius: 4px;
  overflow: hidden;
  min-width: 120px;
}
.cost-strip-bar > span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-deep) 100%);
}
.cost-strip-ratio {
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--ink-soft);
  white-space: nowrap;
}
.cost-strip-reset {
  font-size: 0.66rem;
  color: var(--muted);
  white-space: nowrap;
}
.cost-strip.warning {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--warn) 14%, var(--paper)) 0%,
    color-mix(in srgb, var(--warn) 6%, var(--paper)) 100%);
  border-color: color-mix(in srgb, var(--warn) 42%, var(--rule));
}
.cost-strip.warning .cost-strip-bar > span {
  background: linear-gradient(90deg, var(--accent-deep) 0%, var(--warn) 100%);
}
.cost-strip.danger {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--danger) 12%, var(--paper)) 0%,
    color-mix(in srgb, var(--danger) 4%, var(--paper)) 100%);
  border-color: color-mix(in srgb, var(--danger) 38%, var(--rule));
}
.cost-strip.danger .cost-strip-bar > span {
  background: var(--danger);
}

/* Service-unavailable banner — shown when /v1/upsample/* proxy 404s. */
.upsample-banner {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.55rem 0.8rem;
  margin-bottom: 0.8rem;
  border-radius: 8px;
  font-size: 0.8rem;
  line-height: 1.5;
}
.upsample-banner-warn {
  background: color-mix(in srgb, var(--warn) 12%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--warn) 38%, var(--rule));
  color: var(--warn-text-deep);
}
.upsample-banner-icon { display: inline-flex; }

/* Section header — Data upsampler title + scope toggle + dispatch CTA. */
.ops-head-data-upsampler {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  margin-bottom: 0.7rem;
}
.ops-head-data-upsampler .page-title {
  font-family: var(--serif);
  font-size: 1.4rem;
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--ink);
}
.ops-head-data-upsampler .dispatch-trigger { margin-left: auto; }
.ops-head-data-upsampler .scope-toggle {
  display: inline-flex;
  border: 1px solid var(--rule);
  border-radius: 5px;
  overflow: hidden;
  font-size: 0.72rem;
}
.ops-head-data-upsampler .scope-btn {
  padding: 0.22rem 0.55rem;
  color: var(--muted);
  background: var(--paper);
  text-decoration: none;
  font-size: 0.72rem;
}
.ops-head-data-upsampler .scope-btn.active {
  background: var(--accent-tint);
  color: var(--accent-deep);
  font-weight: 600;
}

/* Worker-pill — color-coded by worker family via tokens (see tokens.css). */
.worker-pill {
  display: inline-flex;
  padding: 0.12rem 0.42rem;
  border-radius: 4px;
  font-size: 0.66rem;
  font-weight: 500;
  font-family: var(--mono);
  background: var(--bone);
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  white-space: nowrap;
}
.worker-pill.worker-q_and_a {
  background: var(--worker-qa-bg);
  color: var(--worker-qa-text);
  border-color: var(--worker-qa-border);
}
.worker-pill.worker-work_products {
  background: var(--worker-wp-bg);
  color: var(--worker-wp-text);
  border-color: var(--worker-wp-border);
}
.worker-pill.worker-communications {
  background: var(--worker-comm-bg);
  color: var(--worker-comm-text);
  border-color: var(--worker-comm-border);
}
.worker-pill.worker-references {
  background: var(--worker-ref-bg);
  color: var(--worker-ref-text);
  border-color: var(--worker-ref-border);
}

/* Status-pill states for upsample runs. */
.status-pill.queued    { background: var(--bone-soft); border-color: var(--rule); color: var(--muted); }
.status-pill.running   { background: var(--accent-tint); border-color: var(--accent); color: var(--accent-deep); }
.status-pill.succeeded { background: var(--status-succeeded-bg); border-color: var(--status-succeeded-border); color: var(--status-succeeded-text); }
.status-pill.failed    { background: color-mix(in srgb, var(--danger) 14%, var(--paper)); border-color: color-mix(in srgb, var(--danger) 28%, var(--rule)); color: var(--danger); }
.status-pill.cancelled { background: var(--bone-soft); border-color: var(--rule); color: var(--muted); text-decoration: line-through; }
.status-pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; display: inline-block; }

/* Runs table — minor overrides on data-table. */
.upsample-runs-table .progress-cell { min-width: 9rem; }
.progress-row {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--ink-soft);
}
.progress-row .pb {
  flex: 1;
  height: 4px;
  background: var(--rule-soft);
  border-radius: 2px;
  overflow: hidden;
  min-width: 60px;
}
.progress-row .pb > span {
  display: block;
  height: 100%;
  background: var(--accent);
}
.progress-row .eta {
  color: var(--muted);
  font-size: 0.66rem;
}
.upsample-runs-table td.calls {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--ink-soft);
}

/* Run-detail page — head + grid. */
.detail-head-upsample {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1rem;
  margin-bottom: 1rem;
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--rule-soft);
}
.detail-head-upsample .detail-title {
  font-family: var(--serif);
  font-size: 1.2rem;
  margin: 0 0 0.2rem;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}
.upsample-run-id {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--ink-soft);
  background: var(--bone-soft);
  padding: 1px 6px;
  border-radius: 3px;
}
.detail-head-upsample .detail-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  font-size: 0.74rem;
  color: var(--muted);
}
.detail-head-upsample .detail-meta strong { color: var(--ink); }
.detail-head-upsample .detail-actions {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  align-items: center;
}
.upsample-back { font-size: 0.74rem; margin: 0 0 0.6rem; }
.upsample-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 1.1rem;
  align-items: start;
}
@media (max-width: 980px) {
  .upsample-grid { grid-template-columns: 1fr; }
  .detail-head-upsample { grid-template-columns: 1fr; }
}

/* SSE stream card — terminal palette tokens in tokens.css. */
.stream-card {
  background: var(--stream-bg);
  color: var(--stream-fg);
  border-radius: 8px;
  padding: 0.6rem 0.8rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  line-height: 1.5;
  max-height: 480px;
  overflow-y: auto;
}
.stream-card .stream-live {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.18rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--stream-fg) 12%, var(--stream-bg));
  color: var(--stream-fg);
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.5rem;
}
.stream-card .stream-live .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--stream-ok);
  display: inline-block;
  animation: upsampleStreamBlink 1.4s ease-in-out infinite;
}
@keyframes upsampleStreamBlink { 50% { opacity: 0.3; } }
.stream-card .ts  { color: var(--stream-ts); }
.stream-card .ln  { padding: 0.04rem 0; }
.stream-card .err { color: var(--stream-err); }
.stream-card .ok  { color: var(--stream-ok); }
.stream-card .info { color: var(--stream-info); }
.stream-card .warn { color: var(--stream-warn); }

/* Meta cards — right column on run detail. */
.upsample-cost .upsample-impact {
  margin-top: 0.55rem;
  padding: 0.45rem 0.6rem;
  background: var(--accent-soft);
  border-radius: 5px;
  font-size: 0.7rem;
  color: var(--accent-deep);
  line-height: 1.5;
}
.btn-mt { margin-top: 0.55rem; }
.mt-08 { margin-top: 0.7rem; }
.mt-q { margin-top: 0.25rem; }
.opacity-60 { opacity: 0.62; }
.btn.disabled, .btn[aria-disabled="true"] {
  opacity: 0.5;
  pointer-events: none;
  cursor: not-allowed;
}

/* Error catalog — failure-mode block on run detail. */
.err-card {
  background: color-mix(in srgb, var(--danger) 7%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--danger) 30%, var(--rule));
  border-radius: 6px;
  padding: 0.7rem 0.85rem;
  margin-top: 1rem;
  font-size: 0.78rem;
  color: var(--ink);
}
.err-card.warn {
  background: color-mix(in srgb, var(--warn) 10%, var(--paper));
  border-color: color-mix(in srgb, var(--warn) 32%, var(--rule));
}
.err-card h4 {
  margin: 0 0 0.3rem;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--danger);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.err-card.warn h4 { color: var(--warn-text-deep); }
.err-card pre {
  margin: 0.32rem 0;
  font-family: var(--mono);
  font-size: 0.68rem;
  background: var(--paper);
  padding: 0.42rem 0.55rem;
  border-radius: 4px;
  border: 1px solid var(--rule);
  overflow-x: auto;
}
.err-card .next {
  margin-top: 0.42rem;
  padding: 0.45rem 0.6rem;
  background: var(--paper);
  border-radius: 5px;
  font-size: 0.74rem;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.err-card .next strong { color: var(--ink); }
.err-card .next .actions {
  margin-left: auto;
  display: flex;
  gap: 0.4rem;
}
.err-card .next .actions a {
  font-size: 0.72rem;
  padding: 0.16rem 0.5rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
  text-decoration: none;
  color: var(--ink-soft);
  background: var(--paper);
}
.err-card .next .actions a:hover {
  background: var(--bone-soft);
  color: var(--ink);
}

/* Dispatch form. */
.upsample-dispatch-form { width: min(720px, 100%); }
.radio-strip.upsample-worker-strip { display: flex; gap: 0.4rem; flex-wrap: wrap; padding-top: 0.2rem; }
.upsample-worker-strip .radio-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.32rem;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--rule);
  border-radius: 5px;
  cursor: pointer;
  font-size: 0.74rem;
  font-weight: 500;
}
.upsample-worker-strip .radio-pill input[type=radio] {
  margin: 0;
  accent-color: var(--accent);
}
.upsample-worker-strip .radio-pill:has(input:checked) {
  background: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent-deep);
}
.upsample-worker-strip .radio-pill.disabled {
  opacity: 0.45;
  cursor: not-allowed;
  background: var(--bone-soft);
}

.upsample-cost-est {
  margin-top: 0.6rem;
  padding: 0.65rem 0.8rem;
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--rule));
  border-radius: 6px;
  font-size: 0.74rem;
}
.upsample-cost-est h4 {
  margin: 0 0 0.3rem;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-deep);
}
.upsample-cost-est .est-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.6rem;
  font-family: var(--mono);
}
.upsample-cost-est .est-grid .e .l {
  font-size: 0.6rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.upsample-cost-est .est-grid .e .v {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--ink);
}
.upsample-cost-est .est-grid .e .s {
  font-size: 0.62rem;
  color: var(--muted);
}
.upsample-cost-est .quota-warn {
  margin-top: 0.45rem;
  padding: 0.32rem 0.5rem;
  background: color-mix(in srgb, var(--warn) 18%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--warn) 38%, var(--rule));
  border-radius: 4px;
  font-size: 0.7rem;
  color: var(--warn-text-deep);
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.dispatch-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.4rem;
  margin-top: 0.65rem;
}
.upsample-dispatch-err {
  margin-top: 0.55rem;
  padding: 0.45rem 0.6rem;
  background: color-mix(in srgb, var(--danger) 10%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--danger) 32%, var(--rule));
  border-radius: 5px;
  font-size: 0.76rem;
  color: var(--ink);
}

@media (max-width: 720px) {
  .cost-strip { grid-template-columns: 1fr; gap: 0.45rem; }
  .upsample-cost-est .est-grid { grid-template-columns: repeat(2, 1fr); }
  .ops-head-data-upsampler { gap: 0.5rem; }
  .ops-head-data-upsampler .dispatch-trigger { margin-left: 0; width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════
   2026-05-26 Side B — data-upsampler Phase 2 + 3 UI surfaces
   ═══════════════════════════════════════════════════════════════════
   Failure-mode catalog: 5 known buckets + unknown. Each gets a
   subtle background tint + accent border so an operator scanning
   the page can spot which kind of failure happened by color before
   reading the title. Color tokens for the buckets live as
   color-mix() against existing tokens so they auto-adapt to
   dark-mode. */

.err-card-bucket-tag {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 1px 6px;
  border-radius: 3px;
  font-family: var(--mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  background: color-mix(in srgb, currentColor 12%, var(--paper));
  border: 1px solid color-mix(in srgb, currentColor 24%, var(--rule));
  vertical-align: middle;
  text-transform: uppercase;
}
.err-card-summary {
  margin: 0.2rem 0 0.5rem;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--ink);
}
.err-card-trace {
  margin: 0.3rem 0;
}
.err-card-trace summary {
  cursor: pointer;
  font-size: 0.72rem;
  color: var(--muted);
}
.err-card-trace pre {
  margin: 0.4rem 0 0;
  max-height: 240px;
  overflow-y: auto;
}

/* Bucket-specific tinting. ``router_throttle`` + ``schema_mismatch``
   are warn-severity (amber/orange); the rest are error-severity
   (red/danger). The classifier sets severity on the envelope so
   the template already adds .err-card-sev-warn / .err-card-sev-error
   for the icon swap; bucket classes layer color on top. */

.err-card.err-card-router_throttle {
  background: color-mix(in srgb, var(--info) 8%, var(--paper));
  border-color: color-mix(in srgb, var(--info) 32%, var(--rule));
}
.err-card.err-card-router_throttle h4 { color: var(--info); }

.err-card.err-card-mlflow_unreachable {
  background: color-mix(in srgb, var(--danger) 7%, var(--paper));
  border-color: color-mix(in srgb, var(--danger) 30%, var(--rule));
}

.err-card.err-card-image_digest_missing {
  background: color-mix(in srgb, var(--queued) 8%, var(--paper));
  border-color: color-mix(in srgb, var(--queued) 30%, var(--rule));
}
.err-card.err-card-image_digest_missing h4 { color: var(--queued); }

.err-card.err-card-schema_mismatch {
  background: color-mix(in srgb, var(--warn) 10%, var(--paper));
  border-color: color-mix(in srgb, var(--warn) 32%, var(--rule));
}
.err-card.err-card-schema_mismatch h4 { color: var(--warn-text-deep); }

.err-card.err-card-deadline_exceeded {
  background: color-mix(in srgb, var(--danger) 9%, var(--paper));
  border-color: color-mix(in srgb, var(--danger) 32%, var(--rule));
}

.err-card.err-card-unknown {
  background: var(--bone-soft);
  border-color: var(--rule);
}
.err-card.err-card-unknown h4 { color: var(--ink-soft); }

/* Provenance pill on /datasets — links back to the upsample run
   that produced this dataset (Phase 2 + outbox). Renders as an
   anchor for tenant-admins, a span (no link) for tenant-members. */
.pill-provenance {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 10%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--rule));
  color: var(--accent-deep);
  font-size: 0.66rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
a.pill-provenance:hover {
  background: var(--accent-tint);
  border-color: var(--accent);
}

/* Per-row "+ Upsample" mini-button on /datasets — matches the
   existing .reingest-btn rhythm (small ghost button). */
.ds-upsample-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  margin-left: 0.3rem;
  color: var(--accent-deep);
  border-color: color-mix(in srgb, var(--accent) 28%, var(--rule));
}
.ds-upsample-btn:hover {
  background: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent-deep);
}

/* ── Wave 12 long-tail Modern finish, data pages A: upsampler run
   detail (2026-06-11, operator-approved renders at
   /tmp/portal_revamp_renders.html section 8). The right-column meta
   cards get a real elevated card body under the sidebar shell (the
   anatomy shipped in PR 518 with no box finish) and the config k/v rows
   pick up hairline separators. The SSE stream-card terminal is
   deliberately untouched: its palette is the pinned stream token set
   (tokens.css), and the honest disabled use-to-train CTA keeps its
   state.
   EVERY selector is .app-frame-scoped so the legacy topbar shell
   stays byte-identical; tokens only; motion rides the Wave 6 curve. */
.app-frame .meta-card {
  background: var(--surface-panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: 0.75rem 0.9rem;
}
.app-frame .meta-card h4 {
  margin: 0 0 0.4rem;
  font-size: 0.82rem;
}
.app-frame .meta-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.28rem 0;
  font-size: 0.78rem;
}
.app-frame .meta-row + .meta-row {
  border-top: 1px solid var(--rule-soft);
}
.app-frame .meta-row .k {
  color: var(--muted);
  flex: none;
}
.app-frame .meta-row .v {
  min-width: 0;
  text-align: right;
  overflow-wrap: anywhere;
}

/* ── Wave 1 revamp (2026-06-09): persistent app sidebar shell ──────────
   Operator-approved renders (see docs/plans + /tmp render pack). Signed-in
   pages render .app-frame = sidebar + .app-main; the topbar remains for
   unauth pages only. Three states:
     desktop expanded (232px) → body.sb-collapsed rail (56px, persisted
     by app_sidebar.js) → ≤900px off-canvas drawer (body.sb-open).
   --shell-chrome-h feeds chat.css's .chat-layout height math: it is the
   vertical chrome above the chat grid (main padding only on desktop;
   mobile-topbar + padding on phones). Unauth keeps the 90px fallback. */
.app-frame { display: flex; min-height: 100vh; --shell-chrome-h: 3rem; }
.app-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.app-main > main { flex: 1; min-width: 0; }
.mobile-topbar { display: none; }

.app-sidebar {
  width: 232px; flex: none;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
  background: var(--surface-inset);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  padding: 0.8rem 0.6rem 0.7rem;
  z-index: 40;
}
.sb-head { display: flex; align-items: center; gap: 0.3rem; padding: 0 0.15rem 0.55rem; }
.app-sidebar .workspace-pill {
  border: 0; background: transparent; padding: 0.2rem 0.3rem;
  display: inline-flex; align-items: center; gap: 0.45rem; min-width: 0;
}
.app-sidebar .workspace-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-collapse {
  margin-left: auto; border: 0; background: transparent; color: var(--muted-2);
  border-radius: var(--radius); width: 1.7rem; height: 1.7rem; flex: none;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.sb-collapse:hover { background: var(--surface-subtle); color: var(--ink-soft); }
.sb-search {
  display: flex; align-items: center; gap: 0.45rem; width: 100%;
  border: 1px solid var(--line); background: var(--surface-panel); color: var(--muted);
  font: inherit; font-size: 0.78rem; border-radius: var(--radius);
  padding: 0.4rem 0.55rem; cursor: pointer; margin: 0 0 0.3rem;
}
.sb-search:hover { border-color: var(--accent-soft-border); color: var(--ink-soft); }
.sb-kbd {
  margin-left: auto; font-family: var(--sans); font-size: 0.65rem; color: var(--muted-2);
  border: 1px solid var(--line-soft); border-radius: 4px; padding: 0 0.3rem;
  background: var(--surface-subtle);
}
.sb-nav { display: flex; flex-direction: column; gap: 0.1rem; }
.sb-group {
  font-size: 0.62rem; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--muted-2); padding: 0.7rem 0.55rem 0.2rem;
}
.sb-item {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.36rem 0.55rem; border-radius: var(--radius);
  font-size: 0.8rem; color: var(--ink-soft); text-decoration: none; min-width: 0;
}
.sb-item:hover { background: var(--surface-subtle); color: var(--ink); }
.sb-item.active { background: var(--accent-soft); color: var(--accent-deep); font-weight: 600; }
.sb-item .sb-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-foot { margin-top: auto; display: flex; flex-direction: column; gap: 0.25rem; padding-top: 0.6rem; }
.sb-new {
  display: flex; align-items: center; justify-content: center; gap: 0.4rem;
  background: var(--accent); color: var(--on-accent); border-radius: var(--radius);
  padding: 0.46rem 0.6rem; font-size: 0.8rem; font-weight: 600; text-decoration: none;
}
.sb-new:hover { background: var(--accent-deep); }
.sb-me {
  display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0.4rem;
  border-radius: var(--radius); color: var(--ink-soft); text-decoration: none;
  font-size: 0.78rem; min-width: 0;
}
.sb-me:hover, .sb-me.active { background: var(--surface-subtle); }
.sb-me .avatar-initials {
  width: 1.55rem; height: 1.55rem; border-radius: 50%; flex: none;
  background: var(--accent-tint); color: var(--accent-deep);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.66rem; font-weight: 600;
}
.sb-me-who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-signout { color: var(--muted-2); font-size: 0.74rem; }
.sb-signout:hover { color: var(--danger); background: var(--danger-soft-bg); }
.sb-scrim[hidden] { display: none; }

/* Collapsed rail is a desktop-only mode; on phones the drawer always
   opens at full width so labels stay readable. */
@media (min-width: 901px) {
  body.sb-collapsed .app-sidebar { width: 56px; padding: 0.8rem 0.35rem 0.7rem; }
  body.sb-collapsed .sb-label,
  body.sb-collapsed .sb-kbd,
  body.sb-collapsed .sb-group { display: none; }
  body.sb-collapsed .sb-item,
  body.sb-collapsed .sb-search,
  body.sb-collapsed .sb-new,
  body.sb-collapsed .sb-me { justify-content: center; padding-left: 0; padding-right: 0; }
  body.sb-collapsed .sb-head { flex-direction: column; gap: 0.35rem; padding: 0 0 0.45rem; }
  body.sb-collapsed .sb-collapse { margin-left: 0; transform: rotate(180deg); }
  body.sb-collapsed .app-sidebar .workspace-pill { padding: 0.2rem; }
}

@media (max-width: 900px) {
  .app-frame { --shell-chrome-h: 6.6rem; }
  .app-sidebar {
    position: fixed; left: 0; top: 0; bottom: 0; height: 100dvh;
    transform: translateX(-100%); box-shadow: var(--shadow-2); z-index: 60;
  }
  body.sb-open .app-sidebar { transform: none; }
  .sb-collapse { display: none; }
  .sb-scrim {
    position: fixed; inset: 0; z-index: 59;
    background: color-mix(in srgb, var(--ink) 38%, transparent);
  }
  .mobile-topbar {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.5rem 0.9rem;
    border-bottom: 1px solid var(--line-soft);
    background: var(--surface-page);
    position: sticky; top: 0; z-index: 30;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .app-sidebar { transition: width 0.18s ease, transform 0.2s ease; }
}

/* ── Wave 2 revamp (2026-06-09): signed-in home dashboard ───────────
   Rendered only under the sidebar shell (routes/home.py gates on
   sidebar_shell_active). Tokens only, same card/pill language as the
   rest of the portal. */
.dash { padding-top: 1.4rem; }
.dash-head { display: flex; align-items: flex-end; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.dash-kick { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--accent); margin-bottom: 0.2rem; }
.dash-title { font-family: var(--serif); font-size: 1.5rem; font-weight: 600; margin: 0; }
.dash-sub { font-size: 0.8rem; margin: 0.25rem 0 0; display: inline-flex; align-items: center; gap: 0.35rem; }
.dash-actions { margin-left: auto; display: flex; gap: 0.5rem; }
.dash-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0.6rem; margin-bottom: 0.8rem; }
.dash-kpi { background: var(--surface-panel); border: 1px solid var(--line-soft); border-radius: var(--radius-lg); padding: 0.75rem 0.9rem; display: flex; flex-direction: column; }
.dash-k { font-size: 0.72rem; color: var(--muted); }
.dash-v { font-family: var(--serif); font-size: 1.35rem; font-weight: 600; margin-top: 0.1rem; }
.dash-v.dash-accent { color: var(--accent); }
.dash-v.dash-na { color: var(--muted-2); }
.dash-d { font-size: 0.7rem; color: var(--muted-2); margin-top: 0.05rem; }
.dash-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 0.6rem; align-items: start; }
.dash-card { background: var(--surface-panel); border: 1px solid var(--line-soft); border-radius: var(--radius-lg); }
.dash-card-pad { padding: 0.8rem 0.95rem; }
.dash-card-head { display: flex; align-items: center; gap: 0.6rem; justify-content: space-between; padding: 0.75rem 0.9rem; font-size: 0.82rem; font-weight: 600; }
.dash-card-head-tight { padding: 0 0 0.45rem; }
.dash-thread { display: flex; align-items: center; gap: 0.55rem; padding: 0.55rem 0.9rem; border-top: 1px solid var(--line-soft); font-size: 0.82rem; color: var(--ink); text-decoration: none; min-width: 0; }
.dash-thread:hover { background: var(--surface-subtle); }
.dash-thread-ic { color: var(--muted-2); display: inline-flex; flex: none; }
.dash-thread-title { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
.dash-thread-ts { font-size: 0.7rem; color: var(--muted-2); flex: none; }
.pill-mini { display: inline-flex; align-items: center; border-radius: 999px; font-size: 0.66rem; font-weight: 600; padding: 0.05rem 0.5rem; background: var(--accent-tint); color: var(--accent-deep); flex: none; }
.pill-mini-gold { background: var(--accent-soft); }
.dash-empty { padding: 0.9rem; font-size: 0.8rem; border-top: 1px solid var(--line-soft); margin: 0; }
.dash-side { display: flex; flex-direction: column; gap: 0.6rem; }
.dash-quick { display: flex; flex-direction: column; gap: 0.45rem; }
.dash-quick a { display: inline-flex; align-items: center; gap: 0.45rem; font-size: 0.8rem; color: var(--ink-soft); text-decoration: none; }
.dash-quick a:hover { color: var(--accent-deep); }
@media (max-width: 900px) {
  .dash-grid { grid-template-columns: 1fr; }
}

/* ── Wave 3 revamp (2026-06-10): shared page-header anatomy ─────────
   Rendered by the page_header() macro (templates/_page_header.html),
   sidebar shell only -- every call site is gated on
   sidebar_shell_active() with the legacy heading markup in the else
   branch. .page-kick and .page-sub are new classes; the container,
   title, and actions refinements are anchored on .page-kick (markup
   only the macro emits) so the legacy .page-head blocks rendered by
   the flag-off shell keep their existing 0.8.0-wave styles untouched.
   Tokens only; mirrors the Wave 2 .dash-head block above. */
.page-kick {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--accent); margin: 0 0 0.2rem;
}
.page-head:has(.page-kick) { align-items: flex-end; gap: 1rem; flex-wrap: wrap; }
.page-kick + .page-title { font-family: var(--serif); font-size: 1.4rem; font-weight: 600; margin: 0; }
.page-sub {
  font-size: 0.8rem; color: var(--muted); margin: 0.25rem 0 0;
  display: inline-flex; align-items: center; gap: 0.35rem; flex-wrap: wrap;
}
.page-head:has(.page-kick) > .page-actions {
  margin-left: auto; display: flex; gap: 0.5rem;
  align-items: center; flex-wrap: wrap;
}

/* ── Wave 5 revamp (2026-06-10): mobile bottom tab bar, sidebar shell
   only (render-pack section 21; finished 2026-06-11 to the approved
   contract, portal_revamp_renders.html s21 + modern_pass_renders.html
   s5). Three highest-frequency surfaces + More, which opens the
   existing off-canvas drawer for everything else. Hidden >=768px and
   in print (phone-only per the contract; 768-900px tablets keep the
   hamburger drawer); every target is >=44px tall; safe-area padding
   for notched phones; the frost rides the Wave 6 @supports rule on
   .app-frame .mobile-tabbar. */
.mobile-tabbar { display: none; }
@media (max-width: 767px) {
  .mobile-tabbar {
    display: flex; position: sticky; bottom: 0; z-index: 35;
    justify-content: space-around;
    background: var(--surface-panel);
    border-top: 1px solid var(--line-soft);
    padding: 0.3rem 0.4rem calc(0.4rem + env(safe-area-inset-bottom, 0px));
  }
  .mtab {
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 0.18rem;
    font-family: inherit; font-size: 0.66rem; color: var(--muted);
    text-decoration: none; border: 0; background: none; cursor: pointer;
    padding: 0.28rem 0.6rem; border-radius: var(--radius);
    min-width: 3.4rem; min-height: 44px;
  }
  .mtab:hover { color: var(--ink); }
  .mtab.active { color: var(--accent-deep); font-weight: 600; }
}
@media print {
  .mobile-tabbar { display: none !important; }
}

/* ── 2026-06-10 modernization (operator-approved render): admin
   segmented control. Replaces the legacy "Admin /" breadcrumb pills
   under the sidebar shell only (_admin_breadcrumb.html gates the
   markup; flag-off keeps .admin-breadcrumb untouched). One rounded
   container, compact items, gold active fill matching .sb-item. */
.admin-seg {
  display: inline-flex; flex-wrap: wrap; gap: 2px;
  background: var(--surface-panel);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 3px;
  margin: 0 0 0.9rem;
}
.admin-seg-item {
  display: inline-flex; align-items: center; gap: 0.35rem;
  border-radius: 7px; padding: 0.3rem 0.65rem;
  font-size: 0.75rem; color: var(--ink-soft);
  text-decoration: none; white-space: nowrap;
}
.admin-seg-item:hover { background: var(--surface-subtle); color: var(--ink); }
.admin-seg-item.active { background: var(--accent-soft); color: var(--accent-deep); font-weight: 600; }

/* ── 2026-06-10 modernization: Platform hub tiles. CSS-only restyle
   scoped to the gated .platform-hub class (platform.html adds it under
   the sidebar shell only), so the flag-off page and the account pages
   that share .account-grid keep their exact current look. Same links,
   same copy; icon becomes an accent chip, title goes serif, the card
   gets a hover ring. */
.platform-hub .account-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.6rem;
}
.platform-hub .account-section-title {
  grid-column: 1 / -1;
  font-size: 0.66rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted-2); margin: 0.6rem 0 0;
}
.platform-hub .card.account-card-flex {
  border-radius: 12px; padding: 0.85rem 0.95rem;
}
.platform-hub .card.account-card-flex:hover {
  border-color: var(--accent-soft-border);
  box-shadow: var(--shadow-2);
}
@media (prefers-reduced-motion: no-preference) {
  .platform-hub .card.account-card-flex {
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
  }
}
.platform-hub .card.account-card-flex h3 {
  display: flex; align-items: center; gap: 0.5rem;
  font-family: var(--serif); font-size: 0.95rem; font-weight: 600;
}
.platform-hub .card.account-card-flex h3 svg {
  width: 1.7rem; height: 1.7rem; padding: 0.4rem; box-sizing: border-box;
  background: var(--accent-soft); color: var(--accent-deep);
  border-radius: 0.55rem; flex: none;
}
.platform-hub .card.account-card-flex p.muted {
  font-size: 0.78rem; line-height: 1.5;
}

/* ── Wave 6 visual system: the Modern Pass finish (2026-06-10,
   operator-approved renders at /tmp/modern_pass_renders.html). All of
   it scoped to .app-frame so the legacy shell is untouched. The five
   rules: elevation over borders, frosted chrome, 14-24px radii, ONE
   gold gradient (primary actions only), display numerals. Tokens live
   in tokens.css; motion in motion.css. */

/* rule 3: radii rebind for the whole shell via custom-property scope */
.app-frame {
  --radius: 11px;
  --radius-lg: 16px;
}

/* rule 1: elevation over borders on the shell's core cards */
.app-frame .card,
.app-frame .dash-kpi,
.app-frame .dash-card,
.app-frame .meta-card,
.app-frame .tile {
  border-color: var(--hairline);
  box-shadow: var(--el-1);
}
.app-frame .dash-thread:hover {
  background: var(--surface-subtle);
}

/* rule 2: frosted chrome, with a solid fallback when the browser
   cannot blur (the color-mix frost tokens are translucent, so they
   ONLY apply inside @supports) */
@supports (backdrop-filter: blur(1px)) {
  .app-frame .app-sidebar {
    background: var(--frost-side);
    backdrop-filter: blur(14px);
  }
  .app-frame .mobile-topbar,
  .app-frame .mobile-tabbar {
    background: var(--frost-bar);
    backdrop-filter: blur(12px);
  }
  .app-frame .chat-header {
    background: var(--frost-bar);
    backdrop-filter: blur(12px);
  }
  .app-frame .composer-static {
    background: var(--frost-panel);
    backdrop-filter: blur(14px);
  }
}

/* rule 4: the one gradient, gold, on primary actions only */
.app-frame .btn.primary,
.app-frame button.primary,
.app-frame .sb-new,
.app-frame .composer-static #send-btn {
  background: var(--grad-gold);
  border-color: transparent;
  color: var(--on-accent);
  box-shadow: var(--el-1);
}
.app-frame .sb-item.active {
  background: var(--grad-gold-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-soft-border) 55%, transparent), var(--el-1);
}
.app-frame .admin-seg-item.active {
  background: var(--grad-gold-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-soft-border) 45%, transparent);
}

/* rule 5: display numerals on the dashboard KPIs */
.app-frame .dash-v {
  font-size: 1.8rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* chat: asymmetric user bubble + floating composer */
.app-frame .bubble.user {
  border-radius: 18px 18px 4px 18px;
  box-shadow: var(--el-1);
}
.app-frame .composer-static {
  box-shadow: var(--el-2);
}

/* bento grid for the home dashboard (home_dashboard.html) */
.dash-bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 0.75rem;
}
.dash-hero {
  grid-column: span 12;
  display: flex;
  align-items: center;
  gap: 1.1rem;
  flex-wrap: wrap;
  background: linear-gradient(135deg, var(--surface-panel), var(--accent-soft));
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--el-1);
  padding: 1.1rem 1.25rem;
}
.dash-bento > .dash-kpi { grid-column: span 3; }
.dash-bento .dash-main { grid-column: span 8; min-width: 0; }
.dash-bento .dash-side { grid-column: span 4; }
@media (max-width: 1100px) {
  .dash-bento > .dash-kpi { grid-column: span 6; }
  .dash-bento .dash-main, .dash-bento .dash-side { grid-column: span 12; }
}
@media (max-width: 640px) {
  .dash-bento > .dash-kpi { grid-column: span 12; }
}

/* ring progress primitive: ships now, lights up wherever a real
   percentage exists (set --pct inline from server data) */
.app-frame .ring {
  width: 4.4rem; height: 4.4rem; border-radius: 50%; flex: none;
  background: conic-gradient(var(--accent) calc(var(--pct, 0) * 1%), var(--rule-soft) 0);
  display: flex; align-items: center; justify-content: center;
}
.app-frame .ring > i {
  width: 3.3rem; height: 3.3rem; border-radius: 50%;
  background: var(--surface-panel);
  display: flex; align-items: center; justify-content: center;
  font-style: normal; font-family: var(--serif);
  font-weight: 700; font-size: 0.9rem; color: var(--accent-deep);
}

/* Wave 6 addenda: the KPI strip spans the bento row; live-dot base */
.dash-bento .dash-kpis { grid-column: span 12; margin-bottom: 0; }
.app-frame .live-dot {
  width: 0.45rem; height: 0.45rem; border-radius: 50%;
  background: var(--accent); display: inline-block; vertical-align: middle;
}

/* ── Wave 8 Sidebar v2 (2026-06-10, operator-approved renders):
   inline pinned + recent threads under Chat, live badge pills. All
   scoped to .app-frame; the collapsed 56px rail hides both (labels
   are hidden there, a bare count would float meaninglessly). */
.app-frame .sb-threads { display: flex; flex-direction: column; }
.app-frame .sb-thread {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.18rem 0.6rem 0.18rem 2rem;
  font-size: 0.72rem;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  border-radius: var(--radius);
}
.app-frame .sb-thread:hover { color: var(--ink); background: var(--surface-subtle); }
.app-frame .sb-thread-title { overflow: hidden; text-overflow: ellipsis; }
.app-frame .sb-thread-pin { color: var(--accent); display: inline-flex; flex: none; }
.app-frame .sb-thread-pinned { color: var(--accent-deep); font-weight: 600; }
.app-frame .sb-thread-all { color: var(--accent); font-weight: 600; }
.app-frame .sb-badge {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.62rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
  background: var(--accent-soft);
  color: var(--accent-deep);
  font-variant-numeric: tabular-nums;
}
.app-frame .sb-badge:empty { display: none; }
.app-frame .sb-badge .live-dot { width: 0.35rem; height: 0.35rem; }
.app-frame .sb-badge-txt { letter-spacing: 0.03em; }
body.sb-collapsed .sb-threads,
body.sb-collapsed .sb-badge { display: none; }

/* ── Wave 9 Dashboard v2 (2026-06-10, operator-approved renders):
   KPI click-through, live training card, activity feed, encounters
   list page. Page-feature classes (enc-*, dash-audit-*) are
   dashboard/encounters-only markup; interactive upgrades scope to
   .app-frame per the visual-system rule. */
.dash-kpi-link { text-decoration: none; color: inherit; }
.app-frame .dash-kpi-link:hover {
  box-shadow: var(--el-2);
  border-color: var(--accent-soft-border);
}
.dash-audit-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.72rem;
  padding: 0.16rem 0;
}
.dash-audit-ts { font-family: var(--mono); color: var(--muted-2); flex: none; }
.dash-audit-action { font-family: var(--mono); color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.enc-list { padding: 0; }
.enc-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.65rem 1rem;
  font-size: 0.82rem;
}
.enc-row + .enc-row { border-top: 1px solid var(--rule-soft); }
.enc-status {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  border-radius: 999px;
  padding: 0.08rem 0.6rem;
  font-size: 0.68rem;
  font-weight: 700;
  background: var(--bone-soft);
  color: var(--muted);
  flex: none;
  min-width: 4.6rem;
  justify-content: center;
}
.enc-status--active { background: var(--accent-soft); color: var(--accent-deep); }
.enc-status--closed { background: var(--ok-bg); color: var(--ok-deep); }
.enc-status--aborted, .enc-status--failed { background: var(--danger-bg); color: var(--danger); }
.enc-when { color: var(--ink-soft); }
.enc-dur { color: var(--muted); font-variant-numeric: tabular-nums; }
.enc-id { margin-left: auto; color: var(--muted-2); font-size: 0.7rem; }
.enc-foot { font-size: 0.74rem; margin-top: 0.6rem; }
.enc-empty .btn { margin-top: 0.5rem; }

/* ── Wave 10 polish (2026-06-10, operator-approved plan): dark-mode
   contrast fix (accent-on-soft), loading skeletons for the lazy
   sidebar threads, summary focus ring, richer empty state. */
/* Dark contrast: repoint the new-shell gold-on-soft text. Light mode
   is byte-identical (the token equals --accent-deep there). */
.app-frame .sb-badge,
.app-frame .sb-thread-pin,
.app-frame .sb-thread-pinned,
.app-frame .sb-thread-all,
.app-frame .ring > i,
.enc-status--active {
  color: var(--accent-on-soft);
}
/* details/summary is neither <a> nor <button>, so the global
   focus-visible rules skip it; the steps timeline + legacy activity
   card are keyboard-toggled through it. */
summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Sidebar threads: skeleton placeholders render until the lazy
   fragment swaps in (shimmer comes from the Wave 6 primitive in
   motion.css; reduced-motion users get the static block). */
.app-frame .sb-thread-skeleton {
  height: 0.8rem;
  margin: 0.22rem 0.6rem 0.22rem 2rem;
  border-radius: var(--radius);
}
.app-frame .sb-thread-skeleton:nth-child(2) { width: 62%; }
/* Richer empty state on the dashboard threads card. */
.dash-empty-cta { margin: 0 1rem 0.9rem; }
/* Static fallback: motion.css only paints the shimmer under
   prefers-reduced-motion: no-preference, so reduced-motion users get
   this calm block instead of an invisible placeholder. */
.app-frame .sb-thread-skeleton { background: var(--rule-soft); }

/* ── Task #72 PR 3: care-team patient-messages inbox + thread. Page
   feature classes (pm-*); interactive bits inherit the Modern Pass
   tokens. Tokens only. */
.pm-list { padding: 0; }
.pm-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.7rem 1rem;
  font-size: 0.84rem;
  color: inherit;
  text-decoration: none;
}
.pm-row + .pm-row { border-top: 1px solid var(--rule-soft); }
.pm-row:hover { background: var(--surface-subtle); }
.pm-status {
  flex: none;
  min-width: 4.4rem;
  justify-content: center;
  display: inline-flex;
  border-radius: 999px;
  padding: 0.08rem 0.55rem;
  font-size: 0.66rem;
  font-weight: 700;
}
.pm-status--unread { background: var(--danger-bg); color: var(--danger); }
.pm-status--open { background: var(--accent-soft); color: var(--accent-on-soft); }
.pm-status--replied { background: var(--ok-bg); color: var(--ok-deep); }
.pm-status--closed { background: var(--bone-soft); color: var(--muted); }
.pm-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.pm-line { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pm-preview {
  font-size: 0.74rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pm-when { flex: none; font-size: 0.7rem; }
.pm-thread { max-width: 46rem; }
.pm-title { margin: 0.2rem 0 0.1rem; }
.pm-sub { margin: 0 0 1rem; }
.pm-bubbles { display: flex; flex-direction: column; gap: 0.7rem; margin-bottom: 1rem; }
.pm-bubble {
  max-width: 80%;
  border-radius: var(--radius-lg);
  padding: 0.6rem 0.85rem;
  font-size: 0.86rem;
}
.pm-bubble p { margin: 0; white-space: pre-wrap; }
.pm-bubble-patient {
  align-self: flex-start;
  background: var(--surface-panel);
  border: 1px solid var(--rule-soft);
}
.pm-bubble-careteam {
  align-self: flex-end;
  background: var(--accent-soft);
  border: 1px solid var(--accent-soft-border);
}
.pm-from { font-size: 0.66rem; font-weight: 700; margin-bottom: 0.15rem; }
.pm-reply textarea {
  width: 100%;
  margin-bottom: 0.5rem;
}
.pm-error { color: var(--danger); font-size: 0.8rem; }
.pm-replynote { display: block; font-size: 0.7rem; margin-top: 0.4rem; }

/* ── Wave 11 Models Hub revamp (2026-06-11, operator-approved renders
   at /tmp/admin_revamp_renders_2026-06-11.html): frosted tab bar,
   bento KPI strips (hub chrome + run summary), catalog rows as
   elevated card rows, run cards with the status-only ring. EVERY
   selector is .app-frame-scoped so the legacy topbar shell stays
   byte-identical until cutover; tokens only; motion rides the Wave 6
   curve (var(--ez)/var(--dur-*), live-dot pulse from motion.css).
   Specificity notes: admin.css loads AFTER app.css on these pages, so
   rules that fight admin.css declarations chain .models-hub-tabs /
   .admin-catalog-compact ancestors to win on specificity, not order. */
.app-frame .models-hub-tabs {
  border: 1px solid var(--hairline);
  border-radius: 13px;
  background: var(--surface-panel);
  box-shadow: var(--el-1);
  padding: 4px;
  gap: 3px;
}
@supports (backdrop-filter: blur(1px)) {
  .app-frame .models-hub-tabs {
    background: var(--frost-bar);
    backdrop-filter: blur(10px);
  }
}
.app-frame .models-hub-tabs .models-hub-tab {
  border: 0;
  border-radius: 9px;
  min-height: 1.9rem;
  padding: 0.32rem 0.8rem;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted);
}
.app-frame .models-hub-tabs .models-hub-tab:hover {
  background: var(--surface-subtle);
  color: var(--ink);
}
.app-frame .models-hub-tabs .models-hub-tab.active {
  background: var(--surface-panel);
  color: var(--accent-on-soft);
  box-shadow: var(--el-1);
}

/* bento KPI strips: hub chrome (admin_models.html, catalog tab) and
   the run summary inside the runs fragment. Reuses the Wave 2/6
   .dash-kpi primitives (elevation + display numerals ride along). */
.app-frame .hub-kpis,
.app-frame .run-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 0.6rem;
  margin: 0 0 0.8rem;
}
.app-frame .hub-kpis .dash-kpi,
.app-frame .run-kpis .dash-kpi {
  padding: 0.7rem 0.9rem;
}
.app-frame .hub-kpis .hub-kpi-name {
  font-size: 1.02rem;
  line-height: 1.3;
  padding-top: 0.45rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-frame .run-kpis .dash-v {
  font-size: 1.45rem;
}
.app-frame .run-kpis .dash-d {
  font-size: 0.68rem;
}

/* catalog rows -> elevated card rows: the table shell becomes the one
   elevated surface; rows go transparent with hairline separators and
   a soft hover surface. Selector shapes mirror admin.css's
   .admin-catalog-compact rules (chained ancestors beat them). */
.app-frame .admin-catalog-card-shell {
  background: var(--surface-panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--el-1);
  overflow: hidden;
}
.app-frame .admin-catalog-compact .admin-catalog-table td {
  background: transparent;
  border-bottom: 0;
  border-top: 1px solid var(--rule-soft);
}
.app-frame .admin-catalog-compact .admin-catalog-table tr[data-row]:first-child td {
  border-top: 1px solid var(--rule-soft);
}
.app-frame .admin-catalog-compact .admin-catalog-table tr[data-row]:last-of-type td {
  border-bottom: 0;
}
.app-frame .admin-catalog-compact .admin-catalog-table tr[data-row]:hover td {
  background: var(--surface-subtle);
  border-color: var(--rule-soft);
}
.app-frame .model-grants-card .model-grants-item:hover {
  background: var(--surface-subtle);
}

/* run cards (runs tab + standalone training-runs page, shell branch
   of _admin_training_runs_table.html). The ring is the Wave 6
   primitive; --pct is set inline ONLY for finished runs (100, a real
   state). Live/failed/idle rings stay dormant: status is carried by
   the pill + icon, never a fabricated percent. */
.app-frame .run-cards {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.app-frame .run-card-row {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  background: var(--surface-panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--el-1);
  padding: 0.75rem 1rem;
}
.app-frame .run-card-row:hover {
  box-shadow: var(--el-2);
  border-color: var(--accent-soft-border);
}
.app-frame .run-cards .ring {
  width: 3rem;
  height: 3rem;
}
.app-frame .run-cards .ring > i {
  width: 2.2rem;
  height: 2.2rem;
  font-size: 0.7rem;
}
.app-frame .run-ring--done {
  background: conic-gradient(var(--ok) calc(var(--pct, 0) * 1%), var(--rule-soft) 0);
}
.app-frame .run-ring--done > i {
  color: var(--ok-deep);
}
.app-frame .run-ring--fail > i {
  color: var(--danger);
}
.app-frame .run-ring--live > i,
.app-frame .run-ring--idle > i {
  color: var(--muted-2);
}
.app-frame .run-card-main {
  flex: 1;
  min-width: 0;
}
.app-frame .run-card-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  font-size: 0.9rem;
}
.app-frame .run-card-title .run-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.app-frame .run-card-sub {
  font-size: 0.74rem;
  color: var(--muted);
  margin-top: 0.15rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-frame .run-card-metrics {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  flex: none;
  text-align: center;
}
.app-frame .run-metric-v {
  display: block;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 0.86rem;
}
.app-frame .run-metric-k {
  font-size: 0.64rem;
  color: var(--muted-2);
}
.app-frame .run-card-no-evals {
  font-size: 0.72rem;
  color: var(--muted-2);
}
.app-frame .run-card-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: none;
}
.app-frame .run-card-select {
  /* Live-walk finding 2026-06-11: a global input width rule inflated
     this checkbox to ~680px (the giant empty card box in prod).
     flex: none alone does not constrain width; pin it explicitly. */
  flex: none;
  width: 0.95rem;
  height: 0.95rem;
  accent-color: var(--accent);
}
@media (max-width: 640px) {
  .app-frame .run-card-row {
    flex-wrap: wrap;
  }
  .app-frame .run-card-metrics {
    width: 100%;
    justify-content: flex-start;
    text-align: left;
  }
}

/* ── Wave 12 Model detail alignment (2026-06-11, operator-approved
   renders at /tmp/admin_revamp_renders_2026-06-11.html section 4):
   the /admin/models/{id} detail page joins the Modern Pass family --
   elevated cards, hairline separators, the one gold gradient kept to
   .btn.primary. PURE CSS: no template changes, every selector is
   .app-frame-scoped so the legacy topbar shell stays byte-identical;
   tokens only; motion rides the Wave 6 curve. These rules live in
   app.css but must beat admin.css's later-loading page rules, so each
   selector chains .app-frame for specificity, not order. */

/* elevated cards: runtime panel, grants panel, HTI provenance panel,
   display-name editor, picker preview -- one elevated family */
.app-frame .model-detail-card {
  background: var(--surface-panel);
}
.app-frame .hti-source-panel {
  background: var(--surface-panel);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--el-1);
}
.app-frame .name-editor {
  background: var(--surface-panel);
  box-shadow: var(--el-1);
  border-radius: var(--radius-lg);
}
.app-frame .preview-card {
  border-color: var(--hairline);
}

/* hairline separators: runtime facts + tenant-grant rows. Grant rows
   drop the boxed border/tint for hairline-separated card rows with a
   calm hover, the same row language as the datasets list. */
.app-frame .model-detail-facts div {
  border-bottom-color: var(--rule-soft);
}
.app-frame .model-grant-list {
  gap: 0;
  margin: 0.4rem 0;
}
.app-frame .model-grant-row {
  border: 0;
  background: transparent;
  border-radius: 9px;
  padding: 0.55rem 0.6rem;
  transition: background var(--dur-1) var(--ez);
}
.app-frame .model-grant-row + .model-grant-row {
  border-top: 1px solid var(--rule-soft);
}
.app-frame .model-grant-row:hover {
  background: var(--surface-subtle);
}
.app-frame .model-grant-new {
  border-top-color: var(--rule-soft);
}

/* one gradient: .btn.primary (Grant / Load adapter) already wears the
   Wave 6 gold gradient under the shell. The display-name Save was a
   second solid-accent block competing with it; under the shell it
   reads as an elevated secondary instead. */
.app-frame .btn-save {
  background: var(--surface-panel);
  color: var(--accent-on-soft);
  border: 1px solid var(--hairline);
  box-shadow: var(--el-1);
  transition: background var(--dur-1) var(--ez);
}
.app-frame .btn-save:hover {
  background: var(--surface-subtle);
}

/* ── Second pass 2026-06-11 (operator-approved render: platform hub
   tiles finish). Serif tile titles, accent icon chips, hover ring;
   extends the PR-571 .platform-hub block. Tokens only. ── */
.app-frame .platform-hub .card.account-card-flex {
  box-shadow: var(--el-1);
  border-color: var(--hairline);
  transition: box-shadow var(--dur-1, 0.15s) var(--ez, ease),
              border-color var(--dur-1, 0.15s) var(--ez, ease),
              transform var(--dur-1, 0.15s) var(--ez, ease);
}
.app-frame .platform-hub .card.account-card-flex:hover {
  box-shadow: var(--el-2);
  border-color: var(--accent-soft-border);
  transform: translateY(-1px);
}
.app-frame .platform-hub h2,
.app-frame .platform-hub h3 {
  font-family: var(--serif);
}
.app-frame .platform-hub .btn.primary {
  background: var(--grad-gold);
  border: none;
}

/* ── Render-goal pass 2026-06-11: platform tile ICON CHIPS (render
   contract: accent chip behind each tile icon). Shell-scoped. ── */
.app-frame .platform-hub .card.account-card-flex h3 {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.app-frame .platform-hub .card.account-card-flex h3 > svg {
  background: var(--accent-soft);
  color: var(--accent-on-soft);
  border-radius: 0.55rem;
  padding: 0.32rem;
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  box-sizing: content-box;
  box-shadow: inset 0 0 0 1px var(--accent-soft-border);
}

/* ── Platform tiles DEPTH pass 2026-06-11 (operator live finding:
   long paragraphs made tiles tall and uneven, CTAs read as bare
   links, the grid left orphan holes). Compact equal-height tiles,
   2-line descriptions, real CTA buttons. Shell-scoped, tokens only. */
.app-frame .platform-hub .account-grid {
  align-items: stretch;
}
.app-frame .platform-hub .card.account-card-flex {
  display: flex;
  flex-direction: column;
  padding: 0.85rem 1rem;
}
.app-frame .platform-hub .card.account-card-flex p.muted {
  font-size: 0.76rem;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0.45rem 0 0.7rem;
}
.app-frame .platform-hub .card.account-card-flex > *:last-child {
  margin-top: auto;
}
.app-frame .platform-hub .card.account-card-flex a.btn:not(.primary) {
  border: 1px solid var(--hairline);
  background: var(--surface-subtle);
  border-radius: 10px;
  padding: 0.4rem 0.8rem;
  font-size: 0.76rem;
  width: fit-content;
}
.app-frame .platform-hub .card.account-card-flex a.btn:not(.primary):hover {
  border-color: var(--accent-soft-border);
  background: var(--accent-soft);
  color: var(--accent-on-soft);
}

/* ── Runs ledger bugfix 2026-06-11 (measured live in the operator's
   browser): #compare-form kept a rhythm-era 2-track grid (682px +
   326px) -- the card list sat in track 1 and track 2 rendered as a
   dead column. And run-card-metrics was variable-width, so judge /
   delta / loss never lined up between rows. Cards now span the full
   form; metric cells are fixed-width right-aligned columns; rows
   without eval signals reserve the same slot so columns stay true. */
.app-frame #compare-form .run-cards,
.app-frame .compare-toolbar ~ .run-cards,
.app-frame form .run-cards {
  grid-column: 1 / -1;
  width: 100%;
}
.app-frame .run-card-metrics {
  flex: none;
  display: flex;
  justify-content: flex-end;
  gap: 0.9rem;
  min-width: 17rem;
  margin-left: auto;
}
.app-frame .run-metric {
  width: 3.9rem;
  flex: none;
  text-align: right;
}
.app-frame .run-metric .run-metric-v {
  display: block;
  font-family: var(--mono);
  font-weight: 600;
}
.app-frame .run-metric .run-metric-k {
  font-size: 0.6rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.app-frame .run-card-no-evals {
  flex: none;
  min-width: 17rem;
  margin-left: auto;
  text-align: right;
  font-size: 0.74rem;
  font-style: italic;
  color: var(--muted-2);
  align-self: center;
}
.app-frame .run-card-actions {
  flex: none;
  margin-left: 1rem;
}

/* ── Watcher S1 companion: the '-' sentinel in an absent metric slot
   reads muted so real values dominate. ── */
.app-frame .run-metric-absent {
  color: var(--muted-2);
  font-weight: 400;
}
