/* ============================================================
   EduMarkets — Component Styles
   ============================================================ */

/* ── Unified Header (logo + tabs + settings) ── */
.app-header {
  display: flex;
  align-items: stretch;
  padding: 0;
  /* viewport-fit=cover (set in app.html) lets content flow under the iOS
     status bar / dynamic island. Pad the top of the sticky header so its
     content sits below the system clock + battery, not behind them.
     env() resolves to 0 on devices without a safe area, so this is a
     no-op on desktop. */
  padding-top: env(safe-area-inset-top);
  /* Locked height (not min-height) — non-negotiable, the in-module
     lockup must wrap to 2 lines inside this height without growing it.
     Module-name font is tuned in .header-logo-mount below so 2-line
     wrap fits within the fixed 64px content area. Overflow clipping
     lives on .header-logo, NOT here — dropdowns (settings hamburger,
     theme toggle popovers, etc.) open below the header and would be
     clipped if we hid overflow on .app-header. */
  height: calc(64px + env(safe-area-inset-top));
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  background: var(--bg-header, var(--bg-surface));
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  flex-shrink: 0;
}
[data-theme="light"] .app-header {
  border-bottom: none;
  box-shadow: 0 1px 3px rgba(27, 58, 92, 0.08);
}

/* The header is a sticky stacking context at var(--z-sticky), so the settings
   dropdown nested inside it can't out-stack a body-level floating chart
   (var(--z-max)) or the formula panel by its own z-index. While the dropdown is
   open, lift the whole header above them so the menu is never hidden behind an
   open chart or formula dialog. Scoped to the open state, so normal stacking is
   unchanged. (:has() is already used this way elsewhere in this file.) */
.app-header:has(.settings-dropdown:not([hidden])) {
  z-index: calc(var(--z-max) + 2);
}

.header-logo {
  display: flex;
  align-items: center;
  width: calc(var(--workspace-gutter) + var(--sidebar-width));
  flex-shrink: 0;
  /* Aligns the lockup bar's CENTRE with the workspace-content divider
     line (border-left on .workspace-content in base.css), which sits at
     `var(--workspace-gutter)` from the page-left edge — the same X as
     the sidebar's left edge when one is visible.
     Math: line center = gutter + 0.5px (1px border). Bar centre wanted
     at the same X. Bar width at --led-wm-size:16 = max(2, 16*0.25) = 4px,
     so bar half-width = 2px. Required button-left X = gutter + 0.5 - 2
     = gutter - 1.5px. (Mobile/tablet media queries below override this
     padding-left, where the gutter collapses to 0 anyway.) */
  padding-left: calc(var(--workspace-gutter) - 1.5px);
  /* Clips the lockup if its content somehow exceeds the locked 64px
     header height. This is the column where the lockup lives — clipping
     here (not on .app-header) keeps settings dropdowns / popovers that
     open below the header visible. */
  overflow: hidden;
}

.header-logo-img {
  height: 50px;
  width: auto;
  display: block;
}

/* ── Tab Navigation (inside header) ── */
/* Wrap holds the scrollable nav plus the chevron scroll buttons + edge fades. */
.header-tab-nav-wrap {
  flex: 1;
  min-width: 0;
  position: relative;
  display: flex;
  align-items: stretch;
}
/* Edge-fade tied to distance-from-edge (set by JS as --tab-fade-left/right).
   The chevron buttons read the same scroll position to ramp their own opacity,
   so the fade and the chevron disappear together as the user nears the edge. */
.header-tab-nav-wrap::before,
.header-tab-nav-wrap::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  width: 36px;
  pointer-events: none;
  z-index: 1;
}
.header-tab-nav-wrap::before {
  left: 0;
  background: linear-gradient(to right, var(--bg-header, var(--bg-surface)), transparent);
  opacity: var(--tab-fade-left, 0);
}
.header-tab-nav-wrap::after {
  right: 0;
  background: linear-gradient(to left, var(--bg-header, var(--bg-surface)), transparent);
  opacity: var(--tab-fade-right, 0);
}

/* Chevron scroll buttons — distinct from header-icon cluster: subtle pill
   with a teal accent border. Bottom-anchored so it sits on the same
   vertical baseline as the tab text rather than the header midline. */
.tab-scroll-btn {
  position: absolute;
  top: auto;
  bottom: 15px;     /* tab-text center ≈ 27 px from header bottom; pill is 24 tall → bottom 15 + 12 = 27 */
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-header, var(--bg-surface));
  border: 1px solid var(--color-accent-light, var(--color-accent));
  border-radius: 999px;
  color: var(--color-accent-light, var(--color-accent));
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
  padding: 0;
  transition: background var(--transition), color var(--transition);
}
.tab-scroll-btn:hover {
  background: var(--color-accent-light, var(--color-accent));
  color: #fff;
}
.tab-scroll-btn[hidden] { display: none; }
.tab-scroll-btn--left  { left: 6px; }
.tab-scroll-btn--right { right: 16px; }   /* visible gap before right-cluster */

.header-tab-nav {
  flex: 1;
  display: flex;
  align-items: flex-end;
  gap: 0;
  overflow-x: auto;
  min-width: 0;
  scroll-behavior: smooth;
  /* Hide native scrollbar — discoverability comes from fade + chevrons. */
  scrollbar-width: none;
}
.header-tab-nav::-webkit-scrollbar { display: none; }
/* No reserved padding for the chevrons: they sit inside the 36px edge-fade
   gradient as opaque pills, so any tab text behind them is already faded.
   Skipping the padding keeps the tab strip from reflowing as the chevron
   ramps in/out near the edges. */

/* Legacy .tab-nav fallback (modules that haven't migrated yet) */
.tab-nav {
  display: flex;
  align-items: flex-end;
  gap: 0;
  padding: 0 24px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
  overflow-x: auto;
  flex-shrink: 0;
  z-index: var(--z-dropdown);
}

.tab-btn {
  padding: 20px 22px 14px;
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--transition), border-color var(--transition), background var(--transition), font-weight var(--transition);
  font-family: var(--font);
}

.tab-btn:hover { color: var(--text); background: rgba(255,255,255,0.04); }
[data-theme="light"] .tab-btn:hover { background: rgba(27,58,92,0.04); }
.tab-btn.active {
  color: var(--text);
  font-weight: 600;
  letter-spacing: 0;
  border-bottom-color: var(--color-accent-light);
}
[data-theme="light"] .tab-btn.active {
  border-bottom-color: var(--color-accent);
}

/* ── Logo trigger (module switcher) ── */
.logo-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 1px 8px 1px 0;
  border-radius: var(--r-md);
  transition: background var(--transition), opacity var(--transition);
  /* Buttons default to text-align:center, which centres the wrapped
     2-line module name inside the lockup (e.g. "FIXED INCOME"). */
  text-align: left;
}
/* In-module header lockup. The JS renderer deliberately omits a size
   preset (.xl/.lg/.md/.sm) for this surface so these custom sizes win
   the cascade — the preset rules in edumarkets-lockups.css load after
   components.css and would otherwise override variable-based sizing.
   Header is locked at 64px (see .app-header above); the 12 + 2 + 16*2*1.1
   = 49.2px lockup content fits inside the 52px logo-trigger area, so
   long module names wrap to 2 balanced lines without growing the bar. */
.header-logo-mount .lockup-led {
  --led-wm-size: 16px;
  --led-name-size: 22px;
}
.header-logo-mount .lockup-led .name {
  /* line-height bumped from 1.0 → 1.15 to give descenders (g / y / p)
     enough room inside the line-box on browsers that don't yet support
     text-box-trim (Firefox <140, etc.). text-box trim-start removes
     just the top whitespace on browsers that do. It needs the two-value
     edge form: current Edge does not parse "trim-start cap" and drops
     the whole declaration. With single-line names in the in-module
     header, the extra 3-4px never threatens the 64px locked header
     height. */
  line-height: 1.15;
  font-weight: 700;
  text-transform: none;
  text-box: trim-start cap alphabetic;
}
.logo-trigger:hover { background: rgba(255,255,255,0.04); }
[data-theme="light"] .logo-trigger:hover { background: rgba(27,58,92,0.04); }

.logo-chevron {
  font-size: 0.65rem;
  color: var(--text-muted);
  transition: transform 0.2s ease;
  line-height: 1;
  display: block;
}
.logo-trigger[aria-expanded="true"] .logo-chevron {
  transform: rotate(180deg);
}

/* ── Module Switcher Overlay ── */
.module-switcher-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(var(--blur-sm));
  -webkit-backdrop-filter: blur(var(--blur-sm));
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
}
.module-switcher-overlay[hidden] { display: none; }

@keyframes msOverlayIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes msOverlayOut {
  from { opacity: 1; }
  to   { opacity: 0; }
}
@keyframes msModalIn {
  from { opacity: 0; transform: scale(0.95) translateY(-10px); }
  to   { opacity: 1; transform: scale(1)    translateY(0); }
}
@keyframes msModalOut {
  from { opacity: 1; transform: scale(1)    translateY(0); }
  to   { opacity: 0; transform: scale(0.95) translateY(-10px); }
}
.module-switcher-overlay:not([hidden]) {
  animation: msOverlayIn 0.2s ease;
}
.module-switcher-overlay:not([hidden]) .module-switcher-modal {
  animation: msModalIn 0.2s ease;
}
.module-switcher-overlay.closing {
  animation: msOverlayOut 0.18s ease forwards;
}
.module-switcher-overlay.closing .module-switcher-modal {
  animation: msModalOut 0.16s ease forwards;
}

.module-switcher-modal {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 36px;
  max-width: 1120px;
  width: 92vw;
  /* The grouped catalogue can outgrow short laptop viewports — scroll
     inside the modal rather than clipping. overflow-x is clipped so the
     scrollbar can never introduce a horizontal pan (see the phone rule
     below for the original drift note). */
  max-height: 88vh;
  overflow-y: auto;
  overflow-x: hidden;
  box-shadow: 0 20px 48px rgba(0, 0, 0, 0.35), 0 2px 8px rgba(0, 0, 0, 0.15);
}
[data-theme="light"] .module-switcher-modal {
  box-shadow: 0 20px 48px rgba(27, 58, 92, 0.15), 0 2px 8px rgba(27, 58, 92, 0.06);
}

/* Grouped catalogue: one section per group, mirroring the participant
   dashboard so both surfaces read the same. */
.module-switcher-group + .module-switcher-group { margin-top: 22px; }

/* Matches .module-group-heading (dashboard.css): 16px sentence-case
   section header, so the two surfaces read the same. */
.module-switcher-heading {
  font-size: 16px;
  font-weight: 700;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text-secondary);
  margin: 0 0 10px;
}

.module-switcher-group-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 14px 16px;
}

.module-card {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  /* Buttons default to text-align:center, which centres the wrapped
     2-line module name inside each lockup. */
  text-align: left;
  padding: 14px 16px;
  background: var(--bg-elevated);
  border: 2px solid var(--border-subtle);
  border-radius: var(--r-md);
  cursor: pointer;
  min-height: 104px;
  /* Grid items default to min-width:auto (content min-width); a long
     lockup line could otherwise blow a narrow 1fr track out past the
     modal (see the phone rule below). */
  min-width: 0;
  transition: border-color 0.15s, background 0.15s, transform 0.15s, box-shadow 0.15s;
}
/* Catalogue tile lockup: lg preset sizes, set explicitly so this rule wins
   regardless of stylesheet load order. Matches the dashboard tiles. */
.module-switcher-catalogue .module-card .lockup-led {
  --led-name-size: 26px;
  --led-wm-size: 14px;
}
.module-switcher-catalogue .module-card .lockup-led .name {
  font-weight: 700;
  text-transform: none;
  /* See note on .header-logo-mount above — keep descenders visible.
     text-box needs the two-value edge form: current Edge does not parse
     "trim-start cap" and drops the whole declaration. */
  line-height: 1.15;
  text-box: trim-start cap alphabetic;
}
.module-card:hover:not(.module-card--disabled) {
  border-color: var(--border);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
[data-theme="light"] .module-card:hover:not(.module-card--disabled) {
  box-shadow: 0 4px 12px rgba(27, 58, 92, 0.1);
}
/* Active border picks up the module's own --bar-color (set inline by the
   switcher render in main.js). */
.module-card--active {
  border-color: var(--bar-color, var(--color-accent));
  box-shadow: 0 0 0 1px var(--bar-color, var(--color-accent)), 0 4px 12px rgba(46, 109, 164, 0.15);
}
.module-card--active:hover {
  border-color: var(--bar-color, var(--color-accent));
}
.module-card--disabled {
  opacity: 0.38;
  cursor: default;
}
.module-card-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  text-align: center;
}
.module-card-badge {
  position: absolute;
  top: 7px;
  right: 8px;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--bg-surface);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

/* ── Header search pill (opens command palette) ── */
.header-search-pill {
  display: flex;
  align-items: center;
  align-self: flex-end;
  margin-bottom: 11px;
  gap: 6px;
  height: 32px;
  padding: 0 10px;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-full, 999px);
  color: var(--text-muted);
  font-size: 13px;
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition), background var(--transition);
  margin-right: 4px;
}
.header-search-pill:hover {
  color: var(--text);
  border-color: var(--text-muted);
  background: var(--bg-elevated);
}
.header-search-pill svg {
  flex-shrink: 0;
  opacity: 0.6;
}
.header-search-kbd {
  font-size: 0.65rem;
  font-family: var(--font-mono);
  color: var(--text-muted);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  line-height: 1.3;
}

/* ── Theme toggle button (header, outside hamburger) ── */
.header-theme-btn {
  display: flex;
  align-items: center;
  align-self: flex-end;
  margin-bottom: 9px;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  color: var(--text-muted);
  font-size: 16px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition), background var(--transition);
  margin-right: 0;
}
.header-theme-btn:hover {
  color: var(--text);
  border-color: var(--border);
  background: var(--bg-elevated);
}
/* Desaturate theme emoji (yellow moon/sun → grey) for visual consistency */
.header-theme-btn,
.dash-theme-btn { filter: grayscale(1); }

/* Toggle glyph is driven by CSS off [data-theme] (set in <head> before first
   paint) so the correct icon renders immediately, with no JS-set flash on load.
   Light shows 🌙 (click to go dark); dark shows ☀ (click to go light). */
.header-theme-btn #theme-icon::before { content: "\01F319"; }
[data-theme="dark"] .header-theme-btn #theme-icon::before { content: "\002600"; }

/* ── Settings hamburger button ── */
.header-settings {
  display: flex;
  align-items: center;
  align-self: flex-end;
  margin-bottom: 7px;
  padding: 0 20px 0 8px;
  flex-shrink: 0;
  position: relative;
}

.settings-btn {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  cursor: pointer;
  padding: 9px 8px;
  transition: background var(--transition), border-color var(--transition);
}
.settings-btn:hover {
  background: var(--bg-elevated);
  border-color: var(--border);
}

.hamburger-line {
  display: block;
  width: 18px;
  height: 0;
  border-top: 2px solid var(--text-muted);
  transition: border-color var(--transition);
}
.settings-btn:hover .hamburger-line { border-top-color: var(--text); }

/* ── Settings dropdown ──
   Institutional editorial register: 6px radius, single soft shadow,
   no per-item stagger, no uppercase eyebrow labels, no per-row icons.
   Pattern matches Stripe / Linear / Vercel / GitHub / AWS dropdowns. */
.settings-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 260px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  z-index: var(--z-sidebar);
  overflow-x: hidden;
  overflow-y: auto;
  max-height: calc(100vh - 80px);
  padding: 6px 0;
  animation: dropdown-appear 120ms ease;
}
[data-theme="light"] .settings-dropdown {
  box-shadow: 0 4px 14px rgba(27, 58, 92, 0.10);
}
/* Opacity-only entrance. A translateY here promotes the panel to a GPU
   layer that rasterizes the 1px teal accent rule under the name at sub-pixel
   positions while it slides, so the line renders as a soft ~2px band and then
   snaps crisp when the animation ends — a visible "thicker line" flash. Fading
   in place keeps every hairline painted at its final, crisp position. */
@keyframes dropdown-appear {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.settings-dropdown[hidden] { display: none; }

/* ── User identity block ──
   Single editorial line — name in Newsreader serif, with a teal
   accent rule directly below (the first divider sibling, restyled).
   No avatar disc, no role line. */
.settings-user {
  padding: 12px 16px 10px;
  min-width: 0;
}
.settings-user-name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 1.02rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--text);
  min-width: 0;
}
.settings-user-name-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.settings-user + .settings-divider {
  background: var(--sidebar-accent);
  margin: 0 0 6px;
}

/* ── Demo badge in settings dropdown — matches the topbar .dash-demo-chip ── */
.settings-demo-badge {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  padding: 2px 8px;
  font-family: 'Inter', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(245, 158, 11, 0.9);
  background: rgba(245, 158, 11, 0.10);
  border: 1px solid rgba(245, 158, 11, 0.25);
  border-radius: 3px;
}
[data-theme="light"] .settings-demo-badge {
  color: #b45309;
  background: rgba(245, 158, 11, 0.10);
  border-color: rgba(245, 158, 11, 0.30);
}

/* ── Custom Date Picker ── */
.em-datepicker-wrap {
  position: relative;
  display: inline-block;
  width: 100%;
}
.em-dp-display {
  width: 100%;
  padding: 14px 18px;
  padding-right: 40px;
  font-size: 14px;
  font-family: 'Inter', sans-serif;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.9);
  border-radius: 12px;
  box-sizing: border-box;
  cursor: pointer;
  transition: border-color 0.2s;
}
.em-dp-display:hover { border-color: rgba(255,255,255,0.18); }
.em-dp-display::placeholder { color: rgba(255,255,255,0.25); }
.em-dp-icon {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 15px;
  opacity: 0.4;
  cursor: pointer;
  pointer-events: auto;
}
.em-dp-popup {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 9999;
  width: 280px;
  background: #1c1e23;
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 14px;
  padding: 14px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.5);
  font-family: 'Inter', sans-serif;
  animation: em-dp-in 0.15s ease;
}
@keyframes em-dp-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.em-dp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.em-dp-title {
  font-size: 13px;
  font-weight: 600;
  color: rgba(255,255,255,0.9);
}
.em-dp-nav {
  background: none;
  border: none;
  color: rgba(255,255,255,0.5);
  font-size: 20px;
  cursor: pointer;
  padding: 2px 8px;
  border-radius: 6px;
  line-height: 1;
  transition: background 0.15s, color 0.15s;
}
.em-dp-nav:hover {
  background: rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.9);
}
.em-dp-dow {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  text-align: center;
  margin-bottom: 4px;
}
.em-dp-dow span {
  font-size: 10px;
  font-weight: 600;
  color: rgba(255,255,255,0.3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 4px 0;
}
.em-dp-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.em-dp-day {
  background: none;
  border: none;
  color: rgba(255,255,255,0.7);
  font-size: 12px;
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
  margin: 0 auto;
}
.em-dp-day:hover:not(.em-dp-empty) {
  background: rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.95);
}
.em-dp-day.em-dp-today {
  border: 1px solid rgba(59,130,246,0.4);
  color: #60a5fa;
  font-weight: 600;
}
.em-dp-day.em-dp-selected {
  background: var(--color-accent);
  color: #fff;
  font-weight: 600;
}
.em-dp-day.em-dp-empty { cursor: default; }
.em-dp-actions {
  display: flex;
  gap: 6px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(255,255,255,0.06);
}
.em-dp-action {
  flex: 1;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.06);
  color: rgba(255,255,255,0.5);
  font-size: 11px;
  font-family: 'Inter', sans-serif;
  font-weight: 500;
  padding: 5px 0;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.em-dp-action:hover {
  background: rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.85);
}

/* Date picker — light mode */
[data-theme="light"] .em-dp-display {
  background: rgba(0,0,0,0.03);
  border-color: rgba(0,0,0,0.1);
  color: rgba(0,0,0,0.85);
}
[data-theme="light"] .em-dp-display:hover { border-color: rgba(0,0,0,0.2); }
[data-theme="light"] .em-dp-display::placeholder { color: rgba(0,0,0,0.3); }
[data-theme="light"] .em-dp-icon { opacity: 0.35; }
[data-theme="light"] .em-dp-popup {
  background: #ffffff;
  border-color: rgba(0,0,0,0.1);
  box-shadow: 0 12px 40px rgba(0,0,0,0.12);
}
[data-theme="light"] .em-dp-title { color: rgba(0,0,0,0.85); }
[data-theme="light"] .em-dp-nav { color: rgba(0,0,0,0.4); }
[data-theme="light"] .em-dp-nav:hover { background: rgba(0,0,0,0.05); color: rgba(0,0,0,0.8); }
[data-theme="light"] .em-dp-dow span { color: rgba(0,0,0,0.3); }
[data-theme="light"] .em-dp-day { color: rgba(0,0,0,0.65); }
[data-theme="light"] .em-dp-day:hover:not(.em-dp-empty) { background: rgba(0,0,0,0.05); color: rgba(0,0,0,0.9); }
[data-theme="light"] .em-dp-day.em-dp-today { border-color: rgba(59,130,246,0.4); color: #2563eb; }
[data-theme="light"] .em-dp-day.em-dp-selected { background: var(--color-accent); color: #fff; }
[data-theme="light"] .em-dp-actions { border-top-color: rgba(0,0,0,0.06); }
[data-theme="light"] .em-dp-action {
  background: rgba(0,0,0,0.03);
  border-color: rgba(0,0,0,0.08);
  color: rgba(0,0,0,0.45);
}
[data-theme="light"] .em-dp-action:hover { background: rgba(0,0,0,0.06); color: rgba(0,0,0,0.8); }

/* (Section labels removed — references use hairline-only grouping.
   .settings-section-label is no longer emitted in markup. The class
   is kept reserved-but-empty in case a future dropdown wants it.) */
.settings-section-label { display: none; }

.settings-item {
  display: flex;
  align-items: center;
  padding: 7px 16px;
  font-size: 0.86rem;
  color: var(--text-secondary);
  text-decoration: none;
  transition: background var(--transition), color var(--transition);
  cursor: pointer;
  background: none;
  border: none;
  width: 100%;
  text-align: left;
  font-family: var(--font);
}
.settings-item:hover { background: var(--bg-elevated); color: var(--text); text-decoration: none; }
.settings-item.module-active { color: var(--color-accent-light); font-weight: 600; }
/* (Sign-out no longer styled red. Sits at the bottom isolated by a
   hairline — same visual weight as every other row. References
   converge on this; red text in idle dropdowns is a known UX
   anti-pattern. Class kept for any external CSS that targets it.) */

/* (Per-row icons removed — references either use no icons or
   icons on ~1 in 4 rows. Class kept hidden so existing markup
   that emits .settings-item-icon spans degrades cleanly.) */
.settings-item-icon { display: none; }

.settings-divider {
  height: 1px;
  /* Use --border (not --border-subtle) so the regular dividers have a
     definite, consistent weight regardless of which buttons sit above
     and below. --border-subtle was rendering as visually different
     between dividers depending on the contrast against neighbouring
     hover-state surfaces. */
  background: var(--border);
  margin: 6px 0;
}

/* (Per-item stagger animation removed — the panel is one object,
   not a parade of children. Stripe / Linear / Vercel all fade the
   panel as a whole.) */

/* ── Tab Panels ── */
.tab-panel { display: none; }
.tab-panel.active {
  display: block;
  flex-shrink: 0;
  /* Fallback fade-in for browsers without the View Transitions API
     (Firefox < 136, Safari < 18). Modern browsers use document.startViewTransition()
     in js/main.js, which provides a true cross-fade between the outgoing and
     incoming panel; the @supports block below disables this keyframe there to
     avoid double-animating. */
  animation: tabPanelFadeIn 0.2s cubic-bezier(0.05, 0.7, 0.1, 1.0);
}

@keyframes tabPanelFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@supports (view-transition-name: none) {
  .tab-panel.active { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .tab-panel.active { animation: none; }
}

/* Reference tab: fills viewport height, content scrolls internally.
   Sub-tabs & sidebar never move — only the text area scrolls. */
.tab-panel.active:has(.ref-sub-tabs-wrap) {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.tab-panel.active:has(.ref-sub-tabs-wrap) > .tab-content-area {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  padding-bottom: 0;
}
.tab-panel.active:has(.ref-sub-tabs-wrap) > .tab-content-area > div {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ── Params Sidebar ── */
.params-sidebar {
  width: 0;
  min-width: 0;
  overflow: hidden;
  background: var(--bg-surface);
  flex-shrink: 0;
  align-self: stretch;
  /* no transition — instant collapse avoids layout reflow jank */
  display: flex;
  flex-direction: column;
  /* Divider line lives on .workspace-content (base.css) — single source of truth. */
}

.params-sidebar.visible {
  width: var(--sidebar-width);
  min-width: var(--sidebar-width);
  overflow-y: auto;
}

/* Collapsed by user while still on trade/sensitivity tab */
.workspace.sidebar-collapsed .params-sidebar.visible {
  width: 0;
  min-width: 0;
  overflow: hidden;
}

#params-content,
#fx-params-content,
#mm-params-content,
#futures-params-content,
#commodities-params-content,
#digital-assets-params-content,
#market-making-params-content {
  padding: 20px 16px;
  min-width: var(--sidebar-width);  /* prevents reflow during animation */
}

.sidebar-data-asof {
  display: none;
  padding: 8px 16px;
  font-size: 0.65rem;
  color: var(--text-muted);
}
.sidebar-data-asof.active {
  display: block;
  margin-top: auto;
}
.sidebar-data-asof.active + .sidebar-disclaimer {
  margin-top: 0;
}

.sidebar-disclaimer {
  padding: 20px 16px 16px;
  margin-top: auto;
  font-size: 0.65rem;
  line-height: 1.5;
  color: var(--text-muted);
  border-top: 1px solid var(--border);
  min-width: var(--sidebar-width);
}

.params-sidebar-inner {
  padding: 20px 16px;
}

.params-sidebar-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  margin-bottom: 16px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.params-section {
  margin-bottom: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border-subtle);
}
.params-section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }

/* Sidebar toggle button — floats on workspace border */
.sidebar-toggle-btn {
  position: absolute;
  top: 20px;
  left: calc(var(--workspace-gutter) + var(--sidebar-width) - 12px);
  z-index: var(--z-dropdown);
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  transition: background var(--transition), border-color var(--transition), color var(--transition);
  font-family: var(--font);
  line-height: 1;
}
.sidebar-toggle-btn:hover {
  background: var(--bg-elevated);
  border-color: var(--color-accent);
  color: var(--color-accent-light);
}
/* The base rule above sets display: flex, which overrides the UA stylesheet's
   default [hidden] { display: none }. Re-assert it so JS can hide the toggle
   on tabs without a sidebar (Handbook, Exercises, etc.). */
.sidebar-toggle-btn[hidden] { display: none; }
.workspace.sidebar-collapsed .sidebar-toggle-btn,
.workspace:not(:has(.params-sidebar.visible)) .sidebar-toggle-btn {
  left: calc(var(--workspace-gutter) - 12px);
}
.workspace { position: relative; }

/* ── Cards ── */
.card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 20px;
  transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
}
.card:hover {
  border-color: var(--border);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  background: color-mix(in srgb, var(--sidebar-accent) 2%, var(--bg-surface));
}
[data-theme="light"] .card:hover {
  background: color-mix(in srgb, var(--sidebar-accent) 1%, var(--bg-surface));
  box-shadow: 0 2px 8px rgba(27, 58, 92, 0.08);
}

.card-sm { padding: 16px 18px; }

.card-title {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 6px;
}

.card-value {
  font-size: 1.45rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: lining-nums tabular-nums;  /* A1: lining figures; A2: tabular alignment */
  font-family: var(--font-mono);                   /* A2: monospaced for aligned numerals */
}

/* ── Interactive-tool card title + body (shared) ──
   The global .card provides the surface; these give a readable, non-uppercase
   heading and prose for pricer/tool sections, unlike the muted/small .card-title.
   Promoted from the Futures module so every module shares one definition; the
   .fut-card-* aliases keep existing Futures markup working unchanged. */
.tool-card-title,
.fut-card-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 12px;
  flex-wrap: wrap;
}
.tool-card-body,
.fut-card-body {
  color: var(--text);
  font-size: 0.95rem;
  line-height: 1.65;
}
.tool-card-body p,            .fut-card-body p            { margin: 0 0 12px; color: var(--text); }
.tool-card-body p:last-child, .fut-card-body p:last-child { margin-bottom: 0; }
.tool-card-body ul,           .fut-card-body ul,
.tool-card-body ol,           .fut-card-body ol           { margin: 8px 0 12px; padding-left: 22px; color: var(--text); }
.tool-card-body li,           .fut-card-body li           { margin-bottom: 6px; }
.tool-card-body strong,       .fut-card-body strong       { color: var(--text); }
.tool-card-body h4,           .fut-card-body h4           { margin: 16px 0 8px; color: var(--text); font-size: 0.95rem; font-weight: 700; }
.tool-card-body h4:first-child, .fut-card-body h4:first-child { margin-top: 0; }

/* Sub-panel heading inside a card body — one level below the card/section
   title. Labels grouped spec-card panels (e.g. futures Hedge / STIR cards).
   Accent colour sets it apart from the main heading. */
.spec-card-title {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--color-accent-text);
  margin-bottom: 8px;
}

/* ── Metrics header (Valuation label + model badge) ── */
.metrics-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.metrics-section-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.card-value.positive { color: var(--positive); }
.card-value.negative { color: var(--negative); }

/* ── Metric row ── */
.metrics-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}

/* ── Buttons ── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 18px;
  font-size: 0.9rem;
  font-weight: 500;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition), box-shadow var(--transition), transform var(--transition), opacity var(--transition);
  font-family: var(--font);
  white-space: nowrap;
  text-align: center;
}

.btn-primary {
  background: var(--color-accent);
  color: #fff;
  border-color: var(--color-accent);
}
.btn-primary:hover { background: var(--color-accent-light); box-shadow: 0 2px 8px rgba(46, 109, 164, 0.35); }

.btn-secondary {
  background: color-mix(in srgb, var(--color-accent) 22%, var(--bg-elevated));
  color: var(--text);
  border-color: var(--color-accent);
  font-weight: 600;
}
.btn-secondary:hover {
  background: color-mix(in srgb, var(--color-accent) 38%, var(--bg-elevated));
  border-color: var(--color-accent-light);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--color-accent) 30%, transparent);
}

.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border-color: var(--text-muted);
  font-weight: 600;
}
.btn-ghost:hover {
  background: var(--bg-elevated);
  color: var(--text);
  border-color: var(--text-secondary);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
}

.btn-sm { padding: 6px 12px; font-size: 0.82rem; }
/* Modifier for buttons sitting inline next to .sidebar-input / .sidebar-select.
   Native form inputs and CSS-styled buttons resolve `line-height: normal`
   slightly differently (browser-internal font metrics vs CSS box layout),
   so theoretical padding-matching always leaves a 1-2px gap. We pin an
   explicit height instead — `box-sizing: border-box` is set globally, so
   this is the final rendered height. Adjust the px value here if the
   inputs' rendered height changes platform-wide. */
.btn-match-input {
  padding: 0 14px;
  font-size: 0.88rem;
  line-height: 1;
  height: 34px;
}
/* "Advanced" disclosure for hiding power-user controls (e.g., random seed)
   beneath a primary action. Pairs with the "Simulate new path" button
   pattern across the platform. */
.sb-advanced > summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.78rem;
  padding: 4px 0;
  user-select: none;
  list-style: none;
}
.sb-advanced > summary::before {
  content: "\25B8 ";
  display: inline-block;
  transition: transform 0.15s;
}
.sb-advanced[open] > summary::before { transform: rotate(90deg); }
.sb-advanced > summary:hover { color: var(--text-secondary); }
.sb-advanced[open] > summary { margin-bottom: 4px; }
.btn-icon { padding: 7px; width: 34px; height: 34px; justify-content: center; }
.btn:disabled, .btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
/* Same look as :disabled but hoverable, so a data-tip can explain why the
   action is unavailable. The click must be guarded in JS. */
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}
.btn:active:not(:disabled):not([aria-disabled="true"]) {
  transform: scale(0.97);
  opacity: 0.85;
}

/* ── Generic subtab nav (used across all modules) ── */
/*
   The hedge-fund module historically owned an .hf-scenario-btn class with
   identical primary-tier styling. The selectors below cover both so there is
   one canonical rule for the pill button look. The hedge-fund module retains
   .hf-scenario-btn--sub and .hf-scenario-btn--leaf modifiers for nested
   subtab tiers (those live in hedge-fund.css).
*/
/* Underline-on-active text-tab pattern. Replaces the prior pill design.
   Rank in multi-tier callers (.tier-row + .tier-row--sub / --leaf, see
   hedge-fund.css) is communicated by size + weight + indent, never by
   hue. Same primitive across all modules. */
.subtab-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0 1.5rem;
  border-bottom: 1px solid var(--border);
  margin: 0 0 1.25rem;
  padding: 0;
}

.subtab-btn,
.hf-scenario-btn {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  border-radius: 0;
  margin: 0;
  padding: 0.5rem 0 0.625rem;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  line-height: 1.3;
  cursor: pointer;
  transition: color 0.12s ease;
}
.subtab-btn:hover,
.hf-scenario-btn:hover {
  color: var(--text);
}
.subtab-btn:focus-visible,
.hf-scenario-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.subtab-btn.selected,
.hf-scenario-btn.selected {
  color: var(--text);
  font-weight: 600;
}
.subtab-btn.selected::after,
.hf-scenario-btn.selected::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  bottom: -1px;                  /* overlap the shared rule */
  height: 2px;
  background: var(--color-accent);
  border-radius: 1px;
}

/* Multi-tier callers use .tier-row; single-tier callers use .subtab-nav.
   Same primitive in row form. */
.tier-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  border-bottom: 1px solid var(--border);
  gap: 0 1.5rem;
  margin: 0 0 1.25rem;
  padding: 0;
}

/* Spacing between stacked cards inside a subtab content area, matching the
   hedge-fund pattern. Descendant selector covers cards nested in
   per-subtab wrapper divs (e.g. fut-spec-header) — a trailing 16px on the
   last card is harmless breathing room. */
.subtab-content .card {
  margin-bottom: 16px;
}

/* ── Form controls ── */
.form-group {
  margin-bottom: 14px;
}

.form-label {
  display: block;
  font-size: 0.84rem;
  font-weight: 500;
  color: var(--text);
  margin-bottom: 5px;
}

.form-control {
  width: 100%;
  padding: 9px 12px;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text);
  font-size: 0.925rem;
  font-family: var(--font);
  transition: border-color var(--transition);
  -webkit-appearance: none;
  appearance: none;
}
input[type="number"].form-control { font-family: var(--font-mono); }

.form-control:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 1.5px rgba(46, 109, 164, 0.15);
}

/* B1: Inline input + action button layout (e.g., rate input + "Par" button) */
.input-with-action {
  display: flex;
  gap: 6px;
  align-items: center;
}
.input-with-action .form-control,
.input-with-action .sidebar-input { flex: 1; min-width: 0; }

/* D1: "Par" solver button — inline, compact */
.btn-par {
  white-space: nowrap;
  padding: 6px 9px;
  font-size: 0.72rem;
  font-weight: 600;
  border-radius: var(--r-sm);
  border: 1px solid var(--color-accent);
  background: transparent;
  color: var(--color-accent-light);
  cursor: pointer;
  font-family: var(--font);
  transition: background var(--transition);
  flex-shrink: 0;
}
.btn-par:hover { background: rgba(46,109,164,0.12); }

.form-control::placeholder { color: var(--text-muted); }

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

/* Field hint — formatted display below a number input */
.field-hint {
  display: block;
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}

/* Range slider */
input[type="range"] {
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 5px;
  background: var(--border);
  border-radius: 3px;
  outline: none;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  background: var(--color-accent);
  border-radius: 50%;
  cursor: pointer;
  border: 2px solid var(--bg-surface);
}
input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  background: var(--color-accent);
  border-radius: 50%;
  border: 2px solid var(--bg-surface);
  cursor: pointer;
}

/* ── Shared sidebar slider (signed shock sliders) ──────────────────────────
   Used by the rates modules (FI / Credit / IR-Options), Futures and TRS.
   Promoted here from the module stylesheets to remove triplication. The
   equity pricer keeps its own #eq-params-content-scoped variant, which wins
   on specificity and is intentionally left untouched. */
.sidebar-slider {
  width: 100%;
  margin: 2px 0;
  accent-color: var(--color-accent);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 3px;
  outline: none;
}
[data-theme="light"] .sidebar-slider {
  background: rgba(0, 0, 0, 0.1);
}

/* Thumb styling */
.sidebar-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  border: none;
  cursor: pointer;
  transition: background 0.15s;
}
.sidebar-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  border: none;
  cursor: pointer;
  transition: background 0.15s;
}

/* Positive → green thumb */
.sidebar-slider.slider-positive::-webkit-slider-thumb {
  background: #34d399;
}
.sidebar-slider.slider-positive::-moz-range-thumb {
  background: #34d399;
}

/* Negative → red thumb */
.sidebar-slider.slider-negative::-webkit-slider-thumb {
  background: #f87171;
}
.sidebar-slider.slider-negative::-moz-range-thumb {
  background: #f87171;
}

[data-theme="light"] .sidebar-slider::-webkit-slider-thumb {
  background: rgba(0, 0, 0, 0.35);
}
[data-theme="light"] .sidebar-slider::-moz-range-thumb {
  background: rgba(0, 0, 0, 0.35);
}
[data-theme="light"] .sidebar-slider.slider-positive::-webkit-slider-thumb {
  background: #059669;
}
[data-theme="light"] .sidebar-slider.slider-positive::-moz-range-thumb {
  background: #059669;
}
[data-theme="light"] .sidebar-slider.slider-negative::-webkit-slider-thumb {
  background: #dc2626;
}
[data-theme="light"] .sidebar-slider.slider-negative::-moz-range-thumb {
  background: #dc2626;
}

/* Remove number input spinners globally */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"] { -moz-appearance: textfield; }

.slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.slider-row .form-control { width: 70px; text-align: right; }

/* Checkbox / toggle */
.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 0;
}
.toggle-label {
  font-size: 0.9rem;
  color: var(--text-secondary);
}

.toggle {
  position: relative;
  display: inline-block;
  width: 38px;
  height: 22px;
}
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--border);
  border-radius: 22px;
  transition: background var(--transition);
}
.toggle-slider::before {
  content: '';
  position: absolute;
  height: 16px; width: 16px;
  left: 3px; top: 3px;
  background: white;
  border-radius: 50%;
  transition: transform var(--transition);
}
.toggle input:checked + .toggle-slider { background: var(--color-accent); }
.toggle input:checked + .toggle-slider::before { transform: translateX(16px); }

/* Radio group */
.radio-group {
  display: flex;
  gap: 2px;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 3px;
}
.radio-group label {
  flex: 1;
  text-align: center;
  padding: 6px 8px;
  font-size: 0.85rem;
  font-weight: 500;
  border-radius: 6px;
  cursor: pointer;
  color: var(--text-muted);
  transition: background var(--transition), color var(--transition);
}
.radio-group input[type="radio"] { display: none; }
.radio-group input[type="radio"]:checked + label {
  background: var(--color-accent);
  color: white;
}

/* B3: Perspective toggle — semantic colors for bilateral contract direction */
/* Receive USD = blue (default accent, you receive USD fixed = long rates) */
/* Pay USD     = red  (you pay USD fixed = short rates) */
#tb-p-pay:checked + label {
  background: var(--negative);
  color: white;
}

/* ── Section divider ── */
.section-divider {
  border: none;
  border-top: 1px solid var(--border-subtle);
  margin: 20px 0;
}

/* ── Callout / insight box ──
   Type-only treatment: hairline rules above and below, no left bar, no
   tinted background. The container stops carrying state — the heading
   colour does, sparingly, when the variant warrants it. */
.callout {
  border: 0;
  border-top: 1px solid var(--border-subtle);
  border-bottom: 1px solid var(--border-subtle);
  background: transparent;
  border-radius: 0;
  padding: 14px 0;
  font-size: 0.925rem;
  color: var(--text-secondary);
  margin: 16px 0;
}

.callout.positive,
.callout.negative,
.callout.info { background: transparent; }

.callout-title {
  font-weight: 600;
  font-size: 0.95rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  margin-bottom: 4px;
}
.callout.positive .callout-title { color: var(--positive); }
.callout.negative .callout-title { color: var(--negative); }
.callout.info     .callout-title { color: var(--info); }

/* ── Badge ──
   Radius 20px → 4px. The pill shape + outlined chip + tinted background
   was the v0/Lovable default (T10 + T18). At 4px it reads as a small
   labelled tag — finance-document register, not Notion-template. */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: 4px;
  border: 1px solid;
}
.badge-default  { color: var(--text-muted);  border-color: var(--border); }
.badge-accent   { color: var(--color-accent-light); border-color: var(--color-accent); background: rgba(46,109,164,0.1); }
.badge-positive { color: var(--positive); border-color: var(--positive); background: var(--positive-bg); }
.badge-negative { color: var(--negative); border-color: var(--negative); background: var(--negative-bg); }
.badge-warning  { color: var(--warning);  border-color: var(--warning);  background: rgba(245,158,11,0.08); }  /* E1: static data */
.badge-live     { color: var(--positive); border-color: var(--positive); background: var(--positive-bg); }    /* live market data */

/* ── Data table ── */
.data-table-wrap {
  overflow-x: auto;
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}

/* The header carries no fill band: the 2px bottom rule plus the weight and
   colour do the separating. Re-adding a background here flattens that back
   into the boxed look the rule replaced. The mobile sticky-first-column rule
   (in the 768px block) paints its own opaque background and is unaffected. */
.data-table thead th {
  padding: 8px 10px;                               /* D5: compact header height (was 11px 14px) */
  text-align: right;
  font-weight: 600;
  font-size: 0.78rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-secondary);
  border-bottom: 2px solid var(--border);
  white-space: nowrap;
}
.data-table thead th:first-child { text-align: left; }

.data-table tbody td {
  padding: 6px 10px;                               /* D5: compact row height (was 9px 14px) */
  text-align: right;
  color: var(--text);
  border-bottom: 1px solid var(--border-subtle);
  font-variant-numeric: lining-nums tabular-nums;  /* A1: lining + tabular numerals */
  white-space: nowrap;
  transition: background var(--transition);
}
.data-table tbody td:first-child { text-align: left; }
/* Opt-in monospace for numeric VALUES only. Headers are text labels, so they
   stay in the proportional (Inter) font even over numeric columns — only the
   numbers themselves are mono. Non-numeric tables inherit the proportional font. */
.data-table td.num,
.data-table tbody tr td[class*="num"] {
  font-family: var(--font-mono);
}

/* Two-column key/value breakdown. A tall 2-column data-table stretched to the
   full card width pushes its values far from their labels. This modifier
   reflows the body into two balanced columns so each value sits beside its
   label and the full width is used; the header band spans both columns.
   Collapses back to one column when the card is narrow. */
.data-table.data-table--kv2 {
  display: block;
  column-count: 2;
  column-gap: 24px;
}
.data-table--kv2 thead { display: block; column-span: all; }
.data-table--kv2 thead tr { display: flex; }
.data-table--kv2 thead th { flex: 1 1 0; }
.data-table--kv2 tbody { display: block; }
.data-table--kv2 tbody tr {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  break-inside: avoid;
  border-bottom: 1px solid var(--border-subtle);
}
.data-table--kv2 tbody td { border-bottom: none; padding: 6px 10px; }
.data-table--kv2 tbody td:first-child { white-space: normal; }   /* labels may wrap */
@media (max-width: 768px) {
  .data-table.data-table--kv2 { column-count: 1; }
}

/* Cap a small key/value table so its label and value stay close instead of
   stretching to the full card width (used where there are too few rows to
   split into two columns). */
.data-table-wrap.data-table-wrap--narrow { max-width: 34rem; }

/* Commodities tool tables read a step larger than the platform default 0.85rem
   (scoped to the tool cards, so handbook/editorial tables are untouched). */
#commodities-workspace .tool-card-body .data-table { font-size: 0.95rem; }

/* Learn-chapter tables read as editorial prose, not data — they use the
   handbook serif (Newsreader) and drop the proportional-overrides any
   .num-class data table might inherit. */
.learn-chapter-body .data-table td,
.learn-chapter-body .data-table th,
.learn-example .data-table td,
.learn-example .data-table th {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
}

/* Drop the uppercase / letter-spaced / muted-grey thead style for handbook
   tables — that's the T09 AI tell ("FEATURE | TRADITIONAL FUND | HEDGE
   FUND" in pricer-style caps). Editorial tables get a mixed-case bold
   header at body weight. */
.learn-chapter-body .data-table thead th,
.learn-example .data-table thead th {
  font-size: 1.05rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  padding: 12px 14px;
}
.learn-chapter-body .data-table tbody td,
.learn-example .data-table tbody td {
  font-size: 1.05rem;
  color: var(--text);
  padding: 12px 14px;
  white-space: normal;
}

/* ── Spec R5 — handbook table de-chrome ──────────────────────────
   Textbook tables: header weight-only, no row separators, no hover
   stripe, no wrap card frame. Sources cited via .table-source.
   Tables fit within the prose measure by allowing both header AND
   body cells to wrap (the global .data-table thead th nowrap rule
   is what was forcing tables wider than the cap). Tables that
   genuinely need more width opt in via class="data-table-wrap
   full-bleed". */
.learn-chapter-body .data-table-wrap {
  border: 0;
  border-radius: 0;
  margin: var(--space-subsection) 0;
  max-width: var(--measure-prose, 46rem);
}
.learn-chapter-body .data-table-wrap.full-bleed {
  max-width: none;
}
/* Wide-table escape: tables that need more horizontal room than the
   46rem prose cap. Works in tandem with the .learn-content:has(...)
   rule above which lifts the parent's max-width when this class is
   present — together they let the table fill the available chapter
   column without triggering horizontal scroll. */
.learn-chapter-body .data-table-wrap.data-table-wide {
  max-width: none;
  width: 100%;
}
.learn-chapter-body .data-table {
  table-layout: auto;          /* shrink columns to fit available width */
}
.learn-chapter-body .data-table thead th {
  background: transparent;
  border-bottom: 1px solid var(--border);
  text-align: left;
  white-space: normal;         /* allow header wrapping — cap can take effect */
}
.learn-chapter-body .data-table thead th:first-child { text-align: left; }
[data-theme="light"] .learn-chapter-body .data-table thead th {
  background: transparent;
}
.learn-chapter-body .data-table tbody td {
  border-bottom: 0;
  text-align: left;
}
/* Inline-formatting elements (<strong>, <em>, etc.) inside a table cell
   inherit from the cell (1.05rem, Newsreader serif), not from the body
   cascade (1.125rem, Inter). Two problems were appearing in handbook
   tables: bold first-column entries rendered 1px larger than their
   plain-text neighbours, and italic words ("before" / "with" / "after")
   showed Inter italic in the middle of Newsreader serif prose. !important
   is required to beat the iron cascade's font-size / font-family
   !important. */
.learn-chapter-body .data-table :is(strong, em, b, i, u, span, a) {
  font-size: inherit !important;
  font-family: inherit !important;
  line-height: inherit !important;
}
/* Subtle row banding for handbook tables. The textbook-style hairline-
   only separation reads cleanly for 2-3 columns but loses the eye on
   5-7 column tables (conventions / RFR summary etc.) where the reader
   has to track a single row across many cells. A very faint alternating
   tint gives a visual track without re-introducing the SaaS-table
   "striped" look — colour-mix with 2% accent over the surface keeps
   the effect under the threshold of overt decoration. */
.learn-chapter-body .data-table tbody tr:nth-child(even) td {
  background: color-mix(in srgb, var(--text) 3%, transparent);
}
/* Numeric cells stay right-aligned via the .num opt-in (existing). */
.learn-chapter-body .data-table tbody td.num,
.learn-chapter-body .data-table thead th.num { text-align: right; }
.learn-chapter-body .data-table tbody tr:hover td,
.learn-chapter-body .data-table tbody tr:hover td:first-child {
  background: transparent;
  box-shadow: none;                          /* drop the SaaS hover stripe */
}
.learn-chapter-body .data-table caption {
  caption-side: top;
  text-align: left;
  font-family: var(--font);
  font-size: var(--text-meta);
  font-style: italic;
  color: var(--text-secondary);
  padding-bottom: 8px;
}
.learn-chapter-body .table-source,
.learn-chapter-body .figure-source,
.learn-chapter-body .formula-source {
  margin: 4px 0 var(--space-subsection);
  font-family: var(--font);
  font-size: var(--text-meta);
  font-style: italic;
  color: var(--text-muted);
}

/* ── Teaching interactive register ──────────────────────────────────
   Opt-in via .learn-interactive on an interactive tab's content root.
   Mirrors the handbook teaching-table register above (.learn-chapter-body
   .data-table) in size + contrast + mixed-case headers, but KEEPS the
   pricer numeric treatment (tabular mono via .num) instead of the editorial
   Newsreader serif, and keeps the table chrome (header band, row rules).
   Purpose: interactive result tabs read as a lesson, not a desk readout.
   First adopter: counterparty-risk. Other modules opt in by adding the class.
   Nothing here touches the un-gated global .data-table / .chart-section-header
   / --text-* tokens, so every non-adopting module renders unchanged.
   ──────────────────────────────────────────────────────────────────── */

/* Tables: a restrained teaching size + full contrast, mixed-case headers (the
   global thead th is already text-transform:none; only the muted colour and
   compact D5 size read as a desk table). The original teaching bump to 1.05rem
   body / 1rem header with 9-10px row padding read too large with too much row
   spacing, so it steps back toward the compact base (Robert, 2026-07-26). */
.learn-interactive .data-table thead th {
  font-size: 0.92rem;              /* teaching bump was 1rem; base is 0.75rem */
  color: var(--text);              /* was --text-muted */
  padding: 8px 12px;
}
.learn-interactive .data-table tbody td {
  font-size: 0.95rem;              /* teaching bump was 1.05rem; base is 0.85rem */
  color: var(--text);              /* value cells full contrast (was --text-table) */
  padding: 6px 12px;
}
/* Label column a shade quieter for scanning, but not muted. */
.learn-interactive .data-table tbody td:first-child { color: var(--text-secondary); }
/* Headline row: bold + full contrast, still a row (NO size bump, NO card). */
.learn-interactive .data-table tr.ccr-cap-emph td,
.learn-interactive .data-table tr.total-row td {
  font-weight: 700;
  color: var(--text);
}

/* Section dividers: drop the uppercase/letter-spaced/muted micro-caption and
   become a mixed-case sub-heading. Kills the "T09 AI tell" under the wrapper. */
.learn-interactive .chart-section-header {
  font-size: var(--text-h4);       /* 1.0625rem, mixed case */
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  margin-bottom: 12px;
}

/* Teaching prose to body size + legible contrast. The two-sided reading AND
   the section prose (.ccr-result-context paragraphs that sit under a
   sub-heading, e.g. the FVA debate, "How clearing transforms xVA", the
   FRTB-CVA section) are the lesson, not fine print — they read at body size
   like the handbook, not the 0.85rem desk-caption size. */
.learn-interactive .ccr-twosided-col p,
.learn-interactive .ccr-result-context {
  font-size: var(--text-body);
  line-height: 1.55;
  color: var(--text-secondary);
}
/* Genuine footnotes stay caption-sized (the "simplified figures" caveats) but
   lift out of muted grey. */
.learn-interactive .ccr-result-hint { color: var(--text-secondary); }
/* Cross-links read at body size and stack one per line. Inside a hint
   paragraph they would otherwise inherit its 0.85rem (a size used nowhere else
   on the platform) and, being inline-flex, flow side by side until they wrap
   (the Limits tab showed two links on one line and a third below). display:flex
   makes each a block that takes its own line. */
.learn-interactive .ccr-result-hint .xref-link {
  font-size: var(--text-body);
  display: flex;
}

/* Spec R10 — figure captions (Plotly charts and other illustrations).
   Caption sits with the figure in a wrapper so --space-subsection air
   sits OUTSIDE the unit, not between caption and chart. */
.learn-chapter-body .figure {
  margin: var(--space-subsection) 0;
}
.learn-chapter-body .figure-caption {
  margin-top: 8px;
  font-family: var(--font);
  font-size: var(--text-meta);
  font-style: italic;
  color: var(--text-secondary);
  text-align: left;
}

/* Spec R9 — adjacent-block double-margin avoidance. When a paragraph
   immediately precedes a non-paragraph block element that already
   carries top margin, reduce the paragraph's bottom margin so the
   gap doesn't collapse uneven. Modern-browser only (Safari 15.4+,
   Chrome 105+); older browsers fall through with slightly looser
   rhythm. */
.learn-chapter-body p:has(+ .learn-formula-box),
.learn-chapter-body p:has(+ .data-table-wrap),
.learn-chapter-body p:has(+ .learn-key-facts),
.learn-chapter-body p:has(+ .learn-callout),
.learn-chapter-body p:has(+ .learn-example) {
  margin-bottom: calc(var(--space-block) * 0.5);
}

.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover td {
  background: rgba(255,255,255,0.04);
  box-shadow: inset 2px 0 0 var(--color-accent-light);
}
[data-theme="light"] .data-table tbody tr:hover td {
  background: rgba(27,58,92,0.03);
  box-shadow: inset 2px 0 0 var(--color-accent);
}
.data-table tbody tr:hover td:first-child {
  box-shadow: inset 2px 0 0 var(--color-accent-light);
}
[data-theme="light"] .data-table tbody tr:hover td:first-child {
  box-shadow: inset 2px 0 0 var(--color-accent);
}
.data-table tbody tr:hover td:not(:first-child) {
  box-shadow: none;
}

.data-table .positive { color: var(--positive); }
.data-table .negative { color: var(--negative); }
.data-table .neutral  { color: var(--neutral); }  /* B2: zero / unchanged amounts */

/* Vertical divider on the right edge of a column (e.g. between risk and P&L) */
.data-table th.pf-divider-right,
.data-table td.pf-divider-right {
  border-right: 2px solid var(--color-accent);
}
.data-table .row-principal td { font-weight: 600; color: var(--text); }

/* Numeric value cell in results tables — mono font, tabular alignment */
.data-table .metric-val {
  font-family: var(--font-mono);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: 0.88rem;
  text-align: right;
}

/* Text-heavy tables inside learn/reference content — left-align everything */
.learn-chapter-body .data-table thead th,
.learn-chapter-body .data-table tbody td { text-align: left; white-space: normal; }

/* ── Responsive learn/reference tables ──
   On phones, 4-column tables (Measure / Definition / Units / Typical Use)
   collapse cells to ~90px wide and wrap individual words mid-character,
   producing unreadable text. Horizontal scroll fights the page's vertical
   reading flow. Instead, stack each row as a labelled card: first cell is
   the row title, remaining cells become "LABEL: value" pairs underneath.
   Labels come from the <th> headers via data-th set in reference-tab.js. */
@media (max-width: 768px) {
  .learn-chapter-body .data-table-wrap {
    border: none;
    border-radius: 0;
    overflow-x: visible;
  }
  .learn-chapter-body .data-table {
    font-size: 0.9rem;
  }
  .learn-chapter-body .data-table thead { display: none; }
  .learn-chapter-body .data-table,
  .learn-chapter-body .data-table tbody,
  .learn-chapter-body .data-table tr,
  .learn-chapter-body .data-table td { display: block; width: auto; }
  .learn-chapter-body .data-table tr {
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    padding: 12px 14px;
    margin-bottom: 10px;
    background: var(--bg-elevated);
  }
  /* Suppress the row-hover accent stripe — table rows have been
     transformed into card blocks and the accent stripe leaves a
     stray vertical line on the left of the card after a tap on
     touch devices (where :hover sticks until the user taps
     elsewhere). Match the [data-theme] specificity of the dark/
     light hover rules above so this reset wins under both themes. */
  .learn-chapter-body .data-table tbody tr:hover td,
  .learn-chapter-body .data-table tbody tr:hover td:first-child,
  [data-theme="light"] .learn-chapter-body .data-table tbody tr:hover td,
  [data-theme="light"] .learn-chapter-body .data-table tbody tr:hover td:first-child {
    background: transparent;
    box-shadow: none;
  }
  .learn-chapter-body .data-table td {
    padding: 6px 0;
    border: none;
    text-align: left !important;
  }
  /* First cell = row title (Measure name, etc.) — bigger, no label.
     Selector includes .data-table-wrap so it outranks the sticky-
     first-column rule lower in this file (which paints a white bg +
     shadow on td:first-child for horizontal-scroll tables) and then
     resets those properties so the title doesn't get a stray white
     rectangle inside the otherwise off-white card row. */
  .learn-chapter-body .data-table-wrap .data-table tbody td:first-child {
    font-weight: 600;
    font-size: 1rem;
    color: var(--text);
    padding-bottom: 8px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--border-subtle);
    position: static;
    left: auto;
    background: transparent;
    box-shadow: none;
    z-index: auto;
  }
  /* Remaining cells get a label from data-th */
  .learn-chapter-body .data-table td:not(:first-child)::before {
    content: attr(data-th);
    display: block;
    font-size: 0.65rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin-bottom: 2px;
  }
  /* Hide cells that have no label content (avoid empty "LABEL:" rows) */
  .learn-chapter-body .data-table td:not(:first-child):empty { display: none; }
}

/* ── Loading skeleton ── */
.skeleton {
  background: linear-gradient(90deg, var(--bg-elevated) 25%, var(--border) 50%, var(--bg-elevated) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s infinite;
  border-radius: var(--r-sm);
}
@keyframes skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ── Error state ── */
.error-box {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 16px;
  background: var(--negative-bg);
  border: 1px solid rgba(239,68,68,0.25);
  border-radius: var(--r-md);
  font-size: 0.9rem;
  color: var(--negative);
}

/* ============================================================
   Pricer Grid — shared table-based column layout
   ============================================================ */

.pricer-grid-wrap {
  overflow-x: auto;
  margin-bottom: 16px;
}

.pricer-grid {
  border-collapse: collapse;
  font-size: 1.05rem;
}

.pricer-grid colgroup .col-label {
  width: 220px; min-width: 220px; max-width: 220px;
}
.pricer-grid colgroup .col-inst {
  width: 200px; min-width: 200px; max-width: 200px;
}

/* Header row */
.pricer-grid thead th {
  padding: 6px 12px;
  text-align: right;
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-table);
  border-bottom: 2px solid var(--border);
  white-space: nowrap;
}
.pricer-grid thead th:first-child {
  text-align: left;
  color: var(--text-table);
}

/* Body rows */
.pricer-grid tbody td {
  padding: 3px 12px;
  vertical-align: middle;
  border-bottom: 1px solid var(--border-subtle);
  white-space: nowrap;
  text-align: right;
}

/* Label column */
.pricer-grid tbody td:first-child {
  font-weight: 600;
  color: var(--text);
  font-size: 0.95rem;
  text-align: left;
  padding-right: 16px;
}

/* Inputs inside the grid */
.pricer-grid input[type="text"],
.pricer-grid input[type="number"],
.pricer-grid input[type="date"] {
  width: 100%;
  box-sizing: border-box;
  padding: 4px 8px;
  font-size: 1.0rem;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  outline: none;
  text-align: right;
  transition: border-color 0.15s;
}
.pricer-grid input:hover:not(:focus) {
  border-color: var(--sidebar-accent);
  background: color-mix(in srgb, var(--sidebar-accent) 4%, var(--bg-elevated));
}
[data-theme="light"] .pricer-grid input:hover:not(:focus) {
  background: color-mix(in srgb, var(--sidebar-accent) 6%, var(--bg-surface));
}
.pricer-grid input:focus {
  border-color: var(--color-accent);
  box-shadow: var(--focus-ring);
}

.pricer-grid select {
  width: 100%;
  box-sizing: border-box;
  padding: 4px 8px;
  font-size: 1.0rem;
  font-family: var(--font);
  background-color: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  outline: none;
  cursor: pointer;
  text-align: right;
  transition: border-color 0.15s;
}
.pricer-grid select:hover:not(:focus) {
  border-color: var(--sidebar-accent);
}
.pricer-grid select option {
  background-color: var(--bg-input);
  color: var(--text);
}
.pricer-grid select:focus {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 2px rgba(46, 109, 164, 0.2);
}

/* ── Column-grid muting (shared) ──
   A cell input/select is muted when its row does not apply to that column's
   current structure (e.g. Fixed Rate on a Float leg, or CMS Tenor B unless the
   structure is a CMS spread). Used by the XCS Trade Builder and the CMS pricer. */
.pricer-grid input.cell-muted,
.pricer-grid select.cell-muted {
  opacity: 0.45;
  background: var(--bg-surface);
  cursor: not-allowed;
}
.pricer-grid input:disabled,
.pricer-grid select:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Divider row between inputs and outputs */
.pricer-grid .divider-row td {
  padding: 0;
  height: 0;
  border-bottom: none;
  border-top: 2px solid rgba(20, 184, 166, 0.35);
  background: transparent;
}

/* Section header row */
.pricer-grid .section-row td {
  padding: 6px 12px 3px;
  font-weight: 700;
  font-size: 0.88rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgba(20, 184, 166, 0.7);
  border-bottom: 1px solid rgba(20, 184, 166, 0.12);
}

/* Result value cells */
.pricer-grid .result-val {
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--text);
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
/* Unit suffix (%, bp) hangs in a fixed-width slot to the right of the number,
   so every value's digits right-align to one edge regardless of its unit. The
   slot is always emitted (empty when there is no unit) by renderResultValue. */
.pricer-grid .result-val .result-unit {
  display: inline-block;
  width: 9px;           /* fixed px (not font-relative) so every tier's digits
                           right-align to the same edge; matches the editable
                           box's value reserve (1px border + 8px padding) so
                           text outputs and NPV/DV01 boxes share that edge.
                           Units wider than the slot simply hang further right. */
  text-align: left;
  padding-left: 2px;
  box-sizing: border-box;
}
.pricer-grid .result-val.positive { color: var(--positive); }
.pricer-grid .result-val.negative { color: var(--negative); }
.pricer-grid .result-val.neutral  { color: var(--neutral); }
.pricer-grid .result-val.muted {
  color: var(--text-muted);
  font-weight: 400;
}
/* Tier 1 — headline numbers (price, yield, key spread) */
.pricer-grid .result-val.primary {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text);
}
.pricer-grid .result-val.primary.positive { color: var(--positive); }
.pricer-grid .result-val.primary.negative { color: var(--negative); }
/* Tier 3 — supporting calculations */
.pricer-grid .result-val.detail {
  font-size: 0.95rem;
  font-weight: 400;
  color: var(--text-secondary);
}
/* Tier 3 still colours by sign when given a sign class (dealer-book Greeks). */
.pricer-grid .result-val.detail.positive { color: var(--positive); }
.pricer-grid .result-val.detail.negative { color: var(--negative); }
/* Tier 1 label — emphasise key input row labels (hierarchy by type, no accent bar) */
.pricer-grid td.primary-label {
  font-weight: 700;
  color: var(--text);
}
/* Suppress double teal bar when label already has primary-label */
td.primary-label ~ td .result-val.primary {
  border-left: none;
  padding-left: 0;
}

/* NPV solver input */
.pricer-grid .npv-solver-input {
  width: 100%;
  box-sizing: border-box;
  padding: 5px 10px;
  font-size: 1.15rem;
  font-weight: 600;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  background: rgba(20, 184, 166, 0.06);
  border: 1px solid rgba(20, 184, 166, 0.2);
  border-radius: 4px;
  color: var(--text);
  outline: none;
  transition: border-color 0.15s, background 0.15s;
}
.pricer-grid .npv-solver-input:focus {
  border-color: var(--color-accent);
  background: rgba(20, 184, 166, 0.1);
}
.pricer-grid .npv-solver-input.positive { color: var(--positive); }
.pricer-grid .npv-solver-input.negative { color: var(--negative); }
.pricer-grid .npv-solver-input.neutral  { color: var(--neutral); }

[data-theme="light"] .pricer-grid .npv-solver-input {
  background: rgba(20, 184, 166, 0.04);
  border-color: rgba(20, 184, 166, 0.2);
}

/* ── Pricer-grid Type Toggle (Payer/Receiver, Cap/Floor, Pay/Receive Fixed,
   FRA/SPS) — shared by any module rendering a pricer-grid. ── */
.pricer-grid .cp-toggle {
  display: flex;
  gap: 4px;
  width: 100%;
}
.pricer-grid .cp-toggle-btn {
  flex: 1;
  padding: 4px 4px;
  font-size: 0.8rem;
  font-weight: 600;
  font-family: var(--font, Inter, sans-serif);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.04);
  color: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  outline: none;
  border-radius: 4px;
  box-sizing: border-box;
  min-height: 28px;
}
.pricer-grid .cp-toggle-btn.active-call {
  background: rgba(96, 165, 250, 0.2);
  border-color: rgba(96, 165, 250, 0.5);
  color: #60a5fa;
}
.pricer-grid .cp-toggle-btn.active-put {
  background: rgba(251, 191, 36, 0.18);
  border-color: rgba(251, 191, 36, 0.5);
  color: #fbbf24;
}
.pricer-grid .cp-toggle-btn:hover:not(.active-call):not(.active-put) {
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.7);
}
[data-theme="light"] .pricer-grid .cp-toggle-btn {
  border-color: rgba(0, 0, 0, 0.15);
  background: rgba(0, 0, 0, 0.02);
  color: rgba(0, 0, 0, 0.35);
}
[data-theme="light"] .pricer-grid .cp-toggle-btn.active-call {
  background: rgba(37, 99, 235, 0.1);
  border-color: rgba(37, 99, 235, 0.4);
  color: #2563eb;
}
[data-theme="light"] .pricer-grid .cp-toggle-btn.active-put {
  background: rgba(217, 119, 6, 0.12);
  border-color: rgba(217, 119, 6, 0.45);
  color: #d97706;
}

/* Inline dialog buttons */
.inline-dialog-btn {
  padding: 2px 8px;
  font-size: 0.7rem;
  font-family: var(--font);
  font-weight: 600;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.inline-dialog-btn:hover {
  background: var(--bg-input);
  color: var(--text);
}
.inline-dialog-btn:active {
  transform: scale(0.96);
}

/* Inline accent button. Small pill with accent-tinted background and accent
   border. Used for in-row action buttons that open dialogs / chart popups
   (Workings, Price/YTM, Duration, Notional Schedule, etc.). */
.inline-accent-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 20px;
  padding: 0 8px;
  border: 1px solid var(--color-accent);
  border-radius: 4px;
  background: color-mix(in srgb, var(--color-accent) 22%, var(--bg-elevated));
  color: var(--text);
  font-size: 0.72rem;
  font-weight: 600;
  font-family: var(--font);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease;
}
.inline-accent-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-accent) 32%, var(--bg-elevated));
}
.inline-accent-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Portfolio button row */
.pricer-grid .portfolio-btn-row td {
  padding: 3px 14px;
  border-bottom: none;
}
/* Action buttons sitting in pricer-grid table cells (one per column,
   e.g. + Portfolio, Cashflows, DV01 Ladder, Cashflow Chart, Show workings,
   Details). They must all be the same width to read as a clean column;
   stretch each one to fill its <td>. */
.pricer-grid td > .btn { width: 100%; }

/* Buy/Sell & Pay/Receive toggle buttons (shared across all modules) */
.dir-toggle {
  display: flex;
  gap: 4px;
  width: 100%;
}
.dir-toggle-btn {
  flex: 1;
  padding: 6px 4px;
  font-size: 0.85rem;
  font-weight: 700;
  font-family: var(--font);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  outline: none;
  border-radius: 4px;
  box-sizing: border-box;
  min-height: 30px;
}
/* Optional second line inside a direction toggle button (e.g. a CMS spread's
   "Rec 10Y / Pay 2Y"). Mirrors the FX pricer's .cp-fx-sub. */
.dir-toggle-sub {
  display: block;
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: none;
  opacity: 0.75;
  margin-top: 1px;
  line-height: 1.1;
}
.dir-toggle-btn.active-sell {
  background: rgba(248, 113, 113, 0.18);
  border-color: rgba(248, 113, 113, 0.5);
  color: #e8eaf0;
}
.dir-toggle-btn.active-buy {
  background: rgba(52, 211, 153, 0.18);
  border-color: rgba(52, 211, 153, 0.5);
  color: #e8eaf0;
}
.dir-toggle-btn:hover:not(.active-buy):not(.active-sell):not(.active):not(.active-equity):not(.active-fx) {
  background: var(--bg-elevated);
  color: var(--text-secondary);
}

[data-theme="light"] .dir-toggle-btn.active-sell {
  background: rgba(220, 38, 38, 0.12);
  border-color: rgba(220, 38, 38, 0.45);
  color: var(--negative);
}
[data-theme="light"] .dir-toggle-btn.active-buy {
  background: rgba(5, 150, 105, 0.12);
  border-color: rgba(5, 150, 105, 0.45);
  color: var(--positive);
}

/* Pill-shaped segmented toggle (e.g. Spot Δ / Fwd Δ) */
/* Pill-toggle: 18-20px radius was the SaaS default (T10/T18 — uniform
   rounded everything). 6px / 4px aligns with the rest of the UI button
   scale and reads as a segmented control rather than a pill. */
.pill-toggle {
  display: flex;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px;
  margin-bottom: 8px;
}
.pill-toggle-btn {
  flex: 1;
  padding: 4px 0;
  font-size: 0.78rem;
  font-family: var(--font);
  font-weight: 500;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  text-align: center;
}
.pill-toggle-btn.active {
  background: var(--color-accent);
  color: #fff;
}
.pill-toggle-btn:hover:not(.active) {
  color: var(--text);
}

/* ── Chart container ── */
.chart-wrap {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 16px;
  margin-bottom: 16px;
  position: relative;
}
[data-theme="light"] .chart-wrap {
  box-shadow: 0 1px 4px rgba(27, 58, 92, 0.06);
}

/* Yield curve main chart + controls row — constrain width to align with tab labels */
#yield-curve-content > .chart-wrap,
#yield-curve-content > [id$="-curve-overlays"],
#yield-curve-content > #iro-fwd-flatten-wrap {
  max-width: 1020px;
}

/* ── Toggle switch (used for yield-curve overlay controls) ── */
.curve-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-muted);
  user-select: none;
  transition: color var(--transition);
}
.curve-toggle:hover { color: var(--text); }
.curve-toggle input { display: none; }
.curve-toggle-track {
  width: 32px;
  height: 18px;
  background: var(--border);
  border-radius: 9px;
  position: relative;
  transition: background 0.2s;
  flex-shrink: 0;
}
.curve-toggle input:checked + .curve-toggle-track {
  background: var(--color-accent);
}
.curve-toggle-track::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.curve-toggle input:checked + .curve-toggle-track::after {
  transform: translateX(14px);
}

/* "Log tenor scale" switch: anchored bottom-right of the chart-wrap in
   the normal (non-fullscreen) view. Hidden when the wrap enters
   .is-fullscreen — fullscreen mode already gives the chart maximum
   real estate, no need for a controls overlay there. */
.tenor-axis-fs-toggle {
  display: inline-flex;
  position: absolute;
  bottom: 16px;
  right: 16px;
  z-index: 10;
}
.chart-wrap.is-fullscreen .tenor-axis-fs-toggle,
[class*="-chart-wrap"].is-fullscreen .tenor-axis-fs-toggle {
  display: none;
}

/* Zero Rates / 1Y Forward toggles anchored bottom-left of the chart-wrap,
   mirroring .tenor-axis-fs-toggle on the right. Hidden in fullscreen
   for the same reason. */
.chart-overlay-toggles-left {
  display: inline-flex;
  gap: 16px;
  position: absolute;
  bottom: 16px;
  left: 16px;
  z-index: 10;
}
.chart-wrap.is-fullscreen .chart-overlay-toggles-left,
[class*="-chart-wrap"].is-fullscreen .chart-overlay-toggles-left {
  display: none;
}

/* Convention-badge data-tooltip popover — anchored to the small inline
   badge. */
.convention-badge[data-tooltip]:not([data-tooltip=""])::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  width: max-content;
  max-width: 320px;
  padding: 8px 11px;
  background: var(--text);
  color: var(--bg-elevated);
  font-family: var(--font);
  font-size: 0.78rem;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0;
  border-radius: 6px;
  box-shadow: 0 6px 16px rgba(0,0,0,0.25);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s 0.1s, transform 0.15s 0.1s;
  white-space: normal;
  z-index: 30;
}
.convention-badge[data-tooltip]:not([data-tooltip=""]):hover::after,
.convention-badge[data-tooltip]:not([data-tooltip=""]):focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* C1: crosshair cursor on the Plotly drag layer (Bloomberg/TradingView convention) */
.chart-wrap .main-svg { cursor: crosshair; }
.chart-wrap .nsewdrag  { cursor: crosshair !important; }

/* C4: chart section titles rendered in HTML, not inside Plotly (saves chart space) */
.chart-section-header {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
  margin-bottom: 8px;
}

/* D3 / E1: data source footer at bottom of params sidebar */
.data-source-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-top: 12px;
  margin-top: 12px;
  border-top: 1px solid var(--border-subtle);
  font-size: 0.75rem;
  color: var(--text-muted);
  flex-wrap: wrap;
}

/* Fullscreen state — works for .chart-wrap, .macro-chart-wrap, .bf-chart-wrap, etc.
   Uses html prefix + !important on max-width / width to beat per-tab max-width
   rules like `#yield-curve-content > .chart-wrap { max-width: 1020px }` that
   otherwise capped the fullscreen viewport at their content-area width. */
html .chart-wrap.is-fullscreen,
html [class*="-chart-wrap"].is-fullscreen {
  position: fixed;
  inset: 0;
  z-index: var(--z-max);
  border: none;
  border-radius: 0;
  margin: 0;
  padding: 20px;
  max-width: none !important;
  width: 100vw !important;
  height: 100vh !important;
  height: 100dvh !important;
  display: flex;
  flex-direction: column;
  overflow: visible;
  background: var(--bg-surface);
}
.chart-wrap.is-fullscreen > div[id],
[class*="-chart-wrap"].is-fullscreen > div[id] {
  flex: 1;
  min-height: 0;
}

.chart-fullscreen-btn {
  position: absolute;
  top: 8px;
  left: 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.82rem;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
  transition: color var(--transition), border-color var(--transition), opacity var(--transition);
  font-family: var(--font);
  opacity: 0.55;
}
.chart-fullscreen-btn:hover { color: var(--text); border-color: var(--color-accent); opacity: 1; }

.chart-title {
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 12px;
}

/* ── Theme toggle (inside settings dropdown) ── */
#theme-toggle {
  font-size: 0.9rem;
  font-weight: 500;
}

/* ── Loading spinner ── */
.spinner {
  display: inline-block;
  width: 16px; height: 16px;
  border: 2px solid var(--border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Shared pricer loading state ── */
.pricer-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 40px 24px;
  color: var(--text-muted);
  font-size: 0.9rem;
}
.pricer-loading-spinner {
  width: 24px; height: 24px;
  border: 2.5px solid var(--border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: spin 0.75s linear infinite;
  flex-shrink: 0;
}

/* ── Standardised error display (use anywhere via innerHTML) ── */
.result-error,
.error-msg {
  box-sizing: border-box;
  width: 100%;
  padding: 10px 14px;
  background: var(--negative-bg);
  border: 1px solid rgba(239,68,68,0.28);
  border-radius: var(--r-md);
  color: var(--negative);
  font-size: 0.875rem;
  overflow-wrap: break-word;
  word-break: break-word;
}

/* ── Scenario card ── */
.scenario-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 20px;
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}
.scenario-card:hover { border-color: var(--color-accent); }
.scenario-card.selected {
  border-color: var(--color-accent);
  background: rgba(46,109,164,0.06);
}

.scenario-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 8px;
}

.scenario-card-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
}

.scenario-card-meta {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-top: 4px;
}

.scenario-card-narrative {
  font-size: 0.875rem;
  color: var(--text-secondary);
  line-height: 1.55;
  margin-top: 8px;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Glossary (global — used by all modules) ───────────────────────────────
   HTML structure (mirrors .learn-layout: 200px nav rail + content):
     .glossary-layout
       nav.glossary-rail                          [sticky]
         .glossary-rail-section                   [category nav — multi-cat only]
           .glossary-rail-label "Categories"
           button.glossary-filter-btn[.active] ×N
         .glossary-rail-section
           .glossary-rail-label "Jump to"
           .glossary-az-letters
             a.glossary-az-letter[.--empty] ×26(+#)
       .glossary-wrap
         .glossary-controls
           .glossary-search-row
             input.form-control.glossary-search
             span.glossary-count > button.glossary-cross-cat-link
         div#<prefix>-glossary-body
           .glossary-section[.hidden]
             h3.glossary-section-label
             .glossary-cards
               span.glossary-az-anchor              [letter scroll stop]
               .glossary-card[.hidden]
                 .glossary-card-term
                 .glossary-card-def
                 .glossary-card-meta
                   a.xref-link                        [shared cross/deep link]
           p.glossary-empty
   ── */

/* Spec: .scratch/ui-review/26-glossary-spec.md (Q1-Q4, X1-X13).
   Visual + interaction parity with the handbook reading register —
   Newsreader serif body, --measure-prose width, rhythm tokens. */

/* Layout grid mirrors .learn-layout (handbook chapter pages) so the
   glossary content column lands at the same x-position as chapter
   prose. The left rail is a real navigation column — category nav
   (vertical, mirroring the chapter list) + a compact A-Z jump block
   — so it doesn't read as a sparse letter strip in an empty 200px
   gutter. */
.glossary-layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 32px;
  align-items: stretch;
  flex: 1;
  min-height: 0;
}

/* Left rail — sticky like the chapter TOC. Holds category nav +
   A-Z jump. */
.glossary-rail {
  display: flex;
  flex-direction: column;
  gap: 0;
  overflow-y: auto;
  padding-top: 4px;
  padding-right: 8px;
  position: sticky;
  top: var(--space-section);
  align-self: start;
  max-height: calc(100vh - var(--space-section) - 16px);
  font-family: var(--font);
}
.glossary-rail-section { margin-bottom: 24px; }
.glossary-rail-section:last-child { margin-bottom: 0; }
.glossary-rail-label {
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 8px;
  padding: 0 8px;
}

/* Category nav items — vertical list, selected state = bold + tinted
   row (mirrors a selected TOC entry). Not the pill / accent-fill
   chip from before (V4). */
.glossary-filter-btn {
  display: block;
  width: 100%;
  text-align: left;
  padding: 5px 8px;
  background: none;
  border: 0;
  border-radius: 4px;
  cursor: pointer;
  font-family: var(--font);
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--text-secondary);
  letter-spacing: 0;
  transition: color 0.15s ease, background 0.15s ease;
}
.glossary-filter-btn:hover {
  color: var(--text);
  background: var(--bg-elevated);
}
.glossary-filter-btn.active {
  color: var(--text);
  font-weight: 600;
  background: var(--bg-elevated);
}

/* A-Z jump block — compact wrap inside the rail (~6 letters per row
   in the 200px column). Letters with entries are anchors; the rest
   render as muted disabled spans. */
.glossary-az-letters {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 4px;
  padding: 0 4px;
}
.glossary-az-letter {
  display: inline-block;
  min-width: 1.2em;
  text-align: center;
  padding: 1px 3px;
  border-radius: 3px;
  text-decoration: none;
  color: var(--text-secondary);
  font-size: 0.92rem;
  font-variant-numeric: tabular-nums;
  transition: color 0.15s ease, background 0.15s ease;
}
.glossary-az-letter:hover {
  color: var(--text);
  background: var(--bg-elevated);
}
.glossary-az-letter--empty {
  color: var(--text-muted);
  cursor: default;
  pointer-events: none;
}

.glossary-wrap {
  /* Measure cap aligned with handbook (--measure-prose ≈ 736px ≈ 65ch
     at the new 1rem Newsreader definition body). Was 820px — wider
     than the handbook reading width and out of step with the rest of
     the reading register. */
  max-width: var(--measure-prose, 46rem);
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Glossary body: scrolls within the panel */
.glossary-controls + div {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: 48px;
}

.glossary-controls {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 0;
  flex-shrink: 0;
  background: var(--bg);
  padding-top: 8px;
  padding-bottom: 12px;
}

.glossary-search-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.glossary-search {
  flex: 1;
  max-width: 440px;
}

.glossary-count {
  font-size: 0.85rem;
  color: var(--text-secondary);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Cross-category link — appears inside .glossary-count when the active
   category filter hides search hits that exist elsewhere (X11). */
.glossary-cross-cat-link {
  background: none;
  border: 0;
  padding: 0;
  margin-left: 4px;
  font: inherit;
  color: var(--color-accent-text);
  text-decoration: underline;
  cursor: pointer;
}
.glossary-cross-cat-link:hover { color: var(--text); }

/* Anchor stop — invisible per-letter scroll target in the glossary
   body. The controls bar above the scroll container is fixed (not
   inside it), so the target only needs a small breathing margin. */
.glossary-az-anchor {
  display: block;
  height: 0;
  scroll-margin-top: 8px;
}

.glossary-section {
  margin-bottom: var(--space-subsection);
}
.glossary-section.hidden {
  display: none;
}

/* Section label (X3 / V3) — drops the T09 stack (uppercase tracked
   coloured eyebrow + coloured rule) in favour of the handbook
   sentence-case h3 ladder. Above-margin --space-subsection carries
   the section break by air rather than by ink. */
.glossary-section-label {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: var(--text-h3, 1.25rem);
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0;
  text-transform: none;
  padding-bottom: 0;
  border-bottom: 0;
  margin: var(--space-subsection) 0 var(--space-block);
}
.glossary-section:first-child .glossary-section-label {
  margin-top: 0;
}

.glossary-cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-block);
}

/* Type-only entry: term + definition separated from siblings by a
   `--border-subtle` hairline above. Reads as a financial-dictionary
   entry, not a Notion-template card. */
.glossary-card {
  padding: var(--space-block) 0 0;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--border-subtle);
  border-radius: 0;
}
.glossary-card:first-of-type { border-top: 0; padding-top: 0; }
.glossary-card.hidden { display: none; }

/* Term (Q1 / V1) — Newsreader serif at 1.0625rem (17px), mixed-case
   weight 600. Modest step over body so the term reads as a heading
   in its own right but not as a section break. Drops the previous
   0.875rem Inter sans 700 chrome-metadata treatment. */
.glossary-card-term {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.35;
  margin-bottom: 4px;
}

/* Definition (Q1 / V2) — Newsreader serif at 1rem / 1.55. Slight
   downstep from handbook body (1.1875rem) is correct for the short-
   form dictionary register. Drops the 0.82rem Inter sans inheritance
   that read at 69% of handbook body. */
.glossary-card-def {
  font-family: var(--font);
  font-size: 1rem;
  font-weight: 400;
  color: var(--text);
  line-height: 1.55;
}

/* Meta row (X4 / V5) — the pricer deep-link sits here, under the
   definition: italic, body-size, --color-accent — matches the
   formula-panel cross-ref convention rather than the previous
   chip-style chrome top-right of the term. */
.glossary-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  margin-top: 8px;
  font-family: var(--font);
  font-size: 0.92rem;
  font-style: italic;
  color: var(--text-muted);
}
/* Glossary "Try in Pricer" deep-links now use the shared .xref-link
   component (see renderXrefAction); the old .glossary-pricer-link style
   has been removed. */

/* Match highlight (X10 / S4) — matches the handbook .search-hl
   pattern; subdued accent wash so the match is findable without
   dominating the surrounding prose. */
.glossary-match {
  background: color-mix(in srgb, var(--warning) 20%, transparent);
  color: inherit;
  border-radius: 2px;
  padding: 0 1px;
}

/* Empty state (X12 / S6) — quiet italic line when search/filter
   produces zero matches. */
.glossary-empty {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 1rem;
  font-style: italic;
  color: var(--text-muted);
  margin: var(--space-subsection) 0;
  text-align: center;
}

/* ── Reference sub-tab pills ─────────────────────────────────────────────── */

.ref-sub-tabs-wrap {
  position: relative;          /* anchor for the right-pinned .ref-print control */
  flex-shrink: 0;
  background: var(--bg);
  padding: 4px 0 0;
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: var(--space-subsection);
}

.ref-sub-tabs {
  display: flex;
  gap: 4px;
  width: 100%;
  justify-content: center;     /* centre the tab labels under the chapter chrome */
}

/* ── Quiet "Print / PDF" control — pinned to the right of the sub-tab bar,
   absolutely positioned so it never pushes the centred sub-tab labels. Plain
   text + a small caret; no icon / pill / accent fill (deliberately outside the
   AI-UI vocabulary). Opens a tiny menu whose items adapt to the active sub-tab
   (this chapter / this section / this glossary). Wired by shared/print-export.js;
   hidden in print via the @media print block in base.css. ──────────────────── */
.ref-print {
  position: absolute;
  right: 0;
  bottom: 4px;
}
.ref-print-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 4px;
  border: 0;
  border-radius: 0;
  background: transparent;
  cursor: pointer;
  font-family: var(--font);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-muted);
  transition: color 0.15s ease;
}
.ref-print-btn:hover { color: var(--text-secondary); background: transparent; }
.ref-print-caret { font-size: 0.7em; line-height: 1; }
.ref-print-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 2px);
  min-width: 200px;
  padding: 4px;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
  z-index: var(--z-dropdown);
}
.ref-print-menu[hidden] { display: none; }
.ref-print-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  cursor: pointer;
  font-family: var(--font);
  font-size: 0.88rem;
  color: var(--text);
  white-space: nowrap;
}
.ref-print-item:hover { background: var(--bg-elevated); }
@media (max-width: 480px) {
  .ref-print-btn { font-size: 0.78rem; padding: 6px 2px; }
  .ref-print-menu { right: -2px; min-width: 180px; }
}

/* Quiet section selector — no pill chrome, active tab gets a 2px accent
   underline that sits ON the wrap's hairline (replaces it for that tab's
   width). Matches the global .subtab-btn underline-on-active pattern in
   components.css (line 1220 onwards); only difference is the centred
   layout via .ref-sub-tabs and the larger inter-tab margin. */
.ref-sub-tab {
  position: relative;
  padding: 8px 0;
  margin-right: 32px;
  border: 0;
  border-radius: 0;
  background: transparent;
  cursor: pointer;
  font-family: var(--font);
  font-size: 1rem;
  font-weight: 500;
  color: var(--text-muted);
  transition: color 0.15s ease;
}

.ref-sub-tab:hover {
  color: var(--text-secondary);
  background: transparent;
}

.ref-sub-tab.active {
  color: var(--text);
  font-weight: 600;
  background: transparent;
}
.ref-sub-tab.active::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;        /* sits exactly on the wrap's --border-subtle hairline */
  height: 2px;
  background: var(--color-accent);
  border-radius: 1px;
}

.ref-sub-panel {
  display: none;
}

.ref-sub-panel.active {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ── Learn/Conventions layout (sidebar TOC + content) ────────────────────── */

.learn-layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 32px;
  align-items: stretch;
  flex: 1;
  min-height: 0;
}

/* Mobile disclosure — injected by shared/learn.js, hidden on desktop. */
.learn-nav-toggle {
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  /* Grid item in the single-column mobile .learn-layout. Without min-width:0
     it takes its min-CONTENT width, and its label (a nowrap active-chapter
     title, updated on scroll) can be wider than the viewport — that stretches
     the whole column past the screen and the block drifts left/right as the
     active title changes. min-width:0 lets the column stay at viewport width;
     the label truncates instead (below). */
  min-width: 0;
  padding: 10px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  color: var(--text);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}
.learn-nav-toggle-label {
  flex: 1;
  /* min-width:0 is REQUIRED for text-overflow:ellipsis on a flex child — a
     flex item defaults to min-width:auto (its content width), so without this
     the ellipsis never triggers and a long title stretches the button. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.learn-nav-toggle-caret {
  font-size: 0.9rem;
  color: var(--text-muted);
  transition: transform 0.15s ease;
}
.learn-nav-toggle[aria-expanded="true"] .learn-nav-toggle-caret {
  transform: rotate(180deg);
}

.learn-nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
  overflow-y: auto;
  padding-right: 8px;
  padding-top: 4px;
  /* Generous bottom padding so the last chapter clears the scroll edge and
     stays comfortably reachable/clickable on a long (15+ chapter) handbook. */
  padding-bottom: 56px;
  /* Sticky on desktop so a 30-chapter module doesn't require
     scroll-back-to-switch. clears app-header + breath. */
  position: sticky;
  top: var(--space-section);
  align-self: start;
  /* The scroll container is .workspace-content, which sits BELOW the locked
     64px header (--header-height), so the visible height is 100vh minus the
     header. Subtract the header and the sticky top offset so the nav fits its
     container and its own overflow scrolls to the last chapter, instead of
     overflowing the container and clipping the bottom rows. */
  max-height: calc(100vh - var(--header-height) - var(--space-section) - 16px);
  /* Chapter numbering — CSS counter so adding/removing a chapter
     re-numbers automatically without markup change. */
  counter-reset: handbook-chapter;
}

/* Themed group header in the chapter TOC. Quiet uppercase kicker with a hairline
   divider above each group (except the first). Opt-in: only rendered when a
   module's chapters carry a `section`. Not clickable, not counted. */
.learn-nav-group {
  font-size: 0.96rem;
  font-weight: 700;
  color: var(--text-secondary);
  padding: 6px 10px 4px 6px;
  margin-top: 14px;
}
.learn-nav-group:first-child {
  margin-top: 0;
  padding-top: 2px;
}

/* Editorial chapter index — Distill / Stripe Press convention.
   Each row shows a dimmed chapter NUMBER as the visual marker
   (no coloured accent bar — that's the SaaS pattern that read
   out-of-place against handbook prose). On active: number flips
   to accent ink, title goes bold + --text. */
.learn-nav-link {
  display: grid;
  grid-template-columns: 2.2em 1fr;        /* number column + title */
  align-items: baseline;
  gap: 6px;
  padding: 7px 10px 7px 6px;
  border-radius: 4px;
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.15s ease, background 0.15s ease;
  line-height: 1.45;
  counter-increment: handbook-chapter;
}
.learn-nav-link::before {
  content: counter(handbook-chapter) ".";
  /* Chapter number in Inter (not the JetBrains-mono numeral) — reads as
     part of the editorial index rather than a code artefact. Tabular
     numerals keep the number column aligned. */
  font-family: var(--font);
  font-size: 0.82rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  text-align: right;
  padding-right: 4px;
  /* baseline-aligned with the title via grid align-items: baseline */
}

.learn-nav-link:hover {
  color: var(--text-secondary);
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
.learn-nav-link:hover::before {
  color: var(--text-secondary);
}

.learn-nav-link.active {
  color: var(--text);
  font-weight: 600;
  background: transparent;       /* let weight + colour carry it; no bg pill */
}
.learn-nav-link.active::before {
  color: var(--sidebar-accent);  /* teal number = the only ink marker */
  font-weight: 600;
}

/* Estimated reading time — a quiet kicker line at the top of a sub-tab's
   reading column, above the first chapter. */
.handbook-meta {
  margin: 0 0 1.4rem;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

.learn-content {
  min-width: 0;
  max-width: var(--measure-prose, 46rem);
  overflow-y: auto;
  padding-bottom: 48px;
}
/* When a chapter contains a .data-table-wide, lift the prose-measure
   cap so the table has room to render at its declared width without
   triggering horizontal scroll on .learn-content. Prose paragraphs
   continue to self-cap via .learn-chapter-body p { max-width: } so
   the reading measure for normal prose is preserved. */
.learn-content:has(.data-table-wide) {
  max-width: none;
}

.learn-chapter {
  margin-bottom: var(--space-section);
  /* Desktop: .learn-content is the scroll container (overflow-y:
     auto) and nothing is sticky inside it. The .app-header is sticky
     relative to WINDOW, not to .learn-content, so it doesn't need
     clearance here. Just a small breathing margin. Mobile rules
     override this where sub-tabs is sticky. */
  scroll-margin-top: 16px;
}

.learn-chapter:last-child {
  margin-bottom: 24px;
}

/* Search highlight — marks injected by applySearchHighlight(). An
   accent-coloured callout (not a yellow highlighter pen): a clear tinted
   fill + a thin accent ring so it reads at a glance in both themes,
   inherits text colour. Stays until the user clicks elsewhere (the JS
   unwraps it on the next click). Same treatment every reveal action uses. */
mark.search-hl {
  background: color-mix(in srgb, var(--color-accent) 32%, transparent);
  color: inherit;
  border-radius: var(--r-sm, 4px);
  padding: 0 2px;
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--color-accent) 60%, transparent);
}
[data-theme="dark"] mark.search-hl {
  background: color-mix(in srgb, var(--color-accent) 48%, transparent);
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--color-accent) 80%, transparent);
}

.learn-chapter-body p {
  /* Inherits the 19px / 1.7 cascade from `.learn-chapter-body`. The
     previous 0.925rem rule was forcing bare <p> tags to 14.8px and
     overriding the cascade. Margin-bottom from --space-block token
     for consistent rhythm. */
  font-size: inherit;
  line-height: inherit;
  color: var(--text);
  margin-bottom: var(--space-block);
}
.learn-chapter-body p:last-child { margin-bottom: 0; }

/* ── Shared content components (Learn/Conventions) ───────────────────────── */

/* Definition-list grid: flat 2-column term/def layout. The previous
   bg-elevated + 1px border + r-md combo broke the "any two of [bg,
   border, radius] = a card" rule and left the page reading as a card-
   inside-card. Now: pure typography, --space-subsection margin
   matching surrounding rhythm, term column slightly muted to read
   as a label. See `05-separation-system.md` §6. */
.learn-key-facts {
  display: grid;
  grid-template-columns: minmax(120px, max-content) 1fr;
  gap: 14px 24px;
  align-items: baseline;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  margin: var(--space-subsection) 0;
}
/* Modifier: cap the term column when terms are long phrases (rather
   than short symbols / single words). The default max-content track
   lets the term column expand to the widest entry — fine for
   "PV / FV / Discount rate" style symbols, too wide for entries like
   "Buy-Side (asset managers, pension funds)" which then squeeze the
   definition column to half the available width. The cap forces long
   terms to wrap onto two lines and gives the recovered horizontal
   space back to the definitions. .learn-key-sym carries
   `white-space: nowrap` globally (terms are usually single-word /
   short-symbol); override here so long terms can actually wrap inside
   the capped track. */
.learn-key-facts.learn-key-facts--sym-cap {
  grid-template-columns: minmax(160px, 240px) 1fr;
}
.learn-key-facts.learn-key-facts--sym-cap .learn-key-sym {
  white-space: normal;
}

/* Definition-list term: bold-but-quiet, in the same Newsreader serif
   as the definition body. The previous Inter override made the term
   column read as a different family from its own definition (visible
   on FI swap-mechanics: "Fixed Leg / Float Leg / DF(T) / NPV" all in
   sans against Newsreader prose). Inheriting from .learn-chapter-body
   keeps the whole def-list in one typographic register. */
.learn-key-sym {
  font-weight: 600;
  color: var(--text);
  font-size: var(--text-body);   /* track the handbook body size */
  white-space: nowrap;
}

.learn-key-def {
  font-size: var(--text-body);   /* matches handbook body */
  color: var(--text);
  line-height: var(--line-body);
}

/* Display-equation wrapper. Restored from the original flex-center
   approach (commit 0121eb5) which was working — only difference now
   is the card chrome (bg / border / radius) is dropped per the
   de-card spec. The flex column with justify-content + align-items
   centre is what reliably centres KaTeX output (display or inline)
   regardless of which KaTeX version is loaded. */
.learn-formula-box {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 8px;
  padding: 16px 18px;
  margin: var(--space-block) 0;
}
.learn-formula-box .katex-display { margin: 0; }
.learn-formula-note {
  font-size: 0.85rem;
  color: var(--text-secondary);
  text-align: center;
}

.learn-formula-inline {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  color: var(--text);
  margin: 16px 0 0;
}
.learn-formula-inline .katex-display { margin: 0; }

/* Platform "About this topic" expander — surface rules live further down
   in this file. Dismiss button + collapsed-chip rules were removed: the
   model is now one control (the chevron on the <summary> row) with the
   per-tab open state persisted in localStorage. The global "Hide all
   About" preference (em.about.hideAll) still forces every panel closed. */

/* (Removed: 8-colour `:nth-child(8n)` accent-bar rotation on .ref-grid-auto
   children. With the new type-only `.learn-callout` below, sibling concept
   blocks differentiate by typography and position, not by rotating colour.) */

/* Worked-example block: tinted-bg-only treatment per `05-separation-
   system.md` §5. Hairlines were the wrong vocabulary here — they mark
   *section* shifts, but a worked example is a *type* shift (this is
   not flowing prose). Bg-tint says "different content type" without
   re-introducing the card grammar. No border, no radius (any two of
   [bg, border, radius] = card). Tighter spacing — the bg-tint already
   says "this is an aside", so the surrounding air should be in the
   paragraph-rhythm range, not section-rhythm. */
/* "Worked example" aside: fully de-carded. The earlier tinted-bg +
   padding combo read as a leftover card surrounded by de-carded prose;
   the bold .learn-example-title (1.25rem Newsreader serif via the
   sub-section cascade) now carries the "this is an example" cue on
   its own, and the body flows as normal handbook prose. */
.learn-example {
  background: transparent;
  border: 0;
  border-radius: 0;
  margin: var(--space-subsection) 0;
  padding: 0;
  overflow: visible;
}

.learn-example-title {
  padding: 0;
  font-size: 1rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  background: transparent;
  border: 0;
  margin-bottom: 8px;
}

.learn-example-body {
  padding: 0;
  font-size: inherit;        /* inherit handbook 19px / 1.7 */
  color: var(--text);
  line-height: inherit;
}

.learn-example-body strong { color: var(--text); }

/* Result line: plain prose. Tinted-bg of the surrounding .learn-example
   already says "this block is an aside"; the result line just continues
   as flowing body. Inline <strong> on specific words can still mark
   emphasis where the author wants it. */
.learn-example-result {
  margin-top: 10px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  font-size: inherit;
  font-weight: inherit;
  font-style: normal;
  color: var(--text);
}

/* Type-only concept block. No coloured left bar, no tinted background, no
   uniform card shape. Sits in flowing prose with --space-subsection
   margin top + bottom; no per-block hairline (the previous hairline-top
   pattern fired once per callout and read as a section divider where
   the author meant a paragraph cluster — see `05-separation-system.md`
   §3, "if you reach for a hairline twice, ask whether type alone could
   carry it"). The .ref-label inside is the visual anchor.

   Variant modifiers (--note / --info / --warning / --success / --muted)
   survive for semantic targeting but no longer paint the container or
   the label — colour-on-text was decorative across the audited content. */
.learn-callout {
  margin: var(--space-subsection) 0;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: 0;
  font-size: 0.95rem;
  color: var(--text);
  line-height: 1.65;
}
/* (Removed: a previous rule zeroed margins between back-to-back .learn-callout
   blocks on the theory that a cluster of short callouts should read as one
   rhythmic block. In practice, adjacent callouts are almost always distinct
   sub-sections that need a clear gap — the rule pulled them visually together
   in cases like "Worked Example" + "Theory vs Practice", which then read as
   one block. Standard CSS margin-collapse now applies: 32px between adjacent
   callouts via their own var(--space-subsection) top/bottom margins.) */

/* Callouts inside grids: row-major CSS Grid layout. Items in the same
   row align side-by-side (top of row 1 items aligned, top of row 2
   items aligned). Tradeoff: when items in a row have unequal heights,
   the shorter item has visible blank space below it before the next
   row begins. That's content-driven — only the author can match item
   lengths to eliminate it. Each callout has margin: 0 inside the
   grid; row separation comes from row-gap. */
.learn-chapter-body :is(.ref-grid-2, .ref-grid-3, .ref-grid-4, .ref-grid-auto):has(> .learn-callout) > .learn-callout {
  margin: 0;
}
.learn-chapter-body :is(.ref-grid-2, .ref-grid-3, .ref-grid-4, .ref-grid-auto):has(> .learn-callout) {
  row-gap: var(--space-block);
}

/* Callout body inside the handbook inherits the chapter body's reading
   size. The bare `.learn-callout` rule above (0.95rem / 1.65) still applies
   to callouts on non-handbook surfaces. */
.learn-chapter-body .learn-callout {
  font-size: 1.1875rem;
  line-height: 1.7;
}

.learn-callout-title {
  font-weight: 600;
  color: var(--text);
  margin-bottom: 4px;
  font-size: 0.95rem;
}
/* Treat the first <div> child of a callout as the callout header — bolds
   the inline `<div style="margin-bottom:4px">Label</div>` pattern used
   across module learn_content.js files without per-site HTML edits. Also
   reinforces `.learn-callout-title` (overrides 600 → 700 above). Skips
   callouts whose first child is a paragraph/text (e.g. those starting
   with `<strong>Key idea:</strong>`). */
.learn-callout > div:first-child {
  font-weight: 700;
}
/* Inside the handbook, callout titles are sub-section headings
   ("Early Exercise", "Payoff vs P&L", etc.) — they sit below
   .ref-card-title (1.55rem) and above body (1.1875rem). Bumped to
   1.25rem in the same Newsreader serif as other handbook headings;
   the previous 0.95rem rendered smaller than body and made the
   heading visually subordinate to the paragraph it was labelling. */
/* (Removed: explicit .learn-callout-title and .topic-surface
   .learn-callout font-size patches. The platform typography cascade
   handles these — .learn-callout-title matches the H3 pattern, and
   the .learn-callout body inherits via the universal inherit rule.) */

/* (Removed: per-class .topic-surface .hf-scenario-step-title /
   .hf-scenario-step-body / .topic-surface p / .topic-surface li
   patches. All handled by the platform typography cascade above —
   class-name pattern matching catches every -title / -subtitle, and
   the universal inherit rule catches every plain <p> / <li> / module-
   local body class without per-module enumeration.) */

/* Variant classes intentionally have no container styling. Reintroduce here
   only if the content genuinely warrants it (e.g. compliance warning); the
   default is type-only. */
.learn-callout.learn-callout--note,
.learn-callout.learn-callout--info,
.learn-callout.learn-callout--warning,
.learn-callout.learn-callout--success,
.learn-callout.learn-callout--muted { /* type-only — see comment above */ }

/* ── Handbook inline-style scrubber (global) ──────────────────────────────
   Many module-specific handbook emitters (FX, Money Markets, Equities-
   Advanced, Trade Lifecycle, Hedge Fund, …) wrap content in inline
   `style="…"` attributes that bake AI-tells into the markup: 3px
   coloured-left bars on cards, uppercase letter-spaced "WORKED EXAMPLE"
   tags, tiny 0.7–0.95rem prose. Inline styles outrank class selectors,
   so the only globally-applicable fix is `!important`-flagged attribute
   selectors. Patching every module's HTML emitter has been ruled out as
   fragile and inconsistent — these rules normalise behaviour everywhere
   inside `.learn-chapter-body`, and any new module inherits the same
   treatment with zero per-file work.

   Outside `.learn-chapter-body` (pricers, dashboards, charts, lifecycle
   diagrams), inline styles keep their original values — those surfaces
   often need tight font-sizes and bordered cells. */
.learn-chapter-body [style*="border-left"],
.topic-surface [style*="border-left"] {
  border-left: 0 !important;
}
.learn-chapter-body [style*="text-transform:uppercase"],
.learn-chapter-body [style*="text-transform: uppercase"],
.topic-surface [style*="text-transform:uppercase"],
.topic-surface [style*="text-transform: uppercase"] {
  text-transform: none !important;
  letter-spacing: 0 !important;
  color: inherit !important;
}
.learn-chapter-body [style*="letter-spacing"],
.topic-surface [style*="letter-spacing"] {
  letter-spacing: 0 !important;
}
/* Monospace inline blocks (formula displays, code snippets) keep their
   declared size — `:not()` filters preserve the tighter typographic
   register that monospace needs. */
.learn-chapter-body [style*="font-size:0.7"]:not([style*="monospace"]):not([style*="font-mono"]),
.learn-chapter-body [style*="font-size:0.8"]:not([style*="monospace"]):not([style*="font-mono"]),
.learn-chapter-body [style*="font-size:0.9"]:not([style*="monospace"]):not([style*="font-mono"]),
.learn-chapter-body [style*="font-size: 0.7"]:not([style*="monospace"]):not([style*="font-mono"]),
.learn-chapter-body [style*="font-size: 0.8"]:not([style*="monospace"]):not([style*="font-mono"]),
.learn-chapter-body [style*="font-size: 0.9"]:not([style*="monospace"]):not([style*="font-mono"]) {
  font-size: 1.0625rem !important;
}

/* Topic-surface inline font-size bump — same heuristic as handbook
   but with the topic-surface body register (1.1875rem). Applies to
   tabs that haven't been per-tab cleaned up yet. */
.topic-surface [style*="font-size:0.7"]:not([style*="monospace"]):not([style*="font-mono"]):not([style*="0.78"]),
.topic-surface [style*="font-size:0.8"]:not([style*="monospace"]):not([style*="font-mono"]),
.topic-surface [style*="font-size: 0.7"]:not([style*="monospace"]):not([style*="font-mono"]):not([style*="0.78"]),
.topic-surface [style*="font-size: 0.8"]:not([style*="monospace"]):not([style*="font-mono"]) {
  font-size: 1.0625rem !important;
}

/* Inline tinted-card-frame scrubber (same rule as handbook §1A) — drops
   the bg+border+radius card-frame triple. Applies to topic surfaces too
   so unfixed tabs don't render conversation blocks / diagram frames as
   bordered cards. */
.topic-surface [style*="background:var(--bg-elevated)"][style*="border:"],
.topic-surface [style*="background: var(--bg-elevated)"][style*="border:"],
.topic-surface [style*="background:var(--bg-base)"][style*="border-radius"],
.topic-surface [style*="background: var(--bg-base)"][style*="border-radius"] {
  border: 0 !important;
}
.topic-surface [style*="background:var(--bg-elevated)"][style*="border-radius"],
.topic-surface [style*="background: var(--bg-elevated)"][style*="border-radius"] {
  border-radius: 0 !important;
}

/* Iron chrome scrubber (HANDBOOK_SPEC.md §3.2 + §4). Any inline
   style="background:…;border:…" or style="background:…;border-radius:…"
   pattern on a descendant of .learn-chapter-body is treated as a
   leftover card-chrome frame and neutralised. The bg-elevated tint that
   the earlier (softer) scrubber preserved is also dropped — the
   de-carding spec ruled out tinted-only boxes too (see HANDBOOK_SPEC §3.2).
   Padding is cleared so the box collapses to plain flow. */
.learn-chapter-body [style*="background:var(--bg-elevated)"][style*="border:"],
.learn-chapter-body [style*="background: var(--bg-elevated)"][style*="border:"],
.learn-chapter-body [style*="background:var(--bg-elevated)"][style*="border-radius"],
.learn-chapter-body [style*="background: var(--bg-elevated)"][style*="border-radius"],
.learn-chapter-body [style*="background:var(--bg-base)"][style*="border-radius"],
.learn-chapter-body [style*="background: var(--bg-base)"][style*="border-radius"] {
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  padding: 0 !important;
}

/* .badge inside handbook content reads as plain bold text — no chip
   frame, no variant colour. The pill chrome was a Notion-template
   tell (T08); the variant colour ("European" blue, "American" amber,
   "Bermudan" grey) survived the chrome strip but still pulled the
   eye away from the surrounding prose. In handbook tables the
   row-and-column structure already says what kind of thing the
   badge is — the colour adds no information. */
.learn-chapter-body .badge,
.learn-chapter-body .badge-default,
.learn-chapter-body .badge-accent,
.learn-chapter-body .badge-positive,
.learn-chapter-body .badge-negative,
.learn-chapter-body .badge-warning,
.learn-chapter-body .badge-live {
  display: inline;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  font-size: inherit;
  font-weight: 600;
  color: inherit;
}

/* ── Reference-tab shared typography + layout utilities ─────────────────
   Scoped under .learn-chapter-body (the existing wrapper emitted by
   shared/learn.js) so they don't collide with other surfaces. These
   replace per-module inline style attributes in learn/conventions content. */

/* Editorial serif cascade for handbook prose. Body text, paragraphs, list
   items, and inline emphasis inherit Newsreader. Structural and UI
   elements (tables, code, buttons, nav pills, badges, formula boxes) are
   explicitly reverted to the UI sans below — the serif is for *reading*,
   not for chrome. Line-height bumped because serifs need a touch more
   leading than Inter at the same nominal size. */
.learn-chapter-body {
  font-family: var(--font);
  font-size: var(--text-body);
  font-weight: 400;
  font-optical-sizing: auto;
  line-height: var(--line-body);
  color: var(--text);
  /* No max-width on the wrapper — caps go on prose descendants
     instead (paragraphs / headings / lists / blockquotes), so wide
     elements like .data-table-wrap, .learn-formula-box, and grids
     keep the full container width and don't horizontally overflow
     when an equation or table exceeds the prose measure. */
}
/* Font experiment: handbook body in Inter (--font), headings stay in   Newsreader. h1/h2 already restate the family below; h3/h4 + the   callout/example sub-headings only inherited the body family, so pin   them here. */.learn-chapter-body :is(h3, h4, .learn-callout-title, .learn-example-title) {  font-family: 'Newsreader', 'Inter', system-ui, serif;}
/* Numbered subsection headers (<p class="ref-step-title"><strong>1. …</strong></p>):
   the parent <p> matches [class*="-title"] at line ~3934 (Newsreader !important),
   but the inner <strong> is matched directly by the body rule at line ~3860
   (Inter !important) — that wins on the element itself, overriding inheritance.
   Pin the strong explicitly. */
.learn-chapter-body .ref-step-title,
.learn-chapter-body .ref-step-title strong {
  font-family: 'Newsreader', 'Inter', system-ui, serif !important;
}

/* Cap PROSE descendants AND any block that participates in the
   reading flow (formulas, key-facts) so they centre against the same
   measure as the surrounding paragraphs. Without this, equations
   centre within the full 888px container while prose lives at 736px,
   so the equation visually sits to the right of the prose column —
   reads as "right-aligned" even though it's centered within its own
   wider box. Wide tables / grids opt out via .full-bleed. */
.learn-chapter-body p,
.learn-chapter-body :is(h1, h2, h3, h4, h5, h6),
.learn-chapter-body :is(ul, ol),
.learn-chapter-body blockquote,
.learn-chapter-body .learn-callout,
.learn-chapter-body .learn-key-facts,
.learn-chapter-body .learn-example,
.learn-chapter-body .learn-aside,
.learn-chapter-body .learn-formula-box {
  max-width: var(--measure-prose, 46rem);
}

/* ── Spec R13 — handbook aside ──────────────────────────────────
   Small-print indented paragraph for asides / clarifications /
   footnotes inside a chapter. Replaces the inline-style pattern
   `<p style="font-size:0.85rem;color:var(--text-secondary);
   margin:...;padding-left:...">` that was scattered across module
   files (and bypassed the typography cascade). One class, one
   register, one place to tune. */
.learn-chapter-body .learn-aside {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--text-secondary);
  margin: var(--space-block) 0;
  padding-left: var(--space-block);
  border-left: 0;
}
/* Section lede: when .learn-aside directly follows a heading, it's
   acting as the short paragraph under the section title — not a small-
   print aside. Promote to body register (Inter, body size, normal
   colour, flush-left) so heading + lede read as a section opener. */
.learn-chapter-body :is(h2, h3, h4) + .learn-aside {
  font-family: inherit;
  font-size: var(--text-body);
  line-height: var(--line-body);
  color: var(--text);
  padding-left: 0;
}

/* ── Spec R3 — Handbook list typography ─────────────────────────
   Scoped to .learn-chapter-body so pricer / dashboard / chrome
   lists keep their own rules. Fixes the 37 modules that had no
   list rules at all (browser-default 16px margin against platform
   24px --space-block, 40px indent with no hanging). */
.learn-chapter-body :is(ul, ol) {
  margin: var(--space-block) 0;
  padding-left: 1.5em;        /* hanging indent — wrapped lines align under first letter */
}
.learn-chapter-body li {
  margin: 0 0 calc(var(--space-block) * 0.35);
  padding-left: 0.25em;       /* small breath between marker and text */
}
.learn-chapter-body li:last-child { margin-bottom: 0; }
.learn-chapter-body :is(ul, ol) :is(ul, ol) {
  margin: calc(var(--space-block) * 0.5) 0;   /* nested ~50% tighter */
}
.learn-chapter-body ul { list-style: disc; }
.learn-chapter-body ol { list-style: decimal; }

/* ── Spec R3 — Quiet handbook in-text links ─────────────────────
   Override the global `a { color: var(--color-accent-light) }` for
   handbook prose only — every cross-reference was rendering as
   sky-blue against handbook prose, pulling the eye out of reading
   flow on every paragraph. The .xref-link pill component for
   "Try this in [Pricer]" CTAs is intentionally unaffected (its
   own rule wins via specificity / explicit selector). */
.learn-chapter-body a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--text-muted);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
.learn-chapter-body a:hover {
  text-decoration-color: var(--text);
}

/* ── Spec R3 — Handbook heading vertical rhythm ─────────────────
   Token-driven margin-top / margin-bottom. The asymmetry (large top
   margin, small bottom margin) groups each heading visually with
   the content that follows, per Bringhurst §B3. */
.learn-chapter-body h2 {
  margin: var(--space-section) 0 var(--space-block);
}
.learn-chapter-body h3 {
  margin: var(--space-subsection) 0 var(--space-block);
}
.learn-chapter-body h4 {
  margin: var(--space-block) 0 calc(var(--space-block) * 0.5);
}
/* First heading in the chapter doesn't need section-top air — it
   already follows the .learn-section-title which has its own air. */
.learn-chapter-body :is(h2, h3, h4):first-child {
  margin-top: 0;
}

/* ════════════════════════════════════════════════════════════════════════
   PLATFORM TYPOGRAPHY CASCADE
   See `.scratch/ui-review/09-typography-system.md` for the rulebook.

   ONE rule overrides every module-local body-class font-size declaration
   inside the two reading wrappers (.learn-chapter-body, .topic-surface).
   Class-name pattern matching handles all 16 modules without per-module
   patches. Module-local font-size declarations OUTSIDE these wrappers
   (pricer / dashboard / chrome) survive — that's correct.
   ════════════════════════════════════════════════════════════════════════ */

/* ADDITIVE CASCADE — opt-in by element semantics + class-name patterns.
   Each rule explicitly NAMES who gets the typography role. Anything
   not matched keeps its own rules.

   Why additive (not subtractive `*:not(...)`):
   - Bounded enumeration. Body / heading / meta patterns are small
     and conventional. They don't grow each time a new module class
     appears.
   - Loud failure mode. A module that names a body class with a
     non-conforming name (e.g. `.foo-zone`) gets NO inherited body
     register — it renders at its own declared size. Wrong size shows
     up in review and the fix is either to add the convention to the
     pattern, or rename the class to follow the convention.
   - No `:not()` maintenance. Utility / data / form / chrome classes
     are auto-excluded because they don't match any opt-in pattern.

   See `.scratch/ui-review/09-typography-system.md`. */

/* BODY — semantic prose elements + body-class name patterns + named
   platform body classes. Inherits cascade size + sets line-height.
   `:not(.is-data)` opt-out: data-dashboard tabs (macro non-handbook,
   credit convertible, equities company_snapshot, money-markets
   instruments) skip the body register and keep their per-element
   module CSS sizing.

   KaTeX exclusion (`:not(.katex, .katex-display, .katex *, .katex-display *)`):
   KaTeX renders every piece of an equation as a deeply-nested <span>
   whose font-size is em-relative and whose line-height KaTeX's own
   stylesheet pins to 1.2 — that's load-bearing for the vertical
   stacking (fraction bars, big-operator limits, sub/superscripts).
   The bare `span` matcher above would otherwise clobber all of that
   with `var(--text-body)` / `var(--line-body)` (1.7), squashing or
   colliding tall expressions — the bug that appeared when this
   additive `:is()` cascade replaced the old subtractive one in May.
   KaTeX is "chrome", same category as `.data-table` / `code` / inputs;
   it must opt OUT of the prose register. */
/* HANDBOOK BODY (locked with !important — see HANDBOOK_SPEC.md §4).
   Inline style="font-size:…" on any body-class descendant is overridden
   so legacy content files (which scatter font-size:0.8rem, 0.82rem etc.)
   can't damage handbook layout. font-family is locked to Inter for the
   same reason. line-height and colour are NOT !important — authors may
   legitimately override these on .learn-callout variants etc. */
.learn-chapter-body :is(
  p, li, dt, dd, blockquote, em, strong, b, i, u, span, a,
  [class*="-body"], [class*="-text"], [class*="-narrative"],
  [class*="-prose"], [class*="-intro"], [class*="-content"],
  .ref-body, .scenario-extra, .learn-callout
):where(:not(.katex, .katex-display, .katex *, .katex-display *)) {
  font-family: var(--font) !important;
  font-size: var(--text-body) !important;
  line-height: var(--line-body);
  color: var(--text);
}
/* TOPIC-SURFACE BODY — same selectors but no !important. Pricer /
   dashboard tabs legitimately use per-element sizing for chart legends,
   metric tiles, etc. The cascade still establishes a default; modules
   override where needed. */
.topic-surface:not(.is-data) :is(
  p, li, dt, dd, blockquote, em, strong, b, i, u, span, a,
  [class*="-body"], [class*="-text"], [class*="-narrative"],
  [class*="-prose"], [class*="-intro"], [class*="-content"],
  .ref-body, .scenario-extra, .learn-callout
):where(:not(.katex, .katex-display, .katex *, .katex-display *)) {
  font-size: var(--text-body);
  line-height: var(--line-body);
  color: var(--text);
}

/* HANDBOOK CHAPTER TITLE (locked — see HANDBOOK_SPEC.md §4). */
.learn-chapter-body :is(h1, .learn-section-title) {
  font-family: 'Newsreader', 'Inter', system-ui, serif !important;
  font-size: var(--text-h1) !important;
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: var(--line-tight);
}
/* TOPIC-SURFACE CHAPTER TITLE — no !important. */
.topic-surface:not(.is-data) :is(h1, .learn-section-title) {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: var(--text-h1);
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: var(--line-tight);
}

/* SECTION HEADING — h2, h3 + named platform section-title classes.
   h3 moved here (from the sub-section cascade below) because content
   authors use <h3> as the chapter-internal section heading, sitting
   ABOVE rows of .ref-card-title sub-sections. .section-title stays
   at this level. .ref-card-title is now treated as a sub-section
   (catches the line 3822 [class*="-title"] pattern → --text-h3). */
/* HANDBOOK SECTION HEADING (locked — see HANDBOOK_SPEC.md §4). */
.learn-chapter-body :is(h2, h3, .section-title) {
  font-family: 'Newsreader', 'Inter', system-ui, serif !important;
  font-size: var(--text-h2) !important;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: var(--line-tight);
}
/* TOPIC-SURFACE SECTION HEADING — no !important. */
.topic-surface:not(.is-data) :is(h2, h3, .section-title) {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: var(--text-h2);
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: var(--line-tight);
}

/* SUB-SECTION HEADING — h3, h4 + named platform sub-heading classes
   + heading-class name patterns. Catches every module-local class
   matching -title / -subtitle / -heading without naming any of them:
     .hf-scenario-step-title, .hf-detail-panel-title, .hf-risk-header-title,
     .hf-section-subtitle, .lc-detail-title, .fut-card-title,
     .fx-example-title, .cs-banner-title, .macro-section-subtitle,
     .intro-placeholder-title, .learn-callout-title …  */
/* HANDBOOK SUB-SECTION HEADING (locked — see HANDBOOK_SPEC.md §4). */
.learn-chapter-body :is(
  h4, .learn-callout-title,
  [class*="-title"], [class*="-subtitle"], [class*="-heading"]
) {
  font-family: 'Newsreader', 'Inter', system-ui, serif !important;
  font-size: var(--text-h3) !important;
  font-weight: 600;
  line-height: var(--line-tight);
  margin-bottom: 8px;
}
/* TOPIC-SURFACE SUB-SECTION HEADING — no !important. */
.topic-surface:not(.is-data) :is(
  h4, .learn-callout-title,
  [class*="-title"], [class*="-subtitle"], [class*="-heading"]
) {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: var(--text-h3);
  font-weight: 600;
  line-height: var(--line-tight);
  margin-bottom: 8px;
}

/* META / LABEL / CAPTION — pattern-matched. Catches .hf-risk-cell-label,
   .hf-pnl-item-label, .lc-detail-label, .cs-banner-label, .ref-label-caption,
   plus the explicit .eyebrow class. Inter sans on the serif body. */
.learn-chapter-body :is(
  [class*="-label"], [class*="-caption"], [class*="-meta"], .eyebrow
),
.topic-surface :is(
  [class*="-label"], [class*="-caption"], [class*="-meta"], .eyebrow
) {
  font-size: var(--text-meta);
  line-height: var(--line-tight);
  font-family: 'Inter', system-ui, sans-serif;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
}
.learn-chapter-body code,
.learn-chapter-body kbd,
.learn-chapter-body pre,
.learn-chapter-body .btn,
.learn-chapter-body .pill-toggle,
.learn-chapter-body .pill-toggle-btn,
.learn-chapter-body .ref-sub-tab,
.learn-chapter-body .glossary-filter-btn,
.learn-chapter-body .glossary-search,
.learn-chapter-body .learn-formula-box,
.learn-chapter-body .learn-formula-inline,
.learn-chapter-body input,
.learn-chapter-body select,
.learn-chapter-body textarea {
  font-family: var(--font);
}
/* Badge in handbook content reads as bold serif inline text — drop
   the Inter override so it stays in Newsreader and just relies on
   font-weight: 600 to differentiate from surrounding prose. */
/* Note: handbook `.data-table` deliberately NOT in this list — handbook
   tables read as editorial content, so they inherit Newsreader. The
   explicit Newsreader rule lives near the data-table block above. */

/* ── Eyebrow label ──────────────────────────────────────────────────────
   Small editorial subtitle above a heading or a paragraph cluster, used
   to mark a topic shift without a divider. Per `05-separation-system.md`
   §2: budget 0-2 per chapter, never as a substitute for a heading.
   Inter sans even inside the handbook serif body. Sized small with mild
   letter-spacing for the small-caps editorial feel. */
.learn-chapter-body .eyebrow {
  display: block;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 6px;
}

/* ════════════════════════════════════════════════════════════════════════
   TOPIC-SURFACE design language
   See `.scratch/ui-review/06-topic-surface-system.md` for the rulebook.

   Topic surfaces sit between handbook (Newsreader-serif, 56px section
   rhythm) and pricer (Inter-mono, 16-24px density). They mix teaching
   prose with interactive widgets — scenario walkthroughs, clickable
   dashboards, KPI groupings, decision-trees. Apply by adding
   `class="topic-surface"` to the tab's outermost container.
   ════════════════════════════════════════════════════════════════════════ */

/* Wrapper — Newsreader cascade matching handbook prose register.
   `:not(.is-data)`: data-dashboard tabs skip the wrapper register so
   children inherit the platform body default (1rem from base.css)
   and module-local font-size rules (.macro-section-subtitle 0.82rem,
   .macro-card-value 1.5rem, etc.) win without fighting the cascade. */
.topic-surface:not(.is-data) {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: var(--text-body);
  line-height: var(--line-body);
  color: var(--text);
}
/* Chrome / data / inputs revert to Inter — same split as handbook */
.topic-surface code,
.topic-surface kbd,
.topic-surface pre,
.topic-surface .btn,
.topic-surface .badge,
.topic-surface input,
.topic-surface select,
.topic-surface textarea,
.topic-surface .data-table,
.topic-surface .data-table th,
.topic-surface .data-table td,
.topic-surface .hf-risk-cell-value,
.topic-surface .hf-risk-cell-label,
.topic-surface .hf-sim-preset-btn,
.topic-surface .hf-scenario-btn,
.topic-surface .lc-detail-pill,
.topic-surface .lc-ac-btn,
.topic-surface .pill-toggle-btn {
  font-family: var(--font);
}
/* Body paragraph rhythm */
.topic-surface p {
  margin-bottom: var(--space-block);
}
.topic-surface p:last-child { margin-bottom: 0; }

/* Section heading (.section-title) — match handbook .ref-card-title.
   Drop the teal underline; type weight + spacing carry the section break.
   `:not(.is-data)`: data-dashboard tabs keep their module-local
   .section-title sizing. */
.topic-surface:not(.is-data) .section-title {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 1.55rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.3;
  color: var(--text);
  margin: 0 0 12px;
  padding-bottom: 0;
  border-bottom: 0;
}
/* Teal hairline divider between consecutive section headings inside a
   topic-surface tab. Matches every second-or-later section heading via
   the general-sibling combinator — works for h2, .ref-card-title, and
   .section-title interchangeably. The first section heading on the
   page has no preceding sibling heading, so it gets no top divider
   (the chapter-level layout above it provides its own boundary). */
.topic-surface :is(h2, .ref-card-title, .section-title) ~ :is(h2, .ref-card-title, .section-title) {
  margin-top: var(--space-topic-section);
  padding-top: var(--space-topic-section);
  border-top: 2px solid var(--sidebar-accent);
}
/* Fallback margin (no divider) for the simple :not(:first-child)
   case when sibling combinator doesn't apply (e.g. nested wrappers). */
.topic-surface .section-title:not(:first-child) {
  margin-top: var(--space-topic-section);
}
.topic-surface .section-title + p,
.topic-surface .section-title + .topic-section-intro {
  margin-bottom: var(--space-block);
}

/* Section-intro paragraph — semantic class for the lead paragraph below
   a .section-title. Mostly defensive: bare <p> already gets the cascade. */
.topic-surface .topic-section-intro {
  font-size: inherit;
  line-height: inherit;
  color: var(--text);
  margin: 0 0 var(--space-block);
}

/* Generic justified frame — interactive widget / KPI / scenario step.
   Per spec §3 + §5: 1px hairline, 6px radius, no bg, no shadow, no
   coloured-left bar. */
.topic-surface .topic-frame {
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  background: transparent;
  padding: 12px 14px;
}

/* KPI / clickable value cell — frame visible enough to read as a
   clickable button. Earlier --border-subtle on bg made cells blend
   into the page. Now: lifted bg + visible border + clear hover
   accent + cursor:pointer. Static variant keeps the lifted bg but
   loses the cursor and hover affordances. */
.topic-surface .hf-risk-cell {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.topic-surface .hf-risk-cell:hover {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 4%, var(--bg-elevated));
}
.topic-surface .hf-risk-cell.active {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 8%, var(--bg-elevated));
  box-shadow: 0 0 0 1px var(--color-accent);
}
.topic-surface .hf-risk-cell--static,
.topic-surface .hf-risk-cell--static:hover {
  cursor: default;
  border-color: var(--border);
  background: var(--bg-elevated);
  box-shadow: none;
}

/* Detail panel after a click (KPI-card click → reveal detail).
   bg-tint only — drop the bg+border+radius triple. */
.topic-surface .hf-detail-panel {
  background: var(--bg-elevated);
  border: 0;
  border-radius: 0;
  padding: 16px 20px;
  margin: var(--space-block) 0;
}
.topic-surface .hf-detail-panel-title {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-weight: 600;
  font-size: 1.2rem;
  color: var(--text);
  margin-bottom: 8px;
}
.topic-surface .hf-detail-panel p {
  font-size: inherit;
  line-height: inherit;
  margin-bottom: var(--space-block);
}
.topic-surface .hf-detail-panel p:last-child { margin-bottom: 0; }

/* Org-card / interactive-widget frame — matches the .hf-risk-cell
   affordance pattern: lifted bg + visible border +
   accent hover/selected. The earlier transparent + --border-subtle
   combo made cards blend into the page so users couldn't tell they
   were clickable. */
.topic-surface .hf-org-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 14px 16px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.topic-surface .hf-org-card:hover {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 4%, var(--bg-elevated));
}
.topic-surface .hf-org-card.active {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 8%, var(--bg-elevated));
  box-shadow: 0 0 0 1px var(--color-accent);
}
.topic-surface .hf-org-card-dotted {
  border-style: dashed;
}

/* Scenario-step extras — pulled-up classes for the inline-style soup
   that lives in scenario-step `extra` HTML (was style="margin-top:12px",
   style="font-size:0.88rem;color:var(--text)" etc). */
.topic-surface .scenario-extra {
  margin-top: var(--space-block);
}
.topic-surface .scenario-extra > :first-child { margin-top: 0; }
.topic-surface .scenario-extra > :last-child { margin-bottom: 0; }
.topic-surface .scenario-extra ul {
  margin: 0 0 var(--space-block);
  padding-left: 1.25em;
  line-height: inherit;
}
.topic-surface .scenario-extra li { margin-bottom: 6px; }
.topic-surface .scenario-extra li:last-child { margin-bottom: 0; }
.topic-surface .scenario-status {
  font-weight: 600;
}
.topic-surface .scenario-status--positive { color: var(--positive); }
.topic-surface .scenario-status--warning  { color: var(--warning); }
.topic-surface .scenario-status--negative { color: var(--negative); }
.topic-surface .scenario-status--critical { color: var(--negative); }

/* Threshold-card grid (inside .topic-surface — auto-fit responsive grid). */
.topic-surface .threshold-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px;
  margin: var(--space-block) 0;
}

/* Sim-control row — slider + label + value triplet (was inline-styled
   in drawdown_limits.js). */
.topic-surface .sim-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin: var(--space-block) 0;
}
.topic-surface .sim-control-label {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.topic-surface .sim-control-value {
  font-size: 0.8rem;
  color: var(--text-secondary);
}
.topic-surface .sim-preset-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: var(--space-block) 0 8px;
}
.topic-surface .sim-preset-bar-label {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-right: 4px;
}

/* Risk-screen sub-section labels (inside the daily risk-screen widget,
   marking "Sector Exposure" / "Top 10 Positions" / "Factor Exposure" /
   "P&L Summary" sub-blocks). */
.topic-surface .hf-risk-section-label {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--text);
  margin: 16px 0 8px;
}
/* Positions-table side cell (LONG/SHORT label) — smaller font than
   the value cells so the side label sits comfortably in the column. */
.topic-surface .hf-positions-side { font-size: 0.8rem; }
/* Scenario-walkthrough conversation block — bg-tinted, no frame
   (per the spec §5 tinted-block rule). */
.topic-surface .scenario-conversation {
  background: var(--bg-elevated);
  border: 0;
  border-radius: 0;
  padding: 16px;
  margin: var(--space-block) 0 0;
  font-size: inherit;
  line-height: inherit;
}
/* Pod-closing note adjustment — adds the 24px top margin previously
   inline. */
.topic-surface .pod-closing-note { margin-top: 24px; }

/* (Removed: .topic-surface .vaes-alert / .vaes-alert-heading / .vaes-block
   rules. Risk → VaR & ES is reclassified as a pricer surface, not a
   topic-surface — the wrapper class is no longer applied to its panel
   and the markup reverted to the original inline-style alert block.) */

/* Slider input + run button + advanced details + chart + stats — pulled
   up from inline styles in simulator-style topic surfaces. */
.topic-surface .sim-control-range { width: 100%; }
.topic-surface .sim-run-btn { margin-bottom: 6px; }
.topic-surface .sim-advanced { margin-bottom: var(--space-block); }
.topic-surface .sim-advanced > summary { cursor: pointer; }
.topic-surface .sim-advanced .sim-control-label { margin-top: 6px; }
.topic-surface .sim-seed-input {
  width: 80px;
  padding: 4px 8px;
  background: var(--bg-input);
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  color: var(--text);
  font-size: 0.85rem;
}
.topic-surface .sim-chart {
  width: 100%;
  height: 360px;
  margin-bottom: var(--space-block);
}
.topic-surface .sim-stats { margin-bottom: var(--space-block); }

/* Result panel after a single computation (e.g. simulation summary
   block at bottom of stats grid). bg-tint only, no frame. */
.topic-surface .result-panel {
  background: var(--bg-elevated);
  border: 0;
  border-radius: 0;
  padding: 14px 18px;
  margin: var(--space-block) 0;
  font-size: inherit;
  line-height: inherit;
}

/* ════════════════════════════════════════════════════════════════════════
   GLOBAL TOPIC-SURFACE NORMALISERS
   Pattern-driven rules that catch every module's variant of common AI-tells
   without enumerating module classes.
   ════════════════════════════════════════════════════════════════════════ */

/* ── .is-data opt-out — body and headings revert to Inter ──────────────────
   Topic-surface tabs that present primarily structured data (macro
   dashboards, equities company snapshot, money-markets instruments,
   credit convertible) keep all the structural rules (40px section
   spacing, dividers, scrubber, about-toggle) but
   their typography reverts to the platform Inter default. */
.topic-surface.is-data,
.topic-surface.is-data :is(
  h1, h2, h3, h4, h5, h6,
  .section-title, .ref-card-title,
  .learn-section-title, .learn-callout-title,
  .hf-detail-panel-title,
  [class*="-title"], [class*="-subtitle"], [class*="-heading"]
) {
  font-family: var(--font);
}

/* ── Headings caps reset ───────────────────────────────────────────────────
   Topic-surface tabs should never present uppercase, letter-spaced
   headings (T09 LLM-tell). Catches every heading element and every
   module-local class matching -title / -heading / -header / -subtitle. */
.topic-surface :is(
  h1, h2, h3, h4, h5, h6,
  .section-title, .ref-card-title,
  .learn-section-title, .learn-callout-title,
  [class*="-title"], [class*="-heading"], [class*="-header"], [class*="-subtitle"]
) {
  text-transform: none !important;
  letter-spacing: -0.005em;
}

/* ── Table sizing inside .topic-surface ────────────────────────────────────
   Tables already revert to Inter (rule above). They also need to be
   smaller than the body prose register (which is 1.1875rem). 0.85rem
   matches the meta/label register and reads cleanly in dense data. */
.topic-surface .data-table,
.topic-surface table {
  font-size: 0.85rem !important;
  line-height: 1.5;
}
.topic-surface :is(.data-table, table) :is(th, td) {
  font-size: inherit !important;
  line-height: inherit;
}

/* ── Sub-tab buttons — unified Inter, consistent size ──────────────────────
   Catches `.subtab-btn`, `.eq-subtab-btn`, `.macro-subtab-btn`,
   `.hf-scenario-btn`, `.ref-sub-tab` and any `[class*="subtab"]` /
   `[class*="-tab-btn"]` variant across modules. One global rule replaces
   per-module sizing decisions. Active state is bold to read clearly. */
.topic-surface :is(
  [class*="subtab"], [class*="-tab-btn"],
  .hf-scenario-btn, .hf-scenario-btn--sub, .hf-scenario-btn--leaf,
  .ref-sub-tab, .pill-toggle-btn
) {
  font-family: var(--font) !important;
  font-size: 0.85rem !important;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}
.topic-surface :is(
  [class*="subtab"], [class*="-tab-btn"],
  .hf-scenario-btn, .hf-scenario-btn--sub, .hf-scenario-btn--leaf,
  .ref-sub-tab, .pill-toggle-btn
):is(.is-active, .active, .selected, .is-selected, [aria-selected="true"]) {
  font-weight: 600;
}

/* ── Loud-pill softener ────────────────────────────────────────────────────
   Pattern-matches every "pill" / "badge" variant inside .topic-surface and
   neutralises the loud-AI-tell visual (uppercase + saturated background +
   bold colour). They become quiet inline labels — readable, contextual,
   not attention-grabbing. The shape (small padding, rounded corners,
   subtle background) survives so they still read as discrete tags.

   Pattern catches: .ss-event-pill, .ss-case-badge, .intro-eco-status-pill,
   .lc-cl-rung-pill-label, .ca-status-pill, .lc-detail-pill, and every
   future *-pill / *-badge / *-tag / *-chip variant without enumeration. */
.topic-surface :is(
  [class*="-pill"], [class*="-badge"], [class*="-tag"], [class*="-chip"],
  .badge
) {
  font-family: var(--font) !important;
  font-size: 0.78rem !important;
  font-weight: 500 !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
  color: var(--text-secondary) !important;
  background: var(--bg-elevated) !important;
  border: 1px solid var(--border-subtle) !important;
  border-radius: 4px !important;
  padding: 2px 8px !important;
  line-height: 1.3 !important;
}

/* ── Decorative coloured-left-borders on timeline/scenario steps ───────────
   The "coloured left bar = categorical attribute" pattern (T01) is an
   AI-tell when used on lifecycle timelines, scenario steps, status rungs.
   Inside .topic-surface, neutralise the categorical coloured-left-border
   on these step/stage elements UNLESS they're in a selected/active/current
   state (where the accent border is a useful selection affordance per
   the user's "kill decorative, keep selection" directive).

   Pattern targets step / stage / rung / timeline-item class-name
   conventions — narrow enough not to break generic uniformly-bordered
   buttons or cards. */
.topic-surface :is(
  [class*="-step"], [class*="-stage"], [class*="-rung"],
  [class*="-timeline-item"], [class*="-event"]
):not(:is(
  .is-active, .is-selected, .is-current, .active, .selected, .current,
  [aria-selected="true"], [aria-current="true"]
)) {
  border-left-color: transparent !important;
  border-left-width: 0 !important;
}
/* Selected state — accent left border (preserved per user directive).
   The selected timeline step still gets a clear leading accent bar. */
.topic-surface :is(
  [class*="-step"], [class*="-stage"], [class*="-rung"],
  [class*="-timeline-item"], [class*="-event"]
):is(
  .is-active, .is-selected, .is-current, .active, .selected, .current,
  [aria-selected="true"], [aria-current="true"]
) {
  border-left: 3px solid var(--color-accent) !important;
}

/* Platform "About this topic" expander.
   One markup contract: .learn-about / .learn-about-toggle / .learn-about-body
   (equities tabs renamed off the legacy .eq-about-* prefix). The wrapper
   gets a 2px vertical left-rule in var(--border-subtle) plus the
   --space-topic-section gap; no horizontal closing hairline. The teal
   hue stays available for active-state accents elsewhere. */
.learn-about {
  position: relative;
  margin: 0 0 var(--space-topic-section);
  padding: 0 0 0 1rem;
  border-left: 2px solid var(--border-subtle);
}
/* Collapsed = single-line summary, so the open-state breathing room
   below the panel reads as dead space. Tighten only when closed. */
.learn-about:not([open]) { margin-bottom: 16px; }
.learn-about[hidden] { display: none; }

.learn-about-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  font-family: var(--font);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  list-style: none;
  text-decoration: none;
  transition: color var(--transition);
}
.learn-about-toggle:hover { color: var(--text-secondary); }
.learn-about-toggle::-webkit-details-marker { display: none; }

/* + glyph drawn from two pseudo-element bars. Vertical bar fades to 0
   on open, leaving a horizontal − . about-expander.js injects the
   .about-glyph span into the summary when it wires up the panel. */
.learn-about-toggle .about-glyph {
  position: relative;
  width: 11px;
  height: 11px;
  display: inline-block;
  flex: 0 0 auto;
  transition: transform var(--transition);
}
.learn-about-toggle .about-glyph::before,
.learn-about-toggle .about-glyph::after {
  content: '';
  position: absolute;
  background: currentColor;
  border-radius: 1px;
}
.learn-about-toggle .about-glyph::before {
  left: 0; right: 0; top: 50%;
  height: 1.5px; margin-top: -0.75px;
}
.learn-about-toggle .about-glyph::after {
  top: 0; bottom: 0; left: 50%;
  width: 1.5px; margin-left: -0.75px;
  transition: opacity var(--transition);
}
details[open] > .learn-about-toggle .about-glyph::after { opacity: 0; }

.learn-about-toggle .label-text { flex: 0 0 auto; }

/* Body — supporting content register: 0.95rem / 1.7, --text-secondary.
   Capped at 68ch so prose stays digestible at wide content-area widths. */
.learn-about-body {
  margin-top: 10px;
  max-width: 68ch;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font);
  font-size: 0.95rem;
  line-height: 1.7;
}
.learn-about-body p { margin: 0 0 0.85rem; }
/* Zero the trailing margin only when the paragraph is truly the last element.
   Using :last-child (not :last-of-type) keeps the paragraph's bottom margin when
   other content follows it — e.g. the "Learn more" xref links some tools append
   to the About body — so those links are not flush against the prose. */
.learn-about-body p:last-child { margin-bottom: 0; }
.learn-about-body strong { color: var(--text); font-weight: 600; }
.learn-about-body em { color: var(--text); font-style: italic; }
.learn-about-body ul,
.learn-about-body ol {
  padding-left: 20px;
  margin: 0 0 0.85rem;
}
.learn-about-body li { margin-bottom: 4px; }

/* Section block + nested cards: flatten every `.card` in handbook
   context. The framed tile (bg-elevated + 1px border + rounded
   corners + 20px padding) was the canonical "container soup" tell
   (T03 + T15) — every concept wrapped in a separate visual box,
   often nested two deep. With the frame gone, the chapter reads as
   flowing prose: chapter title (Newsreader 2.1rem) → section
   sub-heading (1.55rem) → body paragraphs → next section.

   Inner `.card` instances inside .ref-grid-* grids (e.g. four-up
   convention cards) flatten to text-only grid cells; sibling cards
   differentiate by typography and position, the way FT explainers
   and Bloomberg reports do. */
.learn-chapter-body .card {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0 0 var(--space-subsection);
  transition: none;
}
.learn-chapter-body .card:hover {
  background: transparent;
  border: 0;
  box-shadow: none;
}

/* Outer section blocks. Was --space-section (56px) from the card era,
   when each .ref-card-block had visible chrome (bg + border + radius)
   and a 56px gap read as "next visual card". After de-carding the
   gap reads as a page break, so dropped to --space-subsection (32px) —
   still a clear sub-section break, without the chasm. The per-section
   hairline-rule that lived above each .ref-card-title was over-applied
   (3-5 hairlines per chapter); dropped per `05-separation-system.md`
   §3 — the section heading's typographic weight carries the break. */
.learn-chapter-body .card.ref-card-block {
  margin-bottom: var(--space-subsection);
}
.learn-chapter-body .card.ref-card-block:last-child {
  margin-bottom: 0;
}
/* Last-child margin reset INSIDE a card.
   .learn-key-facts, .learn-callout, .data-table-wrap, .figure, .p etc.
   all carry their own margin-bottom (16–32px). When one of them is the
   last child of a card, its margin used to "leak" out and compound with
   the card's own margin-bottom + the grid's row-gap, producing an 80px
   inter-card gap (see Inter-card spacing audit). Zeroing the last-child
   bottom inside a card leaves the card's own margin (--space-subsection
   = 32px) as the SINGLE source of truth for inter-card separation. */
.learn-chapter-body .card.ref-card-block > *:last-child,
.learn-chapter-body .card.ref-card-block .ref-body > *:last-child {
  margin-bottom: 0;
}
.learn-chapter-body .ref-card-title {
  /* Editorial serif — handbook sub-section heading. Sized to match
     .learn-callout-title (both are sub-sections within an H3 section),
     so a card title and a callout title at the same hierarchy level
     read at the same visual register. Hierarchy:
       chapter title (1.9)  →  h3 section (1.55)  →  card/callout title (1.25). */
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: var(--text-h3);
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.25;
  text-transform: none;
  color: var(--text);
  margin: 0 0 14px;
}
/* Cover the case where chapter HTML uses raw <h1>/<h2> (rather than the
   .ref-card-title div). Same family for typographic consistency. */
.learn-chapter-body h1,
.learn-chapter-body h2 {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  letter-spacing: -0.01em;
}
.learn-chapter-body .ref-body {
  /* font-size and line-height now come from the typography cascade at the
     top of this file (font-size: var(--text-body) = 1.125rem after the
     2026-05-12 token change from 19px → 18px). The previous hard-coded
     1.1875rem was a stale leftover that made direct text in .ref-body
     (any content not wrapped in <p>) render 1px larger than its <p>
     siblings — visible inconsistency between blocks. Colour kept on this
     rule because the cascade body rule sets `var(--text)` too but earlier
     module CSS sometimes overrides it; restating here wins by source
     order for any handbook .ref-body. */
  color: var(--text);
}
.learn-chapter-body .ref-body p { margin: 0 0 var(--space-block); }
.learn-chapter-body .ref-body p:last-child { margin-bottom: 0; }
.learn-chapter-body .ref-label {
  /* Inline heading inside callouts / grid cells. The pattern-match
     rule at the top of the typography cascade ([class*="-label"])
     sets font-family: Inter for meta/caption labels — but .ref-label
     here is a SECTION heading inside flowing handbook prose, so it
     belongs in Newsreader serif. Explicitly restate the family. */
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 1.1875rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  line-height: 1.3;
  margin-bottom: 6px;
}
/* Variant classes (--note / --info / --warning / --success / --muted) remain
   in markup for semantic targeting but no longer paint the label. Across the
   handbook these were used decoratively (e.g. FI's "HUMPED" was given the
   warning colour just to differentiate it from sibling cards, not because
   humped curves are a warning), and a single coloured label among siblings
   reads worse than four did. Genuine status flags should use inline span
   semantics, not a label-colour variant. */

/* (Removed: a min-height: calc(2 * 1.3em) was applied to every .ref-label
   inside a grid, on the theory that single-line labels needed to reserve
   space so their column body text aligned with siblings whose labels
   wrapped to two lines. This inserted a literal empty line below every
   single-line heading — the long-standing "blank row" artefact in
   handbook grids. With column-major layout each column flows
   independently anyway, so cross-column body alignment is no longer
   a concern. If a single label ever does wrap to two lines, the body
   text below it will sit slightly lower than its neighbours — a far
   better failure mode than always-on empty space.) */

.learn-chapter-body .ref-pos { color: var(--positive); }
.learn-chapter-body .ref-neg { color: var(--negative); }

/* Vertical spacing model for stacked cards inside chapter grids:
   the card's own margin-bottom (--space-subsection = 32px) is the
   SINGLE source of inter-card air. row-gap is 0 and the last-child
   inside a card has margin-bottom: 0 (rule near .card.ref-card-block
   below), so the three previous mechanisms (inner content margin +
   card margin + grid row-gap) no longer compound. column-gap stays
   for the 2-up side-by-side case. */
.learn-chapter-body .ref-grid-2 { display: grid; grid-template-columns: 1fr 1fr; column-gap: 1rem; row-gap: 0; }
.learn-chapter-body .ref-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); column-gap: 1rem; row-gap: 0; }
.learn-chapter-body .ref-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); column-gap: 0.75rem; row-gap: 0; }
.learn-chapter-body .ref-grid-auto {
  display: grid;
  /* Hard-coded 2-col so 4-item content always lands as 2x2 regardless
     of viewport width. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 24px;
  row-gap: 0;
}
/* Cap grids to the prose measure so they align with surrounding
   paragraphs (was rendering ~150px wider than the prose column).
   Asymmetric vertical margin: 32px top (--space-subsection) gives a
   clear section-to-content break above the first card; 16px bottom
   (--space-block) keeps the grid tightly coupled to what follows so
   a trailing h3's own top margin governs the next section break.
   .full-bleed opt-in for genuinely-wide grids. */
.learn-chapter-body :is(.ref-grid-2, .ref-grid-3, .ref-grid-4, .ref-grid-auto) {
  max-width: var(--measure-prose, 46rem);
  margin: var(--space-subsection) 0 var(--space-block);
}
.learn-chapter-body :is(.ref-grid-2, .ref-grid-3, .ref-grid-4, .ref-grid-auto).full-bleed {
  max-width: none;
}
/* Auto-stack: a 2-col grid that contains a definition list or a table
   becomes a 2-grid-inside-a-2-grid (cramped terms) or a cramped table.
   Collapse to single column when those children are present. */
.learn-chapter-body .ref-grid-2:has(.learn-key-facts),
.learn-chapter-body .ref-grid-2:has(.data-table-wrap),
.learn-chapter-body .ref-grid-2:has(table) {
  grid-template-columns: 1fr;
}
@media (max-width: 800px) {
  .learn-chapter-body .ref-grid-2,
  .learn-chapter-body .ref-grid-3,
  .learn-chapter-body .ref-grid-4 { grid-template-columns: 1fr; }
}

/* ── MM-lens: hedge-fund audience overlay ───────────────────────────────── */
/* Content visible only when the hedge-fund module is enabled for the tenant */
.mm-lens { display: none; }
body[data-hf="1"] .mm-lens { display: block; }
/* Inline variant for spans/links inside paragraphs */
span.mm-lens, a.mm-lens { display: none; }
body[data-hf="1"] span.mm-lens,
body[data-hf="1"] a.mm-lens { display: inline; }

/* (Glossary pricer deep-link styling moved into the main Glossary
   block above — .glossary-card-meta / .glossary-pricer-link. The old
   .glossary-card-header layout was removed when the pricer link moved
   out of the term row into the meta row below the definition.
   See 26-glossary-spec.md X4.) */

/* ── Learn tab (legacy — kept for backward compat) ── */
.learn-section {
  margin-bottom: 36px;
}

.learn-section-title {
  /* Editorial serif — handbook chapter title. Air + weight carry the
     chapter top, no coloured underline. The 2px teal rule was a T08
     SaaS "page-header underline" that read as a section gate rather
     than a textbook chapter opener (Hull / Fabozzi use weight + air
     alone). --space-subsection below carries the air. */
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 1.9rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.15;
  color: var(--text);
  margin-bottom: var(--space-subsection);
}

/* ── Platform-wide section title — soft teal underline below a heading ── */
.section-title {
  font-weight: 700;
  font-size: 1.2rem;
  letter-spacing: -0.005em;
  color: var(--text);
  margin: 28px 0 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--sidebar-accent) 40%, transparent);
}
.section-title:first-child { margin-top: 0; }

/* ── CIP diagram ── */
.cip-diagram {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 24px;
  text-align: center;
  margin: 16px 0;
}

.flow-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 10px 0;
  font-size: 0.9rem;
}

.flow-box {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 8px 14px;
  font-weight: 600;
  font-size: 0.875rem;
  white-space: nowrap;
}
.flow-box.usd  { border-color: var(--color-accent); color: var(--color-accent-light); }
.flow-box.eur  { border-color: var(--positive);     color: var(--positive); }
.flow-arrow { color: var(--text-secondary); font-size: 1.2rem; }
.flow-label { font-size: 0.82rem; color: var(--text-secondary); }

/* ── Sensitivity controls ── */
.sensitivity-controls {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 18px 20px;
  margin-bottom: 16px;
}

.shock-type-selector {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.shock-type-btn {
  padding: 7px 16px;
  font-size: 0.875rem;
  font-weight: 500;
  border-radius: 20px;
  border: 1px solid var(--border);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition), background var(--transition);
  font-family: var(--font);
}
.shock-type-btn:hover { color: var(--text); border-color: var(--color-accent); }
.shock-type-btn.active {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: white;
}

/* (User menu styles removed — replaced by .settings-* above) */

/* ── Shared sidebar form controls (used by MM, FX, and other modules) ── */

.sidebar-section {
  margin-bottom: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border-subtle);
}
.sidebar-section:last-child {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}

.sidebar-input,
.sidebar-select,
.param-input,
.param-select {
  width: 100%;
  padding: 7px 10px;
  font-size: 0.88rem;
  font-family: var(--font);
  background-color: var(--bg-input);
  color: var(--text);
  color-scheme: dark;
  border: 1px solid var(--sidebar-accent-dim);
  border-radius: var(--r-md);
  outline: none;
  transition: border-color var(--transition);
}
/* Numerical sidebar inputs are right-aligned platform-wide. Targets
   type=number AND text inputs declared numeric via inputmode (text inputs
   are the common pattern for comma-formatted large numbers). Text/select
   fields stay left-aligned. */
.sidebar-input[type="number"],
.sidebar-input[inputmode="numeric"],
.sidebar-input[inputmode="decimal"],
.param-input[type="number"],
.param-input[inputmode="numeric"],
.param-input[inputmode="decimal"] {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
[data-theme="light"] .sidebar-input,
[data-theme="light"] .sidebar-select,
[data-theme="light"] .param-input,
[data-theme="light"] .param-select { color-scheme: light; }
.sidebar-input:hover:not(:focus),
.sidebar-select:hover:not(:focus),
.param-input:hover:not(:focus),
.param-select:hover:not(:focus) {
  border-color: var(--sidebar-accent);
}
.sidebar-input:focus,
.sidebar-select:focus,
.param-input:focus,
.param-select:focus {
  border-color: var(--sidebar-accent);
}

/* ── Collapsible sidebar sections ── */
.sidebar-collapsible {
  cursor: pointer;
  user-select: none;
  padding: 2px 4px;
  margin: -2px -4px;
  border-radius: var(--r-sm);
  transition: background var(--transition), color var(--transition);
}
.sidebar-collapsible:hover {
  background: rgba(255,255,255,0.06);
  color: var(--text);
}
[data-theme="light"] .sidebar-collapsible:hover {
  background: rgba(0,0,0,0.05);
}

/* Param controls (Money Markets, credit, etc.) */
.param-group { margin-bottom: 12px; }

.param-label {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 4px;
}

.param-input-wrap {
  display: flex;
  align-items: stretch;
}
.param-prefix,
.param-suffix {
  display: flex;
  align-items: center;
  padding: 0 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--sidebar-accent-dim);
  font-size: 0.85rem;
  color: var(--text-muted);
  white-space: nowrap;
}
.param-prefix {
  border-right: none;
  border-radius: var(--r-md) 0 0 var(--r-md);
}
.param-suffix {
  border-left: none;
  border-radius: 0 var(--r-md) var(--r-md) 0;
}
.param-input-wrap .param-input {
  border-radius: 0;
}
.param-input-wrap .param-input:first-child { border-radius: var(--r-md) 0 0 var(--r-md); }
.param-input-wrap .param-prefix + .param-input { border-radius: 0; }
.param-input-wrap .param-input:last-child { border-radius: 0 var(--r-md) var(--r-md) 0; }

/* Slider value label */
.slider-val {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
  min-width: 56px;
  text-align: right;
  flex-shrink: 0;
}

/* Inline row for small override fields */
.sidebar-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.sidebar-sublabel {
  font-size: 0.78rem;
  color: var(--text-muted);
  min-width: 76px;
  flex-shrink: 0;
}
.sidebar-input-sm {
  flex: 1;
  min-width: 0;
  padding: 5px 8px;
  font-size: 0.82rem;
  font-family: var(--font);
  background: var(--bg-input);
  color: var(--text);
  border: 1px solid var(--sidebar-accent-dim);
  border-radius: var(--r-md);
  outline: none;
  transition: border-color var(--transition);
}
.sidebar-input-sm:focus {
  border-color: var(--sidebar-accent);
}
/* Mono font only for numeric inputs — text inputs and selects keep Inter */
input[type="number"].sidebar-input,
input[type="number"].sidebar-input-sm,
input[type="number"].param-input,
input[type="number"].form-control { font-family: var(--font-mono); }

/* ── Standardised sidebar typography (.sb-* classes) ─────────────────────── */
/* NEW shared classes — modules migrate to these incrementally.
   Old classes (.param-label, etc.) remain untouched until all modules
   are migrated. */

/* Section heading — UPPERCASE, used sparingly (2-5 per sidebar) */
.sb-heading {
  display: block;
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  margin: 0 0 10px;
}
.sb-heading:not(:first-child) { margin-top: 18px; }

/* Input label — Sentence case, used for every input field */
.sb-label {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text-secondary);
  margin: 8px 0 4px;
}
.sb-label:first-child { margin-top: 0; }

/* Sub-label / hint text below an input */
.sb-hint {
  display: block;
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--text-muted);
  opacity: 0.8;
  margin-top: 2px;
}

/* Section wrapper — with divider */
.sb-section {
  margin-bottom: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border-subtle);
}
.sb-section:last-child {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}

/* Section wrapper — no divider (compact) */
.sb-section-plain {
  margin-bottom: 8px;
  padding-bottom: 8px;
}

/* Two-inputs-per-row sidebar grid. Introduced in counterparty-risk (the first
   module to pair sidebar fields on one row) and promoted here so every module's
   sidebar can use it. Cells reflow via grid auto-flow when some are hidden, so
   no gap is left. Each cell is a <div> holding an .sb-label + a .sidebar-input
   / .sidebar-select. */
.sb-field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px 12px;
  align-items: end;
  /* The cell labels below drop .sb-label's own 8px top margin, so without this the
     row would butt straight up against the field above it. Re-apply the same 8px
     here to keep a paired row on the same rhythm as a full-width field. */
  margin-top: 8px;
}
.sb-field-grid:first-child { margin-top: 0; }
.sb-field-grid .sb-label { margin: 0 0 3px; }

/* Collapsible section toggle (expand/collapse) */
.sb-collapsible-toggle {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
  width: 100%;
  cursor: pointer;
  user-select: none;
  padding: 8px 14px;
  min-height: 38px;
  box-sizing: border-box;
  text-align: left;
  background: var(--bg-elevated);
  border: 1px solid var(--sidebar-accent-dim);
  border-radius: var(--r-md);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  transition: border-color var(--transition), background var(--transition);
}
.sb-collapsible-toggle:hover { border-color: var(--sidebar-accent); }
.sb-collapsible-arrow {
  font-size: 0.7rem;
  color: var(--text-muted);
  margin-left: auto;
  transition: color var(--transition);
}
.sb-collapsible-toggle:hover .sb-collapsible-arrow { color: var(--text); }

/* Primary action button (Run, Calculate, Draw Chart) */
.sb-btn-primary {
  width: 100%;
  padding: 8px 14px;
  min-height: 38px;
  box-sizing: border-box;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: var(--font);
  text-align: left;
  text-transform: none;
  background: var(--color-accent);
  color: #fff;
  border: none;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background var(--transition), opacity var(--transition);
}
.sb-btn-primary:hover { opacity: 0.9; }

/* Secondary action button (Draw Chart, Add Bond, etc.) */
.sb-btn-secondary {
  width: 100%;
  padding: 8px 14px;
  min-height: 38px;
  box-sizing: border-box;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: var(--font);
  text-align: left;
  text-transform: none;
  background: var(--bg-elevated);
  color: var(--text);
  border: 1px solid var(--sidebar-accent-dim);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}
.sb-btn-secondary:hover { border-color: var(--sidebar-accent); }

/* (legacy .sb-btn-reset removed — replaced by shared .btn .btn-ghost .btn-sm) */

/* Mono font for numeric text inputs (inputmode="numeric"/"decimal") */
.sidebar-input[inputmode="numeric"],
.sidebar-input[inputmode="decimal"],
.param-input[inputmode="numeric"],
.param-input[inputmode="decimal"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Slider value — add mono font */
.slider-val { font-family: var(--font-mono); }

/* ── Responsive layout ── */
/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE BREAKPOINT STANDARD (platform-wide)

   Three widths, everywhere — module stylesheets must use these values too:
     1024px  tablet step. Shell narrows the sidebar (340 → 280). Content that
             lives on a sidebar tool tab collapses its multi-column grids here,
             because the in-flow sidebar squeezes the content column through
             the whole tablet range.
     768px   the mobile shell. Tab strip → dropdown, sidebar → drawer + FAB,
             gutter collapses, tables become scrollers, 44px touch floor.
             Content on wide (no-sidebar) tabs collapses its grids here.
      480px  small-phone tier. Density/typography floors, 2-col switcher,
             desktop-only fallback banners replace the five gated tools.
   Plus `@media (pointer: coarse)` for touch-target sizing (input-device
   keyed, not width keyed) and the landscape-phone height combo below.

   Do not introduce new width values. The former 900px module tier was folded
   into 768/1024 in the July 2026 mobile remediation (audit:
   edumarkets/audit/mobile-responsiveness-audit-2026-07.md).
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
  .tab-content-area {
    padding: 20px 20px 40px;
  }
  .header-logo {
    width: auto;
    padding-left: 16px;
    padding-right: 16px;
  }
  /* Sidebar: reduce from 340px to 280px on tablets */
  :root { --sidebar-width: 280px; }
}

@media (max-width: 768px) {
  :root { --sidebar-width: 300px; --workspace-gutter: 0px; }
  .tab-content-area {
    padding: 16px 16px 32px;
  }
  .metrics-row {
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  }
  .tab-btn {
    padding: 12px 14px 11px;
    font-size: 0.875rem;
  }

  /* ── Nav bar: logo shrinks ── */
  .header-logo {
    width: auto;
    padding-left: 8px;
    padding-right: 4px;
    /* Let the lockup give way instead of crushing the tab selector.
       Long module names ("Interest Rate Options") used to hold their
       full one-line width here, squeezing .mobile-tab-select to a bare
       chevron. With the shrink chain open (min-width: 0 down to the
       lockup) the name wraps to its 2-line clamp and the tab selector
       keeps its label (min-width floor below). */
    flex-shrink: 1;
    min-width: 0;
  }
  .logo-trigger { min-width: 0; }
  .header-logo-mount { min-width: 0; }
  .header-logo-mount .lockup-led {
    max-width: 100%;
    /* Phone-size lockup: the desktop 22px module name filled the whole
       64px header once it wrapped to 2 lines (e.g. "Life Cycle of a
       Trade"). Same values as the .sm preset in edumarkets-lockups.css,
       set here because the header lockup carries no size preset (see
       the cascade note on .header-logo-mount .lockup-led above). */
    --led-wm-size: 10px;
    --led-name-size: 16px;
  }
  .header-logo-img { height: 32px; }
  .logo-chevron { display: none; }

  /* ── Tab bar: scroll hint gradient ── */
  .header-tab-nav {
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    mask-image: linear-gradient(to right, black 90%, transparent 100%);
    -webkit-mask-image: linear-gradient(to right, black 90%, transparent 100%);
  }
  .header-tab-nav::-webkit-scrollbar { display: none; }

  /* ── Sidebar → slide-in drawer ──
     On mobile the sidebar is CLOSED by default even when `.visible` (the
     class owned by the per-module tab state). The shared drawer toggle in
     js/main.js adds `.drawer-open` to reveal it and removes it to close. */
  .params-sidebar.visible {
    position: fixed;
    top: 64px;
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    min-width: var(--sidebar-width);
    z-index: var(--z-sidebar);
    box-shadow: 4px 0 24px rgba(0,0,0,0.4);
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    background: var(--bg-surface);
  }
  .params-sidebar.visible.drawer-open {
    transform: translateX(0);
  }
  .params-sidebar:not(.visible) {
    position: fixed;
    transform: translateX(-100%);
  }
  .sidebar-toggle-btn { display: none; }

  /* ── Learn / Conventions layout — collapse to single column ── */
  .learn-layout {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .learn-nav-toggle { display: flex; }

  /* ── Glossary layout — collapse to single column; rail becomes a
     horizontal strip above the entries (categories wrap, then A-Z). ── */
  .glossary-layout {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .glossary-rail {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px 16px;
    max-height: none;
    padding-right: 0;
    padding-bottom: 8px;
    margin-bottom: 8px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .glossary-rail-section { margin-bottom: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
  .glossary-rail-label { margin-bottom: 0; padding: 0; }
  .glossary-filter-btn { width: auto; padding: 3px 6px; }
  .learn-nav {
    /* Closed: collapsed and out of the document flow so it doesn't push
       content. (Display:none would cause a flash on open; height:0 keeps
       transitions cheap if we ever reintroduce them.) */
    max-height: 0;
    padding: 0;
    overflow: hidden;
    border: 1px solid transparent;
    border-radius: var(--r-md);
  }
  /* Open: float as a fixed-positioned dropdown anchored just under the
     sticky toggle. Without this the nav expanded at its original document
     position, which is off-screen once the user has scrolled — clicking
     the toggle a second time looked like nothing happened. */
  .learn-nav.mobile-open {
    position: fixed;
    /* Vertical list. flex-direction is restated here so the dropdown can
       never regress to the old row/wrap multi-column grid if some other
       mobile rule sets `row` on .learn-nav again. */
    flex-direction: column;
    /* top is set at runtime by _positionMobileNav() in learn.js from the
       toggle's live position; this value is only a pre-JS fallback. */
    top: 116px;
    left: 12px;
    right: 12px;
    max-height: 60vh;
    padding: 8px;
    overflow-y: auto;
    border: 1px solid var(--border);
    background: var(--bg-surface);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
    z-index: 6;     /* above the sticky toggle (z:4) and sub-tabs (z:5) */
  }
  /* Nav links need bigger touch targets when the disclosure is open. */
  .learn-nav.mobile-open .learn-nav-link {
    padding: 10px 12px;
    min-height: 40px;
  }
  /* Pad chapter scroll target so scrollIntoView leaves room for the sticky
     stack (sub-tabs ~50px + chapter toggle ~44px + small breathing gap)
     above the chapter title. Without this, click-to-scroll lands the
     title behind the sticky controls. */
  .learn-chapter {
    scroll-margin-top: 124px;
  }
  /* ── Sub-tab bar (Learn / In Practice / Conventions / Market Data /
     Glossary): horizontal-scroll pattern, mirroring .eq-subtab-bar below.
     Each tab stays on one line; the strip scrolls rather than wrapping or
     clipping. Scroll/overflow/fade live on the .ref-sub-tabs-wrap sticky
     rule further down (consolidated there). ── */
  .ref-sub-tabs {
    flex-wrap: nowrap;
    /* `safe center`: centre while the tabs fit (e.g. tablet at 768px) but
       fall back to start-alignment when they overflow a phone, so the first
       tab can't be pushed into unreachable scroll space. Desktop keeps plain
       `center` from the base rule — unchanged. */
    justify-content: safe center;
  }
  .ref-sub-tab {
    flex-shrink: 0;        /* never squeeze a label onto two lines */
    white-space: nowrap;
    margin-right: 20px;    /* tighter than the 32px desktop rhythm */
  }

  /* ── Sidebar backdrop ── */
  .sidebar-backdrop {
    position: fixed;
    inset: 0;
    top: 64px;
    background: rgba(0,0,0,0.45);
    z-index: calc(var(--z-sidebar) - 1);
    opacity: 1;
    transition: opacity 0.2s ease;
  }
  .sidebar-backdrop[hidden] { display: none; }

  /* ── Sidebar toggle → Floating Action Button at bottom-left ──
     The button is inserted into the header by js/main.js, but on mobile
     we lift it out of flow and pin it to the bottom-left corner. It
     stays below the sidebar's z-index, so when the drawer slides open
     it naturally tucks behind the panel; when closed it is the only
     affordance to reopen the drawer. */
  .sidebar-mobile-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    left: 16px;
    bottom: 16px;
    width: 52px;
    height: 52px;
    padding: 0;
    border-radius: 50%;
    background: var(--color-accent);
    color: #fff;
    border: none;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
    cursor: pointer;
    z-index: calc(var(--z-sidebar) - 2);
    transition: transform var(--transition), box-shadow var(--transition);
  }
  .sidebar-mobile-toggle svg {
    width: 22px;
    height: 22px;
    color: currentColor;
  }
  .sidebar-mobile-toggle:hover {
    transform: translateY(-1px);
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.4);
  }
  .sidebar-mobile-toggle:active {
    transform: translateY(0);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  }
  .sidebar-mobile-toggle[hidden] { display: none; }

  /* ── Tables: horizontal scroll with a sticky first column ──
     The wrap sets overflow-x: auto and an opaque background so the
     pinned first column has something to render against. Headers stay
     on one line (white-space: nowrap) so wide labels like "INDEXATION
     LAG" don't wrap to one letter per line. The first column is
     pinned to the left edge with position: sticky; left: 0 — same
     trick we use on the pricer grid — so the row label always stays
     visible while the user swipes the rest of the columns into view. */
  .data-table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
    background: var(--bg-surface);
  }
  .data-table-wrap .data-table thead th { white-space: nowrap; }
  .data-table-wrap .data-table thead th:first-child,
  .data-table-wrap .data-table tbody td:first-child {
    position: sticky;
    left: 0;
    background: var(--bg-surface);
    z-index: 2;
    box-shadow: 1px 0 0 var(--border-subtle);
  }
  .data-table-wrap .data-table thead th:first-child { z-index: 3; }
  /* Convention/learn tables inside chapter bodies */
  .learn-chapter-body table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  .learn-chapter-body .learn-key-facts {
    grid-template-columns: 1fr;
    gap: 6px 0;
  }

  /* ── Generic data-table mobile fallback ──
     Tables that AREN'T inside a wrapper (.data-table-wrap, .table-
     container, ad-hoc overflow-x divs) and AREN'T inside a chapter
     card layout become horizontal scrollers themselves. Columns may
     not align perfectly across rows under display:block — that's a
     known trade-off of the responsive-tables pattern — but it beats
     having cells smashed to 20-30px wide with garbled headers (the
     Risk Portfolio / VaR & ES screenshots showed exactly that). */
  .data-table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
    /* Override per-module inline width:100% / table-layout:fixed so
       cells use their natural width and the table scrolls horizontally
       instead of squashing 8 columns into 30px each (Risk Portfolio
       was the smoking gun). !important is the only way to beat inline
       style here; we intentionally accept the heavy-handedness. */
    width: max-content !important;
    min-width: 100% !important;
    table-layout: auto !important;
  }
  /* When the table IS inside a known wrapper, keep it as a real
     table so column alignment is preserved — the wrapper handles
     the horizontal scroll. */
  .data-table-wrap .data-table {
    display: table;
    overflow-x: visible;
  }
  /* The chapter-body card layout (.learn-chapter-body .data-table)
     has its own block-level rules earlier in this file; it's already
     compatible with display:block. */

  /* Hide inline formula buttons inside tables on phones — at 20x20
     each they squeeze short table cells. Formulas are best read on
     desktop; the dedicated Formulas section in Reference still works. */
  .data-table .formula-btn { display: none; }

  /* ── Touch targets ── */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 36px; }
  .glossary-filter-btn { min-height: 36px; padding: 6px 14px; }
  .ref-sub-tab { min-height: 36px; }

  /* ── Pricer grids: compact + sticky label column ──
     The label column is pinned to the left edge so the field name
     stays visible while the user scrolls horizontally through
     instrument columns. The label column also shrinks (220→130px)
     and instrument columns shrink (200→150px) so two fit within a
     390px viewport. ── */
  .pricer-grid { font-size: 0.85rem; }
  .pricer-grid colgroup .col-label {
    width: 110px; min-width: 110px; max-width: 110px;
  }
  .pricer-grid colgroup .col-inst {
    width: 170px; min-width: 170px; max-width: 170px;
  }
  .pricer-grid thead th,
  .pricer-grid tbody td {
    padding: 4px 8px;
  }
  .pricer-grid tbody td:first-child {
    font-size: 0.85rem;
    padding-right: 8px;
  }
  /* Sticky label column. Background must be opaque so scrolling cells
     don't show through. */
  .pricer-grid thead th:first-child,
  .pricer-grid tbody td:first-child {
    position: sticky;
    left: 0;
    background: var(--bg-surface);
    z-index: 2;
    box-shadow: 1px 0 0 var(--border-subtle);
  }
  .pricer-grid thead th:first-child { z-index: 3; }
  /* Divider/section rows: keep the sticky cell visually consistent
     with the row's accent. */
  .pricer-grid tr.divider-row td:first-child,
  .pricer-grid tr.section-row td:first-child,
  .pricer-grid tr.portfolio-btn-row td:first-child {
    background: var(--bg-surface);
  }

  /* Desktop-only fallback banner: the visibility + sibling-hide rules
     live in the 480px block below — banner shows only on phones, where
     the gated tools genuinely don't fit. Tablets see the actual
     content (it works at tablet width). */

  /* ── Formula panel: full-width on mobile ── */
  .formula-panel {
    width: calc(100vw - 16px);
    right: -100vw;
    top: 64px;
    max-height: calc(100vh - 80px);
  }
  .formula-panel--visible { right: 8px; }

  /* ── Challenge panel: inline on mobile ── */
  .ch-panel-right {
    position: static;
    width: 100%;
    max-height: none;
    margin-top: 16px;
  }

  /* ── Module switcher overlay: 2-column on mobile so logos are
     readable and the rhythm matches the dashboard module grid.
     Also reduce the modal's outer padding so the inner grid gets
     more horizontal room (logos render larger). ── */
  .module-switcher-modal {
    padding: 20px 16px;
    width: 96vw;
    max-height: 92vh;
    overflow-y: auto;
    /* Vertical-scroll modal only. overflow-y:auto alone computes overflow-x to
       `auto`, so any surplus width lets the modal pan left/right as you scroll
       — the "drift". Clip it: with the card sizing below nothing overflows, so
       this just guarantees no horizontal pan can ever happen. */
    overflow-x: hidden;
  }
  .module-switcher-group-grid {
    grid-template-columns: repeat(2, 1fr) !important;
    gap: 12px !important;
  }
  /* The lg lockup (26px name) is too big for a 2-up phone cell — the long
     names (Foreign Exchange, Commodities, Total Return Swaps & CFDs) would
     overflow and push the grid past the modal. Shrink it here so every name
     fits without clipping; matches the dashboard's phone rule. */
  #module-switcher-grid .module-card .lockup-led {
    --led-name-size: 19px;
    --led-wm-size: 11px;
  }

  /* ── Modals: constrain height ── */
  .jb-modal, .jp-path-modal, .ob-modal, .pref-modal {
    max-height: calc(100vh - 40px);
  }

  /* ── Touch targets: ensure 44px minimum ── */
  button, [role="button"], select, .settings-item, .ref-sub-tab,
  .glossary-filter-btn, .exercise-option, .inline-dialog-btn {
    min-height: 44px;
  }
  input[type="text"], input[type="number"], input[type="email"],
  input[type="password"], input[type="search"], textarea, select {
    min-height: 44px;
    font-size: 16px; /* prevents iOS zoom on focus */
  }

  /* ── Tab bar replaced with a dropdown on mobile.
     The horizontal .tab-btn strip was unreadable on narrow phones; we
     swap it for a single "Current tab ▾" button that opens a full list
     of every tab in the active module. ── */
  .header-tab-nav { display: none; }
  .header-tab-nav-wrap { display: none; }
  .tab-scroll-btn { display: none !important; }
  .mobile-tab-select {
    display: flex;
    align-items: center;
    justify-content: space-between;
    align-self: center;
    gap: 8px;
    flex: 1;
    /* Floor, not 0: the current-tab label must survive next to a long
       module lockup (which now shrinks instead — see .header-logo above).
       Long tab labels ellipsize via .mobile-tab-select-label. */
    min-width: 128px;
    max-width: 260px;
    margin: 0 8px;
    padding: 8px 12px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    color: var(--text);
    font-weight: 600;
    font-size: 0.9rem;
    cursor: pointer;
  }
  .mobile-tab-select-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
    text-align: left;
  }
  .mobile-tab-select-caret {
    font-size: 0.85rem;
    color: var(--text-secondary);
    transition: transform 0.15s ease;
  }
  .mobile-tab-select[aria-expanded="true"] .mobile-tab-select-caret {
    transform: rotate(180deg);
  }
  .mobile-tab-select[hidden] { display: none; }

  /* Defensive: when the mobile tab select is hidden (no module loaded
     yet, or none of the module tabs are visible), the right-side
     header cluster used to collapse next to the logo because the
     hidden header-tab-nav and hidden mobile-tab-select left no flex-1
     element between them. margin-left: auto on the first right-side
     button always pushes the cluster to the far right. */
  .header-theme-btn { margin-left: auto; }

  /* ── Sticky Reference sub-tabs + Learn Chapters disclosure ──
     Keep the Learn / Conventions / Glossary pill bar AND the
     "Chapters ▾" disclosure pinned to the top of the scrolling
     workspace-content while the user scrolls a long chapter. Two
     stacked sticky elements: pills at top:0, chapters button just
     under them. Opaque backgrounds so chapter content doesn't show
     through. */
  .ref-sub-tabs-wrap {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--bg);   /* page background, not the card-white var(--bg-surface) */
    padding-top: 8px;
    margin-top: -8px;
    /* Small bottom padding for the visible separator gap between pills
       and the chapter toggle. The exact toggle alignment is set by
       reference-tab.js / learn.js at runtime via getBoundingClientRect
       (CSS pixel values were off by 1-3px on iOS depending on font
       rendering, causing either a transparent gap or visible overlap). */
    padding-bottom: 12px;
    /* Horizontal-scroll the sub-tab strip instead of wrapping/clipping it. */
    overflow-x: auto;
    overflow-y: hidden;      /* the active-tab ::after underline must not spawn a vertical scrollbar */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Kept fully opaque (no right-edge mask fade) so page content does not
       show through the bar while scrolling. Still horizontally scrollable. */
  }
  .ref-sub-tabs-wrap::-webkit-scrollbar { display: none; }
  .learn-nav-toggle {
    /* Quick-return (headroom pattern): sticky just under the sticky sub-tabs
       bar so the Chapters control stays reachable. learn.js adds .nav-autohide
       on scroll-down and removes it on scroll-up, so it tucks away while
       reading and returns the moment you scroll up — no scroll-back-to-top.
       Sticky (not fixed) keeps its space reserved in flow, so removing it never
       jumps the content; its natural position is right under the sub-tabs, so
       there's no natural->stuck travel. --ref-subtabs-h = the measured sub-tabs
       height (set at runtime in learn.js; the fallback covers first paint). */
    position: sticky;
    top: var(--ref-subtabs-h, 50px);
    z-index: 4;                 /* under the sub-tabs bar (z:5), over content */
    background: var(--bg-surface);
    margin: 0 0 12px;
    transition: transform 0.25s ease, opacity 0.2s ease;
    will-change: transform;
  }
  .learn-nav-toggle.nav-autohide {
    /* Tuck up behind the opaque sub-tabs bar. Transform (not top) so it's
       GPU-cheap and doesn't reflow. */
    transform: translateY(calc(-100% - var(--ref-subtabs-h, 50px) - 20px));
    opacity: 0;
    pointer-events: none;
  }
  /* Options module uses a different layout: .ref-sub-tabs-wrap lives
     in .options-reference (outside the scroll container) and the
     scroll container is .ref-sub-panel. So the toggle inside .ref-
     sub-panel doesn't need to leave 72px of clearance for sub-tabs
     — those are already pinned in a separate sticky context above
     the scroll. Stick the toggle to the top of its scroll container
     instead. */
  .options-reference .learn-nav-toggle {
    top: 0;
  }
  .options-reference .learn-chapter {
    /* Smaller scroll-margin too: only need to clear the toggle (~44px)
       since the sub-tabs aren't in this scroll container. */
    scroll-margin-top: 56px;
  }
  .options-reference .learn-nav.mobile-open {
    /* Fixed-position dropdown is viewport-relative; in Options the
       toggle is visible at the top of the in-page scroll area, which
       sits below the app header (~64px) + sub-tab bar (~52px) ≈ 116px.
       The toggle itself adds ~44px so the dropdown opens at ~160px. */
    top: 160px;
  }

  /* ── Module-internal sub-tab bars: enable horizontal scroll instead
     of wrapping/clipping. Covers equities (.eq-subtab-bar) and macro
     (.macro-subtab-bar), which both use a flex row of buttons.
     `overflow-y: hidden` is explicit — without it the CSS spec auto-
     promotes overflow-y from `visible` to `auto`, and the active tab's
     ::after underline at `bottom: -1px` then triggers a tiny vertical
     scrollbar. */
  .eq-subtab-bar,
  .macro-subtab-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Soft fade on the right edge hints that there are more tabs. */
    mask-image: linear-gradient(to right, black 90%, transparent 100%);
    -webkit-mask-image: linear-gradient(to right, black 90%, transparent 100%);
  }
  .eq-subtab-bar::-webkit-scrollbar,
  .macro-subtab-bar::-webkit-scrollbar { display: none; }
  .eq-subtab-btn,
  .macro-subtab-btn {
    flex-shrink: 0;
    white-space: nowrap;
  }

  /* ── Learn content: drop the nested scroll container on mobile.
     The outer .workspace-content is the one and only scroll container, so
     scrollIntoView lands the chapter under the sticky header and the rest
     of the content flows below. Also pins overflow-x so long words and
     wide tables can't push the content off the left edge. ── */
  .learn-layout {
    flex: 0 0 auto;
    min-height: 0;
  }
  .learn-content {
    overflow-y: visible;
    /* `clip` not `hidden`: `hidden` promotes overflow-y to a computed
       `auto`, turning this into a phantom scroll container that traps the
       chapter scroll. `clip` contains horizontally without establishing a
       scroll context, so vertical scroll falls through to .workspace-content
       (the real scroller). Same pattern options.css uses for .options-content. */
    overflow-x: clip;
    padding-bottom: 24px;
  }
  .learn-chapter {
    scroll-margin-top: 80px;  /* clear sticky .app-header (64px) + breathing room */
  }
  .learn-chapter-body,
  .learn-chapter-body p,
  .learn-chapter-body li {
    overflow-wrap: anywhere;
    word-break: break-word;
  }
  /* On mobile we undo the desktop "tab-panel = flex column with
     overflow:hidden + inner .learn-content scrolls" pattern so the
     entire page can scroll naturally inside .workspace-content.
     Without this override the chapter body overflows the clipped
     panel with no scroll ancestor and the page becomes unscrollable. */
  .tab-panel.active:has(.ref-sub-tabs-wrap),
  .tab-panel.active:has(.ref-sub-tabs-wrap) > .tab-content-area,
  .tab-panel.active:has(.ref-sub-tabs-wrap) > .tab-content-area > div {
    display: block;
    overflow: visible;
    flex: none;
    min-height: 0;
  }
  .tab-content-area { overflow-x: hidden; }
  /* Belt-and-braces clip on every Learn-body container so nothing — wide
     tables, pre blocks, ref-grid cards — can force the content past the
     viewport edge. */
  .learn-chapter,
  .learn-chapter-body,
  .learn-chapter-body .card,
  .learn-chapter-body .ref-card-block {
    max-width: 100%;
    min-width: 0;
    /* clip, not hidden: `.learn-chapter` is the click-to-scroll target, and
       hidden's implicit overflow-y:auto made _findScrollContainer() stop on
       the chapter itself — so clicking a chapter never scrolled the page.
       clip contains horizontally without trapping vertical scroll. */
    overflow-x: clip;
  }

  /* ── Pricer grid hint: modules are desktop-first. Show a short note
     above every .pricer-grid-wrap so users understand the grid is meant
     to be scrolled horizontally and the full workflow wants more space. */
  .pricer-grid-wrap::before {
    content: 'Pricer grids are optimised for desktop — scroll horizontally to see all cells.';
    display: block;
    padding: 8px 12px;
    margin-bottom: 8px;
    background: color-mix(in srgb, var(--color-accent) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--color-accent) 30%, transparent);
    border-radius: var(--r-md);
    color: var(--text-secondary);
    font-size: 0.78rem;
    line-height: 1.4;
  }
}

/* ── Mobile tab dropdown panel ──
   The .mobile-tab-select trigger button is hidden on desktop via the
   `@media (min-width: 769px)` rule below; on mobile it is shown by the
   `@media (max-width: 768px)` block above. We do NOT add a default
   `display: none` here because that rule would load after the 768px
   `display: flex` and silently win on mobile. */
.mobile-tab-dropdown {
  position: fixed;
  z-index: var(--z-dropdown, 1200);
  max-height: 60vh;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.4);
  padding: 4px;
  display: flex;
  flex-direction: column;
  min-width: 200px;
}
.mobile-tab-dropdown[hidden] { display: none; }
.mobile-tab-option {
  display: flex;
  align-items: center;
  padding: 10px 14px;
  background: none;
  border: none;
  color: var(--text);
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
  border-radius: 4px;
  min-height: 44px;
}
.mobile-tab-option:hover,
.mobile-tab-option:focus-visible {
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  outline: none;
}
.mobile-tab-option.active {
  color: var(--color-accent-light, var(--color-accent));
  font-weight: 600;
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}

/* Desktop: hide mobile-only elements */
@media (min-width: 769px) {
  .sidebar-mobile-toggle { display: none !important; }
  .sidebar-backdrop { display: none !important; }
  .mobile-tab-select { display: none !important; }
  .mobile-tab-dropdown { display: none !important; }
  .desktop-only-msg { display: none !important; }
}

@media (max-width: 480px) {
  :root { --sidebar-width: 88vw; }

  /* Phone-only Reference sub-tabs: tighter pills so the Learn / Conventions
     / Glossary bar takes less vertical room. The reference-tab.js anchor
     re-measures and updates .learn-nav-toggle's top accordingly so the
     chapter dropdown sits flush with the smaller bar. */
  .ref-sub-tab {
    padding: 4px 12px;
    font-size: 0.78rem;
  }
  .ref-sub-tabs-wrap {
    padding-top: 6px;
    padding-bottom: 10px;
  }
  /* Print / PDF control hidden on phones: printing a handbook chapter from
     a phone is rare, and the right-pinned control overlapped the last
     sub-tab once the strip started scrolling. Stays available on tablet+. */
  .ref-print { display: none; }

  /* ── Desktop-only fallback ──
     Show the banner AND hide the sibling (the actual tool) on phones.
     The five gated tabs (Vol Surface, Portfolio in FI/Credit/IR-Options,
     Market Participants in Intro) genuinely don't render usefully on a
     phone, so the half-broken UI under the banner just looks messy.
     Tablets (481-768px) skip both rules: the actual tool renders and
     no banner is shown, since at tablet width the layouts are usable.
     `!important` here beats the default `display:none` from the base
     rule and the `display:none !important` on min-width:769px. */
  .desktop-only-msg { display: flex !important; }
  .desktop-only-msg + * { display: none !important; }

  /* Pricer grid: shrink the sticky label column further on very narrow
     phones so the instrument column still has enough room for date and
     dropdown values like "Semi-Annual" and "24/04/2026". */
  .pricer-grid colgroup .col-label {
    width: 95px; min-width: 95px; max-width: 95px;
  }
  .pricer-grid colgroup .col-inst {
    width: 160px; min-width: 160px; max-width: 160px;
  }
  .pricer-grid thead th,
  .pricer-grid tbody td { padding: 3px 6px; }
  .pricer-grid tbody td:first-child { font-size: 0.78rem; }

  .metrics-row { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); }
  .tab-btn { padding: 10px 10px 9px; font-size: 0.82rem; }
  .header-logo-img { height: 28px; }
  .card { padding: 14px; }
  .card-sm { padding: 10px 12px; }
  .form-control { padding: 10px 12px; }  /* touch-friendly inputs */
  .sidebar-input, .param-input { padding: 10px 10px; }
  .callout { padding: 10px 14px; }
  /* Mobile: scale down from the 19px desktop reading size, but keep
     proportions of the new editorial hierarchy. */
  .learn-chapter-body p { font-size: 1.0625rem; }
  .learn-chapter-body h3 { font-size: 1.2rem; }
  .learn-section-title { font-size: 1.4rem; }

  /* ── Hide search pill on small phones ── */
  .header-search-pill { display: none; }

  /* ── Header button cluster: tighten so theme / menu fit ── */
  .app-header { padding-right: 6px; }
  .header-theme-btn { width: 32px; min-width: 32px; }
  .settings-btn { width: 34px; min-width: 34px; }
  .sidebar-mobile-toggle { width: 32px; height: 32px; font-size: 1rem; }

  /* ── Font size floors ── */
  .text-sm { font-size: 0.78rem; }
  .data-table td, .data-table th { font-size: 0.78rem; }
  .result-val { font-size: 0.82rem; }

  /* ── Module switcher: 2 columns on phones ── */
  .module-switcher-group-grid {
    grid-template-columns: repeat(2, 1fr) !important;
    gap: 10px !important;
  }

  /* ── Walkthrough tooltip: narrower on phones ── */
  .wt-tooltip { width: 320px; }
  .wt-try-card { width: 340px; }
}

/* ── Landscape phone: give more vertical space ── */
@media (max-height: 500px) and (max-width: 900px) {
  .app-header { min-height: 48px; }
  .header-logo-img { height: 24px; }
  .tab-btn { padding: 8px 12px 7px; font-size: 0.82rem; }
  .params-sidebar.visible { top: 48px; }
  .sidebar-backdrop { top: 48px; }
}

/* ── Touch devices (input-keyed, not width-keyed) ──
   Ported from the Bank ALM sim touch pass. Sliders keep their thin
   visual track (redrawn on the track pseudo-elements, which WebKit
   centres vertically in the input box) but the input becomes a 40px
   hit strip with a 26px thumb; small controls get tap-target floors.
   Mouse/trackpad devices are untouched (pointer: fine). The equities
   sidebar slider variant (#eq-params-content, equities.css) is
   ID-scoped and keeps its own sizing by specificity, as documented
   where the shared .sidebar-slider is defined. */
@media (pointer: coarse) {
  /* The `html` prefix lifts these to (0,1,2): fi.css, credit.css and
     ir-options.css each carry an unscoped `input[type="range"]` rule
     (0,1,1) that loads after this file and would otherwise win the
     height back. The #eq-params-content variant (1,1,0) still wins,
     by design. */
  html input[type="range"] { height: 40px; background: transparent; }
  html input[type="range"]::-webkit-slider-runnable-track {
    height: 5px; background: var(--border); border-radius: 3px;
  }
  html input[type="range"]::-moz-range-track {
    height: 5px; background: var(--border); border-radius: 3px;
  }
  html input[type="range"]::-webkit-slider-thumb {
    width: 26px; height: 26px; margin-top: -10.5px; /* centre on the 5px track */
  }
  html input[type="range"]::-moz-range-thumb { width: 26px; height: 26px; }

  html input.sidebar-slider { height: 40px; background: transparent; }
  /* (0,2,2): the light-theme element background at (0,2,0) would
     otherwise repaint the whole 40px hit strip grey. */
  html[data-theme="light"] input.sidebar-slider { background: transparent; }
  html input.sidebar-slider::-webkit-slider-runnable-track {
    height: 6px; background: rgba(255, 255, 255, 0.12); border-radius: 3px;
  }
  html input.sidebar-slider::-moz-range-track {
    height: 6px; background: rgba(255, 255, 255, 0.12); border-radius: 3px;
  }
  html[data-theme="light"] input.sidebar-slider::-webkit-slider-runnable-track {
    background: rgba(0, 0, 0, 0.1);
  }
  html[data-theme="light"] input.sidebar-slider::-moz-range-track {
    background: rgba(0, 0, 0, 0.1);
  }
  html input.sidebar-slider::-webkit-slider-thumb {
    width: 26px; height: 26px; margin-top: -10px; /* centre on the 6px track */
  }
  html input.sidebar-slider::-moz-range-thumb { width: 26px; height: 26px; }

  /* Tap-target floors for the controls the width-keyed 768 block does
     not reach on touch laptops/tablets at desktop widths. */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 40px; }
  .sidebar-input, .param-input, .sidebar-select, .form-control { min-height: 40px; }
  .formula-btn { width: 26px; height: 26px; }
}

/* ── Desktop-Only Fallback Message ── */
.desktop-only-msg {
  display: none;  /* shown via media query */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 48px 24px;
  min-height: 40vh;
  gap: 12px;
  color: var(--text-secondary);
}
.desktop-only-msg-icon {
  font-size: 2rem;
  opacity: 0.6;
  margin-bottom: 4px;
}
.desktop-only-msg h3 {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--text);
}
.desktop-only-msg p {
  font-size: 0.9rem;
  max-width: 320px;
  line-height: 1.6;
}
.desktop-only-msg .btn {
  margin-top: 8px;
}

/* ── Command Palette (Ctrl+K) ── */
.cmd-palette-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(var(--blur-sm));
  -webkit-backdrop-filter: blur(var(--blur-sm));
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 9vh;
}
.cmd-palette-overlay[hidden] { display: none; }

@keyframes cmdPaletteIn {
  from { opacity: 0; transform: scale(0.97) translateY(-6px); }
  to   { opacity: 1; transform: scale(1)    translateY(0); }
}
.cmd-palette-overlay:not([hidden]) .cmd-palette {
  animation: cmdPaletteIn 0.15s ease;
}

.cmd-palette {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  width: 92vw;
  max-width: 560px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.15);
  overflow: hidden;
}
[data-theme="light"] .cmd-palette {
  box-shadow: 0 16px 40px rgba(27, 58, 92, 0.15), 0 2px 8px rgba(27, 58, 92, 0.06);
}
[data-theme="light"] .cmd-palette-overlay {
  background: rgba(0, 0, 0, 0.35);
}

.cmd-palette-search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}

.cmd-palette-search-icon {
  flex-shrink: 0;
  color: var(--text-muted);
}

.cmd-palette-input {
  flex: 1;
  background: none;
  border: none;
  outline: none;
  font-size: 1rem;
  font-family: var(--font);
  color: var(--text);
  min-width: 0;
}
.cmd-palette-input::placeholder { color: var(--text-muted); }

.cmd-palette-esc-hint {
  font-size: 0.7rem;
  font-family: var(--font-mono);
  color: var(--text-muted);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 2px 6px;
  flex-shrink: 0;
}

.cmd-palette-results {
  max-height: min(62vh, 560px);
  overflow-y: auto;
  padding: 6px;
}
.cmd-palette-results:empty {
  padding: 20px;
  text-align: center;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.cmd-section-label {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
  padding: 10px 10px 4px;
}

.cmd-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background 0.1s;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  font-family: var(--font);
}
.cmd-item:hover,
.cmd-item.selected {
  background: var(--bg-elevated);
}
.cmd-item.selected {
  outline: 1px solid var(--color-accent);
}

.cmd-item-label {
  flex: 1;
  font-size: 0.9rem;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cmd-item-context {
  font-size: 0.78rem;
  color: var(--text-muted);
  white-space: nowrap;
}

.cmd-item-hint {
  font-size: 0.68rem;
  font-family: var(--font-mono);
  color: var(--text-muted);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  flex-shrink: 0;
}

.cmd-no-results {
  padding: 24px 16px;
  text-align: center;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.cmd-item-label mark {
  background: color-mix(in srgb, var(--color-accent) 16%, transparent);
  color: inherit;
  border-radius: var(--r-sm, 4px);
  padding: 0 1px;
}

/* ── Sortable table headers ── */
th.sortable {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
th.sortable:hover { color: var(--color-accent-text); }
th.sort-asc::after  { content: ' \25b2'; opacity: 0.7; font-size: 0.7em; }
th.sort-desc::after { content: ' \25bc'; opacity: 0.7; font-size: 0.7em; }

/* ── Table export button ── */
.table-export-btn {
  display: inline-block;
  margin-bottom: 8px;
  font-size: 0.75rem;
  padding: 3px 10px;
}

/* ── Formula Panel (floating) ──
   Spec 20-formula-spec.md F1-F6 visual sweep applied. Equations
   de-carded (F1), section labels mixed-case (F2), intuition prose
   in Newsreader (F3), positioning via --header-clearance token (F4),
   numeric ink via --formula-numeric token (F5), variables table
   textbook-grade (F6). */
.formula-panel {
  position: fixed;
  top: var(--header-clearance);
  right: -620px;
  width: 560px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - var(--header-clearance) - 24px);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: 0 8px 28px rgba(0,0,0,0.3), 0 1px 4px rgba(0,0,0,0.15);
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  transition: right 0.25s ease;
  overflow: hidden;
}
[data-theme="light"] .formula-panel {
  box-shadow: 0 8px 28px rgba(27, 58, 92, 0.12), 0 1px 4px rgba(27, 58, 92, 0.06);
}
.formula-panel--visible { right: 16px; }

/* When the formula panel is open on top of the bootstrap dialog, make room
   for it. The panel is 560px wide, pinned 16px from the right edge, so it
   reserves ~592px (560 + 16 right + 16 gap). We left-anchor the dialog and
   cap its width to the space left of the panel, instead of shifting a fixed
   distance. A fixed transform clips the dialog's left edge on viewports
   narrower than ~1676px (dialog 1100 + reserved 592), where there is simply
   not enough room for both at full width. Capping the width keeps every part
   of the dialog and the panel visible at any viewport size; the dialog's
   inner grid (minmax(280px,1fr) 2fr) reflows cleanly when narrowed. */
.iro-dialog--bs {
  transition: width 0.25s ease, margin-left 0.25s ease;
}
body:has(.formula-panel--visible) .iro-dialog--bs {
  margin-left: 16px;
  margin-right: auto;
  width: min(1100px, calc(100vw - 592px));
}

/* Rate-table dialog + its formula panel behave as a centered PAIR. A right-
   pinned panel next to a centre-flexed dialog flies to opposite screen edges;
   instead we keep the two together near the middle with clear breathing room
   between them. Geometry (dialog 640, panel 560, gap 40 -> pair 1240): the
   dialog is re-centred then shifted left by (560+40)/2 = 300px, and the panel is
   pulled off the right edge to left = 50% + (640-560)/2 + 40/2 = 50% + 60px, so
   the pair is symmetric about 50% with a 40px gap. Both top-align at the header
   clearance so they read as one unit. Only where the 1240px pair fits
   (>= 1280px); on narrower screens the dialog narrows and the panel stays at the
   right edge so they never overlap. */
.iro-dialog--formula-host {
  transition: transform 0.25s ease, width 0.25s ease, margin 0.25s ease;
}
body:has(.formula-panel--visible) .iro-dialog--formula-host {
  margin-left: 16px;
  margin-right: auto;
  width: min(1100px, calc(100vw - 592px));
}
@media (min-width: 1280px) {
  body:has(.formula-panel--visible) .iro-dialog--formula-host {
    margin: var(--header-clearance) 0 0;
    align-self: flex-start;
    width: 640px;
    transform: translateX(-300px);
  }
  body:has(.iro-dialog--formula-host) .formula-panel--visible {
    left: calc(50% + 60px);
    right: auto;
  }
}

/* While a formula-hosting dialog is on screen (the curve-bootstrap animation,
   or the range-accrual rate table with its per-column ƒ buttons), raise the
   formula panel above the dialog's overlay backdrop so the formula stays
   visible when opened from inside the dialog. Without this, var(--z-max) on the
   overlay sits above var(--z-overlay) on the panel and the panel disappears
   behind the blur. */
body:has(.iro-dialog--bs) .formula-panel,
body:has(.iro-dialog--formula-host) .formula-panel {
  z-index: calc(var(--z-max) + 1);
}

.formula-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  cursor: grab;
  user-select: none;
  flex-shrink: 0;
}
.formula-panel-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
}
.formula-panel-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.3rem;
  cursor: pointer;
  line-height: 1;
  padding: 0 4px;
}
.formula-panel-close:hover { color: var(--text); }

.formula-panel-body {
  padding: 18px 20px;
  overflow-y: auto;
  flex: 1;
}
.formula-section {
  margin-bottom: var(--space-block);
}
.formula-section:last-child { margin-bottom: 0; }

/* F2 — mixed-case quiet semibold (was uppercase + tracked T09 tell) */
.formula-section-label {
  font-family: var(--font);
  font-size: 0.85rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  margin-bottom: 6px;
  font-weight: 600;
}

/* F1 — de-card. Equations sit in flowing white space, flex-centred
   like .learn-formula-box for coherence with handbook R2. */
.formula-symbolic,
.formula-numeric,
.formula-extra {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  margin: 4px 0;
  overflow-x: auto;
  font-size: 1.15rem;
}
.formula-symbolic .katex-display,
.formula-numeric .katex-display,
.formula-extra .katex-display { margin: 0; }
/* The symbolic/numeric sections render KaTeX into the full-width flex
   container itself, but the extra ("Where"/approximation) section renders
   into this child wrapper. Under align-items:center the child shrink-wraps
   to its content, so _wrapIfOverflowing (formula-panel.js) measures a
   too-narrow box and needlessly fragments a formula that fits at 518px.
   Stretch it to full width so the overflow check matches the other sections. */
.formula-extra-content { align-self: stretch; }
.formula-numeric {
  font-size: 1.05rem;
  color: var(--formula-numeric);
}

/* F6 — variables table: textbook-grade header (weight, no caps),
   no per-row separator on the body, subtle top hairline only on
   row transitions (denser than handbook tables — this is a reference
   card, not flowing prose). */
.formula-var-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
  font-family: var(--font);
}
.formula-var-table th {
  text-align: left;
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.85rem;
  color: var(--text);
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}
.formula-var-table td {
  padding: 6px 10px;
  border-bottom: 0;
  color: var(--text);
  vertical-align: top;
}
.formula-var-table tbody tr + tr td {
  border-top: 1px solid var(--border-subtle);
}
.formula-var-sym {
  color: var(--text);
  font-weight: 500;
  min-width: 40px;
  font-family: 'KaTeX_Main', 'STIX Two Text', serif;
  font-style: italic;
}
.formula-var-val {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--formula-numeric);
  text-align: right;
}
.formula-var-unit {
  color: var(--text-muted);
  font-size: 0.78rem;
}

/* F3 — intuition prose as panel chrome (Inter sans, --text-secondary,
   no italic). Treats the intuition block as an annotation/tooltip
   note rather than a textbook commentary. Quieter than the earlier
   Newsreader-italic variant; the tinted background keeps the visual
   separation from the variables table above. */
.formula-intuition {
  font-family: var(--font);
  font-size: 0.88rem;
  font-weight: 400;
  font-style: normal;
  line-height: 1.55;
  color: var(--text-secondary);
  padding: 12px 14px;
  margin-top: var(--space-subsection);
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
  border-radius: 4px;
}

/* F10 — Distill hover-on-symbol-to-highlight. Hovering any rendered
   variable symbol in the formula highlights both that symbol (in
   every formula it appears) and the matching row in the variables
   table. Reverse: hover a row → highlight matching symbols. */
.is-formula-symbol {
  cursor: help;
  transition: background 0.1s, color 0.1s;
  border-radius: 2px;
}
[data-formula-sym].is-highlighted {
  background: color-mix(in srgb, var(--formula-numeric) 28%, transparent);
  color: var(--formula-numeric);
}
.formula-var-table tbody tr.is-highlighted td {
  background: color-mix(in srgb, var(--formula-numeric) 10%, transparent);
}

/* F12 — Copy LaTeX button. Sits next to the section label, hidden
   visual weight at rest, picks up on hover. Briefly turns positive
   colour after a successful copy. */
.formula-section-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.formula-copy-btn {
  background: transparent;
  border: 0;
  color: var(--text-muted);
  padding: 3px 5px;
  cursor: pointer;
  border-radius: 3px;
  display: inline-flex;
  align-items: center;
  transition: color 0.15s, background 0.15s;
}
.formula-copy-btn:hover {
  color: var(--text);
  background: var(--bg-elevated);
}
.formula-copy-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}
.formula-copy-btn.is-copied {
  color: var(--positive);
}

/* F11 — See-in-handbook cross-reference link. Footer slot under the
   intuition prose; quiet underlined link with a trailing arrow. */
.formula-cross-ref {
  margin-top: var(--space-block);
  padding-top: var(--space-block);
  border-top: 1px solid var(--border-subtle);
  font-size: 0.85rem;
}
.formula-cross-ref-link {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--text-muted);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
.formula-cross-ref-link:hover {
  text-decoration-color: var(--text);
}
.formula-cross-ref-link::after { content: ' \2192'; }

/* F13 — Refresh nudge animation. Brief background flash on a numeric
   cell whose value changed during a refreshFormula() pass. */
.formula-var-val.num-flash {
  animation: formula-num-flash 700ms ease-out;
}
@keyframes formula-num-flash {
  0%   { background: color-mix(in srgb, var(--formula-numeric) 28%, transparent); }
  100% { background: transparent; }
}

/* F9 — visible focus ring inside the panel for keyboard users */
.formula-panel button:focus-visible,
.formula-panel a:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ── Formula icon button (next to result labels) ── */

/* The formula button sits inline in the label cell. It is centred via
   vertical-align (below), NOT by making the cell display:flex — a flex cell is
   pulled out of the table's row-height model, so a label cell holding a button
   stops matching the height of its (taller, Tier-1 font) value cell and the
   row's bottom border drifts out of line between the two columns. Keeping the
   cell as a normal table-cell lets both columns share one row height. */

.formula-btn {
  display: inline-flex;
  vertical-align: middle;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-elevated);
  color: var(--text-muted);
  cursor: pointer;
  margin-left: 4px;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
  line-height: 1;
  padding: 0;
  /* Hide the markup's text content (most modules emit a literal 'f'
     character, not the italic ƒ glyph) — the actual glyph is drawn
     via ::before so every button is visually consistent regardless
     of what the per-module markup says. */
  font-size: 0;
}
.formula-btn::before {
  /* Matches the original pre-change Georgia italic ƒ exactly: 0.72rem
     italic, normal weight (no bold). The ::before approach is needed
     because most modules emit a literal 'f' in markup, not the U+0192
     glyph — drawing via ::before guarantees the right character.
     The translateY compensates for the italic ƒ's hook descender:
     align-items: center centers the line box, but the visible glyph
     (including the hook below baseline) sits low. -1.5px nudges it
     to optical centre inside the 20×20 button. */
  content: 'ƒ';
  font-family: Georgia, 'Times New Roman', serif;
  font-style: italic;
  font-weight: normal;
  font-size: 0.72rem;
  display: inline-block;
  line-height: 1;
  transform: translateY(-1.5px);
}
.formula-btn:hover {
  color: var(--color-accent-text);
  border-color: var(--color-accent);
  background: rgba(46,109,164,0.1);
}
.formula-btn--active {
  color: var(--color-accent-text);
  border-color: var(--color-accent);
  background: rgba(46,109,164,0.15);
}

/* ── Custom Tooltip ──
   Quiet popover: 1px border + soft shadow + offset from trigger is enough
   to read as a tooltip without an accent-coloured left stripe. */
.edu-tooltip {
  position: fixed;
  z-index: var(--z-modal);
  max-width: 280px;
  padding: 8px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 4px 12px rgba(0,0,0,0.25), 0 1px 3px rgba(0,0,0,0.1);
  color: var(--text);
  font-size: 0.78rem;
  line-height: 1.45;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
[data-theme="light"] .edu-tooltip {
  box-shadow: 0 4px 12px rgba(27, 58, 92, 0.1), 0 1px 3px rgba(27, 58, 92, 0.06);
}
.edu-tooltip--visible {
  opacity: 1;
  transform: translateY(0);
}

/* ── Portfolio Save/Load Bar ── */
.pf-storage-bar {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.pf-storage-select {
  width: 100%;
  padding: 4px 6px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text);
  font-family: var(--font);
  font-size: 0.72rem;
  transition: border-color 0.15s;
}
.pf-storage-select:focus { border-color: var(--color-accent); outline: none; }
.pf-storage-btn {
  padding: 3px 0;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text-secondary, var(--text));
  font-family: var(--font);
  font-size: 0.7rem;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, border-color 0.15s ease;
  text-align: center;
}
.pf-storage-btn:hover:not(:disabled) {
  border-color: var(--text-muted);
}
.pf-storage-btn:disabled { opacity: 0.3; cursor: default; }
.pf-storage-btn-primary {
  color: var(--color-accent-text);
  border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);
}
.pf-storage-btn-primary:hover:not(:disabled) {
  border-color: var(--color-accent);
}
.pf-storage-btn-danger { color: var(--negative); border-color: transparent; }
.pf-storage-btn-danger:hover:not(:disabled) {
  border-color: var(--negative);
}
/* Button groups */
.pf-storage-actions {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 3px;
}
.pf-storage-io {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px;
}
/* Hint text. Uses --text-secondary (no opacity) so it stays readable in both
   themes: the variable flips darker in light mode, lighter in dark mode. A flat
   opacity knocks the contrast down too far to read. */
.pf-storage-hint {
  font-size: 0.65rem;
  color: var(--text-secondary);
  line-height: 1.3;
  margin-top: 1px;
}

/* ── Portfolio Save Dialog ── */
.pf-save-dialog-overlay {
  position: fixed; inset: 0; z-index: 9999;
  background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  animation: pf-fade-in 0.15s ease;
}
@keyframes pf-fade-in { from { opacity: 0; } to { opacity: 1; } }
.pf-save-dialog {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  width: 380px;
  max-width: 90vw;
  box-shadow: 0 16px 48px rgba(0,0,0,0.25);
  animation: pf-slide-up 0.2s ease;
}
@keyframes pf-slide-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
.pf-save-dialog h3 {
  margin: 0 0 16px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
}
.pf-save-dialog input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text);
  font-family: var(--font);
  font-size: 0.9rem;
  outline: none;
  transition: border-color 0.15s;
  box-sizing: border-box;
}
.pf-save-dialog input[type="text"]:focus { border-color: var(--color-accent); }
.pf-save-dialog-actions {
  display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px;
}
.pf-save-dialog-actions button {
  padding: 8px 20px;
  border-radius: 8px;
  border: 1px solid var(--border);
  font-family: var(--font);
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s ease;
}
.pf-save-dialog-actions .pf-dlg-cancel {
  background: var(--bg-surface); color: var(--text-muted);
}
.pf-save-dialog-actions .pf-dlg-cancel:hover { background: var(--bg-elevated); }
.pf-save-dialog-actions .pf-dlg-confirm {
  background: var(--color-accent); color: #fff; border-color: var(--color-accent);
}
.pf-save-dialog-actions .pf-dlg-confirm:hover { filter: brightness(1.12); }
.pf-save-dialog-actions .pf-dlg-confirm:disabled { opacity: 0.4; cursor: default; }
.pf-save-dialog-actions .pf-dlg-danger {
  background: var(--negative); color: #fff; border-color: var(--negative);
}

/* ============================================================
   Journey — Full Path Modal (shared: dashboard + app)
   ============================================================ */

.jp-path-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.7);
  backdrop-filter: blur(4px);
  z-index: var(--z-modal, 600);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s;
}
.jp-path-overlay.jp-path-visible { opacity: 1; }

.jp-path-modal {
  background: var(--bg-surface, #111214);
  border: 1px solid var(--border, #252830);
  border-radius: 16px;
  width: 640px;
  max-width: 95vw;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.jp-path-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 24px 28px 16px;
}
.jp-path-header h2 {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text);
  margin: 0;
}
.jp-path-subtitle {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin: 4px 0 0;
}
.jp-path-subtitle-complete { color: var(--positive); }
.jp-path-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.4rem;
  cursor: pointer;
  line-height: 1;
}
.jp-path-close:hover { color: var(--text); }

.jp-path-progress {
  height: 4px;
  background: var(--bg-elevated);
  margin: 0 28px 8px;
  border-radius: 2px;
}
.jp-path-progress-bar {
  height: 100%;
  background: var(--color-accent);
  border-radius: 2px;
  transition: width 0.3s;
}
.jp-path-progress-bar-complete { background: var(--positive); }

.jp-path-footer {
  display: flex;
  justify-content: flex-end;
  padding: 14px 28px 20px;
  border-top: 1px solid var(--border);
}

/* ── Base Case comparison panel (UX report R6, shared/base-case-panel.js) ──
   A floating draggable panel reusing the formula-panel shell, offset below
   the formula panel's default spot so both can be open without landing on
   top of each other. Every pricer that offers a Base Case renders through
   these classes. */
.formula-panel.base-case-float {
  width: 620px;
  top: calc(var(--header-clearance) + 48px);
}
/* The toggle button sits in a pricer grid's label cell. A fixed min-width
   sized to the longer label keeps Set/Clear from shifting the column. */
.base-case-toggle { min-width: 132px; }
/* Rows stay on one line whatever the numbers grow to. */
.base-case-table td,
.base-case-table th { white-space: nowrap; }
.base-case-block + .base-case-block { margin-top: 14px; }
.base-case-head {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text-secondary);
  margin-bottom: 6px;
}
.base-case-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.base-case-table th {
  text-align: right;
  padding: 4px 10px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}
.base-case-table td {
  text-align: right;
  padding: 4px 10px;
  font-family: var(--font-mono);
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border-subtle, var(--border));
}
.base-case-table .base-case-label {
  text-align: left;
  font-family: var(--font);
  color: var(--text);
}
/* The change column is the point of the panel — it carries the emphasis. */
.base-case-table .base-case-delta {
  font-weight: 700;
  color: var(--text);
}
.base-case-table .base-case-delta.positive { color: var(--positive); }
.base-case-table .base-case-delta.negative { color: var(--negative); }

/* The step list reads as a syllabus: numbered/✓ markers on a vertical
   connecting rail (Spec §Q3). */
.jp-path-list {
  flex: 1;
  overflow-y: auto;
  padding: 8px 24px 20px;
  position: relative;
}
.jp-step-row {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: 12px;
  align-items: baseline;
  padding: 9px 4px 9px 6px;
  border-radius: 6px;
  text-decoration: none;
  color: var(--text);
  font-size: 0.9rem;
  cursor: pointer;
  position: relative;
}
.jp-step-row:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
/* connecting rail behind the markers */
.jp-step-row::before {
  content: '';
  position: absolute;
  left: 14px; top: 0; bottom: 0;
  width: 1px;
  background: var(--border-subtle);
  z-index: 0;
}
.jp-step-row:first-of-type::before { top: 14px; }
.jp-step-row:last-of-type::before { bottom: calc(100% - 14px); }
/* the marker */
.jp-step-icon {
  position: relative; z-index: 1;
  width: 16px; height: 16px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.7rem; line-height: 1;
  background: var(--bg-surface);
  border: 1.5px solid var(--border);
  color: transparent;
  flex-shrink: 0;
}
.jp-step-todo   .jp-step-icon { border-color: var(--border); }
.jp-step-active .jp-step-icon { border-color: var(--color-accent); }
.jp-step-done   .jp-step-icon { border-color: var(--positive); color: var(--positive); }

/* Module heading above each consecutive run of steps in that module.
   Rows themselves carry only the page label. */
.jp-group-module {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-weight: 600; color: var(--text);
  font-size: 0.95rem;
  padding: 14px 4px 4px 36px;
}
.jp-path-list > .jp-group-module:first-child { padding-top: 6px; }
.jp-step-body { display: block; min-width: 0; }
.jp-step-tab { color: var(--text-secondary); }
.jp-step-done .jp-step-tab { color: var(--text-muted); text-decoration: none; }
/* opt-badges: quiet text, no uppercase tracking, no tinted chip */
.jp-step-opt {
  font-size: 0.78rem; color: var(--text-muted);
  background: transparent; padding: 0; border-radius: 0;
  text-transform: none; letter-spacing: 0;
  margin-left: 6px;
}
.jp-step-note {
  font-size: 0.82rem; color: var(--text-muted);
  font-style: italic; display: block; margin-top: 2px;
}

.jp-path-cert {
  display: flex;
  justify-content: center;
  padding: 16px 20px 8px;
  border-top: 1px solid var(--border);
}
.jp-cert-btn {
  padding: 10px 28px;
  background: var(--positive, #22c55e);
  color: #fff;
  border: none;
  border-radius: 6px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.jp-cert-btn:hover { filter: brightness(1.1); }

/* ── Certificate overlay ── */
.cert-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.8);
  backdrop-filter: blur(8px);
  z-index: var(--z-modal, 600);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 0.3s;
}
.cert-overlay.cert-visible { opacity: 1; }
.cert-modal { max-width: 95vw; max-height: 95vh; display: flex; flex-direction: column; }
.cert-toolbar {
  display: flex; justify-content: flex-end; gap: 12px; padding: 0 0 14px;
}
.cert-save-btn {
  padding: 9px 24px; background: var(--color-accent); color: #fff;
  border: none; border-radius: 6px; font-size: 0.85rem; font-weight: 600;
  cursor: pointer; font-family: 'DM Sans', 'Inter', sans-serif;
}
.cert-save-btn:hover { background: var(--color-accent-light); }
.cert-save-btn:disabled { opacity: 0.5; cursor: default; }
.cert-close-btn {
  background: none; border: none; color: rgba(255,255,255,0.5);
  font-size: 1.5rem; cursor: pointer; line-height: 1;
}
.cert-close-btn:hover { color: #fff; }

/* Certificate card */
.cert-body {
  background: #0B0B0C;
  border-radius: 12px; overflow: hidden; text-align: center;
  /* A4 portrait aspect ratio */
  width: 560px; height: 792px;
  max-width: 95vw; max-height: 82vh;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
  display: flex; flex-direction: column;
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
/* Hidden render target for PDF capture — exact A4 at 96dpi */
.cert-render {
  position: fixed; top: -9999px; left: -9999px;
  width: 794px; height: 1123px;
  max-width: none; max-height: none;
  border-radius: 0; box-shadow: none;
}

.cert-accent-line {
  height: 3px; flex-shrink: 0;
  background: linear-gradient(90deg, #1B3A5C 0%, #2E6DA4 40%, #14b8a6 100%);
}
.cert-inner {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 48px 56px 40px;
}
.cert-logo-img {
  height: 44px; width: auto; margin-bottom: 10px;
}
.cert-render .cert-logo-img { height: 62px; }
.cert-tagline {
  font-family: 'JetBrains Mono', 'Fira Code', monospace;
  font-size: 0.55rem; font-weight: 500; letter-spacing: 2px;
  color: rgba(59,130,246,0.85); margin-bottom: 24px;
}
.cert-render .cert-tagline { font-size: 0.7rem; }
.cert-rule {
  width: 80px; height: 1px; margin: 24px auto;
  background: rgba(255,255,255,0.06);
}
.cert-type {
  font-family: 'DM Sans', 'Inter', sans-serif;
  font-size: 1.6rem; font-weight: 600; color: #f0f0f0;
  letter-spacing: -0.5px; margin-bottom: 32px;
}
.cert-render .cert-type { font-size: 2.2rem; }
.cert-awarded {
  font-size: 0.82rem; font-weight: 300; color: rgba(255,255,255,0.45); margin-bottom: 6px;
}
.cert-render .cert-awarded { font-size: 1rem; }
.cert-name {
  font-family: 'DM Sans', 'Inter', sans-serif;
  font-size: 2rem; font-weight: 600; color: #f0f0f0;
  letter-spacing: -0.5px; margin-bottom: 28px;
}
.cert-render .cert-name { font-size: 2.6rem; }
.cert-journey {
  font-size: 1.1rem; font-weight: 600;
  color: #4B9FD4; margin-bottom: 32px;
}
.cert-render .cert-journey { font-size: 1.4rem; }
.cert-metrics { display: flex; align-items: center; gap: 36px; margin-bottom: 28px; }
.cert-metric { text-align: center; }
.cert-metric-val {
  font-family: 'DM Sans', 'Inter', sans-serif;
  font-size: 1.6rem; font-weight: 600; color: #f0f0f0; display: block;
}
.cert-render .cert-metric-val { font-size: 2rem; }
.cert-metric-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.55rem; color: rgba(255,255,255,0.3);
  text-transform: uppercase; letter-spacing: 1.5px; margin-top: 4px; display: block;
}
.cert-render .cert-metric-label { font-size: 0.65rem; }
.cert-metric-divider { width: 1px; height: 36px; background: rgba(255,255,255,0.06); }
.cert-date { font-size: 0.82rem; font-weight: 300; color: rgba(255,255,255,0.4); margin-bottom: 10px; }
.cert-render .cert-date { font-size: 1rem; }
.cert-legal { font-size: 0.55rem; color: rgba(255,255,255,0.2); }
.cert-render .cert-legal { font-size: 0.65rem; }

[data-theme="light"] .jp-path-modal { background: var(--bg-surface); }
[data-theme="light"] .jp-step-row:hover { background: rgba(0,0,0,0.03); }
[data-theme="light"] .jp-path-progress { background: rgba(0,0,0,0.06); }
[data-theme="light"] .jp-step-done .jp-step-module,
[data-theme="light"] .jp-step-done .jp-step-tab { text-decoration-color: rgba(0,0,0,0.15); }

/* ═══════════════════════════════════════════════════════════════════════════════
   Getting Started Cards
   ═══════════════════════════════════════════════════════════════════════════════ */

.gs-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  padding: 16px 20px;
  margin-bottom: 16px;
  max-width: 720px;
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.gs-card-enter { opacity: 0; transform: translateY(-8px); }
.gs-card-exit  { opacity: 0; transform: translateY(-8px); }

.gs-card-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.gs-card-title {
  font-weight: 600; font-size: 0.9rem; color: var(--text);
}
.gs-card-close {
  background: none; border: none; color: var(--text-muted);
  font-size: 1.2rem; cursor: pointer; padding: 0 4px;
  line-height: 1; transition: color var(--transition);
}
.gs-card-close:hover { color: var(--text); }

.gs-card-desc {
  color: var(--text-secondary); font-size: 0.85rem; line-height: 1.5;
  margin: 0 0 10px;
}

.gs-card-steps { margin-bottom: 10px; }
.gs-card-steps-label {
  font-size: 0.8rem; font-weight: 600; color: var(--text);
  margin-bottom: 4px; display: block;
}
.gs-card-steps ol {
  margin: 4px 0 0; padding-left: 20px;
  list-style: decimal;
}
.gs-card-steps li {
  font-size: 0.82rem; color: var(--text-secondary);
  line-height: 1.5; margin-bottom: 4px;
}

.gs-card-nudge {
  font-size: 0.8rem; color: var(--text-muted);
  margin: 8px 0 12px; font-style: italic;
}

.gs-card-actions {
  display: flex; gap: 8px; justify-content: flex-end;
}
.gs-btn {
  border: none; border-radius: var(--r-sm); padding: 6px 14px;
  font-size: 0.8rem; font-weight: 500; cursor: pointer;
  font-family: var(--font); transition: all var(--transition);
}
.gs-btn-secondary {
  background: var(--color-accent); color: #fff;
}
.gs-btn-secondary:hover { filter: brightness(1.1); }
.gs-btn-tertiary {
  background: transparent; color: var(--text-muted);
  border: 1px solid var(--border);
}
.gs-btn-tertiary:hover { color: var(--text); border-color: var(--text-muted); }

.gs-help-btn {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--bg-elevated); border: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.85rem; font-weight: 600;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: all var(--transition);
}
.gs-help-btn:hover { color: var(--text); border-color: var(--text-muted); }

/* ═══════════════════════════════════════════════════════════════════════════════
   Guidance buttons (Try It launcher and any future toolbar buttons)
   ═══════════════════════════════════════════════════════════════════════════════ */

.guidance-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 5px 12px;
  font-size: 0.8rem; font-weight: 500; color: var(--text-secondary);
  cursor: pointer; font-family: var(--font);
  transition: all var(--transition);
}
.guidance-btn:hover { color: var(--text); border-color: var(--text-muted); }
.guidance-btn.active {
  background: var(--color-accent); color: #fff;
  border-color: var(--color-accent);
}
.guidance-btn-icon { font-size: 0.9rem; }

/* ═══════════════════════════════════════════════════════════════════════════════
   Try It puzzle panel — floating draggable card, walkthrough-tooltip styling
   ═══════════════════════════════════════════════════════════════════════════════ */

.puzzle-panel {
  /* Match the walkthrough tooltip's visual language so guidance surfaces
     read as one family. */
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md, 8px);
  padding: 20px 24px;
  /* Wider than the original 420 so the taller states (a mechanism check plus
     the held explanation) wrap onto fewer lines and stay short enough to fit. */
  width: 600px;
  max-width: 92vw;
  /* max-height is set in JS (_capPanelHeight) to the room below the panel's
     top, so a very tall state scrolls inside the panel rather than running off
     the bottom of the screen. On a roomy display it never scrolls. */
  overflow-y: auto;
  box-shadow: 0 10px 32px rgba(0, 0, 0, 0.30), 0 1px 4px rgba(0, 0, 0, 0.15);
  font-family: var(--font);
  transition: border-color var(--transition);
  display: flex; flex-direction: column; gap: 12px;
}
[data-theme="light"] .puzzle-panel {
  box-shadow: 0 10px 32px rgba(27, 58, 92, 0.15), 0 1px 4px rgba(27, 58, 92, 0.06);
}
.puzzle-panel-solved { border-color: var(--positive); }

.puzzle-panel-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
.puzzle-step-count {
  font-size: 0.88rem; font-weight: 500;
  color: var(--text-muted);
}
.puzzle-checkmark {
  color: var(--positive); font-weight: 600;
}
.puzzle-close {
  background: none; border: none; padding: 0 2px;
  color: var(--text-muted); font-size: 1.35rem; line-height: 1;
  cursor: pointer;
}
.puzzle-close:hover { color: var(--text); }

.puzzle-title {
  font-family: var(--font);
  font-size: 1.05rem; font-weight: 600;
  color: var(--text);
  line-height: 1.35;
}

.puzzle-col-note {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-top: -2px;
}

.puzzle-status {
  display: flex; flex-direction: column; gap: 5px;
  padding: 10px 12px;
  background: var(--bg-elevated);
  border-radius: var(--r-sm);
  transition: background var(--transition);
}

.puzzle-status-row {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 0.92rem;
}
.puzzle-status-label {
  color: var(--text-muted);
  min-width: 68px;
}
.puzzle-current-val, .puzzle-target-val {
  font-family: var(--font-mono); font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.puzzle-diff-val {
  font-family: var(--font-mono);
  color: var(--text-muted);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
}
.puzzle-status[data-state="close"]  .puzzle-diff-val { color: var(--warning); }
.puzzle-status[data-state="solved"] .puzzle-diff-val { color: var(--positive); }

.puzzle-hint {
  font-size: 0.95rem; color: var(--text-secondary);
  line-height: 1.5;
  padding: 10px 12px;
  background: var(--bg-elevated);
  border-radius: var(--r-sm);
  font-style: italic;
}
.puzzle-explanation {
  font-size: 0.98rem; color: var(--text);
  line-height: 1.5;
  padding: 12px 14px;
  background: var(--bg-elevated);
  border-radius: var(--r-sm);
}

.puzzle-actions {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px;
  margin-top: 4px;
}
.puzzle-actions-right {
  display: flex; gap: 6px;
}
.puzzle-btn {
  padding: 8px 16px;
  border-radius: var(--r-md, 8px);
  font-size: 0.92rem; font-weight: 600;
  cursor: pointer; border: none;
  font-family: var(--font);
  transition: background 0.15s, transform 0.1s, color 0.15s, border-color 0.15s;
}
.puzzle-btn:active { transform: scale(0.97); }
.puzzle-btn-quiet {
  background: transparent;
  color: var(--text-muted);
}
.puzzle-btn-quiet:hover { color: var(--text); }
.puzzle-btn-primary {
  background: var(--color-accent); color: #fff;
}
.puzzle-btn-primary:hover { background: var(--color-accent-light); }

/* Predict puzzles: three-choice commit, then observe, then verdict. Styled to
   match the puzzle panel family (same surfaces, radii, spacing). */
.puzzle-predict-choices {
  display: flex; gap: 8px;
}
.puzzle-choice-btn {
  flex: 1;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-md, 8px);
  background: var(--bg-elevated);
  color: var(--text);
  font-family: var(--font);
  font-size: 0.92rem; font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.1s;
}
.puzzle-choice-btn:hover {
  border-color: var(--color-accent);
  color: var(--color-accent-text);
}
.puzzle-choice-btn:active { transform: scale(0.97); }

.puzzle-predict-observe {
  display: flex; flex-direction: column; gap: 10px;
}
.puzzle-predict-instruction {
  font-size: 0.95rem;
  color: var(--text-secondary);
}
.puzzle-predict-choice-val {
  font-weight: 600;
  color: var(--text);
}
.puzzle-predict-verdict {
  font-size: 0.98rem;
  color: var(--text);
  line-height: 1.5;
}

/* Mechanism check ("Quick check"): a one-question multiple-choice beat shown
   after a puzzle resolves, holding the explanation until it is answered. Reuses
   the puzzle-choice-btn surfaces, stacked and left-aligned for full-text options. */
.puzzle-debrief {
  display: flex; flex-direction: column; gap: 10px;
}
.puzzle-debrief-label {
  font-size: 0.76rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted);
}
.puzzle-debrief-prompt {
  font-size: 0.98rem; font-weight: 500;
  color: var(--text); line-height: 1.4;
}
.puzzle-debrief-options {
  display: flex; flex-direction: column; gap: 8px;
}
.puzzle-debrief-opt {
  text-align: left;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-md, 8px);
  background: var(--bg-elevated);
  color: var(--text);
  font-family: var(--font);
  font-size: 0.92rem; line-height: 1.4;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.1s;
}
.puzzle-debrief-opt:hover:not(:disabled) {
  border-color: var(--color-accent);
  color: var(--color-accent-text);
}
.puzzle-debrief-opt:active:not(:disabled) { transform: scale(0.99); }
.puzzle-debrief-opt:disabled { cursor: default; }
.puzzle-debrief-opt[data-debrief-state="correct"] {
  border-color: var(--positive); color: var(--positive);
}
.puzzle-debrief-opt[data-debrief-state="wrong"] {
  border-color: var(--negative); color: var(--negative);
}
.puzzle-debrief-actions {
  display: flex; justify-content: flex-end;
}
/* These containers set display:flex, which overrides the UA stylesheet's
   default [hidden] { display: none }. Re-assert it so the JS can actually hide
   the whole mechanism check and its Skip: otherwise the check leaks onto the
   screen before the puzzle resolves and leaves a stray second Skip. Same trap
   as .sidebar-toggle-btn above. (The predict choice buttons deliberately stay
   visible after commit, so they are not re-asserted here.) */
.puzzle-debrief[hidden],
.puzzle-debrief-actions[hidden] { display: none; }

/* Recap card: the final panel state after a tab's puzzle run. */
.puzzle-recap-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.puzzle-recap-item {
  font-size: 0.95rem; color: var(--text); line-height: 1.45;
}
.puzzle-recap-link {
  margin-top: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   Guidance Toolbar — banner-mode chrome only
   ═══════════════════════════════════════════════════════════════════════════════ */

/* (Old .jm-banner layout overrides removed — replaced by .jm-strip.) */

/* When the journey banner is present, buttons go into its centre slot and
   inherit the banner's layout. When no banner is present, the guidance
   buttons skip the toolbar entirely and float top-right of the viewport
   via `.guidance-btn-floating` (see below). */

/* Floating-button host: stacks multiple buttons vertically in the top-right
   corner with a small gap. Anchored just below the top app bar so it stays
   out of the way of search, theme toggle, hamburger menu. */
.guidance-float-host {
  /* Mounted inside .app-header by pill_host.js. The host is created
     with all its pills already inside (single atomic DOM commit), so
     both Walkthrough and Try It appear together — no opacity gate
     needed.

     `position: fixed` still anchors to the viewport. Living inside
     the header means the host inherits the header's stacking context,
     so the user menu's local z-index (350) correctly stacks above
     the pills. */
  position: fixed;
  top: 90px;
  right: 24px;
  display: flex; flex-direction: column; gap: 8px;
  align-items: flex-end;
  pointer-events: none; /* let the page beneath be interactive */
}
.guidance-float-host > * { pointer-events: auto; }

.guidance-btn-floating {
  /* Compact accent-tinted pill. Both the Walkthrough and Try It pills
     share this class AND the same inner DOM shape: a label span plus an
     optional dismiss span. Inner spans inherit font verbatim from the
     button so both pills render identically regardless of which slot
     they hold. */
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--color-accent);
  color: #fff;
  border: none;
  border-radius: 999px;
  padding: 8px 18px;
  font-family: var(--font);
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  cursor: pointer;
  transition: background var(--transition), transform 0.1s, box-shadow var(--transition);
}
.guidance-btn-floating:hover {
  background: var(--color-accent-light);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.22);
}
.guidance-btn-floating:active { transform: scale(0.97); }
.guidance-btn-floating.active {
  /* Hidden when the corresponding panel is open. */
  display: none;
}

/* Phone/tablet: the top-right anchor lands on the first row of content
   (sub-tab strips, controls bars, stat rows) and the pills covered live
   controls — including the open mobile tab dropdown. Anchor the host
   bottom-right instead, mirroring the drawer FAB at bottom-left. The
   pills still float over content, but over the scrolling bottom edge
   rather than the tab's control row, and each hides while its panel is
   open. This block must stay AFTER the base .guidance-float-host rule
   above: it lives outside the master mobile block (components.css
   ~5264) because that block comes earlier in the file and the base
   top: 90px would win the cascade over it. */
@media (max-width: 768px) {
  .guidance-float-host {
    top: auto;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    right: 16px;
  }
}

.guidance-pill-label,
.guidance-pill-dismiss {
  /* Inherit the pill's font sizing exactly. Stops the historical drift
     where the Walkthrough label sat in a span and rendered subtly
     different from the Try It pill's bare text node. */
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}
.guidance-pill-dismiss {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 2px;
  color: rgba(255, 255, 255, 0.7);
  border-radius: 3px;
  cursor: pointer;
  transition: color var(--transition), background var(--transition);
}
.guidance-pill-dismiss:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.18);
}

/* Legacy `.guidance-toolbar` retained for any banner-mode markup. */
.guidance-toolbar {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 0 0 12px;
  padding: 6px 10px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-sm);
  background: var(--bg-elevated);
}
.guidance-toolbar:empty {
  display: none;
}

/* ============================================================
   Inline Glossary
   ============================================================ */

.gl-term {
  border-bottom: 1px dotted var(--sidebar-accent);
  cursor: help;
  transition: border-color 0.15s;
}
.gl-term:hover { border-color: var(--color-accent-light); }

.gl-tooltip {
  position: fixed;
  z-index: 800;
  background: var(--bg-elevated, #1c1e23);
  border: 1px solid var(--border, #252830);
  border-radius: 8px;
  padding: 12px 16px;
  max-width: 360px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  transform: translateX(-50%) translateY(-100%);
  pointer-events: none;
  animation: glFadeIn 0.15s ease;
}
@keyframes glFadeIn { from { opacity: 0; } to { opacity: 1; } }

.gl-tooltip strong {
  display: block;
  font-size: 0.82rem;
  color: var(--color-accent-light);
  margin-bottom: 4px;
}
.gl-tooltip p {
  font-size: 0.8rem;
  color: var(--text);
  line-height: 1.5;
  margin: 0;
}

[data-theme="light"] .gl-tooltip {
  background: var(--bg-surface);
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

/* Chapter footer: container that groups xref-links at the end of a learn chapter.
   The xref-links inside size themselves; the footer just reserves vertical space. */
.chapter-footer {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.chapter-footer .xref-link { margin-top: 0; margin-bottom: 0; }

/* ==========================================================================
   xref-link — unified deep/cross link
   --------------------------------------------------------------------------
   THE single visual treatment for every cross- and deep-link across the
   platform: an editorial inline link, one accent colour, with a small
   arrow glyph. No boxes, no pills, no per-module variants.

   The kind='try'|'learn' API survives only to vary the WORDING
   ("Try this in…" vs "Learn more in…"); it no longer changes the colour.
   A bare .xref-link (no variant class) renders identically.
   ========================================================================== */
/* Cross-ref link ("Try this in [Pricer]" / "Learn more in [Module]"):
   editorial inline-link treatment, not a SaaS pill CTA. The accent
   colour and small `→` glyph carry the affordance. align-items:
   center on the flex container is correct for icon-plus-text — with
   align-items: baseline the SVG iconwrap's box-baseline (= bottom of
   SVG) sits on the text baseline, pushing the arrow visually high. */
.xref-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: fit-content;
    max-width: 100%;
    margin: 0 0 18px;
    padding: 0;
    border: 0;
    border-radius: 0;
    font-size: inherit;
    font-weight: 600;
    line-height: 1.4;
    text-decoration: none;
    color: var(--color-accent-light);
    cursor: pointer;
    transition: color 0.15s ease;
    box-sizing: border-box;
    -webkit-tap-highlight-color: transparent;
}
.xref-link:hover { color: var(--color-accent-text); text-decoration: underline; text-underline-offset: 3px; }

/* Inline glyph wrap. The 26px filled circle is gone; we keep the wrap
   for backward compatibility with existing markup but render it as a
   plain inline arrow at body weight. Slight downward nudge so the SVG
   appears optically centred on the cap-height of the text rather than
   on the geometric centre of the line-box. */
.xref-link-iconwrap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: auto;
    height: auto;
    border-radius: 0;
    background: transparent;
    color: inherit;
    flex-shrink: 0;
}
.xref-link-icon {
    width: 1em;
    height: 1em;
    display: block;
}
.xref-link-text { flex: 0 1 auto; white-space: normal; overflow: visible; text-overflow: clip; }
.xref-link-sep {
    opacity: 0.55;
    margin: 0 4px;
    font-weight: 400;
}

/* The --try / --learn variants no longer carry their own colour or hover;
   they inherit the single base treatment above. They are kept as no-op
   classes so existing markup (and the kind-based wording) keeps working. */
[data-theme="light"] .xref-link { color: var(--color-accent-text); }

/* Inline variant: a cross/deep link that sits INSIDE flowing prose rather
   than standing alone as a CTA. Renders as plain inline accent text, with
   no flex box and no block bottom-margin. */
.xref-link--inline { display: inline; margin: 0; }

/* When an xref-link is the link inside a glossary meta row or a Related
   Topics list, the surrounding container owns the spacing — drop the
   component's own bottom margin so it doesn't double up. */
.glossary-card-meta .xref-link,
.related-topics-list .xref-link { margin: 0; }

/* Handbook prose: cross links always stack, one per line, with breathing
   room from the paragraph above. The base component is inline-flex, so two
   short consecutive links would otherwise sit side by side while longer
   ones wrap onto their own lines — inconsistent within the same page
   (2026-07-04, Digital Assets perp-basis chapter). No handbook link sits
   mid-sentence (verified platform-wide), so block-level is safe here. */
.learn-chapter-body .xref-link {
    display: flex;
    margin-top: 10px;
}

@media (max-width: 600px) {
    .xref-link { width: 100%; }
    .xref-link-text { white-space: normal; }
}


/* ── Chart expand button + overlay (used by shared/chart-expand.js) ── */

.chart-expand-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 5;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text);
  font-size: 0.88rem;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s, background 0.15s;
}
/* Show on hover of any positioned chart wrapper */
.risk-chart-wrap:hover .chart-expand-btn,
.chart-wrap:hover .chart-expand-btn { opacity: 0.75; }
.chart-expand-btn:hover { opacity: 1 !important; background: var(--bg-elevated); }
[data-theme="light"] .chart-expand-btn {
  border-color: rgba(0,0,0,0.12);
  background: rgba(255,255,255,0.9);
}

.chart-expand-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: pf-fade-in 0.15s ease;
}
.chart-expand-dialog {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  width: 90vw;
  max-width: 1100px;
  max-height: 85vh;
  overflow: auto;
  padding: 24px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.3);
  animation: pf-slide-up 0.2s ease;
}
.chart-expand-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.chart-expand-header h3 {
  margin: 0;
  font-size: 1rem;
  color: var(--text);
}
.chart-expand-close {
  background: none;
  border: none;
  color: var(--text);
  font-size: 1.5rem;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}
.chart-expand-close:hover { opacity: 0.7; }
.chart-expand-body {
  width: 100%;
  height: 520px;
}

/* (The "Suggested first: …" prerequisite nudge bar was removed —
   owner judged it not useful. The .prereq-* rules are gone.) */

/* ═══════════════════════════════════════════════════════════════════════════════
   Light mode overrides
   ═══════════════════════════════════════════════════════════════════════════════ */

[data-theme="light"] .gs-card { background: var(--bg-elevated); }
[data-theme="light"] .ch-panel { background: var(--bg-elevated); }
[data-theme="light"] .ch-result { background: rgba(0,0,0,0.03); }
[data-theme="light"] .ch-estimate-input { background: var(--bg); }
[data-theme="light"] .test-estimate-input { background: var(--bg); }

/* ═══════════════════════════════════════════════════════════════════════════════
   Preferences / Interpolation Modal (shared across dashboard + modules)
   ═══════════════════════════════════════════════════════════════════════════════ */

/* Preferences + Account modal — single-scroll layout (May 2026 redesign).
   Two-pane shell + rail are kept dormant below for one release in case of
   rollback; new markup no longer renders them. */
.pref-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(3px);
  z-index: var(--z-modal, 600);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 0.2s;
  pointer-events: none;
}
.pref-overlay.pref-visible { opacity: 1; pointer-events: auto; }

.pref-modal {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  width: 560px;
  max-width: 95vw;
  max-height: min(85vh, 640px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 10px 32px rgba(0, 0, 0, 0.30), 0 1px 3px rgba(0, 0, 0, 0.12);
}
[data-theme="light"] .pref-modal {
  box-shadow: 0 10px 32px rgba(27, 58, 92, 0.12), 0 1px 3px rgba(27, 58, 92, 0.05);
}

.pref-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.875rem 1.25rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.pref-header h2 {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  margin: 0;
  letter-spacing: -0.005em;
}
.pref-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  background: none;
  border: 0;
  border-radius: 6px;
  color: var(--text-muted);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.pref-close:hover {
  color: var(--text);
  background: color-mix(in srgb, var(--text) 6%, transparent);
}

.pref-body {
  padding: 0.5rem 1.5rem 0.75rem;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  /* scrollbar styling comes from base.css — see the note there before adding
     scrollbar-color or ::-webkit-scrollbar rules here. */
}
.pref-section-label {
  display: block; font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 8px;
}
.pref-options { display: flex; flex-direction: column; gap: 6px; }
.pref-radio {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 8px 14px; background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: 8px;
  cursor: pointer; transition: border-color 0.15s;
}
.pref-radio:hover { border-color: var(--color-accent); background: rgba(46, 109, 164, 0.04); }
[data-theme="light"] .pref-radio:hover { background: rgba(46, 109, 164, 0.03); }
.pref-radio input:focus-visible ~ .pref-radio-box {
  box-shadow: 0 0 0 2px rgba(46, 109, 164, 0.25);
}
.pref-radio input { display: none; }
.pref-radio-box {
  width: 18px; height: 18px; border: 2px solid var(--text-muted);
  border-radius: 50%; flex-shrink: 0; margin-top: 2px;
  transition: border-color 0.15s, background 0.15s;
}
.pref-radio input:checked ~ .pref-radio-box {
  border-color: var(--color-accent); background: var(--color-accent);
}
.pref-opt-label { display: block; font-size: 0.9rem; font-weight: 600; color: var(--text); }
.pref-opt-desc { display: block; font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; }

.pref-footer {
  display: flex; justify-content: flex-end; gap: 0.5rem;
  padding: 0.875rem 1.5rem;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

.pref-btn {
  font-family: var(--font);
  padding: 0.5625rem 1.25rem;
  border-radius: 6px;
  font-size: 0.875rem; font-weight: 600;
  cursor: pointer; border: none;
  transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s;
}
.pref-btn-cancel {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-secondary);
}
.pref-btn-cancel:hover { border-color: var(--text-muted); color: var(--text); }
.pref-btn-save { background: var(--color-accent); color: #fff; }
.pref-btn-save:hover:not(:disabled) { filter: brightness(1.08); }
.pref-btn-save:disabled { opacity: 0.5; cursor: default; }
/* Destructive confirm button — used by the reset confirmation dialog. */
.pref-btn-danger { background: var(--negative); color: #fff; }
.pref-btn-danger:hover { filter: brightness(1.08); }

/* === Section helper (NEW)
   Wraps a .pref-pane-heading and its rows. Provides rhythm between
   sections in the single-scroll layout. Used by Preferences AND Account.
   Symmetric vertical padding so the divider sits in the middle of the
   gap between sections, not tight against the previous section's content. */
.pref-section {
  padding: 1.125rem 0;
  border-top: 1px solid var(--border);
}
.pref-section:first-of-type {
  padding-top: 1rem;
  border-top: 0;
}

.pref-pane-heading {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 0.5rem;
}

.pref-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.75rem 0;
  border-top: 1px solid var(--border-subtle);
}
.pref-row:first-of-type {
  border-top: 0;
  padding-top: 0.5rem;
}
.pref-row--stacked {
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
}
.pref-row-text { flex: 1; min-width: 0; }
.pref-row-control { flex-shrink: 0; }
.pref-row-label {
  display: block;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text);
}
.pref-row-desc {
  display: block;
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.5;
  margin-top: 0.125rem;
}

/* Radio group — dividers between options removed */
.pref-radio-group {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  margin-top: 0.25rem;
}
.pref-radio-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.5rem 0;
  cursor: pointer;
  border-radius: 4px;
}
.pref-radio-row input { display: none; }
.pref-radio-mark {
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border: 1px solid var(--text-muted);
  border-radius: 50%;
  flex-shrink: 0;
  transition: border-color 0.15s, background 0.15s;
  position: relative;
  box-sizing: border-box;
}
.pref-radio-row input:checked ~ .pref-radio-mark {
  border-color: var(--color-accent);
}
.pref-radio-row input:checked ~ .pref-radio-mark::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 9px; height: 9px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--color-accent);
}
.pref-radio-row:hover .pref-radio-mark {
  border-color: var(--text-secondary);
}
.pref-radio-text { min-width: 0; }
.pref-radio-text .pref-row-label { font-weight: 500; }

/* Toggle switch */
.pref-toggle {
  display: inline-block;
  position: relative;
  width: 34px;
  height: 20px;
  cursor: pointer;
}
.pref-toggle input { opacity: 0; width: 0; height: 0; }
.pref-toggle-track {
  position: absolute;
  inset: 0;
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 999px;
  transition: background 0.2s, border-color 0.2s;
}
.pref-toggle-track::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 14px; height: 14px;
  background: var(--text-muted);
  border-radius: 50%;
  transition: transform 0.2s, background 0.2s;
}
.pref-toggle input:checked ~ .pref-toggle-track {
  background: var(--color-accent);
  border-color: var(--color-accent);
}
.pref-toggle input:checked ~ .pref-toggle-track::after {
  transform: translateX(14px);
  background: #fff;
}

/* Action buttons (Replay / Reset) */
.pref-action-btn {
  font-family: var(--font);
  font-size: 0.85rem;
  font-weight: 500;
  padding: 0.4375rem 0.875rem;
  min-width: 88px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
  white-space: nowrap;
  text-align: center;
}
.pref-action-btn:hover {
  border-color: var(--text-secondary);
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
.pref-action-btn--danger {
  color: var(--negative);
  border-color: var(--border);
}
.pref-action-btn--danger:hover {
  border-color: var(--negative);
  background: color-mix(in srgb, var(--negative) 8%, transparent);
}

/* Inline save error banner */
.pref-save-error {
  margin: 0 1.5rem 0.75rem;
  padding: 0.625rem 0.875rem;
  border-radius: 6px;
  border: 1px solid color-mix(in srgb, var(--negative) 35%, transparent);
  background: color-mix(in srgb, var(--negative) 8%, transparent);
  color: var(--negative);
  font-size: 0.85rem;
  line-height: 1.5;
}

@media (max-width: 640px) {
  .pref-modal { width: 95vw; max-height: 92vh; }
}

/* ============================================================
   Account Modal — account-specific elements (read-only field
   display, inline forms, error/info banners).
   ============================================================ */
.acct-readonly {
  font-size: 0.95rem;
  color: var(--text-secondary);
  font-weight: 400;
  text-align: right;
  word-break: break-word;
}
.acct-helper {
  margin: 16px 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.55;
}
.acct-helper a { color: var(--color-accent-text); text-decoration: underline; text-underline-offset: 2px; }
.acct-warning {
  margin: 12px 0 16px;
  padding: 10px 14px;
  border-radius: 6px;
  border: 1px solid rgba(245, 158, 11, 0.4);
  background: rgba(245, 158, 11, 0.08);
  color: var(--warning, #f59e0b);
  font-size: 0.85rem;
  line-height: 1.55;
}
.acct-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 6px;
}
.acct-field { display: flex; flex-direction: column; gap: 6px; }
.acct-field label {
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text);
}
.acct-field input[type="password"],
.acct-field input[type="text"] {
  font-family: var(--font);
  font-size: 0.95rem;
  padding: 10px 12px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text);
  transition: border-color 0.15s, background 0.15s;
}
.acct-field input:focus {
  outline: none;
  border-color: var(--color-accent);
  background: var(--bg-surface);
}
.acct-form-footer {
  margin-top: 6px;
  display: flex;
  justify-content: flex-end;
}
.acct-error,
.acct-info {
  display: none;
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 0.85rem;
  line-height: 1.5;
  margin-bottom: 8px;
}
.acct-error.visible {
  display: block;
  border: 1px solid rgba(220, 53, 53, 0.35);
  background: rgba(220, 53, 53, 0.08);
  color: var(--negative);
}
.acct-info.visible {
  display: block;
  border: 1px solid rgba(34, 197, 94, 0.35);
  background: rgba(34, 197, 94, 0.08);
  color: var(--positive);
}

/* ============================================================
   Help Modal
   ============================================================ */
.help-modal { width: 620px; max-height: 85vh; display: flex; flex-direction: column; }
.help-body {
  padding: 24px 28px; overflow-y: auto; flex: 1;
  /* scrollbar styling comes from base.css — see the note there before adding
     scrollbar-color or ::-webkit-scrollbar rules here. */
}

.help-section { margin-bottom: 28px; }
.help-section:last-child { margin-bottom: 0; }
/* (legacy .help-section-title removed — replaced by shared .section-title in components.css) */
/* Help modal type scale aligns with the rest of the platform: body
   text at 0.92rem (~14.7px) instead of the previous 0.84rem
   (~13.4px), which read as cramped against the platform's 0.95rem
   form-row labels and 1rem reference body. */
.help-text {
  font-size: 0.92rem; color: var(--text-secondary); line-height: 1.6;
  margin: 0 0 10px;
}
.help-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.help-list li {
  font-size: 0.92rem; color: var(--text-secondary); line-height: 1.55;
  padding-left: 14px; position: relative;
}
.help-list li::before {
  content: '\2022'; position: absolute; left: 0;
  color: var(--text-muted);
}
.help-list li strong { color: var(--text); font-weight: 600; }

/* FAQ collapsible */
.help-faq {
  border: 1px solid var(--border); border-radius: 6px;
  margin-bottom: 6px; overflow: hidden;
}
.help-faq summary {
  font-size: 0.94rem; font-weight: 600; color: var(--text);
  padding: 12px 14px; cursor: pointer;
  list-style: none; display: flex; align-items: center; gap: 8px;
}
.help-faq summary::-webkit-details-marker { display: none; }
.help-faq summary::before {
  content: '+'; font-size: 1rem; font-weight: 400;
  color: var(--text-muted); width: 16px; text-align: center; flex-shrink: 0;
}
.help-faq[open] summary::before { content: '\2212'; }
.help-faq summary:hover { background: var(--bg-elevated); }
.help-faq p {
  font-size: 0.9rem; color: var(--text-secondary); line-height: 1.6;
  margin: 0; padding: 0 14px 14px 38px;
}

/* Trainer section */
.help-subsection-label {
  font-size: 0.95rem; font-weight: 600; color: var(--text);
  margin: 16px 0 10px;
}
.help-trainer-steps {
  padding-left: 20px; margin: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.help-trainer-steps li {
  font-size: 0.92rem; color: var(--text-secondary); line-height: 1.55;
}

/* Help button — dashboard variant (matches header style) */
.dash-help-btn {
  display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex-shrink: 0;
  font-family: 'Inter', sans-serif;
  font-size: 14px; font-weight: 500;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid transparent;
  border-radius: var(--r-md, 8px);
  line-height: 1; padding: 0; cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.dash-help-btn:hover {
  color: var(--text);
  border-color: var(--border);
  background: var(--bg-elevated);
}

/* ============================================================
   Guided Walkthrough (spotlight + tooltip + ring + arrows)
   ============================================================ */

/* Tour launcher pill chrome lives with `.guidance-btn-floating` and
   `.guidance-pill-*` above — keeps Walkthrough and Try It identical. */

/* Side-panel walkthrough (body.wt-rail-mode is added for every tour).
   No screen dim — the .wt-overlay becomes a transparent click-through
   scrim; the .wt-ring (a separately-positioned outline over the target,
   not clipped by overflow) does the highlighting; the .wt-tooltip moves
   to a fixed panel on the right. */
body.wt-rail-mode .wt-overlay {
  background: transparent !important;
  clip-path: none !important;
  pointer-events: none !important;
}
body.wt-rail-mode .wt-tooltip,
body.wt-rail-mode .wt-tooltip.wt-centered {
  position: fixed !important;
  right: 16px !important;
  left: auto !important;
  top: var(--header-clearance, 80px) !important;
  bottom: auto !important;
  transform: none !important;
  width: 320px !important;
  max-width: 90vw;
  max-height: calc(100vh - var(--header-clearance, 80px) - 16px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
/* Panel height is fixed to the tallest step (set inline by JS); the
   action row sticks to the bottom so Skip/Back/Next don't shift as you
   page through. */
body.wt-rail-mode .wt-actions { margin-top: auto; }

/* The walkthrough's full-screen layer. body.wt-rail-mode turns it into
   a transparent click-through scrim (above); these are just the shared
   position/stacking. The tooltip is flush (no speech-bubble arrow), the
   step counter is sentence-case and un-pilled. */
.wt-overlay {
  position: fixed;
  inset: 0;
  z-index: 700;
  transition: clip-path 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
}
.wt-overlay-hidden { opacity: 0; }

/* ── Target outline (replaces the pulsing ring) ── */
.wt-ring {
  position: fixed;
  z-index: 701;
  border: 2px solid var(--color-accent);
  border-radius: var(--r-sm, 4px);
  pointer-events: none;
  transition: left 0.2s ease, top 0.2s ease, width 0.2s ease, height 0.2s ease;
}

/* ── Tooltip ── */
.wt-tooltip {
  position: fixed;
  z-index: 702;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md, 8px);
  padding: 18px 22px;
  width: 360px;
  max-width: 90vw;
  box-shadow: 0 10px 32px rgba(0, 0, 0, 0.30), 0 1px 4px rgba(0, 0, 0, 0.15);
}

/* ── Fade transitions ── */
.wt-fade-in {
  animation: wtFadeIn 0.3s ease both;
}
.wt-fade-out {
  animation: wtFadeOut 0.2s ease both;
  pointer-events: none;
}
@keyframes wtFadeIn {
  from { opacity: 0; transform: translateY(6px) translateX(-50%); }
  to   { opacity: 1; }
}
@keyframes wtFadeOut {
  to { opacity: 0; }
}

/* (Speech-bubble arrow removed — the tooltip's position near the
   target connects it; the rotated-square tail was a Shepherd/driver.js
   tell. .wt-arrow-* classes still get applied by the JS positioner but
   now style nothing — harmless.) */

/* ── Content ── */
/* Step counter — sentence-case, quiet, no pill (was T09 uppercase
   0.06em-tracked accent text in a tinted pill). */
.wt-step-count {
  display: block;
  font-family: var(--font);
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
  background: none;
  padding: 0;
  border-radius: 0;
  margin-bottom: 6px;
}
.wt-title {
  font-family: 'Newsreader', 'Inter', system-ui, serif;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
}
.wt-message {
  font-size: 0.95rem;
  color: var(--text);
  line-height: 1.55;
  margin-bottom: 16px;
}
.wt-message strong {
  color: var(--text);
  font-weight: 600;
}

.wt-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.wt-right {
  display: flex;
  gap: 8px;
}
.wt-btn {
  padding: 7px 18px;
  border-radius: var(--r-md, 8px);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  border: none;
  transition: background 0.15s, transform 0.1s;
  font-family: 'Inter', sans-serif;
}
.wt-btn:active {
  transform: scale(0.97);
}
.wt-skip {
  background: transparent;
  color: var(--text-muted);
}
.wt-skip:hover { color: var(--text); }
.wt-back {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-secondary);
}
.wt-back:hover { border-color: var(--text-muted); color: var(--text); }
.wt-next {
  background: var(--color-accent);
  color: #fff;
}
.wt-next:hover { background: var(--color-accent-light); }

[data-theme="light"] .wt-tooltip { box-shadow: 0 10px 32px rgba(27, 58, 92, 0.15), 0 1px 4px rgba(27, 58, 92, 0.06); }

/* ── Centered tooltip (intro + try-this) ── */
.wt-centered {
  position: fixed !important;
  top: 50% !important;
  left: 50% !important;
  transform: translate(-50%, -50%) !important;
  bottom: auto !important;
  right: auto !important;
}

/* ── Try-this card — typographic numbering, no circle badge (R8) ── */
.wt-try-card { width: 480px; }
.wt-try-list {
  list-style: none;
  padding: 0;
  margin: 0 0 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  counter-reset: wt-try;
}
.wt-try-list li {
  counter-increment: wt-try;
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-size: 0.95rem;
  color: var(--text);
  line-height: 1.5;
}
.wt-try-list li::before {
  content: counter(wt-try) ".";
  color: var(--text-muted);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  min-width: 1.3em;
}
.wt-try-footer {
  font-size: 0.9rem;
  color: var(--text-secondary);
  line-height: 1.5;
  margin-bottom: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.wt-try-footer strong {
  color: var(--text);
  font-weight: 600;
}

/* ── Contact Us modal (dashboard-level) ────────────────────────────── */
.contact-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-max);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.contact-modal[hidden] { display: none; }
.contact-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
}
.contact-modal-card {
  position: relative;
  background: var(--bg-elevated);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.45);
  width: min(520px, 100%);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  padding: 28px 32px 24px;
}
body.contact-modal-open { overflow: hidden; }
.contact-modal-close {
  position: absolute;
  top: 10px;
  right: 12px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 1.5rem;
  line-height: 1;
  width: 32px;
  height: 32px;
  border-radius: 4px;
  cursor: pointer;
}
.contact-modal-close:hover { color: var(--text); background: var(--bg-surface); }
.contact-modal-title {
  margin: 0 0 4px;
  font-size: 1.35rem;
  font-weight: 700;
}
.contact-modal-subtitle {
  margin: 0 0 18px;
  color: var(--text-secondary);
  font-size: 0.95rem;
  line-height: 1.55;
}
.contact-form { display: flex; flex-direction: column; gap: 12px; }
.contact-field { display: flex; flex-direction: column; gap: 5px; }
.contact-field label {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}
.contact-field input,
.contact-field textarea,
.contact-field select {
  width: 100%;
  padding: 9px 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
  box-sizing: border-box;
}
.contact-field textarea { resize: vertical; min-height: 120px; }
.contact-field input:focus,
.contact-field textarea:focus,
.contact-field select:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 25%, transparent);
}
.contact-status {
  font-size: 0.88rem;
  min-height: 1.2em;
  color: var(--text-secondary);
}
.contact-status.is-ok    { color: var(--positive); }
.contact-status.is-error { color: var(--negative); }
.contact-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 6px;
}
.contact-btn-ghost,
.contact-btn-primary {
  padding: 9px 18px;
  border-radius: 6px;
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
  font-family: inherit;
}
.contact-btn-ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--text-secondary);
}
.contact-btn-ghost:hover { color: var(--text); border-color: var(--text-muted); }
.contact-btn-primary {
  background: var(--color-accent);
  color: #fff;
}
.contact-btn-primary:hover { filter: brightness(1.05); }
.contact-btn-primary:disabled { opacity: 0.55; cursor: default; }

/* ── Shared case-study cards ───────────────────────────────────
   Originally in equities.css; promoted to shared so Lifecycle
   and future modules can reuse the same accordion markup.
   Module-specific outcome variants stay in their own CSS.       */
.ss-section-intro {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 16px 20px;
  margin-bottom: 20px;
}
.ss-section-intro p {
  font-size: 0.95rem;
  line-height: 1.65;
  margin: 0 0 10px;
  color: var(--text);
}
.ss-section-intro p:last-child { margin-bottom: 0; }

.ss-cases-list { display: flex; flex-direction: column; gap: 12px; }
/* De-AI'd: was a tinted card with a 4px coloured left bar (T01) and a
   hover-lift box-shadow. Now a single hairline frame, transparent bg,
   no coloured edge, no shadow — the outcome reads from the badge text
   colour instead. */
.ss-case {
  background: transparent; border: 1px solid var(--border);
  overflow: hidden;
  transition: border-color 0.15s ease;
}
.ss-case:hover { border-color: var(--text-muted); }
.ss-case summary {
  padding: 14px 18px; cursor: pointer; list-style: none;
  transition: background 0.15s ease;
}
.ss-case summary::-webkit-details-marker { display: none; }
.ss-case summary:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.ss-case-summary-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 20px; align-items: center;
}
.ss-case-summary-text { min-width: 0; }
.ss-case-title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ss-case-chev {
  display: inline-block; width: 10px; height: 10px; flex-shrink: 0;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(-45deg);
  transition: transform 0.2s ease; margin-right: 2px;
}
.ss-case[open] > summary .ss-case-chev { transform: rotate(45deg); }
.ss-case-title { font-size: 1.0625rem; font-weight: 600; color: var(--text); }
/* De-pilled (T08) + de-uppercased (T09): quiet italic colour-text, like
   .lc-feature-case-badge. The per-outcome colour is set by the module's
   .ss-case-badge--<outcome> variants. */
.ss-case-badge {
  display: inline-block; padding: 0; border-radius: 0;
  font-size: 0.875rem; font-weight: 600; font-style: italic;
  text-transform: none; letter-spacing: 0;
  background: transparent;
}
.ss-case-theme { font-size: 0.875rem; color: var(--text-muted); margin-top: 4px; margin-left: 22px; }
.ss-case-stats { display: flex; gap: 18px; align-items: center; flex-shrink: 0; }
.ss-case-stat { text-align: right; min-width: 0; }
.ss-case-stat-label {
  font-size: 0.8125rem; font-weight: 600; text-transform: none;
  letter-spacing: 0; color: var(--text-muted); margin-bottom: 2px;
}
.ss-case-stat-value {
  font-size: 0.9375rem; font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.ss-case-body { padding: 12px 20px 20px; border-top: 1px solid var(--border); }
.ss-case-summary-p {
  font-size: 1rem; line-height: 1.6; color: var(--text); margin: 12px 0 16px;
}
/* De-uppercased (T09): mixed-case sub-heading, no tracking. */
.ss-case-subtitle {
  font-size: 0.9375rem; font-weight: 600; text-transform: none;
  letter-spacing: 0; color: var(--text-secondary); margin: 18px 0 8px;
}
.ss-case-timeline { list-style: none; padding: 0; margin: 0 0 10px; }
.ss-case-timeline li {
  display: grid; grid-template-columns: 180px 1fr; gap: 14px;
  padding: 10px 0; border-top: 1px solid var(--border);
  font-size: 1rem; line-height: 1.55; color: var(--text);
}
.ss-case-timeline li:first-child { border-top: none; }
.ss-case-date { font-weight: 600; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.ss-case-lessons { margin: 0 0 10px; padding-left: 20px; }
.ss-case-lessons li { font-size: 1rem; line-height: 1.55; color: var(--text); margin-bottom: 8px; }
.ss-case-sources { font-size: 0.8125rem; color: var(--text-muted); line-height: 1.55; margin: 0; padding-left: 20px; }
.ss-case-sources a { color: var(--color-accent-text); text-decoration: none; }
.ss-case-sources a:hover { text-decoration: underline; }
@media (max-width: 768px) {
  .ss-case-summary-row { grid-template-columns: 1fr; gap: 10px; }
  .ss-case-stats { flex-wrap: wrap; gap: 12px; }
  .ss-case-stat { text-align: left; flex: 1 1 140px; }
  .ss-case-timeline li { grid-template-columns: 1fr; gap: 4px; }
}
/* Lifecycle outcome variants — colour-text only. No coloured left bar
   (T01) and no tinted/bordered badge chip (T08): the outcome reads from
   the badge text colour, matching the de-AI'd shared base above. */
.ss-case-badge--critical-failure,
.ss-case-badge--systemic-risk,
.ss-case-badge--market-stress       { color: var(--negative); }
.ss-case-badge--regulatory-action   { color: var(--info); }
.ss-case-badge--operational-failure { color: var(--warning); }

/* ── Related Topics Cross-Links ──────────────────────────────────────────── */
.related-topics {
  margin-top: 48px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}
.related-topics-header {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin-bottom: 12px;
}
.related-topics-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Related Topics rows now render as shared .xref-link items (see
   related-topics.js); the old boxed-row styles have been removed. */
/* `.related-topics` is hidden in print by the @media print block in base.css
   (folded in there with the rest of the hide-list — one source of truth). */

/* ── Content tabs (Overview / Handbook / Exercises) — global rules ──
   On these three "content" tabs, every module should look the same:
   no sidebar chevron, and a single vertical divider at the left edge of
   the content area.

   The body[data-active-tab="..."] attribute is set by main.js on every
   tab change, so these rules work regardless of per-module HTML
   structure (.workspace, .options-module, .macro-workspace, etc.) and
   regardless of how each module's activateTab manages chevron state. */

/* 1. Hide both chevron classes used across the platform. */
body[data-active-tab="reference"] .sidebar-toggle-btn,
body[data-active-tab="reference"] .options-sidebar-toggle,
body[data-active-tab="exercises"] .sidebar-toggle-btn,
body[data-active-tab="exercises"] .options-sidebar-toggle {
  display: none !important;
}

/* 2. Force any .options-sidebar to zero width (its default is full width).
   Divider line is drawn by .workspace-content in base.css. */
body[data-active-tab="reference"] .options-sidebar,
body[data-active-tab="exercises"] .options-sidebar {
  width: 0 !important;
  min-width: 0 !important;
  overflow: hidden !important;
}

/* (Divider line for no-sidebar modules now handled by the global
   .workspace-content rule in base.css — single source of truth.) */

/* ═══════════════════════════════════════════════════════════════════════════════
   Session/cookie disclosure bar — global pill-shaped bar (matches landing page).

   ⚠ DO NOT rename this id/class to anything containing "cookie", "consent",
   "notice", "banner" or "gdpr". On iOS Safari a content/cookie blocker injects
   a cosmetic-filter `display:none` rule keyed on those tokens (WebKit content
   blockers match by CSS selector, not text), which dropped the real banner from
   the render tree on iPhone — computed display:none, 0×0 — while desktop and
   headless Chromium rendered it fine. Proven on-device 2026-05-25 with a name
   test (two identical fixed bars differing only in id/class: the cookie-named
   one was hidden, the neutral-named one rendered). Neutral naming IS the fix.
   ═══════════════════════════════════════════════════════════════════════════════ */
.em-session-bar {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  align-items: center;
  gap: 18px;
  max-width: calc(100% - 32px);
  padding: 14px 18px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: 10px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: var(--shadow-lg, 0 12px 40px -12px rgba(0,0,0,0.5));
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 13px;
  color: var(--text-secondary);
}
/* Toggle visibility with a class, not the [hidden] attribute. */
.em-session-bar.em-session-bar--hidden { display: none; }
.em-session-bar-text { flex: 1; min-width: 0; }
.em-session-bar-link {
  color: var(--text-secondary);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--border);
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}
.em-session-bar-link:hover {
  color: var(--text);
  text-decoration-color: var(--text-muted);
}
.em-session-bar-btn {
  flex-shrink: 0;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 400;
  color: var(--text-secondary);
  background: transparent;
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  padding: 7px 16px;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.em-session-bar-btn:hover {
  color: var(--text);
  border-color: var(--border);
  background: var(--bg-input);
}
@media (max-width: 768px) {
  /* Mobile: flush full-width bottom bar, clear of the iOS home indicator. The
     blur is dropped here (the opaque --bg-elevated background reads fine) — this
     is cosmetic, NOT the old iPhone no-show fix. That bug was content-blocker
     cosmetic filtering on the element name; see the note above. */
  .em-session-bar {
    flex-wrap: wrap;
    gap: 10px;
    left: 0;
    right: 0;
    bottom: 0;
    transform: none;
    max-width: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  }
}

/* ── Carry & Roll explainer modal ─────────────────────────────────────
   Single typographic scope so the modal body is consistent: two prose
   sizes (body 1rem, captions 0.875rem), two text colours (primary +
   muted), plus the accent for the deeplink. Replaces 13 inline-style
   declarations spread across the markup. Body sits at 1rem (matches the
   dialog default) so it reads at proper body-text contrast; captions
   step down to 0.875rem so they stay subordinate without going small. */
.cr-modal {
  font-size: 1rem;
  color: #fff;
  line-height: 1.55;
}
/* base.css has a platform-wide `p { color: var(--text-secondary) }`
   rule that beats the container colour on specificity. Re-assert the
   modal's own colour at the paragraph level so body prose actually
   reads as the modal's primary text, not the platform-wide muted grey. */
.cr-modal p {
  margin: 10px 0;
  color: inherit;
}
.cr-modal p:first-child { margin-top: 4px; }
.cr-modal strong { font-weight: 600; }
/* Light theme has a near-white card background, so pure white prose
   disappears. Fall back to the platform's primary text token there. */
[data-theme="light"] .cr-modal,
[data-theme="light"] .cr-modal p { color: var(--text); }

.cr-modal .cr-aside,
.cr-modal .cr-caption {
  font-size: 0.875rem;
  color: rgba(255, 255, 255, 0.78);
  line-height: 1.5;
}
.cr-modal .cr-caption { margin: 4px 0 0; }
[data-theme="light"] .cr-modal .cr-aside,
[data-theme="light"] .cr-modal .cr-caption {
  color: var(--text-secondary);
}

.cr-modal .cr-table {
  width: 100%;
  border-collapse: collapse;
  margin: 12px 0 0;
}
.cr-modal .cr-table th {
  padding: 8px;
  font-weight: 600;
  text-align: left;
  border-bottom: 1px solid var(--border);
}
.cr-modal .cr-table th:first-child { width: 18%; }
.cr-modal .cr-table th:nth-child(2),
.cr-modal .cr-table th:nth-child(3) { width: 41%; }
.cr-modal .cr-table td {
  padding: 10px 8px;
  vertical-align: top;
}
.cr-modal .cr-table td:first-child { font-weight: 600; }
.cr-modal .cr-table tbody tr + tr td {
  border-top: 1px solid var(--border);
}

.cr-modal .cr-notation {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.cr-modal .cr-notation h4 {
  margin: 0 0 8px;
  font-size: 0.95rem;
  font-weight: 600;
  color: inherit;
}
.cr-modal .cr-notation ul {
  margin: 0;
  padding-left: 20px;
}
.cr-modal .cr-notation li { margin: 2px 0; }

.cr-modal .cr-footer {
  margin-top: 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.cr-modal .cr-deeplink {
  color: var(--color-accent-text);
  text-decoration: none;
}
.cr-modal .cr-deeplink:hover { text-decoration: underline; }

/* ============================================================
   Active-Path Next Pill
   In-module footer signal shown when the user is following a
   starter path. Sticky to the bottom of the .workspace-content
   scroll container so it stays visible while reading. Lives in
   components.css (always loaded on app.html) rather than
   dashboard.css (dashboard-only).
   ============================================================ */

.em-path-next {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 200;
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: 340px;
    padding: 10px 12px 10px 14px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.25);
    font-family: 'Inter', system-ui, sans-serif;
}
.em-path-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
    cursor: pointer;
}
.em-path-status {
    font-size: 0.7rem;
    font-weight: 500;
    color: var(--text-muted);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.em-path-name {
    font-size: 0.85rem;
    color: var(--text);
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.em-path-sep {
    color: var(--text-muted);
    margin: 0 2px;
}
.em-path-next-btn {
    flex-shrink: 0;
    padding: 6px 12px;
    background: var(--color-accent);
    color: #fff;
    border: none;
    border-radius: 6px;
    font-family: 'Inter', system-ui, sans-serif;
    font-size: 0.78rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s;
}
.em-path-next-btn:hover {
    background: var(--color-accent-light, var(--color-accent));
}
.em-path-collapse {
    flex-shrink: 0;
    background: none;
    border: 0;
    padding: 2px 6px;
    color: var(--text-muted);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    transition: color 0.15s;
}
.em-path-collapse:hover {
    color: var(--text);
}
.em-path-complete .em-path-status {
    color: var(--positive, #22c55e);
}

/* Collapsed state: a narrow tab pinned to the right edge of the
   viewport at the same vertical position as the expanded pill.
   The user clicks it to restore the full pill. */
.em-path-next.em-path-collapsed {
    bottom: 20px;
    right: 0;
    transform: none;
    padding: 14px 6px;
    max-width: none;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-right: 0;
    border-radius: 8px 0 0 8px;
    box-shadow: -4px 8px 20px rgba(0, 0, 0, 0.22);
    cursor: pointer;
    color: var(--text-muted);
    transition: color 0.15s, background 0.15s;
}
.em-path-next.em-path-collapsed:hover {
    color: var(--text);
    background: var(--bg);
}
.em-path-collapsed-icon {
    display: block;
    font-size: 1rem;
    line-height: 1;
}

[data-theme="light"] .em-path-next {
    background: var(--bg);
}
[data-theme="light"] .em-path-next.em-path-collapsed {
    background: var(--bg-elevated);
}

/* ── Deep-dive / "how is this priced" modal body ──────────────────────────
   Shared typographic system for long-form explanatory dialogs: one base font
   and line-height, accent section headings, restrained bold, dedicated
   equation blocks (KaTeX), and calm un-boxed inline math. Used by the options
   vol-swaps deep dives and the credit convertible pricing modal. */
.deepdive {
  font-size: 0.92rem;
  line-height: 1.7;
  color: var(--text-secondary);
}
.deepdive-section {
  margin-bottom: 22px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--border);
}
.deepdive-section:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}
.deepdive h4 {
  margin: 0 0 12px;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--color-accent-text);
}
.deepdive p { margin: 0 0 10px; }
.deepdive p:last-child { margin-bottom: 0; }
.deepdive ul { margin: 0 0 10px; padding-left: 18px; }
.deepdive li { margin-bottom: 6px; }
.deepdive li:last-child { margin-bottom: 0; }
.deepdive strong { color: var(--text); font-weight: 600; }

/* Inline math: monospace, no pill background, so it reads as part of the prose. */
.deepdive code {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.88em;
  color: var(--text);
}

/* Display equation block — holds a KaTeX-rendered formula. */
.deepdive .deepdive-eq {
  margin: 12px 0;
  padding: 14px 18px;
  background: var(--bg-elevated);
  border-radius: var(--r-md);
  overflow-x: auto;
}
.deepdive .deepdive-eq .katex-display { margin: 0; text-align: left; }
/* Keep math legible against the 0.92rem prose: inline a touch larger, display
   equations clearly larger (subscripts/superscripts shrink from this base). */
.deepdive .katex { color: var(--text); font-size: 1.12em; }
.deepdive .deepdive-eq .katex { font-size: 1.35em; }
.deepdive .deepdive-eq-note {
  display: block;
  margin-top: 10px;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* Inline source citation. */
.deepdive .deepdive-src {
  font-size: 0.82rem;
  color: var(--text-muted);
  font-style: italic;
}

/* Sources list (final section). */
.deepdive .deepdive-sources strong {
  display: block;
  margin-bottom: 8px;
  color: var(--text);
}
.deepdive .deepdive-sources a {
  color: var(--color-accent-text);
  text-decoration: none;
}
.deepdive .deepdive-sources a:hover { text-decoration: underline; }

/* ── Handbook audio player (pilot) — draggable mini-player ────────────────
   A small pill, top-right of the reading area by default and draggable.
   Collapsed it reads "▶ Listen"; expanded it shows the full controls at a
   FIXED width (so a short/long chapter title never resizes it). Mounted per
   module via reference-tab.js when config.audio is set, inside #module-mount
   (which is not a stacking context), so the fixed pill resolves in the ROOT
   stacking context alongside .app-header. The settings/user dropdown lives
   INSIDE .app-header (z var(--z-sticky)) and so cannot rise above that layer
   at the root, whatever its own local z-index. The pill must therefore stay
   below --z-sticky for an open header dropdown to overlap it. Above page
   content, below the header layer and modals. See js/shared/audio/. */
.hb-audio-fab {
  position: fixed;
  top: 84px;
  right: 24px;
  z-index: calc(var(--z-sticky) - 1);
  touch-action: none;          /* let pointer-drag own the gesture */
}
.hb-audio-fab[hidden] { display: none; }
.hb-audio-panel {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.22);
  cursor: grab;
  user-select: none;
}
.hb-audio-fab.dragging .hb-audio-panel {
  cursor: grabbing;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.30);
}
/* Fixed width once expanded. */
.hb-audio-fab:not(.collapsed) .hb-audio-panel { width: 440px; }
/* Show/hide controls by state. */
.hb-audio-fab.collapsed .hb-exp { display: none; }
.hb-audio-fab:not(.collapsed) .hb-col { display: none; }
.hb-audio-fab .btn-icon {
  width: 32px;
  height: 32px;
  padding: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.hb-audio-fab .btn-icon svg { width: 17px; height: 17px; display: block; fill: currentColor; }
.hb-audio-fab .hb-audio-play { color: var(--color-accent-text); }
/* Loading: a spinner replaces the play glyph while a cold chunk synthesizes. */
.hb-audio-fab.loading .hb-audio-play svg { display: none; }
.hb-audio-fab.loading .hb-audio-play::after {
  content: "";
  width: 15px;
  height: 15px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: hb-audio-spin 0.7s linear infinite;
}
@keyframes hb-audio-spin { to { transform: rotate(360deg); } }
.hb-audio-fab .hb-audio-collapse { color: var(--text-muted); }
.hb-audio-fab .hb-audio-collapse svg { fill: none; }
.hb-audio-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  padding: 0 8px 0 2px;
  white-space: nowrap;
}
.hb-audio-now {
  flex: 1;
  min-width: 0;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 4px;
}
.hb-audio-fab select {
  flex-shrink: 0;
  font-family: var(--font);
  font-size: 0.8rem;
  color: var(--text);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 3px 6px;
  cursor: pointer;
}
@media (max-width: 768px) {
  /* Bottom-right on phones (JS positionDefault() drives this too — this is the
     CSS fallback for before JS runs / if it doesn't). */
  .hb-audio-fab {
    top: auto;
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    right: 12px;
  }
  .hb-audio-fab:not(.collapsed) .hb-audio-panel { width: min(440px, calc(100vw - 24px)); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   CROSS-MODULE PRICER PRIMITIVES (promoted 2026-07-10)
   .link-toggle and .sidebar-select-sm originated inside options.css and
   ir-options.css but are emitted by the shared grid_kit builders and used by
   several modules (options, ir-options, structured-products). Promoted here
   verbatim so new modules get them without carrying copies. The original
   module-CSS definitions are identical and harmless; they can be retired in a
   later cleanup.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Link/unlink button next to row labels in the pricer grids.
   Unlinked is the standout state (accent fill and icon, faint accent border)
   so a broken link is easy to spot. Linked (.active, below) is the muted default. */
.link-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 4px;
  padding: 2px 6px;
  font-size: 0.75rem;
  line-height: 1;
  background: color-mix(in srgb, var(--color-accent) 12%, var(--bg-elevated));
  color: var(--color-accent-text);
  border: 1px solid color-mix(in srgb, var(--color-accent) 25%, var(--border));
  border-radius: 4px;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.link-toggle:hover {
  background: color-mix(in srgb, var(--color-accent) 18%, var(--bg-elevated));
  color: var(--color-accent-text);
  border-color: var(--color-accent);
}
/* Linked = the quiet default for most rows: it blends into the elevated
   surface with a muted icon and neutral border. */
.link-toggle.active {
  background: var(--bg-elevated);
  color: var(--text-secondary);
  border-color: var(--border);
}
.link-toggle.active:hover {
  color: var(--color-accent-text);
  border-color: var(--color-accent);
}

/* Small sidebar select (sweep / metric pickers in pricer sidebars). */
.sidebar-select-sm {
  width: 100%;
  padding: 4px 6px;
  font-size: 0.8rem;
  font-family: var(--font);
  background-color: var(--bg-input);
  color: var(--text);
  border: 1px solid var(--sidebar-accent-dim);
  border-radius: var(--r-md);
  outline: none;
  cursor: pointer;
  margin-top: 2px;
  transition: border-color var(--transition);
}
.sidebar-select-sm:focus {
  border-color: var(--sidebar-accent);
}
.sidebar-select-sm option {
  background-color: var(--bg-input);
  color: var(--text);
}
[data-theme="light"] .sidebar-select-sm {
  background-color: #f5f5f5;
  border-color: var(--sidebar-accent-dim);
  color: #222;
  color-scheme: light;
}
[data-theme="light"] .sidebar-select-sm option {
  background-color: #f5f5f5;
  color: #222;
}

/* Sidebar mini-charts (promoted from options.css with the other pricer
   primitives; used by options and structured-products sidebars). */
.ma-side-chart { width: 100%; height: 200px; margin-top: 8px; }
.ma-chart-clickable { cursor: pointer; }
.ma-side-note {
  font-size: 0.75rem;
  line-height: 1.45;
  color: var(--text-muted);
  margin-top: 8px;
}
.ma-chart-empty {
  color: var(--text-muted);
  font-size: 0.8rem;
  text-align: center;
  padding: 40px 0;
}

/* Repricing indicator: while an async reprice (esp. a Monte-Carlo sim) runs, the
   tab blurs + dims its pricing grid so it is obvious the value is recomputing and
   the user does not fire off another change thinking nothing happened. Applied to
   the grid content, never the sidebar; only shown after a short delay so fast
   closed-form reprices do not flash (see reprice_blur.js). */
.sp-repricing {
  filter: blur(2px);
  opacity: 0.5;
  pointer-events: none;
  transition: filter 0.15s ease, opacity 0.15s ease;
}
/* The "running" pill sits over the blurred grid but is a body-level sibling, so
   it stays sharp. Positioned to cover the grid's rect (set inline by blurWhile). */
.sp-repricing-overlay {
  position: fixed;
  z-index: var(--z-overlay);
  display: flex;
  align-items: flex-start;      /* pill sits near the TOP of the grid, not centred */
  justify-content: center;
  padding-top: 56px;            /* down to roughly the first input rows */
  pointer-events: none;
}
.sp-repricing-msg {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 10px 18px;
  color: var(--text);
  font-size: 0.9rem;
  font-weight: 500;
  box-shadow: var(--shadow-lg);
  animation: sp-repricing-pulse 1.1s ease-in-out infinite;
}
@keyframes sp-repricing-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.65; }
}

/* Row-delete button on portfolio/leg tables. Promoted here from four
   identical module copies (credit/fi/ir-options/options) by the July 2026
   audit, like the .sidebar-slider promotion before it. */
.remove-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.9rem;
  padding: 2px 6px;
  border-radius: var(--r-sm);
  transition: color var(--transition), background var(--transition);
}
.remove-btn:hover {
  color: #ef4444;
  background: rgba(239, 68, 68, 0.1);
}


/* ─────────────────────────────────────────────────────────────────────────
   Shared data-viz chart cards
   Moved here from macro.css (2026-06 redesign) so the macro module and any
   other module (e.g. the money-markets Rates Board) render the same chart-card
   chrome from one source. Macro keeps only its layout-specific rules (sub-tab
   panels, cb-charts, the per-content grid). Cascade preserved: base first,
   redesign overrides after.
   ───────────────────────────────────────────────────────────────────────── */
.macro-section-subtitle {
  font-size: 0.85rem;
  color: var(--text);
  margin: -8px 0 16px;
}
.macro-chart-title {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text);
  margin: 0 0 6px;
  cursor: default;
}
.macro-chart-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 20px;
}
.macro-chart-wrap {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--bg-surface);
}
.macro-empty {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-muted);
  font-size: 0.92rem;
}
.macro-section-subtitle {
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 64ch;
  margin: 0 0 22px;
}
.macro-chart-row { gap: 12px; margin-bottom: 12px; }
.macro-chart-row:has(> div:only-child) { grid-template-columns: 1fr; }
.macro-chart-row > div, .macro-cb-charts > div, .macro-chart-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-width: 0;
  transition: border-color var(--transition), box-shadow var(--transition);
}
.macro-chart-row > div:hover, .macro-cb-charts > div:hover, .macro-chart-card:hover {
  border-color: color-mix(in srgb, var(--color-accent) 55%, var(--border));
}
.macro-chart-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 11px;
  border-bottom: 1px solid var(--border);
}
.macro-chart-head-title {
  font-family: var(--font);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--text);
  line-height: 1.25;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.macro-chart-head--notitle .macro-chart-head-title { color: var(--text-muted); }
.macro-chart-tools { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.macro-chart-tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--transition), background var(--transition);
}
.macro-chart-tool:hover { color: var(--color-accent-light); background: var(--bg-input); }
[data-theme="light"] .macro-chart-tool:hover { color: var(--color-accent-text); }
.macro-chart-tool svg { display: block; }
.macro-chart-row > div > .macro-chart-wrap,
.macro-cb-charts > div > .macro-chart-wrap,
.macro-chart-card > .macro-chart-wrap {
  border: 0;
  border-radius: 0;
  padding: 8px 9px 9px;
  overflow: hidden;
}
[data-theme="light"] .macro-chart-row > div,
[data-theme="light"] .macro-cb-charts > div,
[data-theme="light"] .macro-chart-card,
[data-theme="light"] .macro-cb-card,
[data-theme="light"] .macro-country-selector,
[data-theme="light"] .macro-anim-controls { box-shadow: var(--shadow-sm); }
@media (max-width: 768px) {
  .macro-chart-row { grid-template-columns: 1fr; }
}


/* ── Shared period dial (moved from macro.css; used by macro data tabs and the
      money-markets Rates Board). Cascade preserved: base label then override. */
.macro-period-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

.macro-period-btn {
  padding: 5px 14px;
  font-size: 0.82rem;
  font-weight: 600;
  font-family: var(--font, Inter, sans-serif);
  background: var(--bg-elevated);
  color: var(--text-muted);
  border: 1px solid var(--sidebar-accent-dim);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--transition), color var(--transition), background var(--transition);
}
.macro-period-btn:hover {
  border-color: var(--sidebar-accent);
  color: var(--text);
}
.macro-period-btn.active {
  background: rgba(20, 184, 166, 0.10);
  border-color: var(--sidebar-accent);
  color: var(--sidebar-accent);
}

[data-theme="light"] .macro-period-btn.active {
  background: rgba(46, 109, 164, 0.1);
}

.macro-period-label {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-right: 4px;
}
.macro-period-label {
  font-size: 0.95rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
  font-weight: 600;
  margin-right: 8px;
}
@media (max-width: 768px) {
  .macro-period-bar { flex-wrap: wrap; gap: 4px; }
}
