/*
 * Lodestone — style.css
 *
 * Palette + layout + motion vocabulary from SPEC §4.
 * CVD-tuned per §22.2.
 *
 * NOTE: External font vendoring is intentionally skipped — CSP default-src
 * 'self' blocks Google Fonts CDN. System monospace stack is used instead.
 * Next polish step: vendor JetBrains Mono into static/fonts/ and reference
 * via @font-face.
 *
 * §22.8 note: light-mode colour-scheme is a non-goal per spec. Only dark mode
 * is supported. No @media (prefers-color-scheme: light) overrides are present.
 */

/* ==========================================================================
   Custom properties — palette (§4).
   ========================================================================== */

:root {
  /* === Design language v5 (2026-05-24) ===
     Canonical spec: design/CREATIVE-DIRECTION-v5.md.
     Cyan (#22D3EE) deleted from the palette — brand is now Klein blue --ink.
     Geometry (bracket glyph) carries state, not hue. --live (lime) lives
     ONLY inside `[ ● LIVE ]` as glyph fill — never text, never border. */

  /* === Surfaces — warmer (Mira's R2 tungsten-dark, all three concurred) === */
  --canvas:    #0F0E0D;          /* Warmer than v4 #0E1116. Leather notebook, not VS Code. */
  --surface:   #16110A;          /* First lift — header, status strip, modal. */
  --surface-2: #323B49;          /* Back-compat — unused after v5 sweep. */

  /* Back-compat alias — old --bg references keep rendering during migration. */
  --bg: var(--canvas);

  /* === Foreground ramps === */
  --fg:     #E8E2D6;            /* Off-white, paper-warm cast. */
  --dim:    #9A968E;            /* Secondary text. */
  --off:    #5B574F;            /* UI borders + terminal-state EXPIRED text. */
  --border: #2A2520;            /* Warm border to match the surface temperature. */

  /* === Brand — Klein blue (Mira's pick, unanimous) ===
     Appears at MOST 3 places per viewport: wordmark left bracket,
     active-row left-edge marker, primary CTA fill. Nowhere else. */
  --ink:        #1B2BBB;
  --ink-rgb:    27 43 187;
  --ink-low:    rgb(var(--ink-rgb) / 0.12);

  /* v5 step 8 — back-compat --accent alias DELETED. Every call-site
     references --ink (load-bearing brand) or --fg (everything else).
     Future surfaces needing Klein blue write var(--ink) explicitly so
     the author counts slots against the 3-per-viewport budget at the
     point of writing. */

  /* === Status semantics ===
     --live: lime, KEPT — glyph-fill inside brackets ONLY. No text, no border.
     --warn: warmer / less saturated amber.
     --bad:  more brick / less neon orange. */
  --live:   #A3E635;
  --warn:   #E0A82E;
  --bad:    #D9572A;

  --live-rgb: 163 230 53;
  --warn-rgb: 224 168 46;
  --bad-rgb:  217 87 42;

  /* v5 step 8 — legacy --ok / --ok-rgb aliases DELETED. No remaining
     call-sites; state is now expressed via the .bk bracket atom + glyph. */

  /* === Live-halo tokens (static; no breathe) === */
  --live-halo-rest-inset: 0 0 0 1px rgb(var(--live-rgb) / 0.18) inset;
  --live-halo-rest-glow:  0 0 12px rgb(var(--live-rgb) / 0.04);

  /* === Row-hover tint === */
  --row-hover-tint: rgb(255 255 255 / 0.02);

  /* === Focus ring — single source of truth === */
  --focus-ring-width:  2px;
  --focus-ring-offset: 2px;

  /* === Font features === */
  --font-feat-mono: "tnum" 1, "cv11" 1, "ss01" 1, "ss03" 1;
  --font-feat-sans: "cv11" 1, "ss01" 1, "ss03" 1;

  /* === Type stacks (v5) ===
     Söhne (Klim, ~$600) and Berkeley Mono (US Graphics, ~$75) are licensed
     separately — files are NOT vendored in this repo. Until Sir drops the
     OTF/WOFF2 into static/fonts/ and the matching @font-face declarations
     land, the system silently falls back to JetBrains Mono + system-ui.
     The reference order below ensures correct rendering the moment the
     vendored faces appear on disk.

     NOTE: the brand-mark weight-pulse and bracket weight-pulse animations
     (v5 §4, §6) require Berkeley Mono Variable's wght axis — they will be
     wired in a follow-up commit once the variable font is on disk. */
  --font-mono: "Berkeley Mono", "Berkeley Mono Variable", ui-monospace, "JetBrains Mono", "SF Mono", "Cascadia Mono", Menlo, monospace;
  --font-sans: "Söhne", "Söhne Buch", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;

  /* === Type scale — 4 cockpit levels + 1 editorial hero === */
  --text-xs:   0.75rem;         /* 12px — labels, footers, badges */
  --text-sm:   0.875rem;        /* 14px — body default, table cells */
  --text-base: 0.9375rem;       /* 15px — L1 row content, primary CTA */
  --text-lg:   1.125rem;        /* 18px — page titles */
  --text-hero: 2.25rem;         /* 36px — marketing/auth H1 + marketing brand-mark ONLY */

  /* === Spacing scale === */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 2rem;
  --space-5: 4rem;

  /* Back-compat — DEPRECATED. */
  --gap:    var(--space-3);
  --gap-sm: var(--space-2);
  --gap-xs: var(--space-1);
  --gap-lg: var(--space-4);
  --gap-xl: var(--space-5);
  /* --gap-md, --radius, --ease-default deleted (unreferenced after v4 sweep). */

  /* === Motion vocabulary === */
  --dur-tick:     0.15s;        /* state-tick: pip colour swap, counter update */
  --dur-row:      0.35s;        /* transition-row: new row appearing / status change */
  --dur-terminal: 0.6s;         /* transition-terminal: INVALIDATED / EXPIRED fade */
  --dur-pulse:    1.6s;         /* brand-mark left-bracket phosphor pulse on /intel poll */

  --ease-arrival:  cubic-bezier(0.0, 0.0, 0.2, 1);
  --ease-terminal: cubic-bezier(0.4, 0.0, 0.6, 1);
  --ease-glow:     cubic-bezier(0.4, 0.0, 0.2, 1);

  --stagger-row: 80ms;
}

/* ==========================================================================
   §22.7 — prefers-reduced-motion: zero out all durations.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-tick: 0s;
    --dur-row: 0s;
    --dur-terminal: 0s;
    --dur-pulse: 0s;
  }
  *, *::before, *::after {
    animation-duration: 0s !important;
    transition-duration: 0s !important;
  }
  /* Brand-mark phosphor pulse — keep the bracket lit, no animation. */
  .brand-mark__bracket--left.is-pulsing {
    animation: none;
    opacity: 1;
    color: var(--ink);
  }
}

/* ==========================================================================
   v5 §8.1 — `.bk` bracket atom.

   Every measurement, state, action, status announcement in the product
   wraps in `.bk`. The brackets are GENERATED via ::before / ::after so
   the underlying markup stays clean ASCII. Single source of truth for
   the brand visual rule (v5 §2 — brackets are the only chrome).

   Bracket text colour stays --fg by default; state variants apply a
   colour to the contained geometric glyph only (see .bk__glyph below).
   Brand variant (.bk--ink) paints the brackets themselves Klein blue —
   reserved for the wordmark + primary CTA (max 3 per viewport, v5 §3).
   ========================================================================== */

.bk {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--fg);
  white-space: nowrap;
}

.bk::before { content: "[ "; }
.bk::after  { content: " ]"; }

/* Brand variant — brackets paint Klein blue.
   Reserved for the wordmark, the active-row left edge, and the primary CTA.
   Three appearances per viewport, max — count them before adding a fourth. */
.bk--ink::before,
.bk--ink::after {
  color: var(--ink);
}

/* The geometric glyph inside a state-bearing bracket. It's the ONLY part of
   `.bk` that paints a state colour. The bracket text stays --fg so the
   register reads as "measurement", and the glyph reads as "state". */
.bk__glyph {
  display: inline-block;
  margin-right: 0.25em;
}

.bk--state-live    .bk__glyph { color: var(--live); }
.bk--state-pending .bk__glyph { color: var(--warn); }
.bk--state-decay   .bk__glyph { color: var(--warn); }
.bk--state-invalid .bk__glyph { color: var(--bad); }
.bk--state-expired .bk__glyph { color: var(--off); }

/* Action atom (button/anchor as .bk). Honours the bracket grammar at the
   primary CTA. Hover lifts the bracket colour rather than adding a box. */
button.bk,
a.bk {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: inherit;
  text-decoration: none;
}

button.bk:hover::before,
button.bk:hover::after,
a.bk:hover::before,
a.bk:hover::after {
  color: var(--ink);
}

a.bk:hover {
  text-decoration: none;
}

/* Bracket weight-pulse — the only ambient motion in v5 (§4, §6).
   Currently a no-op opacity-pulse fallback: animating
   `font-variation-settings: wght` requires Berkeley Mono Variable on disk.
   Once the variable face is vendored, swap the keyframe body to:
       0%, 100% { font-variation-settings: "wght" 400; }
       50%      { font-variation-settings: "wght" 700; }
   prefers-reduced-motion holds the bracket static (handled in the global
   reduced-motion rule below). */
@keyframes bk-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.65; }
}

.bk--pulsing::before,
.bk--pulsing::after {
  animation: bk-pulse var(--dur-pulse) var(--ease-glow) 1;
}

/* ==========================================================================
   §10 — Brand mark (v4 wiring, v5 colours).
   The `[lodestone]` wordmark IS the brand. Three spans inside .brand-mark.
   Left bracket carries the phosphor pulse on /intel polls (web.js wires it).
   ========================================================================== */

.brand-mark {
  display: inline-flex;
  align-items: baseline;
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--fg);
  text-decoration: none;
  line-height: 1;
}
.brand-mark__bracket {
  color: var(--ink);
  display: inline-block;
}
.brand-mark__bracket--left  { margin-inline-end: 0.15em; }
.brand-mark__bracket--right { margin-inline-start: 0.15em; }
.brand-mark__word { color: var(--fg); }

@keyframes accent-pulse {
  0%   { color: var(--ink); opacity: 1; }
  50%  { color: var(--ink); opacity: 0.6; }
  100% { color: var(--ink); opacity: 1; }
}
.brand-mark__bracket--left.is-pulsing {
  animation: accent-pulse var(--dur-pulse) var(--ease-glow) 1;
}

/* ==========================================================================
   Reset + base.
   ========================================================================== */

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

html {
  font-size: 16px;
  background-color: var(--bg);
  color: var(--fg);
}

body {
  font-family: var(--font-sans);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  line-height: 1.6;
}

a {
  color: var(--ink);
  text-decoration: none;
}

/* Mouse hover on links — editorial underline only, NO boxed outline.
   The outline-on-hover pattern is a 2008 Bootstrap tell; modern craft
   reserves outlines for keyboard focus (:focus-visible) below. */
a:hover {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

/* Visible focus styles — KEYBOARD focus only (§22). The outline appears
   when the user reaches the element via Tab, NOT when the mouse passes
   over it. Mouse hover gets the subtle treatments defined per-component. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* Rev 20 (Codex R1 #3) — skip-to-content. WCAG 2.4.1 Bypass Blocks.
   Visually hidden until focused; on Tab from the URL bar it springs into the
   top-left so keyboard + screen-reader users skip the header chrome. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
  background-color: var(--surface);
  color: var(--fg);
  padding: 0.5rem 1rem;
  border: 2px solid var(--ink);
  z-index: 1000;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  text-decoration: none;
}

.skip-link:focus {
  left: 1rem;
  top: 1rem;
  width: auto;
  height: auto;
  overflow: visible;
}

/* §12.5 step 1c — .page-title split (v4).
   Three variants:
   - .page-title             — default mono 18px --fg (status-detail, generic pages)
   - .page-title--cockpit    — small uppercase --dim (dashboard, intel, journal)
   - .page-title--hero       — --text-hero Inter 600 (marketing/auth hero)
   Untagged .page-title now ships the default; cockpit templates migrate to
   --cockpit modifier in the same commit. */
.page-title {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  font-weight: 500;
  color: var(--fg);
  letter-spacing: 0;
  text-transform: none;
  margin: 0 0 var(--space-3) 0;
}
.page-title--cockpit {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  margin: 0 0 var(--space-2) 0;
  font-weight: 500;
}
.page-title--hero {
  font-family: var(--font-sans);
  font-size: var(--text-hero);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin: 0 0 var(--space-3) 0;
  line-height: 1.1;
}

button, input, select, textarea {
  font-family: inherit;
  font-size: inherit;
}

/* ==========================================================================
   Layout — header / main / footer (a11y landmarks §22).
   ========================================================================== */

[role="banner"] {
  background-color: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: var(--gap-sm) var(--gap);
  display: flex;
  align-items: center;
  gap: var(--gap);
  flex-wrap: wrap;
}

/* §10 — header brand wrapper. .brand-mark carries all type/colour;
   wrapper only enforces the 44×44 touch target (§22.10). */
.header-brand {
  display: inline-flex;
  align-items: center;
}

.header-brand .brand-mark {
  font-size: var(--text-base);
  min-height: 44px;
  padding: 0 8px;
}

.header-brand .brand-mark:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: var(--focus-ring-offset);
}

.header-controls {
  display: flex;
  align-items: center;
  gap: var(--gap);
  flex: 1;
  justify-content: flex-end;
  flex-wrap: wrap;
}

.header-clock {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--dim);
  min-width: 8ch;
}

[role="main"] {
  flex: 1;
  padding: var(--gap);
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
}

[role="contentinfo"] {
  background-color: var(--surface);
  border-top: 1px solid var(--border);
  padding: var(--gap-sm) var(--gap);
  font-size: 0.75rem;
  color: var(--dim);
  text-align: center;
}

/* ==========================================================================
   §17 — system status strip.
   ========================================================================== */

[role="status"][aria-label="System status"] {
  display: flex;
  gap: var(--gap);
  align-items: center;
  flex-wrap: wrap;
  padding: var(--gap-xs) var(--gap);
  background-color: var(--surface);
  border-bottom: 1px solid var(--border);
  font-size: 0.8rem;
  font-family: var(--font-mono);
}

.status-component {
  display: flex;
  align-items: center;
  gap: var(--gap-xs);
  color: var(--dim);
}

.status-pip {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  transition: background-color var(--dur-tick) var(--ease-default);
}

.status-pip--ok,
.status-pip--live  { background-color: var(--live); }
.status-pip--warn  { background-color: var(--warn); }
.status-pip--bad   { background-color: var(--bad); }
.status-pip--off   { background-color: var(--off); }

.status-label {
  color: var(--dim);
}

.status-age {
  font-size: 0.75em;
}

/* Exposure summary line (§17) */
.exposure-line {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--dim);
  padding: var(--gap-xs) var(--gap);
  border-bottom: 1px solid var(--border);
}

/* ==========================================================================
   Mode toggle.
   ========================================================================== */

.mode-toggle {
  display: flex;
  gap: var(--gap-xs);
  align-items: center;
}

.mode-toggle form {
  display: inline;
}

.btn-mode {
  background: none;
  border: 1px solid var(--border);
  color: var(--dim);
  /* Rev-17 UX-stage — §22.10 WCAG 2.5.5 floor: 44 × 44 CSS px on every
     interactive control. The prior 2px × 8px padding rendered at 127 × 20
     and failed touch-target sizing on phones. */
  min-height: 44px;
  min-width: 44px;
  padding: 10px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 0.8rem;
  transition: border-color var(--dur-tick), color var(--dur-tick);
}

/* Mouse hover — border + colour shift only. No outline ring. */
.btn-mode:hover {
  border-color: var(--ink);
  color: var(--fg);
}
/* Keyboard focus — inherits the global :focus-visible outline above.
   We don't redeclare it here; the inheritance handles it. */

.btn-mode--active {
  border-color: var(--ink);
  color: var(--ink);
}

/* ==========================================================================
   §18.1 — recommendation rows.
   ========================================================================== */

[role="region"][aria-label="Recommendations"] {
  margin-top: var(--gap);
}

.rec-list {
  list-style: none;
}

/* §18.1.1 — per-row staleness pip + refresh countdown */
.rec-row {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: var(--gap-sm);
  padding: var(--gap-sm) var(--gap);
  cursor: pointer;
  transition:
    border-color var(--dur-row) var(--ease-default),
    opacity var(--dur-terminal) var(--ease-default);
  position: relative;
}

.rec-row:hover,
.rec-row:focus-within {
  border-color: var(--dim);
}

/* Keyboard focus picks up the global :focus-visible outline.
   Removed explicit .rec-row:focus rule that fired on mouse click too. */

/* §18.1 two-line row format */
.rec-thesis {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  flex-wrap: wrap;
}

.rec-structural {
  font-size: 0.8rem;
  color: var(--dim);
  margin-top: var(--gap-xs);
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  flex-wrap: wrap;
}

/* Status glyphs → accessible names via aria-label (§22.5) */
.rec-status-glyph {
  font-size: 1rem;
  flex-shrink: 0;
}

/* Status colour coding */
/* Status semantics — paint state colour, NEVER brand --accent.
   .status-LIVE was the third cyan-on-state collision missed by the
   4378a57 sweep (R3A ship-readiness gate caught it). When a LIVE rec
   row finally renders, the green pip would otherwise paint cyan
   under a cyan brand mark — exact spec §3.6 collision the v4 was
   designed to prevent. */
.status-LIVE        { color: var(--live); }
.status-PENDING     { color: var(--warn); }
.status-DECAYING    { color: var(--warn); }
.status-EXPIRED     { color: var(--off); }
.status-INVALIDATED { color: var(--bad); }

/* §18.1.1 staleness pip */
.rec-stale-pip {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
  transition: background-color var(--dur-tick) var(--ease-default);
}

/* Freshness state pip — paints --live (lime), not brand --accent.
   Same collision class as .status-LIVE / .text-ok / .rec-dir-bracket--LONG:
   the brand cyan must never indicate "fresh data" or "ok state". */
.rec-stale-pip--fresh   { background-color: var(--live); }
.rec-stale-pip--stale   { background-color: var(--warn); }
.rec-stale-pip--expired { background-color: var(--bad); }

/* §18.1.7 intel drill-down — hidden by default, toggled by JS */
.rec-detail {
  display: none;
  margin-top: var(--gap-sm);
  padding-top: var(--gap-sm);
  border-top: 1px solid var(--border);
  font-size: 0.8rem;
  color: var(--dim);
}

.rec-row[aria-expanded="true"] .rec-detail {
  display: block;
}

/* §18.1.2 row-state transition keyframes */
@keyframes row-enter {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes row-invalidate {
  0%   { opacity: 1; }
  30%  { background-color: rgb(var(--bad-rgb) / 0.12); }
  100% { opacity: 0.45; }
}

@keyframes row-expire {
  0%   { opacity: 1; }
  100% { opacity: 0.35; }
}

@keyframes row-live {
  /* Rev 19 UX-stage — was rgba(31,184,122,*), the pre-Rev-9 --ok colour.
     Now uses --ok-rgb so the brand-green flash matches the actual palette
     rather than ghost-emitting the old token. */
  0%   { background-color: rgb(var(--live-rgb) / 0); }
  25%  { background-color: rgb(var(--live-rgb) / 0.15); }
  100% { background-color: rgb(var(--live-rgb) / 0); }
}

/* State-transition CSS classes applied by web.js on htmx:afterSwap.
   Rev 19 UX-stage — purposeful easing tokens (Sonnet R2 motion cohesion).
   Arrivals get --ease-arrival; terminal departures get --ease-terminal. */
.rec-row--entering {
  animation: row-enter var(--dur-row) var(--ease-arrival) both;
}

/* Batch-arrival stagger — when ≥2 rows enter on the same swap, space the
   animation starts by --stagger-row so the eye reads "system reacting to
   events", not "table reload". CSS-only via :nth-child; up to 6 rows in
   one batch (beyond that the stagger flattens — diminishing returns). */
.rec-row--entering:nth-child(2) { animation-delay: calc(var(--stagger-row) * 1); }
.rec-row--entering:nth-child(3) { animation-delay: calc(var(--stagger-row) * 2); }
.rec-row--entering:nth-child(4) { animation-delay: calc(var(--stagger-row) * 3); }
.rec-row--entering:nth-child(5) { animation-delay: calc(var(--stagger-row) * 4); }
.rec-row--entering:nth-child(6) { animation-delay: calc(var(--stagger-row) * 5); }

.rec-row--live-flash {
  animation: row-live var(--dur-row) var(--ease-default) both;
}

/* Terminal row states */
.rec-row[data-curr-status="INVALIDATED"] {
  animation: row-invalidate var(--dur-terminal) var(--ease-terminal) both;
  border-color: rgb(var(--bad-rgb) / 0.4);
}

.rec-row[data-curr-status="EXPIRED"] {
  animation: row-expire var(--dur-terminal) var(--ease-terminal) both;
  opacity: 0.45;
}

/* Direction badges — Rev 21 (Opus R1 #3) — chip→bracket migration complete.
   .rec-direction (legacy chip) is gone. Every direction surface — L1 row,
   journal open/closed tables, history list, trade-ticket instrument section —
   now uses the cockpit bracket treatment. No background, no border, brand
   text colour. The "SaaS tell" the Rev 19 refactor meant to kill is dead. */
.rec-dir-bracket {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  background: none;
  border: 0;
  padding: 0;
}
/* Direction brackets — paint the brackets only, text stays --fg.
   v5 §3 — --live (lime) is glyph-fill inside brackets ONLY, never text.
   When .rec-dir-bracket is also .bk, the ::before / ::after carry the
   state colour; the text inside reads in --fg. */
.bk.rec-dir-bracket--LONG::before,
.bk.rec-dir-bracket--LONG::after  { color: var(--live); }
.bk.rec-dir-bracket--SHORT::before,
.bk.rec-dir-bracket--SHORT::after { color: var(--bad); }
.rec-dir-bracket { color: var(--fg); }

/* Pair label on L1 — mono bold, the eye-anchor next to the direction. */
.rec-pair {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* The rationale on L1 — must read as the focal point, not as a footnote.
   Rev 19 Opus R2: L1 is thesis-first; the rationale gets full --fg weight. */
.rec-rationale {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 0.9rem;
  flex: 1;
  min-width: 0;
}
.rec-rationale--historical {
  color: var(--dim);
  font-style: italic;
  font-size: 0.85rem;
}

/* L2 metadata atoms — Rev 19 UX-stage (Opus R2 terse format).
   Pipe separators are out; dot separators in dim are in. */
.rec-meta-sep {
  color: var(--off);
  margin: 0 0.1rem;
}
.rec-meta-pips {
  color: var(--dim);
  font-family: var(--font-mono);
  margin-left: 0.2rem;
}
.rec-meta-conf {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
}
/* v5 §3 — confidence is data, not brand. HIGH gets --fg weight, not --ink.
   Klein blue stays at the wordmark / active row / primary CTA only. */
.rec-meta-conf--HIGH   { color: var(--fg); }
.rec-meta-conf--MEDIUM { color: var(--fg); }
.rec-meta-conf--LOW    { color: var(--dim); }

/* §18.1.1 staleness counter populated client-side by web.js */
.rec-stale-counter {
  font-family: var(--font-mono);
  color: var(--dim);
}
.rec-stale-counter--stale { color: var(--warn); }
.rec-stale-counter--bad   { color: var(--bad); }

/* Legacy class kept for history list; live recs no longer render this. */
.rec-confidence {
  font-size: 0.7rem;
  font-family: var(--font-mono);
  color: var(--dim);
}

/* §18.1.5 calendar countdown */
.rec-event-countdown {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--warn);
}

/* Refresh countdown (§18.1.1) */
.refresh-countdown {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--dim);
}

/* ==========================================================================
   §18.1.4 — SINCE LAST OPEN delta strip.
   ========================================================================== */

.since-last-open {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--gap-sm) var(--gap);
  margin-bottom: var(--gap);
  font-size: 0.8rem;
  font-family: var(--font-mono);
  color: var(--dim);
}

/* ==========================================================================
   §18.1.5 — next-4h calendar peek.
   ========================================================================== */

.calendar-peek {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--gap-sm) var(--gap);
  margin-bottom: var(--gap);
  font-size: 0.85rem;
}

.calendar-peek__title {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--dim);
  margin-bottom: var(--gap-xs);
}

.calendar-peek__item {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  padding: 0.15rem 0;
  color: var(--fg);
}

.calendar-peek__item--urgent {
  color: var(--warn);
  font-weight: 700;
}

.calendar-peek__sep {
  color: var(--off);
}

.calendar-peek__in {
  color: var(--dim);
}

.calendar-event {
  display: flex;
  gap: var(--gap-sm);
  align-items: baseline;
  padding: var(--gap-xs) 0;
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.calendar-event:last-child {
  border-bottom: none;
}

.calendar-event__time {
  color: var(--warn);
  min-width: 7ch;
  flex-shrink: 0;
}

.calendar-event__title {
  color: var(--fg);
}

.calendar-event__currency {
  color: var(--dim);
  font-size: 0.75rem;
}

/* ==========================================================================
   §10 — activity canvas (minimal placeholder, real canvas drawn by web.js).
   ========================================================================== */

.activity-canvas {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  height: 80px;
  margin-bottom: var(--gap);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  /* Rev-17 UX-stage — was `var(--off)` which fails WCAG AA: per §4 palette
     comment, --off is the UI-only token (3:1 contrast against --surface is
     fine for borders, fails 4.5:1 for text). --dim is the text token
     (4.6:1 against --bg, Rev 9 fix). */
  color: var(--dim);
  font-family: var(--font-mono);
  overflow: hidden;
}

.activity-pair {
  margin: 0 0.6rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--dim);
  letter-spacing: 0.02em;
}

/* v5 §6 — activity-canvas breathe DELETED. "Pages don't breathe. Pips
   don't pulse. The chrome doesn't decorate." JS may still apply the
   .activity-warming class as a no-op; the class no longer carries any
   animation. The activity canvas is now a static row of pair labels. */

/* ==========================================================================
   §15.1 — first-run disclaimer flow.
   ========================================================================== */

.disclaimer-gate {
  background-color: var(--surface);
  border: 1px solid var(--warn);
  border-radius: var(--radius);
  padding: var(--gap);
  margin-bottom: var(--gap);
  max-width: 640px;
}

.disclaimer-gate h2 {
  font-size: 1rem;
  margin-bottom: var(--gap-sm);
  color: var(--warn);
}

.disclaimer-gate p {
  font-size: 0.85rem;
  color: var(--dim);
  margin-bottom: var(--gap-sm);
}

/* ==========================================================================
   Forms — login / signup / journal / invites.
   ========================================================================== */

.form-card {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--gap);
  max-width: 420px;
  margin: var(--gap) auto;
}

.form-card h1 {
  font-size: 1.1rem;
  margin-bottom: var(--gap);
  font-family: var(--font-mono);
}

/* Rev 21 (Opus R1 #9) — brand anchor for unauthenticated pages.
   The login + signup cards were a bare 420px form-card on grey with a 1.1rem
   <h1>Lodestone</h1>, giving a visitor zero brand cue. Add a 3× hex glyph
   above the H1 and a one-line subtitle below so the page identifies itself
   before the form chrome. Centered for the marketing-card register. */
.form-card--anchored {
  text-align: center;
}

/* v4: legacy .form-card__glyph retained as no-op for back-compat (no live
   markup references it after the login/signup brand-mark migration). */
.form-card--anchored .form-card__glyph {
  display: none;
}

/* v4: the [lodestone] brand mark in the auth-card register. */
.form-card--anchored .form-card__brand {
  display: inline-flex;
  font-size: 1.5rem;
  margin-bottom: var(--space-3);
  padding: 0;
  min-height: auto;
}

.form-card--anchored h1 {
  text-align: center;
  margin-bottom: var(--gap-xs);
}

.form-card--anchored .form-card__subtitle {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--dim);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: var(--gap);
}

/* Form content inside an anchored card stays left-aligned — labels + inputs
   read best flush-left even when the brand header above is centered. */
.form-card--anchored form,
.form-card--anchored .form-error {
  text-align: left;
}

.form-group {
  margin-bottom: var(--gap-sm);
}

.form-group label {
  display: block;
  font-size: 0.8rem;
  color: var(--dim);
  margin-bottom: var(--gap-xs);
}

.form-group input,
.form-group select,
.form-group textarea {
  width: 100%;
  background-color: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--fg);
  padding: 6px 10px;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  transition: border-color var(--dur-tick);
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
  border-color: var(--ink);
  outline: 2px solid var(--ink);
  outline-offset: 0;
}

.form-group textarea {
  resize: vertical;
  min-height: 80px;
}

.form-error {
  color: var(--bad);
  font-size: 0.8rem;
  margin-bottom: var(--gap-sm);
  padding: var(--gap-xs) var(--gap-sm);
  background-color: rgb(var(--bad-rgb) / 0.1);
  border-radius: var(--radius);
  border: 1px solid rgb(var(--bad-rgb) / 0.3);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-xs);
  background-color: var(--ink);
  color: #000;
  border: none;
  border-radius: var(--radius);
  padding: 8px 16px;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: opacity var(--dur-tick);
  text-decoration: none;
}

/* Mouse hover — opacity dip only. The global :focus-visible rule paints
   the keyboard outline (lines 215-219). Removed the boxed outline that
   was firing on every mouse hover. */
.btn:hover {
  opacity: 0.85;
  text-decoration: none;
  color: #000;
}

.btn--secondary {
  background-color: transparent;
  color: var(--dim);
  border: 1px solid var(--border);
}

.btn--secondary:hover {
  /* v5 §3 — secondary action hover lifts to --fg, not --ink. Klein blue
     is reserved for the primary CTA fill. */
  border-color: var(--fg);
  color: var(--fg);
}

.btn--danger {
  background-color: transparent;
  color: var(--bad);
  border: 1px solid rgb(var(--bad-rgb) / 0.5);
}

.btn--danger:hover {
  background-color: rgb(var(--bad-rgb) / 0.1);
  color: var(--bad);
}
/* .btn--danger keyboard focus inherits the global :focus-visible outline
   in --accent. If a future review wants a red-tinted focus ring on the
   danger variant specifically, declare it here as :focus-visible. */

.btn--sm {
  padding: 4px 10px;
  font-size: 0.78rem;
}

/* ==========================================================================
   Tables — journal, invites, status detail.
   ========================================================================== */

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
  font-family: var(--font-mono);
  margin-bottom: var(--gap);
}

.data-table th,
.data-table td {
  text-align: left;
  padding: var(--gap-xs) var(--gap-sm);
  border-bottom: 1px solid var(--border);
}

.data-table th {
  color: var(--dim);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}

.data-table td {
  color: var(--fg);
  vertical-align: middle;
  /* Rev 20 (Opus R1 #2) — tnum on every data-table cell so price columns
     and timestamps don't jitter as digits advance. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.data-table tr:hover td {
  background-color: rgba(255, 255, 255, 0.02);
}

/* ==========================================================================
   Invite list.
   ========================================================================== */

.invite-code {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  /* v5 §3 — code IS data, not brand. Reads at --fg, paper-warm white. */
  color: var(--fg);
  letter-spacing: 0.04em;
}

.invite-used {
  color: var(--off);
  text-decoration: line-through;
}

/* ==========================================================================
   §23.5 — journal analytics: CSS-only bar chart.
   ========================================================================== */

.bar-chart {
  display: flex;
  flex-direction: column;
  gap: var(--gap-sm);
  margin: var(--gap) 0;
}

.bar-chart__row {
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  font-size: 0.85rem;
  font-family: var(--font-mono);
}

.bar-chart__label {
  min-width: 12ch;
  color: var(--dim);
  text-align: right;
  flex-shrink: 0;
}

.bar-chart__track {
  flex: 1;
  height: 20px;
  background-color: var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.bar-chart__fill {
  height: 100%;
  border-radius: var(--radius);
  transition: width var(--dur-row) var(--ease-default);
}

.bar-chart__fill--ok      { background-color: var(--live); }
.bar-chart__fill--warn    { background-color: var(--warn); }
.bar-chart__fill--bad     { background-color: var(--bad); }

.bar-chart__value {
  min-width: 5ch;
  color: var(--fg);
}

/* ==========================================================================
   Section headings + utility.
   ========================================================================== */

.section-title {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--dim);
  margin-bottom: var(--gap-sm);
}

/* §12.5 step 1c — duplicate .page-title block deleted (was at line 1059).
   Canonical definition now at the top of the file. Three variants:
   .page-title / .page-title--cockpit / .page-title--hero. */

/* Status semantics — .text-ok paints --live (lime), NOT --accent (cyan).
   Brand accent is for brand/CTA/focus only; the "ok" state earns its own
   colour. Earlier version painted .text-ok with --accent and broke the
   §3.6 brand-vs-state non-collision on every status-bearing surface. */
.text-ok   { color: var(--live); }
.text-warn { color: var(--warn); }
.text-bad  { color: var(--bad); }
.text-dim  { color: var(--dim); }
.text-mono {
  font-family: var(--font-mono);
  /* Rev 20 (Opus R1 #2) — tabular-nums on every mono surface. The cockpit
     pivots on five-decimal prices and tick timers; non-tnum digits jitter
     as values advance ("1.08543 → 1.08621" shifts columns), betraying the
     register. Same rule on .data-table cells + .rec-pair below. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.mt-gap  { margin-top: var(--gap); }
.mb-gap  { margin-bottom: var(--gap); }

/* Empty state */
.empty-state {
  text-align: center;
  padding: 2rem;
  color: var(--dim);
  font-size: 0.85rem;
}

/* Sound toggle button */
.sound-toggle {
  background: none;
  border: 1px solid var(--border);
  color: var(--dim);
  /* Rev-17 UX-stage — §22.10 touch-target floor. */
  min-height: 44px;
  min-width: 44px;
  padding: 10px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 0.8rem;
  font-family: var(--font-mono);
  transition: border-color var(--dur-tick), color var(--dur-tick);
}

.sound-toggle:hover {
  border-color: var(--fg);
  color: var(--fg);
}
/* Keyboard focus inherits the global :focus-visible outline (--ink) — the
   focus ring is one of the canonical Klein-blue slots per v5 §3. */

.sound-toggle[aria-pressed="true"] {
  /* "On" reads as state (data earned it). --fg keeps the brand budget intact. */
  border-color: var(--fg);
  color: var(--fg);
}

/* Nav links in header */
.header-nav {
  display: flex;
  align-items: center;
  gap: var(--gap);
  font-size: 0.85rem;
}

.header-nav a {
  color: var(--dim);
  /* Rev-17 UX-stage — §22.10 touch-target floor. Inline-block + padding so
     anchors hit the 44 × 44 minimum on phones. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 10px;
}

.header-nav a:hover {
  /* Subtle hover — colour shift only. The active-page treatment below
     uses the accent underline; hover earns no chrome, just clarity. */
  color: var(--fg);
  text-decoration: none;
}

/* Rev 20 (Codex R1 #1) — active page indicator. The aria-current attribute is
   the source of truth (Jinja sets it in base.html); CSS surfaces it visually.
   Bottom-border in --ok mirrors the focus-visible token so the cockpit register
   stays consistent. */
.header-nav a[aria-current="page"] {
  color: var(--fg);
  border-bottom: 2px solid var(--ink);
}

/* Run cycle button */
.btn-run-cycle {
  background: none;
  border: 1px solid var(--border);
  color: var(--dim);
  /* Rev-17 UX-stage — §22.10 touch-target floor. */
  min-height: 44px;
  min-width: 44px;
  padding: 10px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 0.8rem;
  font-family: var(--font-mono);
  transition: border-color var(--dur-tick), color var(--dur-tick);
}

.btn-run-cycle:hover,
.btn-run-cycle:focus-visible {
  border-color: var(--ink);
  color: var(--ink);
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}

/* Status detail health table — legacy classes (most call-sites have moved
   to .bk bracket atoms in step 6). Remaining .health-ok cells render the
   "last success" timestamp as --fg per v5 §3 — state vs brand separation. */
.health-ok   { color: var(--fg); }
.health-warn { color: var(--warn); }
.health-bad  { color: var(--bad); }

/* Quota bar */
.quota-bar {
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  margin-bottom: var(--gap);
  font-size: 0.85rem;
  font-family: var(--font-mono);
}

.quota-bar__label { color: var(--dim); }
.quota-bar__count { color: var(--fg); }

/* Logout form inline */
.logout-form {
  display: inline;
}

.logout-form button {
  background: none;
  border: none;
  color: var(--dim);
  cursor: pointer;
  font-size: 0.85rem;
  /* Rev-17 UX-stage — §22.10 touch-target floor (was 56×16). */
  min-height: 44px;
  min-width: 44px;
  padding: 10px 12px;
  text-decoration: underline;
}

.logout-form button:hover,
.logout-form button:focus-visible {
  color: var(--bad);
  outline: 2px solid var(--bad);
  outline-offset: 2px;
}

/* Inline radio group */
.radio-group {
  display: flex;
  gap: var(--gap);
  align-items: center;
}

.radio-group label {
  display: flex;
  align-items: center;
  gap: var(--gap-xs);
  font-size: 0.85rem;
  color: var(--fg);
  cursor: pointer;
}

/* Suggested action text (beginner mode §8.9) */
.suggested-action {
  font-size: 0.78rem;
  color: var(--dim);
  font-style: italic;
  margin-top: var(--gap-xs);
}

/* ==========================================================================
   Trade-ticket worksheet (Rev 18 — small-circle trading-safety move).
   Per-rec broker-ready ticket page at /trade-ticket/<rec_id>. Sir's audit
   ask: every field he needs to fill in to take a rec to TradingView /
   MT4 / cTrader / OANDA / IG. Derived fields show auto; risk fields
   visually distinguished (--surface background) so it's clear they're
   user-supplied.
   ========================================================================== */

.trade-ticket {
  max-width: 820px;
  margin: var(--gap) auto;
  padding: 0 var(--gap);
  font-family: var(--font-sans);
  line-height: 1.5;
}

.ticket-header {
  border-bottom: 1px solid var(--border);
  padding-bottom: var(--gap-sm);
  margin-bottom: var(--gap);
}

.ticket-header h1 {
  font-family: var(--font-mono);
  font-size: 1.1rem;
  margin: 0 0 var(--gap-xs) 0;
}

.ticket-sub {
  font-size: 0.8rem;
  margin: var(--gap-xs) 0;
}

.ticket-section {
  margin-bottom: var(--gap);
  padding: var(--gap);
  background: var(--surface);
  border: 1px solid var(--border);
}

.ticket-section h2 {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  margin: 0 0 var(--gap-sm) 0;
  color: var(--fg);
  border-bottom: 1px dotted var(--border);
  padding-bottom: var(--gap-xs);
}

/* User-fill sections get a tinted left border so it's obvious Sir owns them */
.ticket-section--user {
  border-left: 3px solid var(--warn);
}

.ticket-section--checks {
  /* v5 §3 — checks section is sectioning, not brand. --fg keeps it
     visible without burning a Klein-blue slot. */
  border-left: 3px solid var(--fg);
}

.ticket-dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--gap-xs) var(--gap);
  font-size: 0.85rem;
  margin: 0;
}

.ticket-dl dt {
  color: var(--dim);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  padding-top: 0.1rem;
}

.ticket-dl dd {
  margin: 0;
  color: var(--fg);
}

.ticket-fillin {
  color: var(--dim);
  font-style: italic;
}

/* Links inside body text must be distinguishable beyond colour alone —
   axe-core `link-in-text-block` (Rev 19 Cluster G+ closure). Underlining
   inline anchors satisfies WCAG 1.4.1 without disturbing the cockpit
   register of standalone .btn / .header-nav anchors elsewhere.
   Rev 20 (Codex R1 secondary #2) — selector extended to cover .text-dim,
   .empty-state, and journal-analytics intro <p>. The link-in-text-block
   contrast issue Codex flagged at journal_analytics.html:17 is dominated
   by the underline once it's in place. */
.ticket-fillin a,
.ticket-dl dd a,
.ticket-section a,
.empty-state a,
.text-dim a,
p.text-dim a,
.help-text a {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.ticket-tp-ladder {
  list-style: none;
  padding: 0;
  margin: var(--gap-xs) 0;
}

.ticket-tp-ladder li {
  padding: 0.2rem 0;
  font-size: 0.85rem;
}

.ticket-checklist {
  list-style: none;
  padding: 0;
  margin: 0;
}

.ticket-checklist li {
  padding: 0.5rem 0;
  border-bottom: 1px dotted var(--border);
  font-size: 0.85rem;
}

.ticket-checklist li:last-child {
  border-bottom: 0;
}

.ticket-checklist input[type="checkbox"] {
  margin-right: 0.5rem;
  /* Touch target floor §22.10 */
  width: 20px;
  height: 20px;
  vertical-align: middle;
}

.ticket-checklist label {
  cursor: pointer;
}

.warn-box {
  padding: var(--gap-sm) var(--gap);
  margin-bottom: var(--gap);
  border-left: 4px solid var(--warn);
  background: var(--surface);
  font-size: 0.85rem;
}

.warn-box--bad {
  border-left-color: var(--bad);
}

.warn-box--warn {
  border-left-color: var(--warn);
}

.warn-box strong {
  display: block;
  margin-bottom: 0.25rem;
}

.warn-box ul {
  margin: 0.4rem 0 0 1rem;
  padding: 0;
  font-size: 0.8rem;
}

.ticket-footer {
  margin-top: var(--gap);
  padding-top: var(--gap-sm);
  border-top: 1px dotted var(--border);
  font-size: 0.75rem;
}

.text-bad { color: var(--bad); }
.text-warn { color: var(--warn); }

/* §18.1 cluster-sibling line — Rev 19 Cluster G+. Renders under the rec
   row's L2 when two+ open recs share a driver_cluster_id. Indented +
   --dim so it reads as supplementary context, not a fourth primary field. */
.rec-cluster-sibling {
  margin-top: var(--gap-xs);
  margin-left: 1.5rem;
  color: var(--dim);
  font-size: 0.78rem;
  font-family: var(--font-mono);
}

/* ==========================================================================
   Rev 20 — UX cohesion R1 → R2 implementation.
   Aliveness pulse, HTMX loading + error feedback, toast, mobile breakpoint.
   ========================================================================== */

/* v5 §6 — cockpit-pulse DELETED. The aliveness pulse violated the
   "delight only when the data earned it" rule: the dot flashed on
   every poll regardless of whether anything changed. The brand-mark
   weight-pulse (§4) is now the only ambient signal. The DOM node is
   left in dashboard.html as a 0-byte hook; web.js may still toggle
   the classes harmlessly. */
.cockpit-pulse,
.cockpit-pulse--active {
  display: none;
}

/* Sonnet R1 #2 — HTMX loading state. The hx-indicator on #intel-container
   never had a CSS hook, so the indicator silently never fired. The
   .htmx-request class is added to the request element AND any
   hx-indicator targets. We dim #intel-container while a swap is in flight. */
#intel-container.htmx-request {
  opacity: 0.6;
  transition: opacity 0.12s var(--ease-arrival);
}

/* Sonnet R1 #2 — error banner. Sits above #intel-container, dim, polite,
   not a row replacement. Auto-clears after 12s via web.js. */
.htmx-error-banner {
  display: block;
  min-height: 1.2em;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--warn);
  padding: 0.15rem 0;
}

.htmx-error-banner:empty {
  display: none;
}

/* Per-panel error helper — Sonnet R1 #13 (since-last-open / exposure /
   calendar-peek silent .catch). Appended after a panel by markPanelError(). */
.panel-error {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--warn);
}

.panel-error:empty {
  display: none;
}

/* Sonnet R1 #4 — run-cycle toast. Bottom-right, dim --surface, slides in. */
.run-cycle-toast {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  background-color: var(--surface);
  border: 1px solid var(--ink);
  color: var(--fg);
  padding: 0.6rem 1rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  z-index: 100;
  max-width: 320px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s var(--ease-arrival),
              transform 0.2s var(--ease-arrival);
  pointer-events: none;
}

.run-cycle-toast--visible {
  opacity: 1;
  transform: translateY(0);
}

/* Single-flight state on the run-cycle button. */
.btn-run-cycle[disabled],
.btn-run-cycle[aria-busy="true"] {
  opacity: 0.55;
  cursor: wait;
}

/* ==========================================================================
   Codex R1 #4 — minimum-viable mobile at ≤640 px.
   The header was wrapping into a 5–7 row pile on iPhone-SE-class widths,
   pushing the disclaimer gate below the fold on first visit. One media
   query: collapse the nav to a horizontally-scrollable strip, condense
   the controls row, keep the 44 px touch-target height.
   ========================================================================== */

@media (max-width: 640px) {
  header[role="banner"] {
    flex-wrap: wrap;
    padding: 0.4rem 0.75rem;
  }

  .header-brand {
    flex: 0 0 auto;
  }

  .header-nav {
    flex: 1 1 100%;
    order: 3;
    overflow-x: auto;
    overflow-y: hidden;
    white-space: nowrap;
    gap: 0.5rem;
    padding: 0.15rem 0;
    /* Hide the horizontal scrollbar; the strip is short enough to drag. */
    scrollbar-width: none;
  }

  .header-nav::-webkit-scrollbar {
    display: none;
  }

  .header-nav a {
    font-size: 0.78rem;
    padding: 0 8px;
  }

  .header-controls {
    flex: 1 1 auto;
    gap: 0.4rem;
    flex-wrap: wrap;
  }

  .btn-mode,
  .sound-toggle {
    font-size: 0.7rem;
    padding: 0 8px;
  }

  /* Status strip — drop the inline padding so it fits one row. */
  section[role="status"] > div {
    padding: 0.25rem 0.75rem !important;
    font-size: 0.72rem !important;
  }

  /* Page-title h1 stays mono but tighter. */
  .page-title {
    font-size: 0.72rem;
  }

  /* Dashboard pre-amble strips — tighten vertical rhythm. */
  .since-last-open,
  .exposure-line,
  .calendar-peek {
    padding: 0.5rem 0.75rem;
    font-size: 0.78rem;
  }
}
