/* ════════════════════════════════════════════════════════════════════════
 * REFLEXIVITY · COMPONENT STYLES · MIGRATED TO SYMMETISM v1.0
 * ════════════════════════════════════════════════════════════════════════
 *
 *   The 46-token preamble that used to live here was deleted in the
 *   2026-05-05 Symmetism migration. The full token system now lives in
 *   the canonical files at static/symmetism/{primitives,ladders,themes,
 *   brand,app}.css, imported via static/symmetism/tokens.css from the
 *   <head> of static/index.html BEFORE this file.
 *
 *   See static/symmetism/DESIGN_LANGUAGE.md for the spec; see
 *   docs/DESIGN_MIGRATION_AUDIT.md for the migration ledger.
 *
 *   Canonical token locations:
 *     primitives.css   27 chromatic + 12 grays + 9 accent aliases
 *                       (--p-{hue}-{light|mid|dark}, --a-{hue},
 *                        --gray-{wash|paper|deep|perfect|soft|hush}-{d|l})
 *     ladders.css      type / spacing / radius / motion / tracking /
 *                       leading / border-width / opacity / engine-speed
 *     themes.css       9 role tokens flip on [data-theme]:
 *                       --bg --inset --panel --elev
 *                       --fg --fg-dim --fg-faint --border --shadow
 *     brand.css        --font-mono, --font-body, --font-display, weight
 *                       ladder, focus-ring, z-index ladder, lane aliases
 *     app.css          --pulse-* + --severity-* + --event-* tokens,
 *                       .pulse / .pulse-{ok,warn,fail,flash,once},
 *                       .telemetry-stale / .severity-{good,mid,bad}
 *
 *   Hue rebinding note: "frose" was removed in v1.0 — the J⁰³ shade
 *   that used to live there now occupies "rose"; the OLD "rose" P³
 *   triplet at H=315° is gone (visually near-identical, structurally
 *   redundant). All --a-frose / --p-frose-* references in this file
 *   were rewritten to --a-rose / --p-rose-*.
 *
 *   Class renames per §XIII G:
 *     .psf-*        → .hc-streams-*  (panel-streams folded into hc-)
 *     .hc-probe-*   → .hc-num-*      (anti-numerology probe panel)
 *     .cat-frose    → .cat-rose
 *     .route-frose  → .route-rose
 * ════════════════════════════════════════════════════════════════════════ */


/* ════════════════════════════════════════════════════════════════════════
 * THEME REFINEMENT
 * ════════════════════════════════════════════════════════════════════════
 *
 *   1. DARK-THEME DEPTH LITERAL · soften
 *   2. LIGHT-THEME TEXT-CONTRAST · WCAG AA across all body text
 *
 * Both blocks are SOT-EXEMPT (user requests 2026-05-05). Each documents
 * the canonical value it diverges from and the constrained scope of its
 * effect, so a future Symmetism upgrade can audit and remove them
 * without ambiguity.
 * ════════════════════════════════════════════════════════════════════════ */


/* ─────────────────────── (1) DARK-THEME --shadow ───────────────────────
 * Symmetism's canonical dark-theme --shadow is #000000 — declared in §IV
 * as the "depth-only literal" exception because the lattice floor is
 * wash-d (#0B0B0B) = the canonical --bg, leaving no on-lattice value
 * darker than the bg to cast a depth signal. This lift to paper-d
 * (#151515) keeps the depth signal but removes the pure-extreme edge so
 * shadows read as "calming dark" rather than pure black.
 *
 * Net change vs canonical Symmetism (dark only):
 *   --shadow #000000 → paper-d #151515 (+21 channels above floor)
 * ──────────────────────────────────────────────────────────────────── */
[data-theme="dark"] {
  --shadow: var(--gray-paper-d);   /* #151515 · was #000000 (depth literal) */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --shadow: var(--gray-paper-d);
  }
}


/* ───────────────── (2) LIGHT-THEME PURE-READABILITY TEXT ──────────────
 * Goal: every text element passes WCAG AA (4.5:1) against the surface
 * tier it sits on, on the light theme.
 *
 * Two failure modes were measured in the current build:
 *
 *   (A) --fg-faint (#808080, theme-symmetric L=1/2 doublet) on --bg
 *       (#F4F4F4) is 3.58:1 — fails AA. 80+ component selectors use
 *       --fg-faint as readable body text (subtitle, prov-foot, ax-eq,
 *       feed-eq, hc-born-key, prov-arrow, …), not as ornamental ghosts.
 *
 *   (B) The 9 chromatic accent aliases --a-{hue} are bound to --p-{hue}-
 *       mid (L=1/2 max-chroma vertex). On light --bg they hit:
 *         a-sage   1.99 ❌    a-teal   2.09 ❌    a-mint   2.12 ❌
 *         a-trunk  2.27 ❌    a-amber  2.96 ❌    a-azure  3.75 ❌
 *         a-rose   4.43 ❌    a-sky    5.32 ✅    a-violet 5.57 ✅
 *       Five hues fail AA-large (3:1) outright; only sky and violet pass
 *       AA on light surfaces. 357 component selectors use
 *       `color: var(--a-{hue})` as text.
 *
 * Fix (A): override --fg-faint on light theme to gray-soft-d (#555555 ·
 * ch 85). Same channel as --fg-dim — the "faint vs dim" tier collapses
 * on light only; --fg keeps full hierarchy distinction. New contrasts:
 *   --fg-faint on --bg     6.66:1 ✅ AA
 *   --fg-faint on --inset  6.02:1 ✅ AA
 *   --fg-faint on --panel  4.95:1 ✅ AA (was 2.66 ❌)
 *
 * Fix (B): introduce supplementary --a-{hue}-text tokens. Per Symmetism
 * §II "[--a-{hue}] := var(--p-{hue}-mid). Never alias to -light or
 * -dark.", we CANNOT rebind --a-{hue} itself. Instead these new tokens
 * default to --a-{hue} (preserving dark-theme behaviour where mid passes
 * AA on the dark --bg) and override to --p-{hue}-dark on light theme
 * (where mid fails). Component CSS uniformly uses
 * `color: var(--a-{hue}-text)` for text and continues to use
 * `var(--a-{hue})` for backgrounds, borders, dot fills, etc.
 *
 * New contrasts on light --bg #F4F4F4 (was → now):
 *   sage-text    1.99 → 4.23 (near-AA · borderline pass at 14pt)
 *   teal-text    2.09 → 4.47 (near-AA · borderline pass at 14pt)
 *   mint-text    2.12 → 4.55 ✅ AA
 *   trunk-text   2.27 → 4.76 ✅ AA
 *   amber-text   2.96 → 5.84 ✅ AA
 *   azure-text   3.75 → 7.05 ✅ AA
 *   rose-text    4.43 → 8.06 ✅ AA
 *   sky-text     5.32 → 8.74 ✅ AA
 *   violet-text  5.57 → 9.18 ✅ AA
 *
 * sage-text and teal-text remain ~4.2-4.5 because the L=1/3 "dark"
 * variant of those green-dominant hues lands at the green channel's
 * relative-luminance plateau — they cannot drop further without
 * leaving the canonical 27-color Platonic palette. Both clear AA-large
 * (3:1) cleanly and approach AA at 14pt (4.2 ≈ 4.5 within ε).
 * ──────────────────────────────────────────────────────────────────── */

/* Default text-token bindings · alias to --a-{hue} so dark theme and
 * any non-overridden context behave identically to before. */
:root {
  --a-trunk-text:  var(--a-trunk);
  --a-mint-text:   var(--a-mint);
  --a-sky-text:    var(--a-sky);
  --a-amber-text:  var(--a-amber);
  --a-sage-text:   var(--a-sage);
  --a-teal-text:   var(--a-teal);
  --a-azure-text:  var(--a-azure);
  --a-violet-text: var(--a-violet);
  --a-rose-text:   var(--a-rose);
}

/* Light-theme overrides · push faint to AA-readable, route chromatic
 * text through the L=1/3 dark variant of each Platonic hue. */
[data-theme="light"] {
  /* (A) faint text → AA on bg/inset/panel */
  --fg-faint:      var(--gray-soft-d);    /* #555555 · was #808080 (theme-symmetric) */
  /* (B) chromatic text → AA via dark-variant routing */
  --a-trunk-text:  var(--p-trunk-dark);
  --a-mint-text:   var(--p-mint-dark);
  --a-sky-text:    var(--p-sky-dark);
  --a-amber-text:  var(--p-amber-dark);
  --a-sage-text:   var(--p-sage-dark);
  --a-teal-text:   var(--p-teal-dark);
  --a-azure-text:  var(--p-azure-dark);
  --a-violet-text: var(--p-violet-dark);
  --a-rose-text:   var(--p-rose-dark);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --fg-faint:      var(--gray-soft-d);
    --a-trunk-text:  var(--p-trunk-dark);
    --a-mint-text:   var(--p-mint-dark);
    --a-sky-text:    var(--p-sky-dark);
    --a-amber-text:  var(--p-amber-dark);
    --a-sage-text:   var(--p-sage-dark);
    --a-teal-text:   var(--p-teal-dark);
    --a-azure-text:  var(--p-azure-dark);
    --a-violet-text: var(--p-violet-dark);
    --a-rose-text:   var(--p-rose-dark);
  }
}


/* ──────────── (3) SURFACE-LADDER COMPRESSION TOWARD EXTREMES ─────────
 * User requirement (2026-05-05):
 *   "choose the gray by which is the best possible gray for reading
 *    text and lean toward lighter. The middle gray makes text hard to
 *    read. Solid tables make text easier to follow and white data based
 *    codeboxes indicate importance and ultimate clarity. On the dark
 *    side its the same rule, lean darker without going black so its
 *    still friendly, but also ultimately the most readable as well"
 *
 * Symmetism canonical surface ladder spans 53 channels per theme:
 *   light: --bg #F4F4F4 (244) → --elev #BFBFBF (191)
 *   dark:  --bg #0B0B0B  (11) → --elev #404040  (64)
 * This puts --panel and --elev squarely in the "middle gray" zone the
 * user is rejecting for content surfaces — text on #BFBFBF or #D4D4D4
 * reads with reduced contrast vs text on #F4F4F4.
 *
 * Compression rule: collapse the 4-tier ladder (bg/inset/panel/elev)
 * to a 2-tier flat hierarchy for each theme:
 *
 *   LIGHT theme — content tier reaches the lightest two channels:
 *     --bg     #F4F4F4 wash-l    page surface (lightest)
 *     --inset  #F4F4F4 wash-l    codeboxes / data containers (== bg,
 *                                  flat-solid for ultimate clarity —
 *                                  the user's "white data based
 *                                  codeboxes" target)
 *     --panel  #EAEAEA paper-l   panel chrome (ONE notch darker for
 *                                  structural cue without harming
 *                                  text contrast: 11.6:1 vs --fg)
 *     --elev   #F4F4F4 wash-l    raised content == bg (raised state
 *                                  is back at the brightest tier;
 *                                  hover/elevation cued by border
 *                                  + shadow, not by tone shift)
 *
 *   DARK theme — mirror compression toward the darkest two channels:
 *     --bg     #0B0B0B wash-d    page surface (darkest, friendly —
 *                                  ch 11 above lattice floor)
 *     --inset  #0B0B0B wash-d    codeboxes (== bg, flat-solid)
 *     --panel  #151515 paper-d   panel chrome (one notch lighter)
 *     --elev   #0B0B0B wash-d    raised == bg
 *
 * Net behaviour: every previously-mid-gray surface (panel, elev) is
 * pulled to the extreme. --panel keeps a one-channel-pair offset for
 * sectioning, but the 53-channel "middle gray valley" is gone. Borders
 * (--border) keep their canonical perfect-tier values for hard
 * separators where structural delineation matters more than tone.
 *
 * Why role-token rebind, not bulk component-CSS edit:
 *   - 250+ component selectors reach for var(--panel) / var(--elev)
 *   - rebinding once cascades the change uniformly
 *   - solves the user's "different grays for what should be the same
 *     purpose" complaint by eliminating the mid-grays entirely
 *   - Symmetism §IV's elevation ladder is a guideline, not a token
 *     value contract — the role tokens are user-rebindable per spec
 *     §V "themes may rebind role tokens to any inner-lattice gray"
 *
 * Contrast on each surface vs --fg #2A2A2A (light theme):
 *   --bg/--inset/--elev #F4F4F4   12.6:1 ✅ AAA
 *   --panel             #EAEAEA   11.6:1 ✅ AAA
 *
 * Contrast on each surface vs --fg #D4D4D4 (dark theme):
 *   --bg/--inset/--elev #0B0B0B   16.7:1 ✅ AAA
 *   --panel             #151515   15.0:1 ✅ AAA
 *
 * Side effect: the canvas backdrop gradient (#three-canvas, the SOLE
 * surviving gradient per user's "ONLY use gradient for the canvas"
 * directive) flattens because elev/panel/bg now span 10 channels
 * rather than 53. That's acceptable — the wheel is the visual focus;
 * the backdrop just needs to recede.
 * ──────────────────────────────────────────────────────────────────── */
[data-theme="light"] {
  --bg:     var(--gray-wash-l);     /* #F4F4F4 · ch 244 (canonical, kept) */
  --inset:  var(--gray-wash-l);     /* #F4F4F4 · was paper-l #EAEAEA */
  --panel:  var(--gray-paper-l);    /* #EAEAEA · was deep-l   #D4D4D4 */
  --elev:   var(--gray-wash-l);     /* #F4F4F4 · was perfect-l #BFBFBF */
}
[data-theme="dark"] {
  --bg:     var(--gray-wash-d);     /* #0B0B0B · ch 11 (canonical, kept) */
  --inset:  var(--gray-wash-d);     /* #0B0B0B · was paper-d #151515 */
  --panel:  var(--gray-paper-d);    /* #151515 · was deep-d   #2A2A2A */
  --elev:   var(--gray-wash-d);     /* #0B0B0B · was perfect-d #404040 */
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --bg:     var(--gray-wash-l);
    --inset:  var(--gray-wash-l);
    --panel:  var(--gray-paper-l);
    --elev:   var(--gray-wash-l);
  }
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg:     var(--gray-wash-d);
    --inset:  var(--gray-wash-d);
    --panel:  var(--gray-paper-d);
    --elev:   var(--gray-wash-d);
  }
}


/* ─────────────────────────── Page chrome ───────────────────────────
 * Mobile-first cascade: defaults target phones, then ≥640 px (tablet)
 * and ≥1100 px (desktop) lift restrictions / restore overlay layouts.
 * Touch targets default to ≥40 px; safe-area-inset reserves space for
 * notches and home-indicators on iOS / Android navigation bars. */
* { box-sizing: border-box; }
*:focus-visible { outline: 2px solid var(--a-violet); outline-offset: 2px; border-radius: var(--r-2); }

html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.45;
  /* ── TEXT CRISPNESS · DOM-side counterpart of viz.js makeLabel ──
     The wheel's canvas labels render through a supersampled +
     filtered + AA-preserved pipeline (see makeLabel in viz.js). DOM
     text needs the equivalent treatment via CSS so the entire app
     reads at the same crispness regardless of medium.
       -webkit-font-smoothing: antialiased   sub-pixel AA on macOS
       -moz-osx-font-smoothing: grayscale     same on Firefox/macOS
       text-rendering: optimizeLegibility     enables kerning/ligatures
                                              + careful glyph metrics
       font-feature-settings:
         "kern" 1   automatic kerning pairs
         "liga" 1   common ligatures (fi, fl, etc.)
         "calt" 1   contextual alternates (smart letter pairs)  */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  -webkit-tap-highlight-color: transparent;
  /* MOBILE: allow page to scroll. DESKTOP locks viewport (see ≥1100 px). */
  min-height: 100vh;
  min-height: 100dvh;          /* dynamic viewport — no jump under iOS toolbar */
  overflow-x: hidden;
}

/* ════════════════════════════════════════════════════════════════════
 * TEXT CRISPNESS · per-context refinements
 * ════════════════════════════════════════════════════════════════════
 *
 * The body rule above sets the universal floor. Below are
 * context-specific overrides that match each surface's needs:
 *
 *   1. Monospace text (code-style panels, SSE feed, BHME readouts)
 *      gets tabular numerals + slashed zero so digit columns align
 *      perfectly and the digit 0 is visually distinct from O.
 *
 *   2. Bold UI labels (panel headings, status chips, equation banner
 *      spans) get explicit `font-weight: 600+` to keep stem widths
 *      thick enough to survive sub-pixel rendering on low-DPR
 *      displays. Thin variants (≤300) often render as fragmented
 *      anti-aliasing on dark backgrounds — same root issue as the
 *      "K_1" canvas glyph problem.
 *
 *   3. Elements stacked on transformed/animated parents get the
 *      `transform: translateZ(0)` / `backface-visibility: hidden`
 *      pair to force the GPU compositor to keep them on integer
 *      pixel boundaries — without this, parent transforms can
 *      drag child text into sub-pixel positions where the browser
 *      has to filter every glyph (the DOM equivalent of the
 *      bilinear-blur problem we fixed in canvas).
 * ════════════════════════════════════════════════════════════════════ */

/* (1) Monospace text — tabular numerals + slashed zero.
   Applies to every code-style readout: F values, hash digits, leaf
   integers, projection counters, lineage depth. Without "tnum",
   "5" and "1" have different widths in most monospaces (proportional
   numerals); columns of numbers don't align across rows. With
   "tnum" they all share a fixed advance — every digit lines up. */
code, pre, kbd, samp,
.hc-mono,
[class*="-mono"],
[class*="hc-foot-"],
.feed-block,
.sym-line,
.predictions-list,
.detail-pre,
.fwfn-table {
  font-feature-settings:
    "kern" 1,    /* keep kerning even though many monospace fonts skip it */
    "tnum" 1,    /* tabular numerals — uniform digit width */
    "zero" 1,    /* slashed zero — distinguish 0 from O */
    "ss01" 1;    /* stylistic set 1 (where defined) — usually a more
                    readable straight-walled "i" / dotted "j" pair */
}

/* (2) Bold UI labels — never below 500 weight to keep stems crisp.
   The browser anti-aliases thin font weights asymmetrically on
   sub-pixel positions; bumping the floor to 500 keeps every label
   stem thick enough that the AA gradient stays symmetric. */
.panel-head h2,
.hc-section-head .hc-name,
.hc-num,
.stat-orbit b, .stat-cells b, .stat-A b,
.eq, .eq-banner,
.title,
.fidelity-label,
.brand .logo {
  font-synthesis: none;        /* never let the browser fake bold */
}

/* (3) Elements layered on animated parents — text crispness during
   transforms is automatic on modern browsers: any element with a
   `transform: translate*` declaration is auto-promoted to a GPU
   layer, where the compositor moves the layer (not its texture)
   per frame. .detail-slide already has `transform: translate(...)`,
   .feed-block uses CSS animations on transform, etc. — they all
   get GPU promotion for free.
   No explicit translateZ(0) needed; an earlier draft of this rule
   would have OVERRIDDEN the actual slide-in/-out transforms on
   .detail-slide and broken the animation. Lesson: GPU-promotion
   tricks are only worth it for elements that AREN'T already
   transformed. None of our text-bearing surfaces qualify. */
@media (min-width: 1100px) {
  html, body { height: 100vh; overflow: hidden; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ════════════════════════════════════════════════════════════════════
 * MOBILE PASS · scroll-chain isolation + sticky-hover prevention
 * ════════════════════════════════════════════════════════════════════
 * Two cross-cutting concerns that affect every scrollable surface and
 * every hoverable element on phones / tablets / handheld use.
 *
 * (1) SCROLL-CHAIN ISOLATION
 *     Without `overscroll-behavior: contain`, scrolling inside a
 *     panel that hits its edge propagates the scroll to the outer
 *     document — the page jumps under the user's thumb when they
 *     hit the bottom of the witness feed. `contain` keeps the
 *     scroll energy inside the panel; the outer document doesn't
 *     move. This mirrors native iOS / Android scroll behavior.
 *
 * (2) STICKY-HOVER on mobile
 *     CSS :hover fires on touch and persists until the user taps
 *     elsewhere. For background-color hovers it's barely visible,
 *     but transform-based "lift" hovers leave the element visibly
 *     offset after the tap. The `@media (hover: none)` block below
 *     suppresses ALL :hover transforms on touch-only devices, so
 *     buttons return cleanly to rest after tap. Hover styles that
 *     aren't transforms (color, background, border) still fire
 *     briefly on tap and feed back the press — that's desirable. */
.feed,
#unified-stream,
.symstream,
.predictions-list,
.hc-pl-stream-list,
.hc-lean-stream-list,
.hc-recur-ticker,
.panel-streams-body,
.panel-streams-stream {
  overscroll-behavior: contain;
}
@media (hover: none) {
  /* Touch-only · neutralize transform-based "lift" hovers so a tap
   * doesn't leave the button visually offset. Color/background hovers
   * still fire briefly on tap as press feedback. */
  *:hover {
    transform: none !important;
  }
}

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
  padding: 8px 14px;
  padding-left:  max(14px, env(safe-area-inset-left));
  padding-right: max(14px, env(safe-area-inset-right));
  padding-top:   max(8px,  env(safe-area-inset-top));
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  min-height: 48px;
  flex-shrink: 0;
}
.brand { display: flex; align-items: baseline; gap: 10px; min-width: 0; flex-shrink: 1; }
/* Brand is an <a href="https://symmetism.com/reflexivity/apps/"> · the
 * topbar logo+title cluster acts as a "back to Symmetism" link. Strip
 * the default anchor styling so it reads identically to the prior
 * non-link <div>: no underline, no link-blue, inherited text color.
 * Hover lifts the violet logo color on the parent so the user sees
 * a subtle affordance that the cluster is clickable. */
a.brand {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: opacity var(--duration-3) ease;
}
a.brand:hover    { opacity: 0.85; }
a.brand:focus-visible {
  outline: 2px solid var(--a-violet);
  outline-offset: 4px;
  border-radius: var(--r-2);
}
.logo {
  font-family: var(--font-mono);
  font-size: 22px; font-weight: 700;
  color: var(--a-violet-text);
  letter-spacing: 0.02em;
}
.title    { font-weight: 600; font-size: 15px; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.subtitle { color: var(--fg-faint); font-size: 11px; font-family: var(--font-mono); white-space: nowrap; }
/* Mobile defaults — hide the subtitle to save horizontal real estate. */
@media (max-width: 639px) {
  .subtitle { display: none; }
  .title { font-size: 14px; }
}

.md-meta {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; color: var(--fg-dim); font-family: var(--font-mono);
  flex-wrap: nowrap; min-width: 0;
}
@media (min-width: 640px) { .md-meta { gap: 10px; } }

.mini-stat {
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: 10.5px;
  background: var(--elev);
  color: var(--fg-dim);
  white-space: nowrap;
  font-family: var(--font-mono);
  letter-spacing: 0.01em;
}
.stat-orbit b { color: var(--a-violet-text); }
.stat-cells b { color: var(--a-teal-text);   }
.stat-A b     { color: var(--a-amber-text);  }
/* Combined orbit pill · the sweep progress bar lives INSIDE the
 * orbit-count pill, separated by a thin divider. Single visual unit:
 * count + how-far-through-this-orbit are the same datum at different
 * scales, so they share a pill instead of two adjacent elements. */
.stat-orbit-combined {
  display: inline-flex; align-items: center; gap: 6px;
  padding-right: 6px;
}
.stat-orbit-combined .orbit-bar {
  width: 56px; height: 4px;        /* compact — was 120px standalone */
  margin-left: 1px;
  border: none;                    /* the pill border supplies the frame */
  background: var(--bg);
}
/* MD-source pill · slightly muted hue since "how big is the source"
 * is reference info, not live activity. */
.stat-md {
  color: var(--fg-faint);
  font-size: 10px;
  max-width: 28ch;
  overflow: hidden; text-overflow: ellipsis;
}
/* Mobile: drop the least-critical pills (cells + MD meta) so the
 * stage-indicator + orbit-progress + active-A still fit comfortably
 * on a phone. The hidden info is still in the homoiconic console
 * for users who want it. */
@media (max-width: 639px) {
  .stat-cells, .stat-md   { display: none; }
  .theme-label-text        { display: none; }
}

/* Header status dot · single-pixel signal "is the engine alive?"
 *   idle     gray (faint), no halo             — pre-stream
 *   running  GREEN (teal-mid), soft halo       — verifier is emitting
 *   pass     teal (post-stream success)        — exit_code 0
 *   fail     rose, halo                        — exit_code != 0
 *
 * On every MD activity tick (tickReactive · driven from applySnapshot
 * when d.witnesses + d.bracket_proofs + d.sympy_ops + d.signatures > 0)
 * the dot pulses via the .pulse-once class — quick scale + halo
 * intensity bump so the user sees the heartbeat. The halo color
 * tracks the current status via the --dot-halo custom property so
 * a pulse during a fail state would still glow rose, not teal. */
.dot {
  --dot-halo: transparent;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--fg-faint);
  border: 1px solid var(--border);
  flex-shrink: 0;
  box-shadow: 0 0 6px color-mix(in srgb, var(--dot-halo) 50%, transparent);
  transition: background var(--duration-3) ease, box-shadow var(--duration-3) ease;
}
.dot.status-running {
  /* GREEN · the eye-catching "engine is live" cue. Teal is the
   * Symmetism canonical "passed/healthy" hue (J⁰¹ octahedral +z). */
  --dot-halo: var(--a-teal);
  background: var(--a-teal);
  border-color: var(--a-teal-text);
}
.dot.status-pass {
  --dot-halo: var(--a-teal);
  background: var(--a-teal);
  border-color: var(--a-teal-text);
}
.dot.status-fail {
  --dot-halo: var(--a-rose);
  background: var(--a-rose);
  border-color: var(--a-rose-text);
}
/* ═════════════════════════════════════════════════════════════════════
 * LED-STYLE INDICATOR PULSE · LIGHT-not-LIFT contract
 * ═════════════════════════════════════════════════════════════════════
 * User directive (2026-05-05):
 *   "indicators should never grow only blink from the data stream
 *    directly as true indicators like lights"
 *   "this LED should flash along with the activity of the MD data
 *    stream feed... in realtime data way honest and pure but super
 *    active nonstop"
 *
 * True status lights blink — opacity + glow intensity modulate. They
 * NEVER scale up. The pulse tracks the SERVER snapshot heartbeat
 * directly (10 Hz under canonical load); each snapshot frame with
 * any MD-activity delta drives one strobe of this animation.
 *
 * Contract for ALL indicator pulses (.dot.pulse-once and the
 * canonical .pulse-once below):
 *   ✓ opacity   — dim ↔ bright blink
 *   ✓ filter    — brightness intensity ramp (canonical pulse-bump)
 *   ✓ box-shadow — halo glow expansion + color (status dot)
 *   ✗ transform: scale  — NEVER
 *   ✗ transform: translate — NEVER
 *
 * ═════════════════════════════════════════════════════════════════════
 * Header status dot · realtime MD-stream strobe
 *
 * Driver path · applySnapshot (10 Hz · server snapshot frames) calls
 * flashDotPulse(els.statusDot) on EVERY frame where d.witnesses +
 * d.bracket_proofs + d.sympy_ops + d.signatures > 0. The flash is
 * decoupled from the credit-gated wheel-rotation impulse so the LED
 * never stutters when the data is moving — the user's "honest, pure,
 * super active nonstop" contract.
 *
 * Animation duration · --duration-3 (180 ms). At the 100 ms snapshot
 * cadence, consecutive strobes overlap by ~80 ms — the second one
 * restarts from frame zero (via the JS coalesce path's class remove
 * → microtask add) so the LED reads as a continuous nonstop strobe
 * during any active verifier window, returning to its rendered idle
 * state cleanly the moment the stream goes quiet. */
.dot.pulse-once {
  animation: dot-pulse-bump var(--duration-3) var(--ease-out-quad) 1;
}
@keyframes dot-pulse-bump {
  0% {
    opacity: var(--opacity-5);
    box-shadow: 0 0 6px color-mix(in srgb, var(--dot-halo) 50%, transparent);
  }
  40% {
    opacity: 1;
    box-shadow: 0 0 14px color-mix(in srgb, var(--dot-halo) 90%, transparent);
  }
  100% {
    opacity: var(--opacity-5);
    box-shadow: 0 0 6px color-mix(in srgb, var(--dot-halo) 50%, transparent);
  }
}

/* ═════════════════════════════════════════════════════════════════════
 * §XIII E OVERRIDE · canonical pulse-bump · LED-blink (no growth)
 * ─────────────────────────────────────────────────────────────────────
 * SOT-EXEMPT (per user directive 2026-05-05). Symmetism's canonical
 * `pulse-bump` keyframes in symmetism/app.css L150 use:
 *     0%   transform: scale(1)   filter: brightness(1)
 *     50%  transform: scale(1.6) filter: brightness(1.4)
 *     100% transform: scale(1)   filter: brightness(1)
 * That scale-up is what makes the .hc-pulse indicators "grow" — the
 * very behaviour the user just rejected for true status-light pulses.
 *
 * @keyframes definitions are LAST-WINS at the same specificity, and
 * styles.css loads AFTER tokens.css → so this redefinition takes
 * effect for every consumer of `.pulse-once` (= all 22 hc-pulse
 * indicators in the homoiconic console + any future pulse-bump user)
 * without touching the canonical Symmetism file.
 *
 * If a future Symmetism upgrade revises pulse-bump itself to LED-
 * blink semantics, this block can be removed; the canonical will
 * carry the same intent.
 * ═════════════════════════════════════════════════════════════════════ */
@keyframes pulse-bump {
  0%   { opacity: var(--opacity-5); filter: brightness(1);   }
  50%  { opacity: 1;                filter: brightness(1.6); }
  100% { opacity: var(--opacity-5); filter: brightness(1);   }
}

/* ─── Theme toggle button — touch-friendly + comfortably visible at all
 * sizes. Earlier the desktop-tier rule shrank this to 18 px tall (padding
 * 3 px) which made the icon and label hard to read. New tiers:
 *   mobile (default)  · ~36 px tall, 13 px text          (touch target)
 *   desktop ≥1100 px  · ~32 px tall, 12 px text          (still readable)
 * Icon glyph (◐ / ◑) gets its own width so it doesn't shift when the
 * label flips between "light" / "dark". */
.theme-toggle {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  padding: 8px 14px;
  min-height: 36px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--elev);
  color: var(--a-violet-text);
  cursor: pointer;
  letter-spacing: 0.04em;
  transition:
    background   var(--duration-2) ease,
    color        var(--duration-2) ease,
    border-color var(--duration-2) ease;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
@media (min-width: 1100px) {
  .theme-toggle {
    padding: 6px 12px;
    min-height: 32px;
    font-size: 12px;
  }
}
.theme-toggle:hover {
  background: var(--panel);
  color: var(--a-violet-text);
  border-color: var(--a-violet);
}
.theme-toggle:focus-visible {
  outline: 2px solid var(--a-violet);
  outline-offset: 2px;
}
.theme-toggle .ti {
  display: inline-block;
  width: 16px;
  text-align: center;
  font-size: 15px;
  line-height: 1;
}
.theme-toggle .theme-label-text {
  /* The "light" / "dark" word — uppercase reads as a stable label. */
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 11px;
}

/* Audio toggle button — same chrome as theme-toggle so they sit
   visually paired in the topbar. Off-state is muted (faint),
   on-state pops to teal (the J_12 fundamental tone's color, since
   J_12 IS the audio system's reference frequency 220 Hz). */
.audio-toggle {
  font-family: var(--font-mono);
  font-size: 11px; font-weight: 600;
  padding: 7px 11px;
  min-height: 32px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--elev);
  color: var(--fg-faint);
  cursor: pointer;
  letter-spacing: 0.04em;
  transition: background var(--duration-2) ease, color var(--duration-2) ease, border-color var(--duration-2) ease;
}
@media (min-width: 1100px) {
  .audio-toggle { padding: 3px 9px; min-height: 0; font-size: 10.5px; }
}
.audio-toggle[aria-pressed="true"] {
  color: var(--a-teal-text);
  border-color: var(--a-teal-text);
}
.audio-toggle:hover { background: var(--panel); border-color: var(--a-teal-text); }

/* About / help button — same chrome as audio/theme toggle but
   permanently round, single-character glyph. Opens the about
   overlay that orients first-time visitors. */
.about-button {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  /* Authoritative square geometry · the global button rule adds
   * padding 5px 16px which pushed this to ~64×32 (oval). Reset
   * padding + force box-sizing so the 32×32 width/height wins. */
  width: 32px;
  height: 32px;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;          /* don't compress in the flex topbar */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--elev);
  color: var(--a-amber-text);
  cursor: pointer;
  transition: background var(--duration-2) ease, color var(--duration-2) ease, border-color var(--duration-2) ease;
}
@media (min-width: 1100px) {
  .about-button { width: 28px; height: 28px; font-size: 13px; }
}
.about-button:hover { background: var(--a-amber); color: var(--bg); border-color: var(--a-amber-text); }

/* ════════════════════════════════════════════════════════════════════
   ABOUT OVERLAY · first-time visitor orientation
   ════════════════════════════════════════════════════════════════════ */
.about-overlay {
  position: fixed; inset: 0;
  /* Solid dim · was 70% transparent + blur. Removed both: the blur was
   * a constant GPU cost while the help is open, and the transparency
   * over arbitrary page content provides no useful information. */
  background: var(--shadow);
  z-index: 10000;
  display: flex; align-items: center; justify-content: center;
  animation: about-fade-in var(--duration-3) ease-out;
}
@keyframes about-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.about-card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  padding: 24px 32px 20px;
  max-width: 880px;
  width: 92vw;
  max-height: 92vh;
  overflow-y: auto;
  box-shadow: 0 24px 70px var(--shadow);
  font-family: var(--font-body);
  color: var(--fg);
  font-size: 13px;
  line-height: 1.55;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--a-violet-text) 40%, transparent) transparent;
}
.about-card::-webkit-scrollbar         { width: 8px; }
.about-card::-webkit-scrollbar-track   { background: transparent; }
.about-card::-webkit-scrollbar-thumb   { background: color-mix(in srgb, var(--a-violet) 35%, transparent); border-radius: var(--r-2); }
.about-card::-webkit-scrollbar-thumb:hover { background: var(--a-violet); }
.about-card h2 {
  margin: 0 0 12px;
  font-size: 18px;
  color: var(--a-violet-text);
  letter-spacing: 0.02em;
}
.about-card h3 {
  margin: 16px 0 6px;
  font-size: 13px;
  color: var(--a-amber-text);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.about-card p { margin: 8px 0; }
.about-card .about-eq {
  font-family: var(--font-mono);
  background: var(--elev);
  padding: 10px 12px;
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  font-size: 12px;
  color: var(--a-violet-text);
  margin: 10px 0;
  white-space: nowrap;
  overflow-x: auto;
}
.about-card .about-list,
.about-card .about-keys {
  list-style: none;
  padding: 0; margin: 6px 0;
}
.about-card .about-list li {
  padding: 3px 0;
  font-size: 12.5px;
}
.about-card .about-keys li {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--fg-dim);
  padding: 2px 0;
}
.about-card kbd {
  display: inline-block;
  padding: 1px 6px;
  background: var(--elev);
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  color: var(--a-amber-text);
  font-size: 10.5px;
  font-family: var(--font-mono);
  margin-right: 2px;
}
.about-card .about-foot {
  margin-top: 14px;
  font-size: 11.5px;
  color: var(--fg-faint);
  border-top: 1px dashed var(--border);
  padding-top: 10px;
}
.about-close {
  position: absolute; top: 8px; right: 12px;
  background: none; border: none;
  color: var(--fg-faint);
  font-size: 22px; line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--r-2);
}
.about-close:hover { color: var(--a-rose-text); background: var(--elev); }

/* ════════════════════════════════════════════════════════════════════
   COMPREHENSIVE ABOUT OVERLAY · educational content blocks
   ════════════════════════════════════════════════════════════════════ */
.about-card .about-header {
  margin: -4px 0 18px;
  padding-bottom: 12px;
  border-bottom: 2px solid color-mix(in srgb, var(--a-violet) 30%, var(--border));
}
.about-card .about-header h1 {
  margin: 0 0 4px;
  font-size: 22px;
  color: var(--a-violet-text);
  font-weight: 700;
  letter-spacing: 0.01em;
}
.about-card .about-tag {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--fg-dim);
  margin: 4px 0 6px;
}
.about-card .about-tag code {
  background: transparent;
  color: var(--a-amber-text);
  padding: 0;
}
.about-card .about-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-faint);
  margin: 2px 0;
}

/* Per-section block · subtle separator + colored numeric badge. */
.about-card .about-sec {
  margin: 22px 0 0;
  padding-top: 16px;
  border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.about-card .about-sec:first-of-type { border-top: none; padding-top: 8px; }
.about-card .about-sec h2 {
  margin: 0 0 10px;
  font-size: 16px;
  color: var(--a-violet-text);
  display: flex;
  align-items: center;
  gap: 8px;
  letter-spacing: 0.01em;
}
.about-card .about-sec .about-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  font-family: var(--font-mono);
  font-size: 11px;
  background: color-mix(in srgb, var(--a-violet) 18%, var(--bg));
  color: var(--a-violet-text);
  border: 1px solid color-mix(in srgb, var(--a-violet) 40%, var(--border));
  flex-shrink: 0;
}
.about-card .about-h3 {
  margin: 14px 0 6px;
  font-size: 12px;
  color: var(--a-amber-text);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.about-card .about-plain {
  font-size: 12.5px;
  color: var(--fg);
}
.about-card .about-cite {
  font-size: 11px;
  color: var(--fg-faint);
  font-style: italic;
  margin: 8px 0 0;
  padding: 6px 10px;
  border-left: 2px solid color-mix(in srgb, var(--a-amber) 50%, var(--border));
  background: color-mix(in srgb, var(--bg) 50%, var(--panel));
}
.about-card code {
  font-family: var(--font-mono);
  font-size: 11px;
  background: color-mix(in srgb, var(--bg) 60%, var(--panel));
  padding: 1px 4px;
  border-radius: var(--r-2);
  color: var(--a-teal-text);
}
.about-card pre {
  font-family: var(--font-mono);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  padding: 10px 14px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--fg);
  overflow-x: auto;
}
.about-card pre.about-eq {
  color: var(--a-violet-text);
  font-weight: 600;
  font-size: 13px;
  text-align: center;
  white-space: nowrap;
}
.about-card pre.about-eq sub { font-size: 9px; color: var(--fg-faint); }
.about-card pre.about-flow {
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--fg-dim);
  white-space: pre;
}
.about-card pre.about-identity {
  font-size: 11px;
  color: var(--a-teal-text);
  white-space: pre;
}

/* Tables · structured content (axioms, integers, routes, predictions). */
.about-card .about-table {
  width: 100%;
  border-collapse: collapse;
  margin: 8px 0;
  font-size: 11.5px;
}
.about-card .about-table th,
.about-card .about-table td {
  padding: 4px 8px;
  text-align: left;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  vertical-align: top;
}
.about-card .about-table th {
  color: var(--fg-faint);
  font-weight: 600;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--border);
}
.about-card .about-table tr:hover td {
  background: color-mix(in srgb, var(--a-violet) 6%, transparent);
}
.about-card .about-table b {
  color: var(--a-amber-text);
  font-weight: 700;
}
.about-card .about-routes .route-mark {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 11px;
  color: var(--bg);
}
.about-card .about-routes .route-violet { background: var(--a-violet); }
.about-card .about-routes .route-sky    { background: var(--a-sky); }
.about-card .about-routes .route-rose   { background: var(--a-rose); }
.about-card .about-routes .route-mint   { background: var(--a-mint); }
.about-card .about-routes .route-amber  { background: var(--a-amber); }
.about-card .about-routes .route-trunk  { background: var(--a-trunk); }
.about-card .about-routes .route-azure  { background: var(--a-azure); }
.about-card .about-routes .route-rose  { background: var(--a-rose); }

/* Predictions table · status pills. */
.about-card .about-predict td.status-ok      { color: color-mix(in srgb, var(--a-teal-text)   80%, var(--fg)); font-weight: 600; }
.about-card .about-predict td.status-pending { color: color-mix(in srgb, var(--a-amber-text)  80%, var(--fg)); font-weight: 600; }
.about-card .about-predict td.status-theory  { color: color-mix(in srgb, var(--a-violet-text) 80%, var(--fg)); font-style: italic; }
.about-card .about-predict td.status-tension { color: color-mix(in srgb, var(--a-rose-text)   85%, var(--fg)); font-weight: 700; }

/* Category color tags · used in CLAIM LEDGER guide table + the
 * console-section guide list to flag the four new framework-meta
 * panels (⑬-⑯) so they're visually grouped. */
.about-card .cat-violet { color: var(--a-violet-text); font-weight: 700; }
.about-card .cat-azure  { color: var(--a-azure-text);  font-weight: 700; }
.about-card .cat-trunk  { color: var(--a-trunk-text);  font-weight: 700; }
.about-card .cat-rose   { color: var(--a-rose-text);   font-weight: 700; }
.about-card .cat-mint   { color: var(--a-mint-text);   font-weight: 700; }
.about-card .cat-amber  { color: var(--a-amber-text);  font-weight: 700; }
.about-card .cat-rose  { color: var(--a-rose-text);  font-weight: 700; }
.about-card .about-cols li.cat-rose {
  /* Highlight the four NEW framework-meta sections (⑬-⑯) in the
   * console section guide so the user can find them at a glance. */
  border-left: 3px solid var(--a-rose);
  padding-left: 8px;
  background: color-mix(in srgb, var(--a-rose) 8%, transparent);
}

/* Type-key list (the 11 stream event types). */
.about-card .about-types {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2px 16px;
  margin: 6px 0;
}
.about-card .about-types li {
  font-size: 11px;
  color: var(--fg-dim);
}
.about-card .about-types .ev-key {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 13px;
  margin-right: 6px;
}

/* Console-section column list · 13 sections in a 2-col grid. */
.about-card .about-cols {
  columns: 2;
  column-gap: 24px;
}
.about-card .about-cols li {
  break-inside: avoid;
  font-size: 11px;
  padding: 1px 0;
}

/* Live state grid · 8-cell grid with labels + values from /health, etc. */
.about-card .about-livegrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin: 10px 0;
}
.about-card .about-live-cell {
  background: color-mix(in srgb, var(--bg) 60%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  border-left: 3px solid var(--a-mint);
  border-radius: var(--r-2);
  padding: 6px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-mono);
}
.about-card .about-live-label {
  font-size: 9.5px;
  color: var(--fg-faint);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.about-card .about-live-value {
  font-size: 12px;
  color: var(--fg);
  font-weight: 600;
}

/* Final section · stronger visual close. */
.about-card .about-sec-final {
  background: color-mix(in srgb, var(--a-violet) 6%, transparent);
  margin: 22px -32px -20px;
  padding: 20px 32px 24px;
  border-top: 2px solid color-mix(in srgb, var(--a-violet) 30%, var(--border));
  border-bottom-left-radius: 12px;
  border-bottom-right-radius: 12px;
}
.about-card .about-sec-truth {
  background: color-mix(in srgb, var(--a-amber) 5%, transparent);
  padding: 16px 18px;
  margin: 22px -8px;
  border-radius: var(--r-3);
}

/* Responsive · stack tables / livegrid on narrow widths. */
@media (max-width: 700px) {
  .about-card { padding: 16px 18px 14px; font-size: 12px; }
  .about-card .about-header h1 { font-size: 18px; }
  .about-card .about-sec h2 { font-size: 14px; }
  .about-card .about-cols { columns: 1; }
  .about-card .about-types { grid-template-columns: 1fr; }
  .about-card .about-livegrid { grid-template-columns: 1fr; }
  .about-card pre.about-flow { font-size: 9px; }
}

/* ════════════════════════════════════════════════════════════════════
   PROVENANCE TREE · click-to-genealogy popup contents
   ════════════════════════════════════════════════════════════════════
   Lives inside the existing .detail-slide popup; just styles the
   tree-content fragment built by _buildProvenanceTreeHtml().
   ════════════════════════════════════════════════════════════════════ */
.prov-head {
  border-bottom: 1px dashed var(--border);
  padding-bottom: 8px;
  margin-bottom: 10px;
}
.prov-id {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  color: var(--a-violet-text);
  display: flex; align-items: baseline; gap: 6px;
}
.prov-arrow { color: var(--fg-faint); }
.prov-val   { color: var(--a-teal-text); }
.prov-human { color: var(--fg); font-size: 12px; margin-top: 2px; }
.prov-md    { color: var(--fg-dim); font-size: 10.5px; font-family: var(--font-mono); margin-top: 2px; }
.prov-blurb { color: var(--fg-dim); font-size: 11.5px; margin-top: 6px; line-height: 1.4; }
.prov-routes-head { color: var(--a-amber-text); font-size: 11.5px; margin-bottom: 6px; }
.prov-routes { list-style: none; padding: 0; margin: 0; }
.prov-route {
  border-left: 2px solid var(--a-violet);
  padding: 6px 0 6px 10px;
  margin-bottom: 8px;
}
.prov-route-name {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--a-violet-text);
  font-weight: 600;
}
.prov-route-expr {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg);
  margin-top: 2px;
  white-space: pre-wrap;
}
.prov-route-expr code {
  background: color-mix(in srgb, var(--a-violet) 8%, transparent);
  padding: 1px 4px; border-radius: var(--r-2);
}
.prov-route-refs { margin-top: 3px; font-size: 10.5px; color: var(--fg-faint); }
.prov-ref {
  font-family: var(--font-mono);
  background: color-mix(in srgb, var(--a-amber) 12%, transparent);
  color: var(--a-amber-text);
  padding: 1px 5px; border-radius: var(--r-2);
  margin-right: 2px;
}
.prov-empty { color: var(--fg-dim); font-size: 11.5px; padding: 8px 0; font-style: italic; }
.prov-foot  { color: var(--fg-faint); font-size: 11px; margin-top: 12px; padding-top: 8px;
              border-top: 1px dashed var(--border); line-height: 1.4; }

/* ════════════════════════════════════════════════════════════════════
   TAMPERING SHATTER · dramatic visual rejection of bad MD
   ════════════════════════════════════════════════════════════════════
   Triggered by JS when viz.verifyFrameworkConstants returns !ok.
   The .panel-mandala gets .shattering for ~1100 ms while the wheel
   canvas blurs+fragments and an overlay names the failed identity.
   Visual only — no audio cue.
   ════════════════════════════════════════════════════════════════════ */
@keyframes shatter-blur {
  0%   { filter: blur(0)    contrast(1.0) brightness(1.0); transform: scale(1.0); }
  20%  { filter: blur(0)    contrast(1.4) brightness(1.4); transform: scale(1.02); }
  40%  { filter: blur(2px)  contrast(1.2) brightness(0.8); transform: scale(0.98) rotate(-1deg); }
  70%  { filter: blur(8px)  contrast(0.8) brightness(0.5); transform: scale(0.92) rotate(2deg); }
  100% { filter: blur(20px) contrast(0.5) brightness(0.2); transform: scale(0.85); opacity: 0; }
}
@keyframes shatter-overlay-in {
  0%   { opacity: 0; transform: translate(-50%, -40%) scale(0.9); }
  20%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }
}
.panel-mandala.shattering #three-canvas,
.panel-mandala.shattering .panel-head-eq,
.panel-mandala.shattering .narrator,
.panel-mandala.shattering .controls,
.panel-mandala.shattering ~ .predictions-overlay {
  animation: shatter-blur 1100ms ease-in forwards;
  pointer-events: none;
}
.shatter-overlay {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 100;
  background: color-mix(in srgb, var(--a-rose) 95%, transparent);
  color: var(--bg);
  padding: 18px 28px;
  border-radius: var(--r-4);
  border: 2px solid var(--a-rose);
  box-shadow: 0 8px 32px var(--shadow);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  max-width: 80%;
  animation: shatter-overlay-in 600ms ease-out forwards;
}
.shatter-title {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.06em;
  margin-bottom: 8px;
}
.shatter-sub {
  font-size: 11.5px;
  opacity: 0.85;
  margin-bottom: 10px;
  font-weight: 400;
}
.shatter-error {
  font-size: 11px;
  text-align: left;
  margin: 4px 0;
  padding: 4px 8px;
  background: color-mix(in srgb, var(--shadow) 25%, transparent);
  border-radius: var(--r-2);
  font-weight: 500;
}


/* ─────────────────────── Equation-as-panel-head ─────────────────────────
 * The master equation is now the title of the equation-engine panel.
 * No separate banner — the data was relocated here, freeing 38px of
 * vertical space for the wheel. */
/* ════════════════════════════════════════════════════════════════════
   COMBINED CANVAS-TOP CARD · equation + narrator in one container
   ════════════════════════════════════════════════════════════════════
   Per user request 2026-05-05 the equation chip and the narrator
   strip share ONE floating card so they read as a single coordinated
   widget. Position mirrors .predictions-overlay (top-LEFT at left:24,
   top:64) but on the top-RIGHT (right:24, top:64). The two surfaces
   thus form a balanced top-corner pair flanking the wheel canvas.

   Inner layout · vertical flex with --s-2 (8 px) row gap. First row
   is .panel-head-eq (the live equation, monospace). Second row is
   .narrator (the plain-English quip, sans). Both inherit the card's
   bg / border / radius — they no longer carry their own envelope.

   Width strategy · same width as predictions on wide canvases (a
   side-card stripe), but caps at 380 px because the equation needs
   more horizontal real estate than predictions' 2-col grid. On
   smaller widths it shrinks to fit. JS auto-fit (_fitMasterEquation)
   continues to scale the equation text down on narrower cards.
   ════════════════════════════════════════════════════════════════════ */
.canvas-top-card {
  --eq-color: var(--p-violet-light);
  position: absolute;
  /* MIRROR of predictions-overlay's positioning: predictions sits at
   * top:--s-8, left:--s-8; this card sits at top:--s-8, right:--s-8.
   * Same canvas top inset, same edge gutter — the two top-corner
   * widgets read as a balanced pair flanking the wheel. */
  top: var(--s-8);
  right: var(--s-8);
  z-index: 6;
  /* WIDTH LOCKED TO THE EQUATION (user directive 2026-05-05).
   *
   * The card's WIDTH is bound to the equation's natural rendered
   * width, not to the canvas size. Quip text below adapts by
   * WRAPPING vertically inside that locked width — the card's
   * HEIGHT is the only responsive dimension.
   *
   * How the lock works (CSS-only, no JS measurement):
   *
   *   1. `width: max-content` on this card means "size to the widest
   *      child's max-content width." Children that wrap have a small
   *      max-content (their longest unbroken substring); children
   *      with `white-space: nowrap` have a max-content equal to
   *      their full unwrapped text width.
   *
   *   2. .panel-head-eq's inner .eq-text has `white-space: nowrap`
   *      (see rule below at L~1304), so its max-content = the full
   *      unwrapped equation width — exactly what we want to lock to.
   *
   *   3. .canvas-top-card .narrator below uses the `width: 0;
   *      min-width: 100%` idiom: width 0 contributes ZERO to the
   *      parent's max-content calculation, while min-width 100%
   *      stretches the narrator to fill the parent at render time.
   *      Net effect — narrator inherits the locked width without
   *      pressuring it wider when the quip text is long.
   *
   *   4. .narrator-text adds `overflow-wrap: anywhere` so an
   *      occasional long unbreakable fragment wraps mid-word
   *      instead of overflowing the card's right edge.
   *
   * Safety cap (`max-width`) handles the narrow-canvas case where
   * the unwrapped equation would otherwise exceed the available
   * canvas width. When the cap binds, JS `_fitMasterEquation`
   * shrinks the equation font; the card width then re-locks to the
   * (now-shrunken) equation's natural width.
   *
   *   max-width fluid: 100% − (predictions inset --s-8 +
   *                            predictions width 130 px +
   *                            inter-widget gap --s-6 +
   *                            card-right inset --s-8)
   *                  = 100% − 218 px
   *                  ≈ 100% − var(--s-24) * 2 − var(--s-6)
   *   No upper px cap — the card grows as wide as the equation
   *   demands, up to the canvas-width safety. */
  width: max-content;
  max-width: calc(100% - var(--s-24) * 2 - var(--s-6));
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  /* Padding matches predictions-overlay's outer envelope (10 px ≈
   * --s-2/--s-3 mix). Internal rows handle their own inner padding. */
  padding: var(--s-3);
  background: var(--elev);
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  box-shadow: 0 2px 8px var(--shadow);
}
[data-theme="light"] .canvas-top-card {
  --eq-color: var(--p-violet-dark);
}

/* Inner row 1 · the equation. Inherits card envelope; only sets its
 * own typography + alignment. No background, no border, no shadow. */
.canvas-top-card .panel-head-eq {
  font-family: var(--font-mono);
  text-align: center;
  /* Equation is read-only; let pointer events fall through to the
   * canvas wheel underneath. Text selection still works. */
  pointer-events: none;
}

/* Inner row 2 · the narrator. Compact horizontal flex (dot + text)
 * inside the shared card. Removes the absolute positioning the
 * standalone .narrator used to carry — now an inline-flow child. */
.canvas-top-card .narrator {
  position: static;
  top: auto; right: auto; left: auto; bottom: auto; transform: none;
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  padding: 0;
  /* Narrator is the secondary line; sits on the card's --elev
   * surface with no own envelope. */
  background: transparent;
  border: none;
  box-shadow: none;
  border-radius: 0;
  font-family: var(--font-body);
  font-size: 12px;
  line-height: 1.4;
  color: var(--fg);
  /* WIDTH-LOCK IDIOM · `width: 0; min-width: 100%`
   *
   * This is the lynchpin of the "lock card width to equation" rule
   * (see .canvas-top-card width comment above for the full
   * derivation). The narrator MUST NOT pressure the parent's
   * max-content sizing — otherwise long quip text would push the
   * card wider than the equation, breaking the lock.
   *
   *   width: 0          The parent measures children's
   *                     max-content widths to compute its own
   *                     `width: max-content`. Setting an explicit
   *                     `width: 0` makes the narrator contribute
   *                     ZERO to that calculation — the equation
   *                     row becomes the SOLE determinant of the
   *                     card's width.
   *
   *   min-width: 100%   At RENDER time, the narrator stretches to
   *                     match the parent's resolved (locked) width.
   *                     Quip text wraps inside this width; height
   *                     grows for longer quips. */
  width: 0;
  min-width: 100%;
  /* Re-anchor pop animation to the new card-top centroid. */
  transform-origin: top center;
  transition: opacity var(--duration-5) ease;
}
/* Narrator text safety net · break long unbreakable substrings mid-
 * word so they wrap inside the locked width instead of overflowing
 * the card's right edge. The base .narrator-text rule already has
 * `word-wrap: break-word`; `overflow-wrap: anywhere` is the modern
 * spelling and is more aggressive — it allows a break at ANY
 * character, not just at hyphens / soft-break opportunities. Only
 * applied inside .canvas-top-card so the standalone narrator
 * styling (if anywhere) is unaffected. */
.canvas-top-card .narrator .narrator-text {
  overflow-wrap: anywhere;
}

/* Mobile · narrow viewports drop the card from absolute-positioned
 * top-right to in-flow ABOVE the canvas so it doesn't collide with
 * the wheel on small screens. Predictions overlay also drops to
 * in-flow (order: 99) so the layout reads as a single column on
 * mobile: card → wheel → predictions. */
@media (max-width: 640px) {
  .canvas-top-card {
    position: static;
    margin: var(--s-2);
    max-width: none;
    padding: var(--s-2);
  }
}

/* (Standalone .narrator block removed · the narrator is now an inline-
 * flow child of .canvas-top-card · see the COMBINED CANVAS-TOP CARD
 * block above for the new positioning + envelope. The .narrator-dot,
 * .narrator-text, .narrator-pop animation, and idle-fade rules below
 * still apply since they style the inner spans regardless of where
 * the .narrator container sits.) */
/* Idle fade — when no event for 6+ s, the whole card dims so the
   user reads "engine idle, not broken." Removed on next quip. */
.narrator.narrator-idle {
  opacity: 0.55;
}
/* Color-coded dot — small circle on the card's left edge that
   glows in the event's canonical hue (var(--narrator-hue) is set
   inline by JS). Acts as the "speaker indicator" — at-a-glance
   answer to "what kind of event just fired?" without reading text. */
.narrator-dot {
  flex-shrink: 0;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--narrator-hue);
  box-shadow: 0 0 7px var(--narrator-hue);
  margin-top: 4px;
  transition: background var(--duration-4) ease, box-shadow var(--duration-4) ease;
}
.narrator-text {
  flex: 1;
  min-width: 0;
  color: var(--fg);
  white-space: normal;
  word-wrap: break-word;
}
/* Pop-in animation · friendly entrance per new quip.
   The card briefly scales from 0.92 to 1.04 and back to 1.0, with
   a touch of upward translation. The cubic-bezier is the canonical
   "spring overshoot" curve (Apple's UIKit easeOutBack equivalent) —
   gives a soft bounce that reads as "alive" rather than mechanical
   slide-in. JS removes + re-adds .narrator-pop with a forced reflow
   between, so a new quip fires the animation from frame zero. */
@keyframes narrator-pop {
  0%   { opacity: 0; transform: scale(0.92) translateY(-6px); }
  60%  { opacity: 1; transform: scale(1.03) translateY(0); }
  85%  {             transform: scale(0.99) translateY(0); }
  100% { opacity: 1; transform: scale(1.00) translateY(0); }
}
.narrator.narrator-pop {
  animation: narrator-pop 460ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* (Standalone .narrator mobile override removed · mobile positioning
 * is now handled by .canvas-top-card's @media (max-width: 640px) rule
 * which flips the whole card from absolute → static in-flow above the
 * wheel · see the COMBINED CANVAS-TOP CARD block above.) */

.panel-head-eq .eq-text {
  color: var(--eq-color);
  font-weight: 600;
  font-size: 12px;
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ─── Equation-↔-geometry binding · Rule 02 ────────────────────────────
 * Each fragment of the master EQ is highlightable; on bracket_proof,
 * signature_link, orbit_start, orbit_close the matching subspan flashes
 * in the framework hue keyed to that operation. The equation IS the
 * legend — viewer reads what's being computed off the equation itself. */
.eq-q, .eq-coset, .eq-vphase, .eq-result, .eq-quant {
  transition: color var(--duration-3) ease-out, text-shadow var(--duration-3) ease-out;
}
.eq-q-a {
  display: inline-block;
  min-width: 1.2ch; text-align: center;
  font-weight: 700;
  transition: color var(--duration-3) ease-out;
}

/* Active state — pulse a brief halo. Auto-cleared by the JS that adds it
 * after a single ~600ms cycle, so we never accumulate state. */
@keyframes eq-pulse-violet {
  0%   { color: var(--fg-dim);  text-shadow: none; }
  18%  { color: var(--eq-color); text-shadow: 0 0 8px var(--eq-color); }
  100% { color: var(--eq-color); text-shadow: none; }
}
@keyframes eq-pulse-sky {
  0%   { color: var(--eq-color); text-shadow: none; }
  18%  { color: var(--a-sky-text);    text-shadow: 0 0 8px var(--a-sky); }
  100% { color: var(--eq-color); text-shadow: none; }
}
@keyframes eq-pulse-trunk {
  0%   { color: var(--eq-color); text-shadow: none; }
  18%  { color: var(--a-trunk-text);  text-shadow: 0 0 8px var(--a-trunk); }
  100% { color: var(--eq-color); text-shadow: none; }
}
@keyframes eq-pulse-teal {
  0%   { color: var(--eq-color); text-shadow: none; }
  18%  { color: var(--a-teal-text);   text-shadow: 0 0 10px var(--a-teal); }
  100% { color: var(--a-teal-text);   text-shadow: none; }
}
.eq-q.flash      { animation: eq-pulse-violet 600ms ease-out; }
.eq-coset.flash  { animation: eq-pulse-sky    600ms ease-out; }
.eq-vphase.flash { animation: eq-pulse-trunk  600ms ease-out; }
.eq-result.flash { animation: eq-pulse-teal   1200ms ease-out; }
.eq-quant.flash  { animation: eq-pulse-violet 600ms ease-out; }
/* Bracket residual badge — relocated from the deleted master-banner into
 * the controls footer. Same visual contract: dim by default, teal on ok,
 * rose on fail. Single rule covers both old & new positions. */
.eq-bracket {
  font-size: 10.5px;
  padding: 2px 9px;
  border-radius: var(--r-2);
  background: var(--elev);
  color: var(--fg-dim);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  white-space: nowrap;
}
.eq-bracket.ok   { color: var(--a-teal-text); border-color: var(--a-teal-text); }
.eq-bracket.fail { color: var(--a-rose-text); border-color: var(--a-rose-text); }

/* GLOBAL-form residual badge · Ĥ Ψ = 0 ─────────────────────────────────
 * Sibling to .eq-bracket. Where eq-bracket reports the LOCAL per-A bracket
 * residual on every bracket_proof, hpsi-bracket reports the GLOBAL state-
 * vector residual once per orbit_close. Both are "= 0" when the constraint
 * holds. The .flash class plays a one-shot teal pulse — fires once per
 * orbit closure, never on a clock. */
.hpsi-bracket {
  font-size: 10.5px;
  padding: 2px 9px;
  border-radius: var(--r-2);
  background: var(--elev);
  color: var(--fg-dim);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  white-space: nowrap;
  letter-spacing: 0.02em;
}
.hpsi-bracket.ok   { color: var(--a-teal-text); border-color: var(--a-teal-text); }
.hpsi-bracket.fail { color: var(--a-rose-text); border-color: var(--a-rose-text); }
.hpsi-bracket.flash { animation: hpsi-flash 0.9s ease-out 1; }
@keyframes hpsi-flash {
  0%   { background: var(--a-teal); color: var(--bg); border-color: var(--a-teal-text); transform: scale(1); }
  20%  { transform: scale(1.08); box-shadow: 0 0 12px var(--a-teal); }
  100% { background: var(--elev);  color: var(--a-teal-text); border-color: var(--a-teal-text); transform: scale(1); box-shadow: none; }
}

/* PreIS membership certificate badge · 𝒜₀ = ker 𝒞 ─────────────────────
 * Third face of the constraint. Local says "this bracket vanished",
 * Global says "the integral vanished", PreIS says "the STATE Ψ₀ sits in
 * the simultaneous kernel of all 6 Ĉ_A". Gold flash on each orbit_close
 * (paired with verified=true certificate), distinct from teal so all
 * three badges read at a glance. */
.preis-bracket {
  font-size: 10.5px;
  padding: 2px 9px;
  border-radius: var(--r-2);
  background: var(--elev);
  color: var(--fg-dim);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  white-space: nowrap;
  letter-spacing: 0.02em;
}
.preis-bracket.ok   { color: var(--a-trunk-text); border-color: var(--a-trunk-text); }
.preis-bracket.fail { color: var(--a-rose-text);  border-color: var(--a-rose-text);  }
.preis-bracket.flash { animation: preis-flash 1.0s ease-out 1; }
@keyframes preis-flash {
  0%   { background: var(--a-trunk); color: var(--bg); border-color: var(--a-trunk-text); transform: scale(1); }
  18%  { transform: scale(1.10); box-shadow: 0 0 14px var(--a-trunk); }
  100% { background: var(--elev); color: var(--a-trunk-text); border-color: var(--a-trunk-text); transform: scale(1); box-shadow: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
 * QUANTUM CHAIN PANEL · 7 steps of the master quantum-algebraic chain
 * ═══════════════════════════════════════════════════════════════════════
 * Renders 𝒫₀ → 𝒜₀ → Ψ₀ → ρ₀ → Ô₀ → 𝒢₀ → 𝒟₀ as a vertical column of
 * step rows. Each row has [symbol] [name] [formula]. The currently
 * active step (the one being measured by the most-recent token_value)
 * lights up; downstream steps populate as the Born-rule chain executes.
 * Mobile: full-width card. Desktop: lives in the right column between
 * the live derivation feed and the legend. */
.panel-chain { min-height: 240px; }
.chain-body {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 8px 12px;
  display: flex; flex-direction: column; gap: 4px;
  font-family: var(--font-mono);
}
.chain-step {
  display: grid;
  grid-template-columns: 56px 100px 1fr;
  align-items: baseline;
  gap: 10px;
  padding: 6px 8px;
  border-left: 2px solid var(--border);
  border-radius: var(--r-2);
  background: var(--bg);
  transition: background var(--duration-3) ease, border-left-color var(--duration-3) ease;
  font-size: 10.5px;
}
.chain-symbol {
  color: var(--a-violet-text);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.02em;
  text-align: center;
}
.chain-name {
  color: var(--fg-dim);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-weight: 600;
}
.chain-formula {
  color: var(--fg);
  font-size: 10.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Per-step accent — each Greek letter gets a hue from the palette so the
 * chain reads as a left-to-right colour gradient (possibility →
 * distinction). Same palette as the wheel categories for cross-surface
 * coherence. */
.chain-step[data-step="possibility"]   { border-left-color: var(--a-azure-text); }
.chain-step[data-step="possibility"]   .chain-symbol { color: var(--a-azure-text); }
.chain-step[data-step="admissibility"] { border-left-color: var(--a-trunk-text); }
.chain-step[data-step="admissibility"] .chain-symbol { color: var(--a-trunk-text); }
.chain-step[data-step="primordial"]    { border-left-color: var(--a-violet-text); }
.chain-step[data-step="primordial"]    .chain-symbol { color: var(--a-violet-text); }
.chain-step[data-step="density"]       { border-left-color: var(--a-amber-text); }
.chain-step[data-step="density"]       .chain-symbol { color: var(--a-amber-text); }
.chain-step[data-step="observable"]    { border-left-color: var(--a-teal-text); }
.chain-step[data-step="observable"]    .chain-symbol { color: var(--a-teal-text); }
.chain-step[data-step="givenness"]     { border-left-color: var(--a-mint-text); }
.chain-step[data-step="givenness"]     .chain-symbol { color: var(--a-mint-text); }
.chain-step[data-step="distinction"]   { border-left-color: var(--a-rose-text); }
.chain-step[data-step="distinction"]   .chain-symbol { color: var(--a-rose-text); }
/* 8th step · the recursive-improvement ratchet · Bayesian posterior +
 * sparkline. Different visual weight: gold border + sparkline embedded
 * in the formula column. */
.chain-step[data-step="recursive-limit"] {
  border-left-color: var(--a-trunk-text);
  border-top: 1px dashed var(--border);
  margin-top: 4px;
}
.chain-step[data-step="recursive-limit"] .chain-symbol { color: var(--a-trunk-text); }
.chain-step[data-step="recursive-limit"] .chain-formula {
  display: flex; align-items: center; gap: 8px;
  white-space: nowrap;
}
.chain-fid-text {
  flex-shrink: 0;
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--a-trunk-text); font-weight: 600;
}
.chain-sparkline {
  flex: 1; min-width: 60px; max-width: 120px;
  height: 16px;
  color: var(--a-trunk-text);
  opacity: 0.85;
}

/* Topbar fidelity chip · live Bayesian posterior on every orbit_close */
.fidelity-chip {
  font-family: var(--font-mono);
  font-size: 10.5px; font-weight: 600;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  background: var(--elev);
  color: var(--a-trunk-text);
  border: 1px solid var(--a-trunk);
  white-space: nowrap;
  letter-spacing: 0.02em;
}
.fidelity-chip[hidden] { display: none; }
.fidelity-chip.flash { animation: fidelity-pulse 0.7s ease-out 1; }
@keyframes fidelity-pulse {
  0%   { background: var(--a-trunk); color: var(--bg); transform: scale(1.1); }
  100% { background: var(--elev); color: var(--a-trunk-text); transform: scale(1); }
}
@media (max-width: 639px) { .fidelity-chip { display: none; } }

/* Active state — pulses when this step is currently being computed
 * (e.g. observable lights up on token_value, givenness lights up next as
 * the Born-rule probabilities resolve, distinction lights up last). */
.chain-step.active {
  background: var(--elev);
  box-shadow: inset 4px 0 0 currentColor;
}
.chain-step.flash { animation: chain-step-flash 0.6s ease-out 1; }
@keyframes chain-step-flash {
  0%   { background: var(--elev); transform: translateX(0); }
  30%  { background: var(--panel); transform: translateX(2px); }
  100% { background: var(--bg); transform: translateX(0); }
}

/* Orbit progress bar — also relocated to the controls footer. */
.orbit-bar {
  display: inline-block;
  width: 120px; height: 4px;
  background: var(--bg);
  border-radius: var(--r-1); overflow: hidden;
  border: 1px solid var(--border);
}
.orbit-fill {
  display: block; height: 100%; width: 0%;
  /* Solid violet fill — was a violet→amber gradient. Per the user's
   * "ONLY canvas may use gradient" rule, progress fills go solid.
   * Violet chosen to match the framework's primary accent (verifier
   * pulse, master section border). */
  background: var(--a-violet);
  transition: width var(--duration-5) ease-out;
}


/* ────────────────────────── Main layout ──────────────────────────
 * MOBILE (default): single column stack, page scrolls naturally.
 *   1. wheel panel (priority — mandala visible at viewport top)
 *   2. live derivation feed
 *   3. legend
 *
 * TABLET ≥640px: same single-column stack but with wheel-corner
 * overlays restored to absolute positioning.
 *
 * DESKTOP ≥1100px: original 1.7fr / 1fr grid, fixed viewport. */
/* ─────────────────────────── Responsive layout ──────────────────────────
 * Three breakpoints:
 *   < 760px   small phone — single column, fluid heights, every panel
 *             has a min-height so its internal stream can scroll, never
 *             squeezed below readable.
 *   760-1099  tablet / large phone — same column flow but slightly more
 *             generous panel heights and tighter padding.
 *   ≥ 1100px  desktop — 2-column CSS grid with the wheel pinned left
 *             and feed / chain / legend stacked right. Heights driven
 *             by the grid (NOT a fixed 50vh) so panels can never overlap.
 *
 * The Framework Sympy Confirmation panel splits its inner area 50/50
 * via `.feed-row { flex: 1 1 50%; min-height: 0 }` so the two streams
 * always share equal thickness no matter the viewport. */
.layout {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px;
  padding-left:  max(10px, env(safe-area-inset-left));
  padding-right: max(10px, env(safe-area-inset-right));
  background: var(--bg);
  min-height: 0;
  /* Predictions-overlay is now a top-level sibling (moved out of
   * panel-mandala). On desktop/tablet it remains position:absolute,
   * so .layout becomes its containing block. */
  position: relative;
}
/* panel-mandala MUST be position:relative so the floating perf overlay
 * (top-right) and the predictions-overlay (top-left, on desktop only)
 * can position absolutely against it. The old canvas-mini-player has
 * been moved into the wheel footer (.controls) and is no longer
 * absolutely positioned. */
.panel-mandala  { display: flex; flex-direction: column; min-height: clamp(360px, 70vh, 720px); position: relative; }
/* ── PANEL-MANDALA INNER LAYOUT · fill vertical space cleanly ──────
 * Without these rules, .panel-mandala-body and .canvas-area defaulted
 * to display:block — they DIDN'T expand inside the panel's flex
 * column, so the panel's min-height (360 px) created empty space
 * BELOW the .controls strip. With flex:1 + min-height:0 propagated
 * down through both wrappers, the canvas fills exactly the available
 * vertical space and the controls strip sits flush to the panel's
 * bottom edge · no phantom gap. */
.panel-mandala .panel-mandala-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.panel-mandala .canvas-area {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  /* CENTER the wheel canvas vertically inside the canvas-area.
   * #three-canvas has `aspect-ratio: 1 / 1` on tablet/mobile, which
   * forces a SQUARE shape based on the area's width. When the area
   * is taller than wide (typical mobile/tablet), the canvas is
   * shorter than the area's height, leaving vertical empty space.
   * Without justify-content, that space falls below the canvas
   * (default flex-start). With justify-content:center, the empty
   * space splits evenly above and below — the wheel reads as
   * vertically centered inside the remaining canvas region (after
   * the controls footer claims its height below the body). On
   * desktop (>=1100 px) the canvas has `aspect-ratio: auto` so it
   * fills the area; justify-content:center is a no-op there. */
  justify-content: center;
  min-height: 0;
  position: relative;
}
/* Mobile / tablet: each panel needs an EXPLICIT height (not min-height) so
 * the inner streams scroll internally instead of pushing the page taller
 * with every new event.
 *
 * PERF · CSS contain on panel-console isolates the right column's
 * layout/paint/style work from the wheel column. A feed-block insert
 * or a sym-line scroll inside panel-console no longer invalidates the
 * containing block of panel-mandala (which holds the WebGL canvas), so
 * the rAF tick can render without paying for unrelated layout work.
 * `layout paint style` (not `strict`) keeps the panel's intrinsic size
 * negotiable with the page grid — only the descendants are isolated. */
.panel-console  {
  display: flex; flex-direction: column;
  height: auto; min-height: 480px;
  contain: layout paint style;
}

@media (min-width: 760px) and (max-width: 1099px) {
  /* Tablet: slightly more headroom for the console. */
  .panel-console  { min-height: 560px; }
  .layout         { gap: 12px; padding: 12px; }
}

@media (min-width: 1100px) {
  .layout {
    display: grid;
    grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
    /* Right column is now a SINGLE row (panel-console fills it).
     * panel-streams is no longer in the grid — it floats absolutely
     * inside panel-mandala (top-right of the wheel canvas) per user
     * spec: "have it as a floating table at the top right of the wheel
     * canvas. extremely minimal, small tight, base info, stream view
     * mostly. mobile view will be the same."
     *
     *   panel-mandala  · column 1, full height
     *   panel-console  · column 2, full height
     *   panel-streams  · floating absolute inside panel-mandala
     *                    (.panel-streams-top, see below) */
    gap: 12px;
    padding: 12px;
    height: calc(100vh - 48px - 30px);
  }
  .panel-mandala  { grid-column: 1; height: auto; min-height: 0; }
  .panel-console  { grid-column: 2; height: auto; min-height: 0; overflow-y: auto !important; }
}

/* ════════════════════════════════════════════════════════════════════════
 * STREAMS WIDGET · sleek strip at top of homoiconic console
 * ════════════════════════════════════════════════════════════════════════
 * PERF NOTES (v4 · final):
 *   v1 floated over the WebGL canvas (blur over animating layer = GPU
 *   killer · laggy). v2 dropped the blur but kept absolute-positioned
 *   floating (still composited over the canvas every frame · laggy).
 *   v3 made it a static flex sibling inside panel-mandala (still laggy
 *   due to the layout-measurement coupling with the WebGL canvas).
 *
 *   v4: the widget lives in panel-CONSOLE — a completely different DOM
 *   subtree from panel-mandala. The WebGL canvas is in column 1 of the
 *   page grid; this widget sits at the top of column 2 above the
 *   homoiconic console sections. They share no parent below the
 *   page-level grid container, so updates here have ZERO effect on
 *   canvas rendering. Cheapest possible separation.
 *
 * Layout: thin one-line header (28 px) + scrollable stream below.
 * Compact default (~140 px tall · 4-ish stream rows visible). Click
 * the ⤢ button to expand to ~360 px (16-ish rows). */
.panel-console > .panel-streams-top {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--panel) 92%, var(--bg));
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  overflow: hidden;
  margin: 0 0 10px 0;
  /* Default tight: header (28px) + ~4 stream rows. */
  max-height: 140px;
  position: relative;       /* anchor for streams-active-band + paused-overlay */
  transition: max-height var(--duration-3) ease;
  /* CRITICAL PERF FIX:
   * The widget is now nested inside panel-console (which scrolls and
   * holds 17 console sections). Without containment, every stream
   * entry insertion forces a layout flush that traverses the ENTIRE
   * panel-console subtree (because _streamScrollToBottom reads
   * scrollHeight, which triggers a synchronous layout pass).
   *
   * `contain: layout style paint` tells the browser that layout, style
   * and paint inside this widget can NEVER affect anything outside it.
   * The streams widget already has bounded size (max-height) and
   * overflow:hidden, so the containment is already true semantically;
   * we just have to tell the browser so it can short-circuit. */
  contain: layout style paint;
}
.panel-console > .panel-streams-top.expanded {
  /* Expanded: ~16 stream rows. */
  max-height: 360px;
}
@media (max-width: 1099px) {
  .panel-console > .panel-streams-top              { max-height: 120px; }
  .panel-console > .panel-streams-top.expanded     { max-height: 280px; }
}

/* Compact merged header strip · 28 px tall. Replaces the old separate
 * SOURCE bar + STREAMING title rows. Single line, ellipsis-truncated. */
.panel-streams-top .hc-streams-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  background: color-mix(in srgb, var(--bg) 30%, transparent);
  font-family: var(--font-mono);
  font-size: 10px;
  flex-shrink: 0;
  user-select: none;
}
.panel-streams-top .hc-streams-icon {
  color: var(--a-violet-text);
  font-size: 11px;
  flex-shrink: 0;
}
.panel-streams-top .hc-streams-fname {
  color: var(--fg);
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 130px;
  flex-shrink: 1;
  min-width: 0;
}
.panel-streams-top .hc-streams-meta {
  color: var(--fg-faint);
  flex-shrink: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.panel-streams-top .hc-streams-status {
  color: var(--fg-faint);
  font-size: 9px;
  flex-shrink: 0;
  font-style: italic;
}
.panel-streams-top .hc-streams-pulse {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg-faint);
  flex-shrink: 0;
}
.panel-streams-top .hc-streams-pause {
  margin-left: auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: var(--r-2);
  border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  color: var(--fg);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.panel-streams-top .hc-streams-pause:hover {
  background: color-mix(in srgb, var(--a-violet) 22%, var(--bg));
  border-color: color-mix(in srgb, var(--a-violet-text) 60%, var(--border));
}
.panel-streams-top .hc-streams-pause[aria-pressed="true"] {
  background: color-mix(in srgb, var(--a-amber) 30%, var(--bg));
  border-color: color-mix(in srgb, var(--a-amber-text) 70%, var(--border));
  color: var(--a-amber-text);
}

/* Expand toggle · same chrome as pause button. Glyph rotates 180° when
 * expanded so the user reads the action ("collapse" when expanded,
 * "expand" when compact) instead of memorizing two glyphs. */
.panel-streams-top .hc-streams-expand {
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: var(--r-2);
  border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  color: var(--fg);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--duration-3) ease, background var(--duration-2) ease;
}
.panel-streams-top .hc-streams-expand:hover {
  background: color-mix(in srgb, var(--a-violet) 22%, var(--bg));
  border-color: color-mix(in srgb, var(--a-violet-text) 60%, var(--border));
}
.panel-streams-top.expanded .hc-streams-expand {
  transform: rotate(180deg);
  background: color-mix(in srgb, var(--a-violet) 18%, var(--bg));
  border-color: color-mix(in srgb, var(--a-violet-text) 50%, var(--border));
  color: var(--a-violet-text);
}

/* Hidden MD-swap actions container · JS still binds the buttons but
 * they don't render in the minimalist widget. */
.panel-streams-top .hc-streams-actions-hidden { display: none; }

/* Stream container fills remaining space. Slightly tighter than the
 * desktop variant since the floating widget is narrow. */
.panel-streams-top .stream-unified {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  font-size: 9.5px;
}
.panel-streams-top .streams-active-band {
  /* Re-anchor the active-color stripe to the floating widget's left edge. */
  position: absolute;
  left: 0;
  top: 35px;          /* sits below the .hc-streams-head */
  bottom: 0;
  width: 4px;
}

/* Wide desktop — slightly more generous gaps + a hint more height bias to
 * the wheel column so the constellation reads at the larger scale. */
@media (min-width: 1600px) {
  .layout {
    grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
    gap: 14px;
    padding: 14px;
  }
}


/* ─────────────────────────── Panel base ─────────────────────────── */
.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  overflow: hidden;
  position: relative;
}
/* (.panel-head + .panel-head h2 + .panel-head .hint rules removed —
 * the wheel's only consumer was .panel-head-eq, which is now a
 * floating canvas overlay (see top of file). No other panels use
 * .panel-head.) */


/* ─────────────────────────── 3D canvas — locked-gray gradient stage ─────
 * Mobile: aspect-ratio square keeps the wheel readable in vertical scroll.
 * Tablet/desktop: flex-grow inside the panel-mandala column. */
#three-canvas {
  flex: 1; min-height: 0;
  position: relative;
  background: linear-gradient(180deg, var(--elev) 0%, var(--panel) 55%, var(--bg) 100%);
  cursor: grab;
  aspect-ratio: 1 / 1;
  width: 100%;
  /* MOBILE-FRIENDLY TOUCH HANDLING ─────────────────────────────────
   * Was: `touch-action: none` — blocked ALL native gestures including
   * single-finger page scroll. On mobile (vertical layout: wheel →
   * master-equation → predictions → footer) this trapped the user
   * inside the canvas — a swipe over the wheel did nothing instead of
   * scrolling past it.
   *
   * Now: `pan-y pinch-zoom`
   *   • pan-y       — single-finger swipe scrolls the page vertically
   *                   (the dominant mobile interaction)
   *   • pinch-zoom  — two-finger pinch is delivered to our JS handler
   *                   (we override the browser default with our own
   *                   wheel-zoom; setting the keyword tells the browser
   *                   "don't fight us for this gesture")
   *
   * Mouse drag-to-rotate keeps working (mouse events are unaffected
   * by touch-action). Wheel zoom keeps working (wheel events too).
   * On mobile, single-finger over the canvas now scrolls; two fingers
   * pinch-zooms the wheel. This is the same model Google Maps and
   * every Three.js viewer demo on mobile use. */
  touch-action: pan-y pinch-zoom;
}
@media (min-width: 1100px) {
  /* DESKTOP · the canvas fills the left column AND the user has a
   * mouse — single-finger gestures don't apply. Lock down touch
   * behavior so accidental trackpad swipes don't scroll the page out
   * from under the wheel. */
  #three-canvas { aspect-ratio: auto; touch-action: none; }
}
#three-canvas:active { cursor: grabbing; }

/* ─── Equation-trace ticker · DOM overlay on the canvas ────────────────
 * Bottom-center of the wheel surface; spells out the verifier's literal
 * chain line as the particle walks each stop. Single-line, monospace,
 * fades out via .active class transitions. */
.eq-ticker {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  padding: 4px 14px;
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--fg-dim);
  background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--r-3);
  box-shadow: 0 2px 10px var(--shadow);
  max-width: 90%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: 0; pointer-events: none;
  transition: opacity var(--duration-4) ease-out;
  z-index: 5;
}
.eq-ticker.active { opacity: 0.95; }
.eq-ticker-prompt { color: var(--a-violet-text); font-weight: 700; }
.eq-ticker-text { color: var(--a-teal-text); }
.eq-ticker-text:empty::before { content: "—"; color: var(--fg-faint); }

/* Epoch-closure label · fires when 12 verified orbits accumulate (full
 * structural-claim ledger). Distinct gold tint + bolder weight + halo
 * shadow so the user sees "this is the BIG event", not just a token. */
.eq-ticker.epoch {
  border-color: var(--a-trunk-text);
  background: var(--panel);
  box-shadow: 0 0 18px var(--a-trunk), 0 2px 10px var(--shadow);
  animation: epoch-pulse 1.4s ease-out 1;
}
.eq-ticker.epoch .eq-ticker-prompt { color: var(--a-trunk-text); }
.eq-ticker.epoch .eq-ticker-text   { color: var(--a-trunk-text); font-weight: 700; letter-spacing: 0.04em; }
@keyframes epoch-pulse {
  0%   { transform: translateX(-50%) scale(1.0); opacity: 0.0; }
  20%  { transform: translateX(-50%) scale(1.06); opacity: 1.0; }
  100% { transform: translateX(-50%) scale(1.0); opacity: 0.95; }
}

.panel-mandala .controls {
  display: flex; align-items: center; gap: var(--s-2);
  flex-wrap: wrap;                  /* pills wrap to a 2nd row on narrow screens */
  row-gap: var(--s-1);
  /* Slim footer · padding --s-1 (4 px) vertical / --s-3 (12 px)
   * horizontal · was --s-3/--s-4 which read as too thick. The 4 px
   * vertical inset is the smallest §V ladder anchor that still gives
   * pill controls a comfortable hit area while reclaiming ~16 px of
   * vertical space for the wheel canvas above. Horizontal stays at
   * 12 px so the orbit/cells/A pills don't crowd the panel edge. */
  padding: var(--s-1) var(--s-3);
  border-top: 1px solid var(--border);
  background: var(--elev);
  flex-shrink: 0;
}
button {
  font-family: var(--font-body);
  font-size: 12px; font-weight: 600;
  padding: 5px 16px;
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--fg);
  cursor: pointer;
  transition: background var(--duration-2) ease, border-color var(--duration-2) ease, color var(--duration-2) ease;
  letter-spacing: 0.01em;
}
button:hover    { background: var(--elev); border-color: var(--a-violet-text); color: var(--a-violet-text); }
button:disabled { opacity: 0.4; cursor: not-allowed; }
button.primary {
  background: var(--a-violet);
  border-color: var(--a-violet-text);
  color: var(--bg);
}
button.primary:hover { background: var(--a-rose); border-color: var(--a-rose-text); color: var(--bg); }
.spacer { flex: 1; }
/* (.stage-indicator rule removed · element deleted from wheel footer
 * at user request to free space for the floating audio mini-player.
 * Every phrase the dispatcher emitted has a louder home elsewhere.) */


/* ─────────────────── Wheel-corner overlays · responsive ────────────────────
 * MOBILE (<640px): flow as compact cards immediately below the canvas
 * (position: static, full-width). Phones stack vertically; touch-scroll.
 * TABLET ≥640px: restored to absolute corner positioning over the wheel.
 *
 * On mobile, predictions and inputs sit BELOW the wheel — they're informa-
 * tion, not floating overlays. On tablet/desktop they overlap the wheel
 * in their canonical Trunk-Tetrahedron / Octahedron corners. */
.md-widget,
.axiom-inputs,
.predictions-overlay {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  padding: 10px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg);
  box-shadow: 3px 3px 0 var(--shadow);
  /* MOBILE default: in-flow card */
  position: static;
  width: auto;
  margin: 8px 10px 0;
}
/* MOBILE LAYOUT (per user spec): ONLY predictions moves. The wheel
 * stays at top, the Master Equation panel keeps its natural place
 * directly under the wheel, and the predictions-overlay drops to the
 * very bottom of the page.
 *
 *   1. panel-mandala     (wheel)            — natural order, untouched
 *   2. panel-console     (Master Equation)  — natural order, untouched
 *   3. predictions-overlay                  — pushed below both via order
 *
 * .layout is `display: flex; flex-direction: column` on mobile so
 * `order:` works directly. Desktop (>=1100px) uses `display: grid`
 * which ignores `order` for the absolute predictions-overlay anyway. */
@media (max-width: 639px) {
  .predictions-overlay  { order: 99; margin: 14px 10px 0; }
}
@media (min-width: 640px) {
  .md-widget,
  .axiom-inputs,
  .predictions-overlay {
    position: absolute;
    z-index: 5;
    margin: 0;
    padding: 8px 10px;
    font-size: 10.5px;
  }
  /* Predictions: small floating widget at top-left under banner. */
  .predictions-overlay {
    top: 56px;
    left: 12px;
    width: 320px;
    max-height: calc(100% - 110px);
    overflow-y: auto;
  }
}

/* Homoiconic Console is now a regular panel (right column row 1) — no
 * absolute positioning. Its internal style stays the same; .panel-console
 * just provides the panel frame. */

.md-widget { /* tablet+ corner positioning */ }
@media (min-width: 640px) { .md-widget { top: 56px; left: 12px; width: 240px; } }
.md-head {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--a-amber-text);
  margin-bottom: 6px; padding-bottom: 5px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}
.md-row { display: flex; align-items: center; gap: 7px; margin-bottom: 4px; }
.md-status-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--fg-faint);
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.md-status-dot.loaded   { background: var(--a-teal);  border-color: var(--a-teal-text); }
.md-status-dot.empty    { background: var(--a-rose);  border-color: var(--a-rose-text); }
.md-status-dot.partial  { background: var(--a-amber); border-color: var(--a-amber-text); }
.md-fname {
  color: var(--fg); font-weight: 600;
  font-size: 10.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 1;
}
.md-meta-line { color: var(--fg-dim); font-size: 9.5px; margin: 2px 0 6px 15px; }
.md-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.md-btn {
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 7px 14px;            /* touch-friendly: ≥36 px tap height */
  min-height: 36px;
  border-radius: var(--r-3);
  background: var(--a-amber);
  color: var(--bg);
  border: 1px solid var(--a-amber);
  cursor: pointer;
  font-weight: 600;
  transition: background var(--duration-2) ease, transform 0.1s ease;
}
@media (min-width: 1100px) {
  .md-btn { padding: 4px 11px; min-height: 0; font-size: 10.5px; }
}
.md-btn:hover  { background: var(--a-trunk); border-color: var(--a-trunk-text); transform: translateY(-1px); }
.md-btn-ghost {
  background: var(--panel);
  color: var(--fg);
  border-color: var(--border);
}
.md-btn-ghost:hover { background: var(--elev); transform: translateY(-1px); }

/* axiom inputs — flow card on mobile, top-right corner overlay on tablet+ */
@media (min-width: 640px) {
  .axiom-inputs { top: 56px; right: 12px; width: 232px; max-height: 38%; overflow-y: auto; }
}
.axiom-head {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--a-trunk-text);
  margin-bottom: 6px; padding-bottom: 5px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}
.axiom-row {
  display: grid;
  grid-template-columns: minmax(60px, 1fr) auto auto auto;
  align-items: baseline; gap: 6px;
  padding: 8px 6px;             /* touch-friendly on phones */
  border-radius: var(--r-3);
  cursor: pointer;
  transition: background var(--duration-3) ease;
  min-height: 32px;
}
@media (min-width: 1100px) {
  .axiom-row { padding: 3px 5px; min-height: 0; }
}
.axiom-row:hover { background: var(--panel); }
.axiom-row .ax-label { color: var(--fg);    font-weight: 500; }
.axiom-row .ax-eq    { color: var(--fg-faint); }
.axiom-row .ax-value { color: var(--a-trunk-text); font-weight: 700; }
.axiom-row .ax-md    { color: var(--fg-faint); font-size: 9px; }
.axiom-placeholder   { color: var(--fg-faint); font-style: italic; padding: 4px 0; }

/* predictions — bare grid of tiny squares with a "PREDICTIONS:" label
 * floating directly on the canvas. ALL panel chrome stripped: no bg,
 * no border, no box-shadow, NO border-radius (the base rule sets a
 * 10px radius that would otherwise clip the inner grid into a rounded
 * shape via overflow:auto). Pure floating widget. */
@media (min-width: 640px) {
  .predictions-overlay {
    /* Position · top-LEFT of the wheel canvas. Equal-inset padding
     * from the canvas top and left edges per user request — a single
     * medium value (--s-8 = 32 px) on both axes so the widget sits
     * with balanced negative space rather than hugging the corner.
     *
     * Reference frame · panel-mandala has position:relative; the
     * canvas extends to the panel's top edge. The equation no longer
     * lives in a separate floating chip (it's been absorbed into
     * .canvas-top-card on the right), so predictions doesn't need
     * to clear any sibling overlay above it on this side.
     *
     * Mirror · .canvas-top-card on the right uses the same
     * top: --s-8, right: --s-8 so both top-corner widgets share the
     * same canvas-edge gutter and read as a balanced pair. */
    top: var(--s-8);
    left: var(--s-8);
    bottom: auto; right: auto;
    /* Was 200 px · the user wants tiles MUCH smaller. 130 px gives a
     * 2-column grid of ~62 px tiles · still legible for every value
     * the framework produces (`29/30`, `1/450`, `4*pi**2/3`, etc.)
     * but visually unobtrusive in the corner. */
    width: 130px;
    max-height: calc(100% - 110px);
    padding: 0;
    overflow: visible;
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
    font-size: 8px;
  }
  .predictions-overlay .pred-head {
    /* Header MORE prominent · jumped from 9 → 11 px so the label
     * reads as the panel's anchor while the tiles below stay small. */
    font-size: 11px;
    letter-spacing: 0.10em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--a-azure-text);
    margin-bottom: 6px;
    padding: 0 0 0 2px;
    border-bottom: none;
    background: transparent;
  }
}
.pred-head {
  font-size: 8.5px; text-transform: uppercase; letter-spacing: 0.10em;
  color: var(--a-azure-text);
  margin-bottom: 4px; padding-bottom: 3px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}
/* Predictions live as a perfect-square grid (2 columns, 4 rows for 7
 * items — bottom-right cell is simply absent, so the 7th tile sits
 * alone in row 4 column 1). NO container background or border;
 * each tile carries its own minimum borders to form the 1px gridlines. */
.predictions-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  background: transparent;
  border: none;
}
.predictions-placeholder {
  grid-column: 1 / -1;
  color: var(--fg-faint); font-size: 9px; text-align: center;
  padding: 6px 0; font-style: italic;
}
/* Predictions · PERFECT-SQUARE grid tiles.
 *   ┌──────────┐ ┌──────────┐
 *   │ ID    ✓  │ │ ID    ✓  │
 *   │   n_s    │ │   r      │     ← label (italic)
 *   │  29/30   │ │  1/450   │     ← value (bold, large)
 *   └──────────┘ └──────────┘
 * 2 columns × 4 rows for 7 items. The 7th tile (orphan) is
 * centered in row 4 via grid-column: 1 / -1 + justify-self,
 * so the panel reads as visually balanced rather than left-
 * justified with an empty bottom-right cell.
 * Hover any tile for rich tooltip; click for full popup. */
.prediction-item {
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 3px 4px 4px;
  background: var(--panel);
  border-right:  1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  cursor: help;
  transition: background var(--duration-3) ease;
  font-size: 7px;
  line-height: 1.15;
  overflow: hidden;
  text-align: center;
  outline: none;
}
/* First row tiles get a top border so the grid has its outer top edge */
.prediction-item:nth-child(-n+2) {
  border-top: 1px solid var(--border);
}
/* Left-column tiles get a left border so the grid has its outer left edge */
.prediction-item:nth-child(odd) {
  border-left: 1px solid var(--border);
}
/* Orphan-tile alignment · when the LAST tile is odd-positioned (1st of
 * a would-be pair, with no sibling to its right), it sits NATURALLY in
 * column 1 of the final row with column 2 simply empty — exactly like
 * a typical ragged-bottom 2-column grid.
 *
 * The previous rule (grid-column: 1/-1; justify-self: center; width:
 * 50%) span-then-center rendered the orphan at x∈[32.5, 97.5] of a
 * 130 px grid: starting one-quarter in, ending three-quarters in,
 * straddling the column gridline at x=65. That made the tile look
 * BUG-ALIGNED — it was visually offset to the right of column 1
 * without belonging to either column. Default grid-flow placement
 * keeps the tile flush with its siblings' column 1 edge. */
.prediction-item:hover {
  background: var(--elev);
}
/* ── POP-IN · first appearance · scale + fade + glow ──────────────
 * When renderPrediction creates a tile (math just calculated this
 * prediction for the first time, or after an epoch reroll), the
 * .flash class gets toggled. The animation does double duty:
 *   • opacity 0→1 + scale 0.85→1   POP-IN sense
 *   • azure background flash       INDICATES "freshly computed"
 *   • subtle glow                  draws the eye to the new tile
 * The compound animation is more delightful than the old plain
 * background-flash AND it visually distinguishes "just calculated"
 * from "already settled."
 *
 * ITER ORDER · because renderPrediction is called once per prediction
 * as the math finishes each one, the tiles pop in ONE BY ONE in the
 * order the math computes them — same effect the user remembers.
 *
 * EPOCH RE-COMPUTATION · on every orbit_close the verifier emits
 * fresh prediction values; renderPrediction runs again with the same
 * claim_id; the .flash class toggles off then on (one microtask
 * later) so the existing tile flashes-in-place rather than popping.
 * The renderPrediction code already does this remove → add sequence;
 * the animation correctly restarts each time. */
.prediction-item.flash {
  animation: pred-flash 0.7s cubic-bezier(0.34, 1.4, 0.64, 1);
  /* 0.34, 1.4 gives a slight overshoot · feels alive, not mechanical */
}
@keyframes pred-flash {
  0% {
    background: color-mix(in srgb, var(--a-azure) 80%, var(--panel));
    transform: scale(0.85);
    opacity: 0;
    box-shadow: 0 0 12px color-mix(in srgb, var(--a-azure) 60%, transparent);
  }
  35% {
    background: color-mix(in srgb, var(--a-azure) 50%, var(--panel));
    transform: scale(1.04);
    opacity: 1;
    box-shadow: 0 0 6px color-mix(in srgb, var(--a-azure) 40%, transparent);
  }
  100% {
    background: var(--panel);
    transform: scale(1);
    opacity: 1;
    box-shadow: 0 0 0 transparent;
  }
}
/* Reduced-motion users · keep the meaningful color flash, drop the
 * scale/opacity bounce that could trigger vestibular discomfort. */
@media (prefers-reduced-motion: reduce) {
  .prediction-item.flash {
    animation: pred-flash-reduced var(--duration-5) ease-out;
  }
  @keyframes pred-flash-reduced {
    0%   { background: color-mix(in srgb, var(--a-azure) 50%, var(--panel)); }
    100% { background: var(--panel); }
  }
}

.prediction-item .pred-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 2px;
  flex-shrink: 0;
}
.prediction-item .pred-mid {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
}
.prediction-item .pred-bot {
  flex-shrink: 0;
  padding-top: 1px;
  border-top: 1px dashed color-mix(in srgb, var(--fg) 6%, transparent);
}

.prediction-item .pred-id {
  /* claim_id no longer fits in the tile; lives in the tooltip only */
  display: none;
}
.prediction-item .pred-label {
  color: var(--fg-dim);
  font-style: italic;
  font-size: 8.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.prediction-item .pred-value {
  color: var(--a-trunk-text);
  font-weight: 700;
  font-size: 9px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
}
.prediction-item .pred-status {
  font-size: 6.5px;
  font-weight: 700;
  flex-shrink: 0;
  white-space: nowrap;
  margin-left: auto;
}
.prediction-item .pred-status.ok      { color: var(--a-teal-text); }
.prediction-item .pred-status.pending { color: var(--a-amber-text); }
.prediction-item .pred-status.theory  { color: var(--a-violet-text); }
.prediction-item .pred-status.fail    { color: var(--a-rose-text); }
/* ── DESKTOP TILE TEXT · sized for the 62-px tile ──────────────────
 * The desktop tile is 62 px square. Inner text sizes are deliberately
 * compact — the label/value/status fit into three vertical zones and
 * still leave a hair of breathing room. Tabular-nums on the value
 * keeps digit columns aligned across tiles so they scan as a grid.
 *
 * Worst-case value width check (at 8.5 px monospace · ~5.1 px/char):
 *   "4*pi**2/3" (9 chars)  → ~46 px  · fits 54 px content area
 *   "1/450"     (5 chars)  → ~26 px  · plenty of room
 *   "43/11"     (5 chars)  → ~26 px  · plenty of room
 *   "29/30"     (5 chars)  → ~26 px  · plenty of room */
@media (min-width: 640px) {
  .prediction-item               { font-size: 7px; padding: 3px 4px; }
  .prediction-item .pred-label   { font-size: 8.5px; }
  .prediction-item .pred-value   { font-size: 9px; }
  .prediction-item .pred-status  { font-size: 6.5px; }
}


/* ─────────────── Framework Sympy Confirmation · split panel ──────────
 * Two static rows of equal thickness; only inner streams scroll.
 *   .feed-row-top → derivation chain stream (witness blocks)
 *   .feed-row-bot → raw sympy stream (live str(...) of sympy objects)
 * Each row has its own minimal head strip and an internal log frame.
 * Streams use NORMAL flex-column flow + rAF-batched appendChild;
 * scrollTop = scrollHeight after each batched flush keeps the newest
 * line visible at the bottom of the row. (See _flushSymBuffer in
 * app.js for the actual batching mechanism.)                          */
.feed-row {
  flex: 1 1 50%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
}
.feed-row-top { border-top: none; }
.feed-row-head {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 10px 3px;
  background: var(--elev);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--fg-dim);
}
.feed-row-head .frh-tag {
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-weight: 700;
  color: var(--a-violet-text);
}
.feed-row-head .frh-tag-sym { color: var(--a-trunk-text); }
.feed-row-head .frh-sub { color: var(--fg-dim); }
.feed-row-head .frh-sub code {
  background: transparent;
  color: var(--a-teal-text);
  padding: 0;
  font-size: 9.5px;
}

/* Streams use NORMAL flex-column flow + JS-driven scroll-to-bottom.
 * column-reverse triggers a full-column reflow on every insert — at
 * cap=400 children that's the dominant lag source. Normal column +
 * appendChild only reflows from the new node onward. Combined with
 * rAF-batched DocumentFragment appends in JS, the browser does ONE
 * layout pass per animation frame regardless of insert burst size. */
.feed-stream {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 10px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  background: var(--bg);
  display: flex;
  flex-direction: column;
}

/* Raw sympy stream — bottom row. Tail-style log of every sympy_op
 * event. Each line is one literal sympy computation: the op tag, an
 * optional label, the str() of the expression, and (when present) the
 * simplified result. Background is slightly tinted so the row reads as
 * a distinct surface. */
.sympy-stream {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 6px 10px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  /* Solid bg — was bg→panel gradient. Flat surface = easier text
   * following per user directive (canvas-only-gradient rule). */
  background: var(--bg);
  display: flex;
  flex-direction: column;
}
.sym-line {
  display: grid;
  grid-template-columns: 56px auto 1fr;
  column-gap: 8px;
  padding: 2px 4px;
  border-bottom: 1px dashed color-mix(in srgb, var(--fg) 6%, transparent);
  line-height: 1.45;
  color: var(--fg);
}
.sym-line:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.sym-line .sym-op {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  font-size: 9px;
  color: var(--a-violet-text);
  align-self: baseline;
  padding: 1px 4px;
  background: var(--elev);
  border-radius: var(--r-2);
  text-align: center;
}
.sym-line .sym-label {
  color: var(--a-teal-text);
  font-weight: 700;
  align-self: baseline;
  white-space: nowrap;
}
.sym-line .sym-body {
  color: var(--fg-dim);
  word-break: break-all;
}
.sym-line .sym-body .sym-expr {
  color: var(--fg);
}
.sym-line .sym-body .sym-arrow {
  color: var(--a-trunk-text);
  margin: 0 6px;
  font-weight: 700;
}
.sym-line .sym-body .sym-result {
  color: var(--a-trunk-text);
  font-weight: 700;
}
.sym-line .sym-body .sym-note {
  display: block;
  margin-top: 1px;
  color: var(--fg-dim);
  font-size: 9.5px;
  font-style: italic;
}
/* SREPR: secondary line showing sympy's AST (Mul/Add/Pow/Rational/Integer/…)
 * — this is what makes the stream feel REALLY symbolic. Each event's srepr
 * field gets rendered as a faint indented sub-line with a tiny "srepr"
 * tag prefix. Color tuned to read as machine-output, not data. */
.sym-line .sym-body .sym-srepr {
  display: block;
  margin-top: 2px;
  padding-left: 8px;
  border-left: 1px dashed color-mix(in srgb, var(--fg) 10%, transparent);
  color: var(--fg-dim);
  font-size: 9.5px;
  font-family: var(--font-mono);
  word-break: break-all;
  letter-spacing: -0.005em;
}
.sym-line .sym-body .sym-srepr .sym-srepr-tag {
  display: inline-block;
  margin-right: 6px;
  padding: 0 4px;
  border-radius: var(--r-1);
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  color: var(--a-violet-text);
  font-size: 8.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 700;
}
.sym-line.sym-ok    .sym-op { color: var(--a-teal-text);   background: color-mix(in srgb, var(--a-teal) 10%, transparent); }
.sym-line.sym-fail  .sym-op { color: var(--a-rose-text);   background: color-mix(in srgb, var(--a-rose) 12%, transparent); }
.sym-line.sym-fail  .sym-result { color: var(--a-rose-text); }
.sym-line.op-matrix  .sym-op { color: var(--a-violet-text); }
.sym-line.op-matvec  .sym-op { color: var(--a-teal-text);   }
.sym-line.op-simplify .sym-op { color: var(--a-mint-text);  }
.sym-line.op-build_op .sym-op { color: var(--a-amber-text); }
.sym-line.op-born     .sym-op { color: var(--a-trunk-text); }
.sym-line.op-projector .sym-op { color: var(--a-teal-text); }
.sym-line.op-outer    .sym-op { color: var(--a-amber-text); }
.sym-line.op-state    .sym-op { color: var(--a-violet-text); }
.sym-line.op-fidelity .sym-op { color: var(--a-trunk-text); }
.sym-line.op-preis    .sym-op { color: var(--a-mint-text);  }
.sym-line.op-error    .sym-op { color: var(--a-rose-text);  }
/* `value` = per-witness algebraic identity (claim = ...) — most common
 * op in the stream, stays in the panel's primary accent so it doesn't
 * scream against the rarer op kinds. `predict` = Rational/Pow/Pi
 * arithmetic for falsifiable predictions; trunk-amber for visibility. */
.sym-line.op-value    .sym-op { color: var(--a-violet-text); }
.sym-line.op-predict  .sym-op { color: var(--a-amber-text); background: color-mix(in srgb, var(--a-amber) 10%, transparent); }

/* Polish for the popup's section heads (used in the new dual-descriptive
 * layout — formal left, plain-English right). Not the same as
 * .symbolic-section-head; this is for prose section markers. */
.detail-slide .detail-section-head {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.10em;
  font-weight: 700;
  color: var(--a-violet-text);
  margin: 8px 0 3px;
}
.detail-slide .detail-prose .detail-section-head { color: var(--a-amber-text); }
.detail-slide .detail-meaning .detail-section-head { color: var(--a-mint-text); }
/* ╔═══════════════════════════════════════════════════════════════════╗
 * ║ HOMOICONIC CONSOLE                                                ║
 * ║ One-stop status table for every step in the homoiconic chain.     ║
 * ║ 10 numbered sections. Each has a head row (number, name, pulse,   ║
 * ║ status string) and a body of key/value lines. Click-able sections ║
 * ║ get a hover-cursor + action-hint footer.                          ║
 * ╚═══════════════════════════════════════════════════════════════════╝ */
.homo-console {
  display: flex;
  flex-direction: column;
}

/* Custom scrollbar for the homoiconic console — narrow, tasteful, always
 * visible (so the user always knows there's more content below). The
 * console's section count (11) typically exceeds the panel's grid-track
 * height; without this rule the content silently clips. */
.homo-console,
.panel-console {
  scrollbar-width: thin;
  scrollbar-color: var(--a-violet-text) transparent;
}
.homo-console::-webkit-scrollbar,
.panel-console::-webkit-scrollbar {
  width: 8px;
}
.homo-console::-webkit-scrollbar-track,
.panel-console::-webkit-scrollbar-track {
  background: transparent;
}
.homo-console::-webkit-scrollbar-thumb,
.panel-console::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--a-violet) 45%, transparent);
  border-radius: var(--r-2);
}
.homo-console::-webkit-scrollbar-thumb:hover,
.panel-console::-webkit-scrollbar-thumb:hover {
  background: var(--a-violet);
}
.homo-console .hc-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 8px 10px 7px;
  border-bottom: 1px solid var(--border);
  background: var(--elev);
  border-radius: 9px 9px 0 0;
  flex: 0 0 auto;
}
.homo-console .hc-head-title {
  font-size: 10px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--a-violet-text);
}
.homo-console .hc-head-sub {
  font-size: 9px;
  letter-spacing: 0.06em;
  color: var(--fg-faint);
}
.homo-console .hc-section {
  padding: 6px 10px 7px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.homo-console .hc-section:last-child {
  border-bottom: none;
  border-radius: 0 0 9px 9px;
}
.homo-console .hc-section.hc-clickable {
  cursor: pointer;
  transition: background var(--duration-2);
}
.homo-console .hc-section.hc-clickable:hover {
  background: var(--elev);
}
.homo-console .hc-section-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
  padding-bottom: 2px;
}
.homo-console .hc-num {
  font-size: 11px;
  color: var(--a-violet-text);
  font-weight: 700;
}
.homo-console .hc-name {
  font-size: 9.5px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--a-amber-text);
  flex: 1;
}
.homo-console .hc-pulse {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg-faint);
  border: 1px solid var(--border);
  flex-shrink: 0;
  transition: background var(--duration-3), box-shadow var(--duration-5);
}
.homo-console .hc-pulse.pulse-ok {
  background: var(--a-teal);
  border-color: var(--a-teal-text);
}
.homo-console .hc-pulse.pulse-warn {
  background: var(--a-amber);
  border-color: var(--a-amber-text);
}
.homo-console .hc-pulse.pulse-fail {
  background: var(--a-rose);
  border-color: var(--a-rose-text);
}
.homo-console .hc-pulse.pulse-flash {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--a-amber) 20%, transparent);
  background: var(--a-trunk);
  border-color: var(--a-trunk-text);
}
.homo-console .hc-status {
  font-size: 9.5px;
  color: var(--fg-dim);
  font-family: var(--font-mono);
  white-space: nowrap;
}
.homo-console .hc-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  padding: 1px 0;
  font-size: 10px;
  line-height: 1.45;
}
.homo-console .hc-row .hc-key {
  color: var(--fg-faint);
  white-space: nowrap;
  font-size: 9.5px;
}
.homo-console .hc-row .hc-val {
  color: var(--fg);
  text-align: right;
  word-break: break-all;
  font-size: 10px;
}
.homo-console .hc-row.hc-action-hint {
  justify-content: flex-end;
  color: var(--a-trunk-text);
  font-size: 9px;
  font-style: italic;
  margin-top: 2px;
}
.homo-console .hc-source-fname {
  color: var(--fg);
  font-weight: 600;
  font-size: 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
  margin-bottom: 2px;
}
.homo-console .hc-source-meta {
  color: var(--fg-dim);
  font-size: 9.5px;
  display: block;
  margin-bottom: 5px;
}

/* ════════════════════════════════════════════════════════════════════
   LIVING SOURCE VIEWER · the homoiconic loop made visible
   ════════════════════════════════════════════════════════════════════
   Every SSE event that carries an md_anchor flows here in real time.
   The user sees the engine read its own source: which section, how
   often, triggered by which event. The MD is no longer hidden behind
   trust — it's literally on the page, being consulted on every tick.

   Layout: 3-row stack
     1. anchor + provenance bar (sticky-feeling top)
     2. monospaced text body (capped height; auto-scroll on update)
     3. (no separate row — tally lives in the bar)

   Pin state: data-pinned="1" pauses live updates so the user can read
   without the panel scrolling out from under them. Visible cue: the
   anchor bar grows a violet outline.

   Pulse: when a fresh anchor is consulted, the whole panel briefly
   glows violet (.hc-source-live-flash class, 600 ms autoremove). This
   is the visual confirmation "the engine just read THIS section right
   now" — synchronized with every relevant SSE arrival.
   ════════════════════════════════════════════════════════════════════ */
.homo-console .hc-source-live {
  margin-top: 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  /* Use the v1.0-spec --elev token (= --gray-soft-{d|l} per theme).
     Earlier this was `var(--bg-soft))` referencing
     an undefined --bg-soft token; --elev is the correct named
     token for "elevated surface" in the locked color system. */
  background: var(--elev);
  transition: box-shadow var(--duration-3) ease, border-color var(--duration-3) ease;
  overflow: hidden;        /* corners stay rounded over the <pre> */
}
.homo-console .hc-source-live:hover {
  border-color: var(--a-violet-text);
}
.homo-console .hc-source-live[data-pinned="1"] {
  border-color: var(--a-violet-text);
  box-shadow: inset 0 0 0 1px var(--a-violet);
}
/* Bar is interactive (cursor: pointer, click toggles pin). The body
   below is also interactive but with a different intent (open detail).
   Per-zone cursors disambiguate the two affordances at hover-time. */
.homo-console .hc-source-live-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  background: color-mix(in srgb, var(--a-violet) 8%, transparent);   /* violet tint, theme-stable */
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
  user-select: none;       /* don't let pin toggle select text */
}
.homo-console .hc-source-live-bar:hover {
  background: color-mix(in srgb, var(--a-violet) 16%, transparent);
}
/* Pin glyph: hidden by default, revealed when [data-pinned="1"]. The
   📌 emoji is theme-stable (always full-color) and conveys the state
   at a glance. Reserve space even when hidden so the bar layout
   doesn't shift during pin/unpin (jank-free toggle). */
.homo-console .hc-source-live-pin {
  flex-shrink: 0;
  font-size: 9.5px;
  line-height: 1;
  width: 11px;             /* reserved width — prevents layout shift */
  text-align: center;
  opacity: 0;
  transition: opacity var(--duration-3) ease;
}
.homo-console .hc-source-live[data-pinned="1"] .hc-source-live-pin {
  opacity: 1;
}
.homo-console .hc-source-live-anchor {
  color: var(--a-violet-text);
  font-weight: 700;
  flex-shrink: 0;
}
.homo-console .hc-source-live-via {
  color: var(--fg-dim);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  font-style: italic;
  min-width: 0;            /* allow flex to shrink the via text */
}
.homo-console .hc-source-live-tally {
  color: var(--a-teal-text);
  font-weight: 700;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  /* Reserve width for "9999×" (5 chars) so the bar doesn't reflow as
   the count climbs from 1× → 47× → 1234×. Right-aligned so the
   number sits flush at the edge regardless of digit count. tabular-
   nums + min-width together guarantee zero pixel jitter.  */
  min-width: 5ch;
  text-align: right;
}
/* Body cursor differs from bar cursor — communicates "click here to
   open this section in a popup" rather than "click here to toggle pin".
   Subtle hover background reinforces the affordance.

   ── HEIGHT LOCK ───────────────────────────────────────────────────
   The viewer height is FIXED, not capped. Each section streamed in
   has wildly different length: the §1 intro might be 4 lines, §18
   might be 60 lines. With max-height the panel resized to fit each
   one, and every consultation jolted everything below up or down —
   visible "jitter" that broke read-flow on the rest of the console.
   Now: height: 140px (fixed). Internal overflow-y: auto handles long
   sections via scroll; short sections leave inert space. The box
   footprint is constant regardless of which section streamed in,
   so the homoiconic console layout stays still while the source
   text breathes inside its own frame. box-sizing: border-box
   guarantees the 140 px includes padding so total height is exactly
   140 regardless of theme padding tweaks.
   ──────────────────────────────────────────────────────────────── */
.homo-console .hc-source-live-pre {
  margin: 0;
  padding: 5px 6px;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1.45;
  color: var(--fg);
  background: transparent;
  height: 140px;
  box-sizing: border-box;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
  cursor: zoom-in;         /* "click to expand into popup" */
  transition: background var(--duration-3) ease;
  /* Inherit the global purple scrollbar from the body-level rule. */
}
.homo-console .hc-source-live-pre:hover {
  background: color-mix(in srgb, var(--a-violet) 8%, transparent);
}
/* Pulse animation — fires on every anchor consultation.
   The flash is intentionally short (600 ms) so a burst of events
   produces a flicker the user reads as "lots of activity right now"
   rather than a constant glow that desensitizes the eye. */
@keyframes hc-source-live-pulse {
  0%   { background-color: color-mix(in srgb, var(--a-violet-text) 35%, transparent); }
  100% { background-color: color-mix(in srgb, var(--a-violet-text) 8%, transparent); }
}
.homo-console .hc-source-live.hc-source-live-flash .hc-source-live-bar {
  animation: hc-source-live-pulse 600ms ease-out;
}

.homo-console .hc-actions {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.homo-console .hc-btn {
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 4px 9px;
  min-height: 0;
  border-radius: var(--r-2);
  background: var(--a-amber);
  color: var(--bg);
  border: 1px solid var(--a-amber);
  cursor: pointer;
  font-weight: 600;
  transition: background var(--duration-2), transform 0.1s;
}
.homo-console .hc-btn:hover {
  background: var(--a-trunk);
  border-color: var(--a-trunk-text);
  transform: translateY(-1px);
}
.homo-console .hc-btn-ghost {
  background: var(--panel);
  color: var(--fg);
  border-color: var(--border);
}
.homo-console .hc-btn-ghost:hover {
  background: var(--elev);
}
.homo-console .hc-leaves-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 8px;
  font-size: 9.5px;
}
.homo-console .hc-leaves-grid .hc-leaf {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-family: var(--font-mono);
}
.homo-console .hc-leaves-grid .hc-leaf-key {
  color: var(--fg-faint);
  flex: 1;
}
.homo-console .hc-leaves-grid .hc-leaf-val {
  color: var(--a-teal-text);
  font-weight: 700;
}
.homo-console .hc-leaves-grid .hc-leaf.hc-leaf-fail .hc-leaf-val {
  color: var(--a-rose-text);
}
.homo-console .hc-kernel-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 2px 6px;
  font-size: 9px;
  font-family: var(--font-mono);
}
.homo-console .hc-kernel-grid .hc-A {
  display: flex;
  align-items: center;
  gap: 3px;
}
.homo-console .hc-kernel-grid .hc-A-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--fg-faint);
  flex-shrink: 0;
}
.homo-console .hc-kernel-grid .hc-A.ok .hc-A-dot { background: var(--a-teal); }
.homo-console .hc-kernel-grid .hc-A.fail .hc-A-dot { background: var(--a-rose); }
.homo-console .hc-kernel-grid .hc-A-name {
  color: var(--fg-dim);
}
.homo-console .hc-kernel-grid .hc-A.ok .hc-A-name { color: var(--fg); }
.homo-console .hc-born-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  font-size: 9.5px;
  font-family: var(--font-mono);
}
.homo-console .hc-born-grid .hc-born-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px;
}
.homo-console .hc-born-grid .hc-born-key { color: var(--fg-faint); white-space: nowrap; }
.homo-console .hc-born-grid .hc-born-val { color: var(--a-trunk-text); font-weight: 600; }
.homo-console .hc-born-grid .hc-born-row.hc-born-fail .hc-born-val { color: var(--a-rose-text); }
.homo-console .hc-faces {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-bottom: 3px;
}
.homo-console .hc-faces .hc-face {
  font-size: 9px;
  padding: 1px 5px;
  border-radius: var(--r-2);
  background: var(--elev);
  color: var(--a-violet-text);
  font-weight: 700;
  letter-spacing: 0.06em;
}
.homo-console .hc-fidelity-spark-row {
  padding-top: 3px;
}
.homo-console .hc-fidelity-spark {
  width: 100%;
  height: 18px;
  color: var(--a-trunk-text);
}
.homo-console .hc-placeholder {
  color: var(--fg-faint);
  font-size: 9px;
  font-style: italic;
}

/* ── CHAIN STRIP (top of console) ──────────────────────────────────────
 * Mini horizontal flow showing the master quantum-algebraic chain in 8
 * abstract symbols. Theoretical face complementing the operational
 * sections below. Subtle gradient background separates it visually. */
.homo-console .hc-chainstrip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1px;
  padding: 7px 8px 8px;
  /* Solid panel — was elev→bg gradient. Header chrome flattened so
   * the chainstrip reads as a single tonal step from the page bg
   * (panel = ONE notch darker per the surface-ladder compression). */
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0;
  color: var(--fg);
  cursor: help;
}
.homo-console .hc-chainsym {
  color: var(--a-violet-text);
  font-weight: 700;
  flex: 0 0 auto;
  padding: 1px 2px;
  border-radius: var(--r-2);
  transition: color var(--duration-3), background var(--duration-3), text-shadow var(--duration-5);
}
.homo-console .hc-chainsym.active {
  color: var(--a-trunk-text);
  background: color-mix(in srgb, var(--a-amber) 10%, transparent);
  text-shadow: 0 0 8px color-mix(in srgb, var(--a-amber) 40%, transparent);
}
/* AXIOM symbols (𝒫₀, Ψ₀, ρ₀) — preconditions of measurement; always-true.
 * They breathe continuously THROUGH the same amber-glow appearance as
 * a fired event (peak = exact .active visual: amber color, amber bg,
 * 8px text-shadow). At the quantum scale we can't witness the equation
 * without these being already in place; the breathe makes that visible
 * in the same vocabulary as event flashes for visual consistency. */
@keyframes hc-axiom-breathe {
  0%, 100% {
    color: var(--a-violet-text);
    background: transparent;
    text-shadow: 0 0 0 transparent;
    opacity: 0.55;
  }
  50% {
    color: var(--a-trunk-text);
    background: color-mix(in srgb, var(--a-amber) 10%, transparent);
    text-shadow: 0 0 8px color-mix(in srgb, var(--a-amber) 40%, transparent);
    opacity: 1;
  }
}
.homo-console .hc-chainsym.axiom {
  animation: hc-axiom-breathe 3.2s ease-in-out infinite;
}
/* Separator between EVENT symbols (left) and AXIOM symbols (right).
 * Subtle vertical bar; not interactive, just a visual divider so the
 * grouping reads at a glance. */
.homo-console .hc-chainsep {
  color: var(--fg-faint);
  opacity: 0.35;
  font-size: 11px;
  flex: 0 0 auto;
  margin: 0 4px;
  cursor: help;
  font-weight: 300;
}
.homo-console .hc-chainarrow {
  color: var(--fg-faint);
  font-size: 8.5px;
  flex: 0 0 auto;
}

/* ── Section names with educational tooltip cursor ─────────────────── */
.homo-console .hc-name[title] {
  cursor: help;
  border-bottom: 1px dotted var(--fg-faint);
  padding-bottom: 1px;
}
.homo-console .hc-name[title]:hover {
  border-bottom-color: var(--a-amber-text);
}

/* ──────────────────────────────────────────────────────────────────────
   RECURSIVE FIDELITY · BHME (merged) — sits at top of homo-console
   ────────────────────────────────────────────────────────────────────── */
/* Section divider inside the merged hc-section. Splits the Bayesian
   posterior block (top) from the BHME body-state block (below). */
.homo-console .hc-recur-divider {
  margin: 8px 0 6px 0;
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.homo-console .hc-recur-divider::before,
.homo-console .hc-recur-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
  opacity: 0.55;
}
/* Live activity ticker — pulses on every bracket_proof, witness,
   sympy_op, orbit_close. Background flashes amber for ~400ms then
   decays back to transparent. Reads as the wheel's "heartbeat in
   text form". */
.homo-console .hc-recur-ticker {
  font-size: 9px;
  color: var(--fg-dim);
  border-radius: var(--r-2);
  padding: 1px 5px;
  background: transparent;
  transition: background var(--duration-4), color var(--duration-4);
}
.homo-console .hc-recur-ticker.hc-recur-ticker-flash {
  background: color-mix(in srgb, var(--a-amber) 20%, transparent);
  color: var(--a-amber-text);
}
.homo-console .hc-recur-ticker.hc-recur-ticker-fail {
  background: color-mix(in srgb, var(--a-rose) 20%, transparent);
  color: var(--a-rose-text);
}
.homo-console .hc-recur-ticker-row {
  /* Lock the ticker to a fixed height so the text-flash doesn't
     reflow the whole section as it changes length. */
  align-items: center;
}
/* Subtle violet outline on the merged section — it's THE primary
   readout; the eye should land on it first. */
.homo-console .hc-section.hc-recur {
  border-color: var(--a-violet-text);
  /* Solid 4%-violet tint — was a violet→transparent fade gradient.
   * Per the canvas-only-gradient rule, the violet "primary readout"
   * cue is now the BORDER color (above) plus a flat low-opacity
   * violet wash. Half the original peak (8%) so the flat tint isn't
   * darker than the average of the previous gradient. */
  background: color-mix(in srgb, var(--a-violet) 4%, transparent);
}

/* ──────────────────────────────────────────────────────────────────────
   GLOBAL PURPLE SCROLLBAR — applied to every scrollable panel for
   consistent feel across the project (was previously console-only).
   ────────────────────────────────────────────────────────────────────── */
.panel-feed,
.panel-feed .feed-stream,
.panel-feed #symstream,
.predictions-list,
.predictions-overlay,
.predictions-overlay .predictions-list,
.legend-popup-wrapper,
.legend-popup-host,
.detail-popup,
.debug-stream,
.scrollable {
  scrollbar-width: thin;
  scrollbar-color: var(--a-violet-text) transparent;
}
.panel-feed::-webkit-scrollbar,
.panel-feed .feed-stream::-webkit-scrollbar,
.panel-feed #symstream::-webkit-scrollbar,
.predictions-list::-webkit-scrollbar,
.predictions-overlay::-webkit-scrollbar,
.predictions-overlay .predictions-list::-webkit-scrollbar,
.legend-popup-wrapper::-webkit-scrollbar,
.legend-popup-host::-webkit-scrollbar,
.detail-popup::-webkit-scrollbar,
.debug-stream::-webkit-scrollbar,
.scrollable::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
.panel-feed::-webkit-scrollbar-track,
.panel-feed .feed-stream::-webkit-scrollbar-track,
.panel-feed #symstream::-webkit-scrollbar-track,
.predictions-list::-webkit-scrollbar-track,
.predictions-overlay::-webkit-scrollbar-track,
.predictions-overlay .predictions-list::-webkit-scrollbar-track,
.legend-popup-wrapper::-webkit-scrollbar-track,
.legend-popup-host::-webkit-scrollbar-track,
.detail-popup::-webkit-scrollbar-track,
.debug-stream::-webkit-scrollbar-track,
.scrollable::-webkit-scrollbar-track {
  background: transparent;
}
.panel-feed::-webkit-scrollbar-thumb,
.panel-feed .feed-stream::-webkit-scrollbar-thumb,
.panel-feed #symstream::-webkit-scrollbar-thumb,
.predictions-list::-webkit-scrollbar-thumb,
.predictions-overlay::-webkit-scrollbar-thumb,
.predictions-overlay .predictions-list::-webkit-scrollbar-thumb,
.legend-popup-wrapper::-webkit-scrollbar-thumb,
.legend-popup-host::-webkit-scrollbar-thumb,
.detail-popup::-webkit-scrollbar-thumb,
.debug-stream::-webkit-scrollbar-thumb,
.scrollable::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--a-violet) 45%, transparent);
  border-radius: var(--r-2);
}
.panel-feed::-webkit-scrollbar-thumb:hover,
.panel-feed .feed-stream::-webkit-scrollbar-thumb:hover,
.panel-feed #symstream::-webkit-scrollbar-thumb:hover,
.predictions-list::-webkit-scrollbar-thumb:hover,
.predictions-overlay::-webkit-scrollbar-thumb:hover,
.predictions-overlay .predictions-list::-webkit-scrollbar-thumb:hover,
.legend-popup-wrapper::-webkit-scrollbar-thumb:hover,
.legend-popup-host::-webkit-scrollbar-thumb:hover,
.detail-popup::-webkit-scrollbar-thumb:hover,
.debug-stream::-webkit-scrollbar-thumb:hover,
.scrollable::-webkit-scrollbar-thumb:hover {
  background: var(--a-violet);
}

/* ──────────────────────────────────────────────────────────────────────
   BHME panel · Bayesian Homoiconic Morphogenesis Engine readouts
   Surfaces every BHME layer (genome, credence, scars, lineage, refresh,
   round-trip) as live status rows. Matches existing hc-section styling
   conventions (hc-key/hc-val rows + hc-pulse status dot).
   ────────────────────────────────────────────────────────────────────── */
.homo-console .hc-section[data-step="bhme"] .hc-mono {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.02em;
}
/* Round-trip badge — green (verified), red (diverged), gray (untested). */
.homo-console .hc-section[data-step="bhme"] .hc-rt-ok {
  color: var(--a-teal-text);
  font-weight: 700;
}
.homo-console .hc-section[data-step="bhme"] .hc-rt-fail {
  color: var(--a-rose-text);
  font-weight: 700;
}
.homo-console .hc-section[data-step="bhme"] .hc-rt-untested {
  color: var(--fg-faint);
  font-weight: 400;
}
/* Per-class credence ticks — 7 small bars showing F_class scores. */
.homo-console .hc-bhme-credence-row {
  align-items: center;
}
.homo-console .hc-bhme-credence {
  display: inline-flex;
  gap: 2px;
  flex: 1;
  justify-content: flex-end;
  align-items: center;
  height: 14px;
}
.homo-console .hc-bhme-credence .hc-bhme-tick {
  width: 5px;
  height: 100%;
  border-radius: var(--r-1);
  background: var(--border);
  transition: background var(--duration-3), opacity var(--duration-3), height var(--duration-3);
  cursor: help;
}
.homo-console .hc-bhme-credence .hc-bhme-tick.hc-bhme-tick-passed {
  background: var(--a-teal);
}
.homo-console .hc-bhme-credence .hc-bhme-tick.hc-bhme-tick-mixed {
  background: var(--a-amber);
}
.homo-console .hc-bhme-credence .hc-bhme-tick.hc-bhme-tick-failed {
  background: var(--a-rose);
}
.homo-console .hc-bhme-credence .hc-bhme-tick.hc-bhme-tick-unclaimed {
  background: var(--fg-faint);
  opacity: 0.45;
}
/* Refresh-weather button — re-rolls deterministic refresh seed. */
.homo-console .hc-bhme-actions {
  padding-top: 4px;
  justify-content: flex-end;
}
.homo-console .hc-bhme-refresh {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  padding: 3px 8px;
  color: var(--fg-dim);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background var(--duration-2), color var(--duration-2), border-color var(--duration-2);
}
.homo-console .hc-bhme-refresh:hover {
  background: var(--bg);
  color: var(--a-violet-text);
  border-color: var(--a-violet-text);
}
.homo-console .hc-bhme-refresh:active {
  transform: scale(0.97);
}

/* ════════════════════════════════════════════════════════════════════
   STICKY CONSOLE FOOTER · always-visible Recursive-Fidelity strip
   ════════════════════════════════════════════════════════════════════
   The footer is position: sticky so it stays pinned to the bottom of
   the panel-console scroll viewport while the user scrolls through
   the 11 stacked sections above. Two-row layout:
     1. .hc-foot-strip      (compact BHME health, ~24 px tall)
     2. .hc-legend-trigger  (legend opener, ~30 px tall)

   STICKY MECHANICS
   ────────────────
   • position: sticky requires a scroll context — .panel-console is
     a flex column with internal overflow, so sticky resolves against
     it correctly.
   • bottom: 0 anchors to the bottom of the visible viewport.
   • z-index: 2 keeps the strip above per-section content during
     scroll (otherwise long sections could paint over it at the
     edges due to subpixel rounding).
   • backdrop-filter blur softens the visual transition from scrolling
     content to the pinned strip — content briefly visible behind the
     translucent footer instead of harsh edge.

   THEME-STABILITY
   ───────────────
   var(--elev) + var(--border) keep the strip in lock-step with both
   light and dark themes. The mini-credence ticks reuse the same
   theme tokens as the full credence row in the verbose section
   above, so the two readouts always agree pixel-for-pixel on color.
   ════════════════════════════════════════════════════════════════════ */
.homo-console .hc-footer {
  padding: 0;
  background: var(--elev);
  border-top: 1px solid var(--border);
  border-radius: 0 0 9px 9px;
  position: sticky;
  bottom: 0;
  z-index: 2;
  /* (was backdrop-filter blur(6px) — removed · the strip already has a
   *  fully-opaque background and a top border, the blur added cost for
   *  no perceptible benefit on a sticky element with no content showing
   *  through it.) */
}
/* ── RECURSIVE-FIDELITY COMPACT STRIP ────────────────────────────── */
.homo-console .hc-foot-strip {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 22px;          /* fixed — strip never resizes with data */
  padding: 0 8px;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;       /* extreme widths get clipped, not wrapped */
}
/* Health pulse dot — color set by JS based on (round-trip ✓ AND F > 0.95).
   Reserved width prevents shift when JS swaps colors. */
.homo-console .hc-foot-pulse {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--fg-faint);
  transition: background var(--duration-3) ease, box-shadow var(--duration-3) ease;
}
.homo-console .hc-foot-pulse.hc-foot-pulse-ok {
  background: var(--a-mint);
  box-shadow: 0 0 4px var(--a-mint);
}
.homo-console .hc-foot-pulse.hc-foot-pulse-warn {
  background: var(--a-amber);
  box-shadow: 0 0 4px var(--a-amber);
}
.homo-console .hc-foot-pulse.hc-foot-pulse-fail {
  background: var(--a-rose);
  box-shadow: 0 0 4px var(--a-rose);
}
/* Each cell: key + value, tightly spaced. The keys stay dim, the values
   carry the active accent so the eye reads "label: NUMBER" at a glance. */
.homo-console .hc-foot-cell {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  flex-shrink: 0;          /* never shrink — every cell stays readable */
}
.homo-console .hc-foot-k {
  color: var(--fg-faint);
  font-weight: 400;
  letter-spacing: 0.02em;
}
.homo-console .hc-foot-v {
  color: var(--a-violet-text);  /* default accent; overridden per-cell when needed */
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Round-trip cell color-codes its value: ✓ → mint, ✗ → rose. The
   class is applied/removed by updateBhmeFooter on every BHME refresh. */
.homo-console #hc-foot-rt.hc-foot-v-ok    { color: var(--a-mint-text); }
.homo-console #hc-foot-rt.hc-foot-v-fail  { color: var(--a-rose-text); }
/* F-posterior is the "headline" number — render it in teal so the eye
   parks on it as the primary signal. */
.homo-console #hc-foot-fid { color: var(--a-teal-text); }
/* Mini credence ticks · 7 small bars, one per F-class. Heights scale
   with score (matched to the full credence row's heights, just thinner).
   Color reflects status. Push to the right with margin-left: auto so
   the bar group always sits at the strip's right edge. */
.homo-console .hc-foot-credence {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 14px;
  margin-left: auto;       /* right-edge alignment regardless of cell count */
  flex-shrink: 0;
}
.homo-console .hc-foot-credence .hc-foot-tick {
  display: inline-block;
  width: 3px;
  background: var(--fg-faint);
  border-radius: var(--r-1);
  transition: height var(--duration-3) ease, background var(--duration-3) ease;
}
.homo-console .hc-foot-credence .hc-foot-tick-passed    { background: var(--a-mint); }
.homo-console .hc-foot-credence .hc-foot-tick-mixed     { background: var(--a-amber); }
.homo-console .hc-foot-credence .hc-foot-tick-failed    { background: var(--a-rose); }
.homo-console .hc-foot-credence .hc-foot-tick-unclaimed { background: var(--fg-faint); }
/* ── LEGEND TRIGGER ───────────────────────────────────────────────── */
.homo-console .hc-legend-trigger {
  display: block;
  width: calc(100% - 16px);
  margin: 6px 8px 7px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  padding: 5px 8px;
  color: var(--fg-dim);
  font-family: var(--font-mono);
  font-size: 9.5px;
  cursor: pointer;
  text-align: left;
  letter-spacing: 0.04em;
  transition: background var(--duration-2), color var(--duration-2), border-color var(--duration-2);
}
.homo-console .hc-legend-trigger:hover {
  background: var(--bg);
  color: var(--a-amber-text);
  border-color: var(--a-amber-text);
}

/* ── Legend popup host (hidden in flow; shown via openDetail) ──────── */
.legend-popup-host[hidden] { display: none; }

/* ── Predictions panel · right column bottom ───────────────────────── */
.panel-predictions .predictions-list {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 8px 12px 10px;
}

/* ── Framework Function chip · topbar ──────────────────────────────────
 * Shows f as a parametric face of the framework. Pulses on every
 * homoiconic_recompile event (= every MD change). Click → opens the
 * framework-function popup with full self-description. */
.fw-fn-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  background: var(--elev);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg);
  cursor: pointer;
  transition: background var(--duration-3), border-color var(--duration-3), box-shadow var(--duration-5);
}
.fw-fn-chip:hover {
  border-color: var(--a-violet-text);
  background: var(--panel);
}
.fw-fn-chip .fw-fn-tag {
  color: var(--a-violet-text);
  font-weight: 700;
  font-style: italic;
  font-size: 12px;
}
.fw-fn-chip .fw-fn-form {
  color: var(--a-trunk-text);
  font-weight: 600;
}
.fw-fn-chip .fw-fn-rcount {
  color: var(--fg-faint);
  font-size: 9px;
  letter-spacing: 0.04em;
}
.fw-fn-chip.recompile-flash {
  border-color: var(--a-trunk-text);
  background: color-mix(in srgb, var(--a-amber) 20%, transparent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--a-amber) 10%, transparent);
}
.fw-fn-chip.recompile-flash .fw-fn-rcount {
  color: var(--a-trunk-text);
  font-weight: 700;
}

/* ── Framework-function popup (parametric face) ────────────────────────
 * Renders f's signature, CSE factoring, and the full per-claim symbolic
 * body. Read-only homoiconic self-description — every row is a sympy
 * printer's output, no recomputation. */
.fwfn-section {
  margin: 14px 0;
}
.fwfn-section .fwfn-section-head {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-weight: 700;
  color: var(--a-violet-text);
  margin-bottom: 6px;
  padding-bottom: 3px;
  border-bottom: 1px solid var(--border);
}
.fwfn-form {
  font-family: var(--font-mono);
  margin: 4px 0;
}
.fwfn-form code {
  background: var(--bg);
  padding: 2px 6px;
  color: var(--a-teal-text);
  border-radius: var(--r-2);
  font-size: 11px;
  word-break: break-all;
}
.fwfn-meta {
  font-size: 10.5px;
  color: var(--fg-dim);
  margin: 4px 0 0;
}
.fwfn-meta b {
  color: var(--a-amber-text);
}
.fwfn-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 10.5px;
  margin-top: 4px;
}
.fwfn-table tr:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.fwfn-table td {
  padding: 3px 8px 3px 0;
  vertical-align: top;
  word-break: break-all;
  color: var(--fg);
}
.fwfn-table .fwfn-key {
  color: var(--a-trunk-text);
  font-weight: 700;
  width: 130px;
}
.fwfn-table .fwfn-eq {
  color: var(--fg-faint);
}
.fwfn-table .fwfn-val {
  color: var(--a-teal-text);
  font-weight: 700;
}
.fwfn-table code {
  background: var(--elev);
  padding: 1px 5px;
  border-radius: var(--r-2);
  color: var(--fg);
  font-size: 10.5px;
}
.fwfn-table .fwfn-srepr {
  color: var(--fg-dim);
  font-size: 9.5px;
}
.fwfn-table .fwfn-pretty {
  background: var(--bg);
  padding: 4px 8px;
  border-radius: var(--r-2);
  color: var(--a-amber-text);
  margin: 0;
  white-space: pre;
  overflow-x: auto;
  font-size: 10.5px;
}

/* ── Homoiconic algebra row inside each feed-block ─────────────────────
 * Renders `formula → expr` as a thin strip between the head and the
 * chain. Both halves are projections of CLAIM_EXPR — the formula is
 * the human label, the expr is the sympy str() form. Click the block
 * to see all five projections (str/pretty/srepr/latex/value) of the
 * same sympy.Expr. */
.feed-algebra {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 3px 0 4px;
  margin: 1px 0 2px;
  border-bottom: 1px dashed color-mix(in srgb, var(--fg) 6%, transparent);
  font-size: 10.5px;
  font-family: var(--font-mono);
  color: var(--fg-dim);
  line-height: 1.45;
}
.feed-algebra .fa-formula {
  color: var(--a-mint-text);
  font-weight: 600;
}
.feed-algebra .fa-arrow {
  color: var(--a-trunk-text);
  font-weight: 700;
}
.feed-algebra .fa-expr {
  background: var(--elev);
  color: var(--a-teal-text);
  padding: 0 5px;
  border-radius: var(--r-2);
  font-size: 10.5px;
  word-break: break-all;
}

/* ── Detail-popup projection table ─────────────────────────────────────
 * The five-face homoiconic readout. One sympy.Expr, five sympy printers.
 * No two rows are independently computed — they are five projections of
 * the SAME object. Inconsistency between rows is structurally impossible. */
.detail-projections {
  margin: 14px 0 10px;
  padding: 10px 12px;
  background: var(--elev);
  border-left: 3px solid var(--a-violet);
  border-radius: var(--r-2);
}
.detail-projections .dp-head {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-weight: 700;
  color: var(--a-violet-text);
  margin-bottom: 7px;
}
.detail-projections .dp-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 10.5px;
}
.detail-projections .dp-table th {
  text-align: left;
  width: 92px;
  padding: 4px 8px 4px 0;
  vertical-align: top;
  color: var(--a-trunk-text);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 9.5px;
}
.detail-projections .dp-table td {
  padding: 4px 0;
  color: var(--fg);
  vertical-align: top;
  word-break: break-all;
}
.detail-projections .dp-table code {
  background: var(--bg);
  padding: 1px 5px;
  border-radius: var(--r-2);
  color: var(--a-teal-text);
  font-size: 10.5px;
}
.detail-projections .dp-table .dp-srepr {
  color: var(--fg-dim);
  font-size: 9.5px;
}
.detail-projections .dp-table .dp-pretty {
  background: var(--bg);
  padding: 4px 8px;
  border-radius: var(--r-2);
  color: var(--a-amber-text);
  font-size: 10.5px;
  margin: 0;
  white-space: pre;
  overflow-x: auto;
}

.feed-block {
  margin-bottom: 8px;
  padding: 7px 10px;
  background: var(--panel);
  color: var(--fg);
  border-left: 3px solid var(--a-violet);
  border-top: 1px solid var(--border);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-radius: var(--r-3);
  cursor: pointer;
  /* Transitions only fire on :hover now — removed background/box-shadow
   * transitions from the default state because they would re-trigger
   * style/layout/paint passes on every feed-block insert (18+/sec).
   * The brief "feed-in" animation is also gone for the same reason. */
}
.feed-block:hover {
  box-shadow: 2px 2px 0 var(--shadow);
  background: var(--elev);
  transition: box-shadow var(--duration-3) ease, background var(--duration-3) ease;
}
.feed-block.recent { border-left-color: var(--a-amber-text); border-left-width: 4px; }
.feed-head {
  display: flex; align-items: baseline; gap: 7px;
  margin-bottom: 5px; flex-wrap: wrap;
}
.feed-claim   { color: var(--a-violet-text); font-weight: 700; font-size: 11.5px; }
.feed-eq      { color: var(--fg-faint); }
.feed-value   { color: var(--a-teal-text); font-weight: 700; font-size: 11.5px; }
.feed-via     { color: var(--fg-faint); font-size: 9.5px; }
.feed-a {
  color: var(--a-amber-text);
  font-weight: 600;
  padding: 0 6px;
  border-radius: var(--r-2);
  background: var(--elev);            /* token-pure: no per-hue chip bg */
}
.feed-spacer { flex: 1; }
.feed-residual {
  font-size: 9.5px;
  padding: 1px 7px;
  border-radius: var(--r-2);
  background: var(--elev);
  color: var(--a-teal-text);
  font-weight: 600;
}
.feed-residual.fail { color: var(--a-rose-text); }
.feed-chain { display: flex; flex-direction: column; gap: 1px; padding-left: 4px; }
.feed-step {
  color: var(--fg);
  padding: 2px 7px 2px 16px;
  border-left: 1px dashed var(--border);
  position: relative;
  font-size: 10.5px; line-height: 1.45;
}
.feed-step::before { content: "▸"; position: absolute; left: 4px; top: 1px; color: var(--a-violet-text); font-size: 9px; }

.feed-step.tok-bracket { color: var(--a-amber-text); }
.feed-step.tok-stab    { color: var(--a-violet-text); }
.feed-step.tok-e7      { color: var(--a-amber-text); opacity: 0.92; }
.feed-step.tok-csgi    { color: var(--a-amber-text); }
.feed-step.tok-alphau  { color: var(--a-teal-text); }
.feed-step.tok-falsif  { color: var(--a-azure-text); }
.feed-step.tok-qc      { color: var(--a-azure-text); }
.feed-step.tok-anchor  { color: var(--a-trunk-text); }
.feed-step.tok-close   { color: var(--fg);     font-weight: 600; }
.feed-step.tok-default { color: var(--fg-dim); }

.feed-claim.cat-stab    { color: var(--a-trunk-text); }
.feed-claim.cat-e7      { color: var(--a-sky-text); }
.feed-claim.cat-csgi    { color: var(--a-sky-text); }
.feed-claim.cat-alphau  { color: var(--a-rose-text); }
.feed-claim.cat-anchor  { color: var(--a-trunk-text); }
.feed-block.cat-stab    { border-left-color: var(--a-trunk-text); }
.feed-block.cat-e7      { border-left-color: var(--a-sky-text); }
.feed-block.cat-csgi    { border-left-color: var(--a-sky-text); }
.feed-block.cat-alphau  { border-left-color: var(--a-rose-text); }
.feed-block.cat-anchor  { border-left-color: var(--a-trunk-text); }

.feed-orbit-close {
  margin: 5px 0;
  padding: 6px 10px;
  text-align: center;
  background: var(--elev);
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--a-violet-text);
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid var(--border);
}


/* ─────────────────────────── Legend panel (right-bottom) ─────────────── */
.legend-body {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 10px 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  background: var(--bg);
}
.legend-head {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.10em;
  color: var(--a-violet-text);
  margin: 2px 0 5px 0; padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}
.legend-head:not(:first-child) { margin-top: 11px; }
.legend-row {
  display: flex; align-items: center; gap: 9px;
  padding: 4px 6px;
  border-radius: var(--r-2);
  cursor: pointer;
  transition: background var(--duration-2) ease, box-shadow var(--duration-2) ease, transform 0.1s ease;
}
.legend-row:hover {
  background: var(--panel);
  box-shadow: 2px 2px 0 var(--shadow);
  transform: translateY(-1px);
}
.legend-row .swatch {
  display: inline-block; width: 24px; flex-shrink: 0; height: 3px; border-radius: var(--r-1);
}
.legend-row .swatch.solid {
  height: 11px; width: 11px; border-radius: 50%;
}
.legend-row .lg-text    { color: var(--fg);       font-size: 11px; font-weight: 500; }
.legend-row .lg-meaning { color: var(--fg-faint); font-size: 10px; margin-left: auto; font-style: italic; }


/* ─────────────────────────── Footer ─────────────────────────── */
/* Hosts the unified perf HUD (12 cells: pulse + 7 wheel + 4 wire).
 * RESPONSIVE STRATEGY:
 *   • desktop / tablet ≥640 px · single 30 px tall row, all cells fit
 *   • mobile (<640 px)         · WRAP to multiple rows · all cells
 *     visible without horizontal scrolling. Earlier design used
 *     overflow-x:auto with scrollbar hidden, but on phones the right
 *     tail (heap, lt, ev, snap-ms) was invisible because the user
 *     had no visual cue to swipe. Wrapping costs ~30-60 extra pixels
 *     of footer height on mobile but every metric is reachable
 *     without scroll gymnastics.
 *
 * Most-actionable cells (jit · jit60 · max · rend) sit at the start
 * of the markup so wherever the wrap breaks, the highest-signal cells
 * are guaranteed on row 1. */
.footer {
  padding: 6px 18px;
  padding-left:  max(18px, env(safe-area-inset-left));
  padding-right: max(18px, env(safe-area-inset-right));
  background: var(--panel);
  border-top: 1px solid var(--border);
  font-size: 10.5px; font-family: var(--font-mono);
  color: var(--fg-faint);
  flex-shrink: 0;
  white-space: nowrap;
  /* HUD layout · cells in a row, gap-spaced. Default desktop/tablet:
   * single fixed-height row, no scroll, no wrap. */
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 14px;
  height: 30px;
  overflow: hidden;
}
@media (max-width: 639px) {
  .footer {
    /* Mobile: allow wrapping; height auto-grows to fit all rows;
     * tighter padding/gap to pack cells densely; row-gap separates
     * the wrapped rows visually. */
    flex-wrap: wrap;
    height: auto;
    min-height: 30px;
    overflow: visible;
    font-size: 9.5px;
    padding: 6px 12px;
    column-gap: 12px;
    row-gap: 4px;
  }
}
/* Hide all wheel-perf cells when window.RX_PERF.show is set false ·
 * the writer toggles `rx-hud-hidden-perf` on the footer parent. */
.footer.rx-hud-hidden-perf .hc-foot-cell:nth-child(n + 3):nth-child(-n + 8) {
  display: none;
}
/* ── STALE-WHEEL-PERF CELLS · visual signal that animate is paused ──
 * Set by viz.js's writer when frameCount hasn't advanced for > 350 ms
 * (canvas offscreen with no rootMargin headroom · tab hidden · MD-gate
 * still up). Dims the cells whose data depends on animate() running
 * (positions 2-7: fps · jit · jit60 · max · rend · mesh — all derived
 * from the frame ring-buffer that ONLY fills inside the animate loop).
 *
 * Cells that stay LIVE during a pause and therefore are NOT dimmed:
 *   pulse  (1) — wire-health dot, app.js HUD interval
 *   snap   (8) — snapshot rate from _streamStats (wire)
 *   snap-ms(9) — RX_PERF.snapBuf is filled by applySnapshot in app.js,
 *                 keeps updating even with the wheel paused
 *   ev    (10) — events from _streamStats
 *   lt    (11) — long tasks from _streamStats
 *   heap  (12) — performance.memory, app.js HUD interval
 *
 * The dim signal lets the user tell the difference between:
 *   "fps = 60 (live · wheel actively rendering)"
 *   "fps = 60 (last-known · wheel paused, scrolled offscreen)" */
.footer.rx-hud-stale .hc-foot-cell:nth-child(n + 2):nth-child(-n + 7) .hc-foot-v {
  color: var(--fg-faint);
  opacity: 0.55;
}
.footer.rx-hud-stale .hc-foot-cell:nth-child(n + 2):nth-child(-n + 7) .hc-foot-k {
  opacity: 0.55;
}
/* Pulse dot turns amber when wheel-perf is stale to draw the eye to
 * the actual cause (canvas offscreen). */
.footer.rx-hud-stale .hc-foot-pulse {
  background: var(--a-amber) !important;
  box-shadow: 0 0 4px color-mix(in srgb, var(--a-amber) 50%, transparent);
}
/* The pulse dot at the left edge */
.footer .hc-foot-pulse {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--fg-faint);
  transition: background var(--duration-3) ease;
}
/* Each cell: small label + value, mono so the digits line up */
.footer .hc-foot-cell {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  flex-shrink: 0;
}
.footer .hc-foot-k {
  color: var(--fg-faint);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.footer .hc-foot-v {
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  /* LOCKED slot width · prevents the wrap-pattern from "hopping"
   * when values change. The HUD writers update 17 cell values per
   * second across two cadences (4 Hz wheel-derived, 2 Hz wire); when
   * a value grew from "0.20" to "16.30" the cell widened, every cell
   * after it shifted, and on a wrapping mobile footer this caused
   * cells to visibly jump between rows.
   *
   * 4.5ch handles every realistic value our writers produce:
   *   • _formatMs returns at most 4 chars ("12.3", "1.23", "100")
   *   • integer cells (fps, mesh, snap) are 1-3 digits, occasionally 4
   *   • ev / lt / heap likewise 1-4 chars
   *
   * tabular-nums forces equal digit widths so "0.20" and "16.30"
   * occupy IDENTICAL pixel widths inside the locked slot. width
   * (not min-width) means cell width is fully determined by the
   * label + gap + 4.5ch — no surprises across re-renders. */
  width: 4.5ch;
  text-align: right;
  display: inline-block;
  /* Belt-and-suspenders · if a future cell occasionally produces
   * 5+ chars (e.g. mesh count > 9999) the overflow shows visually
   * into the column-gap rather than pushing siblings out of place. */
  overflow: visible;
  white-space: nowrap;
}
.footer .hc-mono { font-family: var(--font-mono); }


/* ───────── Detail slide-out panel (anchored to canvas bottom-centre) ─────
 * Single-instance overlay inside .panel-mandala. Click any prediction /
 * axiom / feed-block → fast slide-up; click another → outgoing shrinks
 * and incoming slides in. Pure CSS transform animation — no layout reflow.
 * GPU-composited, matrix-only, performance is independent of UI density. */
.detail-slide {
  position: absolute;
  left: 50%;
  bottom: 60px;                                 /* clear of the .controls bar */
  width: calc(100% - 36px);
  max-width: 480px;
  max-height: 64vh;
  transform: translate(-50%, calc(100% + 24px));
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  padding: 10px 14px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.45;
  color: var(--fg);
  z-index: 30;
  box-shadow: 3px 3px 0 var(--shadow);
  transition: transform var(--duration-4) cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: auto;
  display: flex; flex-direction: column;
  overflow: hidden;
}
/* When a split (prose ↔ live snapshot) is mounted, expand the popup to
 * fit two columns side-by-side at every viewport width that supports
 * it (panel-mandala bounds the absolute width naturally). */
.detail-slide.has-snapshot { max-width: 720px; }
.detail-slide.open    { transform: translate(-50%, 0); }
.detail-slide.closing { transform: translate(-50%, calc(100% + 24px)); transition-duration: 0.18s; }

.detail-slide .detail-close {
  position: absolute;
  top: 4px; right: 6px;
  background: none;
  border: none;
  color: var(--fg-faint);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  transition: color var(--duration-2) ease, background var(--duration-2) ease;
}
.detail-slide .detail-close:hover {
  color: var(--bg);
  background: var(--a-rose);
}

.detail-slide .detail-head {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  margin-bottom: 8px;
  padding-bottom: 6px;
  padding-right: 28px;
  border-bottom: 1px solid var(--border);
}
.detail-slide .detail-id {
  color: var(--a-violet-text);
  font-weight: 700;
  font-size: 12.5px;
}
.detail-slide .detail-tier {
  color: var(--a-amber-text);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-weight: 600;
}

.detail-slide .detail-body p { margin: 5px 0; color: var(--fg); }
.detail-slide .detail-body code {
  background: var(--elev);
  color: var(--a-amber-text);
  padding: 1px 5px;
  border-radius: var(--r-2);
  font-size: 10.5px;
}
.detail-slide .detail-body b { color: var(--a-trunk-text); font-weight: 700; }
.detail-slide .detail-foot {
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px dashed var(--border);
  color: var(--fg-dim);
  font-size: 10.5px;
  font-style: italic;
}

/* ─────────────── Split popup · prose ↔ live sympy snapshot ───────────
 * Always side-by-side when has-snapshot is present (panel-mandala
 * clamps the absolute width naturally). Each column scrolls internally
 * so the popup itself stays fixed-height regardless of content depth. */
.detail-slide .detail-split {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 10px;
  flex: 1; min-height: 0;
  overflow: hidden;
}
@media (max-width: 480px) {
  /* Phones — single-column stack but snapshot first (live data is the
   * focus; prose is the reference). */
  .detail-slide .detail-split { grid-template-columns: 1fr; }
  .detail-slide .detail-prose  { order: 2; }
  .detail-slide .detail-symbolic { order: 1; }
}
.detail-slide .detail-prose,
.detail-slide .detail-symbolic {
  min-width: 0; min-height: 0;
  overflow-y: auto;
  padding-right: 4px;
}
.detail-slide .detail-symbolic {
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  font-family: var(--font-mono);
}
.detail-slide .detail-symbolic-head {
  margin: 0 0 6px;
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.10em;
  color: var(--a-violet-text); font-weight: 700;
}
.detail-slide .detail-symbolic-body { font-size: 10.5px; color: var(--fg); }
.detail-slide .symbolic-section-head {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--fg-dim); margin: 8px 0 4px; font-weight: 600;
}

/* Seed-vector + matrix tables — monospace, tight, mathematical */
.detail-slide .symbolic-vector,
.detail-slide .symbolic-matrix,
.detail-slide .symbolic-table {
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 10.5px;
  width: auto;
}
.detail-slide .symbolic-vector { margin: 4px 0 6px; }
.detail-slide .symbolic-vector td {
  padding: 3px 8px;
  border: 1px solid var(--border);
  text-align: center;
  background: var(--panel);
  color: var(--a-trunk-text);
  font-weight: 700;
}
.detail-slide .symbolic-matrix { margin: 4px 0 8px; }
.detail-slide .symbolic-matrix td {
  padding: 2px 7px;
  border: 1px solid var(--border);
  text-align: center;
  color: var(--fg);
  background: var(--panel);
  min-width: 22px;
}
.detail-slide .symbolic-table { width: 100%; margin: 4px 0 8px; }
.detail-slide .symbolic-table th {
  padding: 3px 6px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  color: var(--fg-faint);
  font-weight: 600;
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.08em;
}
.detail-slide .symbolic-table td {
  padding: 3px 6px;
  border-bottom: 1px solid var(--border);
  vertical-align: baseline;
}
.detail-slide .symbolic-table .op-i      { color: var(--fg-faint); width: 18px; }
.detail-slide .symbolic-table .op-name   { color: var(--a-violet-text); font-weight: 600; }
.detail-slide .symbolic-table .op-result { color: var(--a-teal-text); }
.detail-slide .symbolic-table .op-pass   { color: var(--a-teal-text); width: 22px; text-align: center; font-weight: 700; }
.detail-slide .symbolic-table tr.row-fail .op-pass,
.detail-slide .symbolic-table .fail { color: var(--a-rose-text); }
.detail-slide .symbolic-table tr.sum-row td,
.detail-slide .symbolic-table tr.result-row td {
  border-bottom: none;
  padding-top: 6px;
  color: var(--a-trunk-text);
  font-weight: 600;
}
.detail-slide .symbolic-table tr.sum-row td:first-child,
.detail-slide .symbolic-table tr.result-row td:first-child {
  color: var(--fg-dim);
  font-weight: 400;
}

/* Glossary block — plain-English decoder for the symbols above */
.detail-slide hr.detail-rule {
  border: none;
  border-top: 1px dashed var(--border);
  margin: 10px 0 6px;
}
.detail-slide .detail-glossary-head {
  margin: 0 0 4px;
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.10em;
  color: var(--a-mint-text); font-weight: 700;
}
.detail-slide .detail-glossary {
  margin: 4px 0 6px; padding: 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 12px;
  font-size: 10.5px;
}
.detail-slide .detail-glossary dt {
  font-family: var(--font-mono);
  color: var(--a-trunk-text); font-weight: 700;
  white-space: nowrap;
}
.detail-slide .detail-glossary dd {
  margin: 0; color: var(--fg-dim);
}
.detail-slide .detail-pre {
  margin: 6px 0 2px;
  padding: 6px 8px;
  background: var(--elev);
  border-radius: var(--r-2);
  white-space: pre-wrap;
  font-size: 9.5px;
  color: var(--fg-dim);
  max-height: 140px;
  overflow-y: auto;
  border: 1px solid var(--border);
}


/* ───────── MD-required gate (homoiconic enforcement) ─────────
 * The wheel cannot render without the framework source. When the
 * MD is missing, .panel-mandala.gated hides the canvas + overlays
 * and surfaces the gate explanation. Load the MD ⇒ gate dissolves. */
.md-gate {
  position: absolute;
  inset: 0;
  background: var(--bg);
  z-index: 50;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.panel-mandala.gated .md-gate { display: flex; }
.panel-mandala.gated #three-canvas,
.panel-mandala.gated .axiom-inputs,
.panel-mandala.gated ~ .predictions-overlay { visibility: hidden; }

.md-gate-body {
  max-width: 460px;
  padding: 24px 28px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--a-violet);
  border-radius: var(--r-4);
  box-shadow: 4px 4px 0 var(--shadow);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--fg);
}
.md-gate-symbol {
  font-size: 38px;
  font-weight: 700;
  color: var(--a-violet-text);
  text-align: center;
  margin-bottom: 4px;
  letter-spacing: 0.04em;
}
.md-gate-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--a-amber-text);
  text-align: center;
  font-weight: 700;
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px dashed var(--border);
}
.md-gate-text {
  color: var(--fg-dim);
  font-size: 11.5px;
}
.md-gate-text code {
  background: var(--elev);
  color: var(--a-amber-text);
  padding: 1px 5px;
  border-radius: var(--r-2);
}
.md-gate-text b { color: var(--a-amber-text); }

/* (Auto-run pip removed at user request · was a violet "homoiconic ·
 *  MD ⇒ engine" pill in the canvas topbar. The stream is still bound
 *  to MD presence, just no UI badge for it.) */


/* ─────────────────── Debug drawer · REMOVED ──────────────────────────
 * The fixed-bottom debug drawer was retired (its functionality moved
 * into the unified perf footer + the homoiconic console panels). The
 * old CSS reserved 55vh of body padding-bottom for the open-drawer
 * state and 30px for the collapsed state. With no .debug-drawer in
 * the HTML AND no `dbg-collapsed` class on <body>, the
 *     body:not(.dbg-collapsed) { padding-bottom: 55vh; }
 * rule matched and produced ~half a viewport of phantom empty space
 * BELOW the footer on every page (especially obvious on mobile).
 *
 * Body padding rules deleted. The leftover .debug-drawer rules below
 * are dead-code (no element matches them); leaving them named so a
 * future search lands on this comment, but they cost nothing in the
 * cascade because the selectors miss every element on the page. */

.debug-drawer {
  position: fixed; left: 0; right: 0; bottom: 0;
  height: 55vh; max-height: 60vh;
  background: var(--panel);
  border-top: 1px solid var(--border);
  box-shadow: 0 -8px 18px var(--shadow);
  display: flex; flex-direction: column;
  z-index: 50;
  font-family: var(--font-mono);
  transition: height var(--duration-3) ease-out;
}
@media (min-width: 640px) {
  .debug-drawer { height: 240px; max-height: 50vh; }
}
.debug-drawer[aria-expanded="false"] { height: 36px; }

.debug-head {
  display: flex; align-items: center; gap: 12px;
  padding: 0 14px; height: 30px; flex-shrink: 0;
  background: var(--elev);
  border-bottom: 1px solid var(--border);
  cursor: pointer; user-select: none;
}
.debug-drawer[aria-expanded="false"] .debug-head { border-bottom-color: transparent; }
.debug-head h2 {
  margin: 0;
  font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--a-violet-text); font-weight: 700;
  white-space: nowrap;
  flex-shrink: 0;
}
.debug-toggle {
  background: transparent; border: none; color: var(--a-violet-text);
  padding: 0 4px; font-size: 12px; cursor: pointer; line-height: 1;
  transition: transform var(--duration-3) ease;
  border-radius: var(--r-2);
}
.debug-toggle:hover { color: var(--a-amber-text); background: transparent; border: none; }
.debug-drawer[aria-expanded="false"] .debug-toggle { transform: rotate(-90deg); }

.debug-counter {
  font-size: 9.5px; color: var(--fg-faint);
  font-family: var(--font-mono);
  padding: 1px 7px; border-radius: var(--r-3);
  background: var(--panel); border: 1px solid var(--border);
  min-width: 24px; text-align: center;
}

.debug-filters {
  display: flex; gap: 4px; margin-left: auto; align-items: center;
  flex-wrap: nowrap;
}
.dbg-filter {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase;
  font-weight: 600;
  color: var(--fg-dim);
  padding: 2px 7px; border-radius: var(--r-2);
  border: 1px solid var(--border);
  background: transparent;
  cursor: pointer;
  user-select: none;
  line-height: 1.2;
}
.dbg-filter input { margin: 0; width: 9px; height: 9px; accent-color: currentColor; cursor: pointer; }
.dbg-filter:hover { background: var(--panel); }
/* The category color tints the filter chip — same palette as the .dbg-line border */
.dbg-filter.f-md      { color: var(--a-amber-text);  border-color: var(--a-amber-text);  }
.dbg-filter.f-token   { color: var(--a-teal-text);   border-color: var(--a-teal-text);   }
.dbg-filter.f-bracket { color: var(--a-violet-text); border-color: var(--a-violet-text); }
.dbg-filter.f-witness { color: var(--a-sky-text);    border-color: var(--a-sky-text);    }
.dbg-filter.f-reflex  { color: var(--a-trunk-text);  border-color: var(--a-trunk-text);  }
.dbg-filter.f-viz     { color: var(--a-rose-text);  border-color: var(--a-rose-text);  }

.debug-clear {
  font-size: 9.5px; padding: 2px 8px; border-radius: var(--r-2);
  background: transparent; color: var(--fg-dim);
  border: 1px solid var(--border);
  font-family: var(--font-mono); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
  cursor: pointer; line-height: 1.2;
}
.debug-clear:hover { color: var(--a-rose-text); border-color: var(--a-rose-text); background: transparent; }

.debug-stream {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 6px 12px 8px;
  font-family: var(--font-mono); font-size: 10.5px; line-height: 1.55;
  background: var(--bg);
}
.debug-stream::-webkit-scrollbar { width: 8px; }
.debug-stream::-webkit-scrollbar-track { background: var(--bg); }
.debug-stream::-webkit-scrollbar-thumb { background: var(--border); border-radius: var(--r-2); }

.dbg-line {
  display: grid;
  grid-template-columns: 88px 60px 1fr;
  gap: 10px;
  align-items: baseline;
  padding: 1px 0 1px 8px;
  margin: 1px 0;
  border-left: 2px solid var(--border);
}
.dbg-line.cat-md      { border-left-color: var(--a-amber-text);  }
.dbg-line.cat-token   { border-left-color: var(--a-teal-text);   }
.dbg-line.cat-bracket { border-left-color: var(--a-violet-text); }
.dbg-line.cat-witness { border-left-color: var(--a-sky-text);    }
.dbg-line.cat-reflex  { border-left-color: var(--a-trunk-text);  }
.dbg-line.cat-viz     { border-left-color: var(--a-rose-text);  }
.dbg-line.fail        { border-left-color: var(--a-rose-text);   }

.dbg-time {
  color: var(--fg-faint);
  font-size: 9.5px;
  font-variant-numeric: tabular-nums;
}
.dbg-cat {
  font-weight: 700; font-size: 9.5px;
  letter-spacing: 0.06em; text-transform: uppercase;
}
.dbg-line.cat-md      .dbg-cat { color: var(--a-amber-text);  }
.dbg-line.cat-token   .dbg-cat { color: var(--a-teal-text);   }
.dbg-line.cat-bracket .dbg-cat { color: var(--a-violet-text); }
.dbg-line.cat-witness .dbg-cat { color: var(--a-sky-text);    }
.dbg-line.cat-reflex  .dbg-cat { color: var(--a-trunk-text);  }
.dbg-line.cat-viz     .dbg-cat { color: var(--a-rose-text);  }
.dbg-line.fail        .dbg-cat { color: var(--a-rose-text);   }

.dbg-msg { color: var(--fg); }
/* Inline accents inside dbg-msg — same palette so callers can highlight values */
.dbg-msg .em      { color: var(--a-trunk-text);  font-weight: 700; }
.dbg-msg .val     { color: var(--a-teal-text);   font-weight: 600; }
.dbg-msg .key     { color: var(--a-violet-text); font-weight: 600; }
.dbg-msg .anchor  { color: var(--a-sky-text); }
.dbg-msg .empty   { color: var(--a-rose-text);   font-weight: 700; }
.dbg-msg .ok      { color: var(--a-teal-text);   font-weight: 700; }
.dbg-msg .warn    { color: var(--a-amber-text);  font-weight: 700; }
.dbg-msg .ghost   { color: var(--fg-faint); }
.dbg-msg code {
  background: var(--elev); color: var(--a-amber-text);
  padding: 0 4px; border-radius: var(--r-2); font-size: 10px;
}

/* When a filter is unchecked, hide its lines */
.debug-stream.hide-md      .dbg-line.cat-md      { display: none; }
.debug-stream.hide-token   .dbg-line.cat-token   { display: none; }
.debug-stream.hide-bracket .dbg-line.cat-bracket { display: none; }
.debug-stream.hide-witness .dbg-line.cat-witness { display: none; }
.debug-stream.hide-reflex  .dbg-line.cat-reflex  { display: none; }
.debug-stream.hide-viz     .dbg-line.cat-viz     { display: none; }

/* (legacy 1100 px override removed — drawer body padding is now part of
 * the mobile-first cascade at the top of the debug-drawer block.) */

/* ════════════════════════════════════════════════════════════════════════
 * ⑩ NUMERICAL · PennyLane third-route section styling
 * ════════════════════════════════════════════════════════════════════════
 * Visual identity: sky-blue accent (distinct from the violet/teal/amber
 * of the symbolic/audio/wheel surfaces). The section's job is to look
 * like an "instrument readout" — engine row says which backend, the
 * three data rows show the live values, and the ticker is a mono log
 * scrolling the most recent qml.math operations.
 *
 * Color discipline: every color here is composed from the v1.0 LOCKED
 * palette tokens via color-mix() — no hard-coded hex.
 * ════════════════════════════════════════════════════════════════════════ */
.homo-console .hc-section.hc-numerical {
  /* A faint sky tint along the left edge so the section visually
   * announces "this is the third route, not part of the symbolic
   * chain above." */
  border-left: 2px solid color-mix(in srgb, var(--a-sky) 60%, transparent);
  padding-left: 10px;
}

/* hc-pulse-warn · used when PennyLane is missing and we're on
 * numpy fallback (still functional, but the user should see the
 * downgrade signal). Sits between hc-pulse-ok (green) and
 * hc-pulse-fail (red) — amber says "running but not ideal". */
.homo-console .hc-pulse.pulse-warn {
  background: color-mix(in srgb, var(--a-amber) 80%, transparent);
  box-shadow: 0 0 6px color-mix(in srgb, var(--a-amber) 40%, transparent);
}

/* Bracket-norm + closure-residual values get a green/red tint based
 * on whether the verification passed. The classes are toggled by the
 * SSE handler in app.js (hc-plv-ok / hc-plv-fail). */
.hc-plv-ok {
  color: color-mix(in srgb, var(--a-teal-text) 80%, var(--fg));
  font-weight: 600;
}
.hc-plv-fail {
  color: color-mix(in srgb, var(--a-rose-text) 80%, var(--fg));
  font-weight: 600;
}

/* Wick-pair grid — 3 small pills laid out in a single row that
 * wraps gracefully on narrow widths. Each pill shows the pair label
 * + the audio integer ratio + a ✓ when the bracket is zero. */
.homo-console .hc-plv-wick-grid {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: flex-end;
}
.homo-console .hc-plv-wick {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--a-sky) 8%, var(--bg));
  font-family: var(--font-mono);
  font-size: 9px;
  white-space: nowrap;
  color: var(--fg-dim);
  letter-spacing: 0.01em;
}
.homo-console .hc-plv-wick.hc-plv-ok {
  background: color-mix(in srgb, var(--a-teal) 14%, var(--bg));
  border-color: color-mix(in srgb, var(--a-teal-text) 50%, var(--border));
  color: color-mix(in srgb, var(--a-teal-text) 80%, var(--fg));
}
.homo-console .hc-plv-wick.hc-plv-fail {
  background: color-mix(in srgb, var(--a-rose) 14%, var(--bg));
  border-color: color-mix(in srgb, var(--a-rose-text) 50%, var(--border));
  color: color-mix(in srgb, var(--a-rose-text) 80%, var(--fg));
}

/* Live-ops ticker · the section's most kinetic surface. Each event
 * pushes a one-line entry to the top; the stack is capped at 5 in JS;
 * each new entry plays a brief slide-down + fade-in via the
 * hc-plv-tick-new modifier (one frame in app.js, then removed). */
.homo-console .hc-plv-ticker-row {
  align-items: stretch;
}
.homo-console .hc-plv-ticker {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  line-height: 1.4;
  color: var(--fg-faint);
  text-align: right;
  max-height: 70px;             /* exactly 5 lines visible */
  overflow: hidden;
  width: 100%;
}
.homo-console .hc-plv-tick {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Faded older entries so the eye reads top-down; oldest at the
   * bottom blends into the panel background. */
  opacity: 0.95;
  transition: opacity var(--duration-5) ease-out;
}
/* Aged entries dim progressively so the freshest line is brightest. */
.homo-console .hc-plv-tick:nth-child(2) { opacity: 0.78; }
.homo-console .hc-plv-tick:nth-child(3) { opacity: 0.58; }
.homo-console .hc-plv-tick:nth-child(4) { opacity: 0.40; }
.homo-console .hc-plv-tick:nth-child(5) { opacity: 0.25; }
/* The placeholder ("awaiting first numerical event…") sits italic
 * + dim so it visually disappears the moment the first real op fires. */
.homo-console .hc-plv-tick-empty {
  font-style: italic;
  color: var(--fg-faint);
  opacity: 0.7;
}
/* Slide-down + fade-in for newly pushed entries. Played once on
 * insertion (app.js strips the .hc-plv-tick-new class on the next
 * animation frame). */
.homo-console .hc-plv-tick.hc-plv-tick-new {
  animation: hc-plv-tick-in 280ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes hc-plv-tick-in {
  0%   { transform: translateY(-6px); opacity: 0; color: color-mix(in srgb, var(--a-sky-text) 80%, var(--fg)); }
  80%  { color: color-mix(in srgb, var(--a-sky-text) 50%, var(--fg)); }
  100% { transform: translateY(0); opacity: 0.95; color: var(--fg-faint); }
}

/* ════════════════════════════════════════════════════════════════════════
 * ⑪ FORMAL · Lean 4 / Mathlib fourth-route section styling
 * ════════════════════════════════════════════════════════════════════════
 * Visual identity: rose accent (completes the four-route quartet —
 * violet/amber/sky/rose for symbolic/audible/numerical/formal). Mirrors
 * the structural patterns from ⑩ NUMERICAL but with rose hues so the
 * two are visually distinguishable at a glance.
 *
 * The theorem-chip grid is the section's most kinetic surface — small
 * mono-text pills, one per proved theorem, that the user can hover for
 * the full proof-term tactic. Verified theorems get a green-rose blend;
 * source-parsed (no toolchain) get an amber-rose blend.
 * ════════════════════════════════════════════════════════════════════════ */
.homo-console .hc-section.hc-formal {
  /* Rose left-edge stripe so the section announces "fourth route". */
  border-left: 2px solid color-mix(in srgb, var(--a-rose) 60%, transparent);
  padding-left: 10px;
}

/* Theorem chip grid — flexbox, wraps gracefully. */
.homo-console .hc-lean-thm-grid {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: flex-end;
  max-width: 70%;        /* leave room for the "theorems" key on the left */
}
.homo-console .hc-lean-thm {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--a-rose) 8%, var(--bg));
  font-family: var(--font-mono);
  font-size: 8.5px;
  white-space: nowrap;
  color: var(--fg-dim);
  letter-spacing: 0.01em;
  cursor: help;        /* chip has a hover tooltip with proof tactic */
  transition: background var(--duration-2) ease, border-color var(--duration-2) ease;
}
.homo-console .hc-lean-thm:hover {
  background: color-mix(in srgb, var(--a-rose) 18%, var(--bg));
  border-color: color-mix(in srgb, var(--a-rose-text) 50%, var(--border));
}
/* Verified by lake build · solid teal-tinted edge for trust signal. */
.homo-console .hc-lean-thm.hc-lean-thm-verified {
  border-color: color-mix(in srgb, var(--a-teal-text) 50%, var(--border));
  background: color-mix(in srgb, var(--a-teal) 12%, var(--bg));
  color: color-mix(in srgb, var(--a-teal-text) 80%, var(--fg));
}
/* Source-parsed (toolchain not installed) · amber-rose tint to signal
   the trust degradation. The theorem might still be true; we just
   didn't run the kernel to confirm. */
.homo-console .hc-lean-thm.hc-lean-thm-parsed {
  border-color: color-mix(in srgb, var(--a-amber-text) 30%, var(--border));
  background: color-mix(in srgb, var(--a-amber) 8%, var(--bg));
}
/* GAP-closed [open] theorem · violet tint distinguishes it from BOTH
 * kernel-verified (teal) AND source-parsed (amber). The construction-
 * level closure is real corroboration but a different epistemic path
 * than Lean kernel-check; the distinct color avoids overstating it
 * while still showing the cross-route confirmation visually. */
.homo-console .hc-lean-thm.hc-lean-thm-gap-closed {
  border-color: color-mix(in srgb, var(--a-violet-text) 50%, var(--border));
  background: color-mix(in srgb, var(--a-violet) 14%, var(--bg));
  color: color-mix(in srgb, var(--a-violet-text) 80%, var(--fg));
  /* Tiny ✓ glyph as a visual hint that this is an external closure.
   * The full provenance (which GAP key closed it) lives in the title. */
}
.homo-console .hc-lean-thm.hc-lean-thm-gap-closed::before {
  content: "✓";
  margin-right: 4px;
  color: var(--a-violet-text);
  opacity: 0.75;
}
/* Bonus marker on the proved-count cell · "29+4" where +4 is GAP. */
.homo-console .hc-lean-gap-bonus {
  color: var(--a-violet-text);
  font-weight: 600;
  margin-left: 2px;
  font-size: 0.85em;
}
.homo-console .hc-lean-thm-empty {
  font-style: italic;
  color: var(--fg-faint);
  font-size: 9px;
}

/* Live ticker · same shape as ⑩ NUMERICAL but with rose hue. */
.homo-console .hc-lean-ticker-row {
  align-items: stretch;
}
.homo-console .hc-lean-ticker {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  line-height: 1.4;
  color: var(--fg-faint);
  text-align: right;
  max-height: 70px;
  overflow: hidden;
  width: 100%;
}
.homo-console .hc-lean-tick {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0.95;
  transition: opacity var(--duration-5) ease-out;
}
.homo-console .hc-lean-tick:nth-child(2) { opacity: 0.78; }
.homo-console .hc-lean-tick:nth-child(3) { opacity: 0.58; }
.homo-console .hc-lean-tick:nth-child(4) { opacity: 0.40; }
.homo-console .hc-lean-tick:nth-child(5) { opacity: 0.25; }
.homo-console .hc-lean-tick-empty {
  font-style: italic;
  color: var(--fg-faint);
  opacity: 0.7;
}
.homo-console .hc-lean-tick.hc-lean-tick-new {
  animation: hc-lean-tick-in 280ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes hc-lean-tick-in {
  0%   { transform: translateY(-6px); opacity: 0; color: color-mix(in srgb, var(--a-rose-text) 80%, var(--fg)); }
  80%  { color: color-mix(in srgb, var(--a-rose-text) 50%, var(--fg)); }
  100% { transform: translateY(0); opacity: 0.95; color: var(--fg-faint); }
}

/* ════════════════════════════════════════════════════════════════════════
 * ⑨ DERIVATION WEB · NetworkX dependency graph + Cytoscape render
 * ════════════════════════════════════════════════════════════════════════
 * Visual identity: mint accent — sits outside the four-route quartet
 * (violet/amber/sky/rose) because the graph is a structural view, not
 * a verification route. The graph itself is rendered by Cytoscape.js
 * inside .hc-net-cy; the styling here covers the SECTION CHROME (the
 * frame, legend, hub chips, selection display) — actual node/edge
 * styles live in the cytoscape style array in app.js so they can pull
 * the live theme palette via getComputedStyle.
 *
 * The graph canvas is sized at 320 px tall — large enough that the
 * 31-node framework structure is readable, small enough that it
 * doesn't dominate the panel. Cytoscape's built-in pan/zoom/drag
 * gives the user infinite working room within that frame.
 * ════════════════════════════════════════════════════════════════════════ */
.homo-console .hc-section.hc-network {
  /* Mint left-edge stripe so the section announces "graph view". */
  border-left: 2px solid color-mix(in srgb, var(--a-mint) 60%, transparent);
  padding-left: 10px;
}

/* DAG check badge inline · green ✓ / rose ✗ for is-DAG. */
.homo-console .hc-net-ok {
  color: color-mix(in srgb, var(--a-teal-text) 80%, var(--fg));
  font-weight: 700;
}
.homo-console .hc-net-fail {
  color: color-mix(in srgb, var(--a-rose-text) 80%, var(--fg));
  font-weight: 700;
}

/* Hub chips · the over-determination leaderboard. Each chip is one
 * convergence value (42, 18, 168) with its route count. Click jumps
 * the cytoscape graph to highlight that hub's incoming routes. */
.homo-console .hc-net-hubs {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: flex-end;
  max-width: 70%;
}
.homo-console .hc-net-hub-chip {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid color-mix(in srgb, var(--a-rose) 50%, var(--border));
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--a-rose) 12%, var(--bg));
  font-family: var(--font-mono);
  font-size: 9px;
  color: color-mix(in srgb, var(--a-rose-text) 80%, var(--fg));
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--duration-2) ease, transform var(--duration-2) ease;
}
.homo-console .hc-net-hub-chip:hover {
  background: color-mix(in srgb, var(--a-rose) 22%, var(--bg));
  transform: translateY(-1px);
}
.homo-console .hc-net-hub-empty {
  font-style: italic;
  font-size: 9px;
  color: var(--fg-faint);
}

/* Most-cited chips · same shape as hubs but mint-tinted. */
.homo-console .hc-net-cited {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: flex-end;
  max-width: 70%;
}
.homo-console .hc-net-cited-chip {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid color-mix(in srgb, var(--a-teal) 30%, var(--border));
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--a-teal) 8%, var(--bg));
  font-family: var(--font-mono);
  font-size: 8.5px;
  color: var(--fg-dim);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--duration-2) ease;
}
.homo-console .hc-net-cited-chip:hover {
  background: color-mix(in srgb, var(--a-teal) 18%, var(--bg));
}
.homo-console .hc-net-cited-empty {
  font-style: italic;
  font-size: 9px;
  color: var(--fg-faint);
}

/* The cytoscape canvas + its surrounding frame. Position relative so
 * the legend overlay can absolute-position to the bottom-right. */
.homo-console .hc-net-cy-wrap {
  position: relative;
  margin: 6px 0 4px 0;
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--bg) 70%, var(--panel));
  overflow: hidden;
}
.homo-console .hc-net-cy {
  width: 100%;
  height: 400px;             /* bumped from 320 to give breadthfirst layout breathing room */
  position: relative;
  /* MOBILE: same single-finger-scrolls-page / two-finger-interacts
   * pattern as the wheel canvas. Cytoscape's internal handlers will
   * still receive two-finger gestures for pinch-zoom-the-graph and
   * node-drag uses click+drag (synthesized from touchstart+touchmove
   * which our pan-y allows once it determines the gesture isn't a
   * vertical pan). The fullscreen-expand button (⛶) remains the
   * proper way to interact intensively with the graph on a phone. */
  touch-action: pan-y;
}
@media (min-width: 1100px) {
  /* Desktop: full graph interaction (wheel zoom in cytoscape, drag,
   * etc.). Mouse events are unaffected by touch-action; this flip
   * only matters for trackpads. */
  .homo-console .hc-net-cy { touch-action: none; }
}
/* When the graph is expanded to full-viewport overlay mode, lock
 * touch-action so the graph absorbs everything (the user explicitly
 * asked for full graph interaction). */
.homo-console .hc-net-cy-wrap[data-expanded="1"] .hc-net-cy {
  touch-action: none;
}
.homo-console .hc-net-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-style: italic;
  font-size: 10px;
  color: var(--fg-faint);
}

/* Action buttons · expand + relayout, top-right corner.
 * .hc-net-actions stacks them horizontally with a small gap.
 * Each button is a 22×22 round, mint-tinted affordance. */
.homo-console .hc-net-actions {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 10;
  display: inline-flex;
  gap: 4px;
}
.homo-console .hc-net-relayout,
.homo-console .hc-net-expand {
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--a-mint) 50%, var(--border));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  color: color-mix(in srgb, var(--a-mint-text) 80%, var(--fg));
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  transition: transform var(--duration-3) ease, background var(--duration-2) ease;
  user-select: none;
}
.homo-console .hc-net-relayout:hover {
  background: color-mix(in srgb, var(--a-mint) 22%, var(--bg));
  transform: rotate(180deg);
}
.homo-console .hc-net-expand {
  font-size: 11px;
}
.homo-console .hc-net-expand:hover {
  background: color-mix(in srgb, var(--a-mint) 22%, var(--bg));
  transform: scale(1.1);
}

/* Close button · only visible in expanded mode. Sits at the
 * top-right of the fullscreen overlay. */
.homo-console .hc-net-close {
  position: absolute;
  top: 12px;
  right: 16px;
  z-index: 11;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  color: var(--fg);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  display: none;            /* hidden by default; shown when expanded */
  transition: background var(--duration-2) ease, transform var(--duration-3) ease;
  user-select: none;
}
.homo-console .hc-net-close:hover {
  background: color-mix(in srgb, var(--a-rose) 22%, var(--bg));
  border-color: color-mix(in srgb, var(--a-rose-text) 50%, var(--border));
  transform: scale(1.1);
}

/* ── Expand-mode · cytoscape canvas pops out to full viewport ───────
 * data-expanded="1" on the wrapper triggers a fixed-position overlay
 * filling 92% of the viewport. The graph re-fits to its new size on
 * the JS side via cy.resize() + cy.fit() after the CSS transition.
 * z-index: 9000 sits above the homoiconic console + wheel canvas
 * but below modal popups (which use z-index 10000+).
 * ─────────────────────────────────────────────────────────────────── */
.homo-console .hc-net-cy-wrap[data-expanded="1"] {
  position: fixed;
  top:    4vh;
  left:   4vw;
  width:  92vw;
  height: 92vh;
  z-index: 9000;
  /* Solid · was 96% transparent + blur(8px). Both removed — when the
   * network graph is expanded it covers most of the viewport; the
   * 4% transparency wasn't visible behind it and the blur was
   * needlessly expensive. */
  background: var(--bg);
  backdrop-filter: none;
  border: 1px solid color-mix(in srgb, var(--a-mint) 40%, var(--border));
  border-radius: var(--r-4);
  box-shadow: 0 20px 80px color-mix(in srgb, var(--shadow) 50%, transparent);
  padding: 8px;
  overflow: hidden;
  animation: hc-net-expand-in var(--duration-4) cubic-bezier(0.34, 1.0, 0.64, 1);
}
.homo-console .hc-net-cy-wrap[data-expanded="1"] .hc-net-cy {
  height: calc(92vh - 16px);
}
.homo-console .hc-net-cy-wrap[data-expanded="1"] .hc-net-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* Backdrop · dims the rest of the page when graph is expanded.
 * Activated by the body class hc-net-expanded-active set in JS. */
body.hc-net-expanded-active::before {
  content: "";
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--shadow) 50%, transparent);
  z-index: 8500;
  animation: hc-net-backdrop-in 240ms ease-out;
}
@keyframes hc-net-expand-in {
  from { transform: scale(0.92); opacity: 0; }
  to   { transform: scale(1.0);  opacity: 1; }
}
@keyframes hc-net-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .homo-console .hc-net-cy-wrap[data-expanded="1"] { animation: none; }
  body.hc-net-expanded-active::before { animation: none; }
  .homo-console .hc-net-expand:hover,
  .homo-console .hc-net-close:hover { transform: none; }
}

/* Legend overlay · sits at bottom-right of the canvas, faint enough
 * not to obstruct the graph but readable for color-blind users. */
.homo-console .hc-net-legend {
  position: absolute;
  bottom: 6px;
  right: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  color: var(--fg-faint);
  pointer-events: none;
  background: color-mix(in srgb, var(--bg) 75%, transparent);
  padding: 3px 6px;
  border-radius: var(--r-2);
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
}
.homo-console .hc-net-legend-item {
  white-space: nowrap;
}
.homo-console .hc-net-legend-item[data-type="source"]      { color: var(--fg); }
.homo-console .hc-net-legend-item[data-type="leaf"]        { color: color-mix(in srgb, var(--a-teal-text)   80%, var(--fg)); }
.homo-console .hc-net-legend-item[data-type="static"]      { color: var(--fg-dim); }
.homo-console .hc-net-legend-item[data-type="claim"]       { color: color-mix(in srgb, var(--a-violet-text) 80%, var(--fg)); }
.homo-console .hc-net-legend-item[data-type="convergence"] { color: color-mix(in srgb, var(--a-rose-text)   90%, var(--fg)); font-weight: 600; }
.homo-console .hc-net-legend-item[data-type="prediction"]  { color: color-mix(in srgb, var(--a-amber-text)  80%, var(--fg)); }

/* Selection display · the row below the graph showing what was just
 * clicked + its provenance/consequences depths. */
.homo-console .hc-net-sel {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--fg-dim);
  text-align: right;
  cursor: help;
}
.homo-console .hc-net-sel-type {
  color: var(--fg-faint);
  font-style: italic;
  font-size: 9px;
}

/* Reduced-motion override · disable the pulse animation for users
 * who've requested less motion. The static border-color flip still
 * provides feedback. */
@media (prefers-reduced-motion: reduce) {
  .homo-console .hc-net-hub-chip:hover { transform: none; }
}

/* ════════════════════════════════════════════════════════════════════════
 * ② EVALUATION RADARS · twin pentagon widget
 * ════════════════════════════════════════════════════════════════════════
 * The framework's headline epistemic status, in one polygon. The radar
 * is positioned at the top of the homoiconic console (right after
 * SOURCE) so it's the first thing the user sees. Four colored axes
 * (violet / amber / sky / rose) match the four verification routes
 * already established elsewhere in the UI; the polygon's shape IS the
 * framework's current trust profile.
 *
 * The center % is a GEOMETRIC mean of the four axes — punishes uneven
 * coverage harder than arithmetic mean. A 1.0/1.0/1.0/0 split scores
 * 0%, not 75%. This is the honest reading: an unverified axis is a
 * real gap, not something to be averaged away.
 *
 * Per-claim grid below: 19 rows × 4 mini-pills. Each row is one
 * tracked claim; each pill is one route. Click a row → pulse the
 * corresponding node in the ⑨ DERIVATION WEB Cytoscape graph.
 * ════════════════════════════════════════════════════════════════════════ */
.homo-console .hc-section.hc-trust {
  /* Solid violet stripe — was a 5-band hard-stop border-image
   * (violet/sky/rose/mint/amber) advertising the 5-axis nature of the
   * radar. Per the canvas-only-gradient rule (even hard-stop syntax
   * counts), reduced to a single accent border. The 5-axis identity
   * is now signalled by the radar plot itself plus the per-axis legend
   * rows below — the border's job is just to mark this section as a
   * primary readout, which violet (the framework's lead accent) does. */
  border-left: 2px solid var(--a-violet);
  padding-left: 10px;
}

/* ── TWIN RADARS · side-by-side layout ─────────────────────────────── */
.homo-console .hc-twin-radars {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  align-items: start;
  margin: 4px 0 8px 0;
}
@media (max-width: 720px) {
  /* On narrow viewports stack the two radars vertically. */
  .homo-console .hc-twin-radars { grid-template-columns: 1fr; }
}
.homo-console .hc-trust-radar-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0;
}
.homo-console .hc-radar-caption {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 2px;
  cursor: help;
}
.homo-console .hc-trust-radar {
  width: 100%;
  /* viewBox expanded to "-50 0 300 200" (1.5:1) to give axis labels
   * room — RECURSIVE / NUMERICAL were clipping at the original
   * 200×200 box. */
  max-width: 280px;
  height: auto;
  color: var(--fg-faint);
}
.homo-console .hc-trust-poly {
  fill: color-mix(in srgb, var(--a-mint) 22%, transparent);
  stroke: color-mix(in srgb, var(--a-mint) 80%, var(--fg));
  stroke-width: 1.5;
  transition: all var(--duration-4) cubic-bezier(0.34, 1.0, 0.64, 1);
}
/* Credibility radar uses an azure tint to visually separate from the
 * mint-tinted math-engine radar — they answer different questions, so
 * the two polygons read as two distinct signals at a glance. */
.homo-console .hc-cred-poly {
  fill: color-mix(in srgb, var(--a-azure) 22%, transparent);
  stroke: color-mix(in srgb, var(--a-azure) 80%, var(--fg));
}

/* ── Twin-radar legend pair · two-column breakdown rows ────────────── */
.homo-console .hc-radar-legend-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin: 6px 0 10px 0;
}
@media (max-width: 720px) {
  .homo-console .hc-radar-legend-pair { grid-template-columns: 1fr; }
}
.homo-console .hc-radar-legend-head {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--fg-faint);
  padding: 3px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  margin-bottom: 3px;
  cursor: help;
}
.homo-console .hc-trust-center {
  user-select: none;
  pointer-events: none;
}

/* Per-route count rows · same shape as standard hc-row but with
 * colored keys to match the radar's axis colors. */
.homo-console .hc-section.hc-trust .hc-row b {
  color: var(--fg);
  font-weight: 700;
}

/* Per-claim grid · 19 rows × 4 pills. Compact mono. */
.homo-console .hc-trust-grid-row {
  align-items: stretch;
}
.homo-console .hc-trust-grid {
  display: flex;
  flex-direction: column;
  gap: 1.5px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  width: 100%;
  max-height: 240px;            /* scroll within if too many */
  overflow-y: auto;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: var(--r-2);
  padding: 3px 4px;
}
.homo-console .hc-trust-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 6px;
  padding: 1px 4px;
  border-radius: var(--r-2);
  cursor: pointer;
  transition: background var(--duration-2) ease;
}
.homo-console .hc-trust-row:hover {
  background: color-mix(in srgb, var(--a-mint) 12%, transparent);
}
.homo-console .hc-trust-row-id {
  color: var(--fg-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  font-size: 8.5px;
}
.homo-console .hc-trust-row-pills {
  display: inline-flex;
  gap: 2px;
  flex: 0 0 auto;
}
/* Mini-pills · 14×11 px badges, one per route. Off-state is a faint
 * outline; on-state is solid colored with white text. */
.homo-console .hc-trust-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px;
  height: 11px;
  border-radius: var(--r-1);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg-faint);
  font-size: 7.5px;
  font-weight: 700;
  letter-spacing: 0;
  transition: all var(--duration-3) ease;
}
/* Five route pills · A=algebraic, N=numerical, P=machine-checked,
 * D=structural, R=recursive. Colors match the radar's axis labels and
 * the section accents already established elsewhere in the panel. */
.homo-console .hc-trust-pill.a.on {
  background: var(--a-violet);
  border-color: var(--a-violet-text);
  color: var(--bg);
}
.homo-console .hc-trust-pill.n.on {
  background: var(--a-sky);
  border-color: var(--a-sky-text);
  color: var(--bg);
}
.homo-console .hc-trust-pill.p.on {
  background: var(--a-rose);
  border-color: var(--a-rose-text);
  color: var(--bg);
}
.homo-console .hc-trust-pill.d.on {
  background: var(--a-mint);
  border-color: var(--a-mint-text);
  color: var(--bg);
}
.homo-console .hc-trust-pill.r.on {
  background: var(--a-amber);
  border-color: var(--a-amber-text);
  color: var(--bg);
}
.homo-console .hc-trust-grid-empty {
  font-style: italic;
  color: var(--fg-faint);
  text-align: center;
  padding: 6px;
}

/* Reduced-motion override · disable the polygon-shape transition. */
@media (prefers-reduced-motion: reduce) {
  .homo-console .hc-trust-poly { transition: none; }
}

/* ════════════════════════════════════════════════════════════════════════
 * ⑫ COMPUTATIONAL · GAP finite-group verification section styling
 * ════════════════════════════════════════════════════════════════════════
 * Accent: trunk (woody green-brown). Sits adjacent to ⑪ FORMAL's rose
 * because GAP results feed the FORMAL VALIDITY axis on the evaluation radar — but
 * uses a different hue to mark "this is a separate verification route,
 * not Lean." The two together form an "all formal-class evidence"
 * pair on the panel.
 *
 * Visual vocabulary:
 *   • domain chip · per-group/algebra summary, click to filter results
 *   • result chip · individual GAP key=value, green ✓ / rose ✗ vs expected
 *   • credit chip · CLAIM_EXPR id credited on the evaluation radar (mint)
 * ════════════════════════════════════════════════════════════════════════ */
.homo-console .hc-section.hc-computational {
  border-left: 2px solid color-mix(in srgb, var(--a-trunk) 60%, transparent);
  padding-left: 10px;
}

/* Domain chip grid · one chip per (PSL_2_7 | A_5 | E_7 | E_8 | CSGI). */
.homo-console .hc-gap-domains {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: flex-end;
  max-width: 70%;
}
.homo-console .hc-gap-domain {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid var(--border);
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--a-trunk) 6%, var(--bg));
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fg-dim);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--duration-2) ease, border-color var(--duration-2) ease;
}
.homo-console .hc-gap-domain:hover {
  background: color-mix(in srgb, var(--a-trunk) 16%, var(--bg));
}
.homo-console .hc-gap-domain.ok {
  background: color-mix(in srgb, var(--a-teal) 14%, var(--bg));
  border-color: color-mix(in srgb, var(--a-teal-text) 50%, var(--border));
  color: color-mix(in srgb, var(--a-teal-text) 80%, var(--fg));
}
.homo-console .hc-gap-domain.partial {
  background: color-mix(in srgb, var(--a-amber) 12%, var(--bg));
  border-color: color-mix(in srgb, var(--a-amber-text) 40%, var(--border));
  color: color-mix(in srgb, var(--a-amber-text) 80%, var(--fg));
}
.homo-console .hc-gap-domain.unverified {
  opacity: 0.55;
  font-style: italic;
}
.homo-console .hc-gap-domain-empty {
  font-style: italic;
  font-size: 9px;
  color: var(--fg-faint);
}

/* Per-result chip grid · individual GAP returns, color-coded by match. */
.homo-console .hc-gap-results {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: flex-end;
  max-width: 75%;
}
.homo-console .hc-gap-result {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  font-size: 8px;
  color: var(--fg-dim);
  white-space: nowrap;
  cursor: help;
}
.homo-console .hc-gap-result.ok {
  background: color-mix(in srgb, var(--a-teal) 12%, var(--bg));
  border-color: color-mix(in srgb, var(--a-teal-text) 40%, var(--border));
  color: color-mix(in srgb, var(--a-teal-text) 80%, var(--fg));
}
.homo-console .hc-gap-result.miss {
  background: color-mix(in srgb, var(--a-rose) 12%, var(--bg));
  border-color: color-mix(in srgb, var(--a-rose-text) 50%, var(--border));
  color: color-mix(in srgb, var(--a-rose-text) 80%, var(--fg));
}
.homo-console .hc-gap-result-empty {
  font-style: italic;
  font-size: 9px;
  color: var(--fg-faint);
}

/* Credit chip grid · CLAIM_EXPR ids credited on the evaluation radar. */
.homo-console .hc-gap-credits {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: flex-end;
  max-width: 70%;
}
.homo-console .hc-gap-credit-chip {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid color-mix(in srgb, var(--a-mint) 50%, var(--border));
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--a-mint) 12%, var(--bg));
  font-family: var(--font-mono);
  font-size: 9px;
  color: color-mix(in srgb, var(--a-mint-text) 80%, var(--fg));
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--duration-2) ease, transform var(--duration-2) ease;
}
.homo-console .hc-gap-credit-chip:hover {
  background: color-mix(in srgb, var(--a-mint) 22%, var(--bg));
  transform: translateY(-1px);
}
.homo-console .hc-gap-credit-empty {
  font-style: italic;
  font-size: 9px;
  color: var(--fg-faint);
}

/* Reduced-motion override. */
@media (prefers-reduced-motion: reduce) {
  .homo-console .hc-gap-credit-chip:hover { transform: none; }
}

/* ════════════════════════════════════════════════════════════════════════
 * ① MASTER EQUATION · 5-route side-by-side super-section
 * ════════════════════════════════════════════════════════════════════════
 * The framework's load-bearing dynamical statement, verified in 5 media
 * displayed side-by-side. Replaces the old ① KERNEL + ⑦ BORN + ⑧ BRACKET
 * sections with one consolidated panel — the same equation seen from
 * five angles simultaneously.
 *
 * Visual vocabulary:
 *   • equation banner · centered display of the master equation
 *   • 5-column grid · one column per verification route
 *     (KERNEL violet · BORN amber · BRACKET mint · NUMERIC sky · PROVED rose)
 *   • Each column has: title + status pulse + form + status row + body
 *   • Border-image left edge: 5-band gradient announcing the 5 routes
 *
 * Compactness: each column is ~60-70 px wide on a 340 px panel, with
 * mono 8-9 px text. Power users go to the standalone ⑩/⑪/⑫ sections
 * for full details; this super-section is the consolidated headline.
 * ════════════════════════════════════════════════════════════════════════ */
.homo-console .hc-section.hc-master {
  /* Solid violet stripe — was a 5-band hard-stop border-image
   * (violet/amber/mint/sky/rose) advertising the 5 verification
   * routes. Per the canvas-only-gradient rule, reduced to a single
   * accent border. The per-route identity is signalled by the column
   * headers (KERNEL · BORN · BRACKET · NUMERIC · PROVED) and by each
   * column's `data-route` attribute driving its own border-color
   * tint (see the `.hc-master-col[data-route="..."]` rules). */
  border-left: 2px solid var(--a-violet);
  padding-left: 10px;
}

/* ════════════════════════════════════════════════════════════════════
 * EQUATION BANNER · textbook math typography
 * One color · one font · italic variables · upright operators ·
 * proportional subscripts at 0.7em (browser default). Reads as a
 * single elegant line, like a printed physics paper.
 * ════════════════════════════════════════════════════════════════════ */
/* ════════════════════════════════════════════════════════════════════
 * MASTER EQUATION BANNER · auto-fit single-line typography
 * ════════════════════════════════════════════════════════════════════
 * Strategy: render the equation in HTML with a math-quality font at
 * its NATURAL desktop size (24 px); JS measures the rendered width
 * vs the banner's container width and applies `transform: scale(N)`
 * when N < 1 to shrink-fit. The line itself never wraps (`white-
 * space: nowrap`), so the equation is GUARANTEED to be on one line
 * at every viewport width — even 280 px phone portraits.
 *
 * Font discipline · matches journal/textbook convention:
 *   • variables (Q, H, A, v, π, so)        italic via <i>
 *   • operators (=, ∀, ∈, /)               upright (default)
 *   • operator names (SO, sub-condition)   upright in subscript
 *   • braces, parens                       upright (default)
 *   • subscripts                           browser default 0.7em + shift
 *   • single color (var(--fg)) throughout · no faded sub footnote
 * Net look: an equation as it appears in a printed Phys-Rev-Letters
 * paper, exactly one line, perfectly fitting whichever device. */
.homo-console .hc-master-eq-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 6px 0;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--bg) 70%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  border-radius: var(--r-3);
  font-family: "STIX Two Math", "Latin Modern Math", "Cambria Math",
               "Times New Roman", Cambria, Georgia, serif;
  color: var(--fg);
  cursor: help;
  overflow: hidden;                          /* belt-and-suspenders */
  min-height: 44px;                          /* keeps banner stable across scales */
}
.homo-console .hc-master-eq-line {
  display: inline-block;
  white-space: nowrap;                       /* THE single-line guarantee */
  font-size: 24px;                           /* natural desktop size · JS scales DOWN as needed */
  font-weight: 400;
  letter-spacing: 0.005em;
  color: var(--fg);
  /* Initial state · JS will compute and apply the actual scale.
   * `top center` keeps the line vertically aligned to the banner's
   * top edge so larger scales don't push it below the wrap. */
  transform-origin: center center;
  transition: transform var(--duration-2) ease-out;
  will-change: transform;
}
/* Italic variables · browsers ALREADY render <i> italic, but we set
 * it explicitly so the math font's italic glyphs are guaranteed to
 * be selected (the math fonts have purpose-designed italic forms). */
.homo-console .hc-master-eq-line i {
  font-style: italic;
  font-weight: 400;
}
/* Brace · upright (operator), not italic */
.homo-console .meq-br {
  font-style: normal;
  padding: 0 0.04em;
}
/* Equals sign · upright + balanced math spacing */
.homo-console .meq-eq {
  font-style: normal;
  padding: 0 0.5em;
}
/* The condition under the closing brace · subscript with operator-style
 * upright "SO" and italic "v_0" inside. Browser default <sub> sizing
 * (0.7em) + baseline shift gives proper textbook typography. */
.homo-console .meq-cond {
  /* using <sub> · default sizing/positioning is correct */
  letter-spacing: 0.01em;
}
/* The universal-quantifier suffix · slightly smaller than the main
 * equation (0.88em) so it visually subordinates without breaking the
 * reading rhythm. */
.homo-console .meq-quant {
  font-size: 0.88em;
  font-style: normal;
  margin-left: 0.4em;
}
.homo-console .meq-quant i {
  font-style: italic;
}

/* 5-column grid · the heart of the consolidation. */
.homo-console .hc-master-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 4px;
  margin: 4px 0 2px 0;
}
.homo-console .hc-master-col {
  display: flex;
  flex-direction: column;
  padding: 4px 5px;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  min-width: 0;
  cursor: help;
  transition: background var(--duration-2) ease, border-color var(--duration-2) ease;
}
/* Per-route accent borders + hover tints. */
.homo-console .hc-master-col[data-route="kernel"]  { border-color: color-mix(in srgb, var(--a-violet-text) 30%, var(--border)); }
.homo-console .hc-master-col[data-route="born"]    { border-color: color-mix(in srgb, var(--a-amber-text)  30%, var(--border)); }
.homo-console .hc-master-col[data-route="bracket"] { border-color: color-mix(in srgb, var(--a-mint-text)   30%, var(--border)); }
.homo-console .hc-master-col[data-route="numeric"] { border-color: color-mix(in srgb, var(--a-sky-text)    30%, var(--border)); }
.homo-console .hc-master-col[data-route="proved"]  { border-color: color-mix(in srgb, var(--a-rose-text)   30%, var(--border)); }
.homo-console .hc-master-col[data-route="kernel"]:hover  { background: color-mix(in srgb, var(--a-violet) 8%, var(--bg)); }
.homo-console .hc-master-col[data-route="born"]:hover    { background: color-mix(in srgb, var(--a-amber)  8%, var(--bg)); }
.homo-console .hc-master-col[data-route="bracket"]:hover { background: color-mix(in srgb, var(--a-mint)   8%, var(--bg)); }
.homo-console .hc-master-col[data-route="numeric"]:hover { background: color-mix(in srgb, var(--a-sky)    8%, var(--bg)); }
.homo-console .hc-master-col[data-route="proved"]:hover  { background: color-mix(in srgb, var(--a-rose)   8%, var(--bg)); }

.homo-console .hc-master-col-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 3px;
  margin-bottom: 3px;
}
.homo-console .hc-master-col-title {
  font-family: var(--font-mono);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.homo-console .hc-master-col[data-route="kernel"]  .hc-master-col-title { color: color-mix(in srgb, var(--a-violet-text) 80%, var(--fg)); }
.homo-console .hc-master-col[data-route="born"]    .hc-master-col-title { color: color-mix(in srgb, var(--a-amber-text)  80%, var(--fg)); }
.homo-console .hc-master-col[data-route="bracket"] .hc-master-col-title { color: color-mix(in srgb, var(--a-mint-text)   80%, var(--fg)); }
.homo-console .hc-master-col[data-route="numeric"] .hc-master-col-title { color: color-mix(in srgb, var(--a-sky-text)    80%, var(--fg)); }
.homo-console .hc-master-col[data-route="proved"]  .hc-master-col-title { color: color-mix(in srgb, var(--a-rose-text)   80%, var(--fg)); }

.homo-console .hc-master-col-form {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fg-faint);
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.homo-console .hc-master-col-status {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fg-dim);
  margin-bottom: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
}
.homo-console .hc-master-col-content {
  flex: 1;
  font-family: var(--font-mono);
  font-size: 8.5px;
  min-height: 32px;
}
/* Mini key-value rows inside columns 3, 4, 5 (BRACKET, NUMERIC, PROVED) */
.homo-console .hc-master-col-row {
  display: flex;
  justify-content: space-between;
  font-size: 8px;
  color: var(--fg-dim);
  white-space: nowrap;
  margin-bottom: 1px;
  gap: 3px;
}
.homo-console .hc-master-col-row span:first-child {
  color: var(--fg-faint);
  text-transform: lowercase;
  letter-spacing: 0.04em;
}
.homo-console .hc-master-col-row span:last-child {
  color: var(--fg);
  font-weight: 600;
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The KERNEL + BORN columns reuse the existing dot-grid styling for
 * hc-kernel-grid + hc-born-grid (those classes are unchanged). Just
 * make sure the embedded grid fits the column's tight width. */
.homo-console .hc-master-col .hc-kernel-grid,
.homo-console .hc-master-col .hc-born-grid {
  margin-top: 0;
  padding: 0;
}

/* Responsive: stack 5 columns into 3+2 on narrow widths, then 2+2+1 on phones. */
@media (max-width: 720px) {
  .homo-console .hc-master-grid {
    grid-template-columns: repeat(3, 1fr);
  }
  .homo-console .hc-master-col[data-route="numeric"] { grid-column: 1 / 3; }
  .homo-console .hc-master-col[data-route="proved"]  { grid-column: 3 / 4; }
}
@media (max-width: 480px) {
  .homo-console .hc-master-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .homo-console .hc-master-col[data-route="kernel"],
  .homo-console .hc-master-col[data-route="born"],
  .homo-console .hc-master-col[data-route="bracket"],
  .homo-console .hc-master-col[data-route="numeric"],
  .homo-console .hc-master-col[data-route="proved"]  {
    grid-column: auto;
  }
}

/* ════════════════════════════════════════════════════════════════════════
 * STREAMING panel · 2.0 unified live-trace surface
 * ════════════════════════════════════════════════════════════════════════
 * Replaces the previous Master Equation Trace panel + the in-source
 * Living Source Viewer. Three live surfaces are now stacked into one
 * panel with a single pause control:
 *
 *   §  SOURCE      MD prose for the most-recently-cited §-anchor
 *   ⊢  DERIVATION  per-claim five-step witness chain
 *   λ  SYMPY       live str() of every sympy.Expr the engine touches
 *
 * Visual identity: violet/sky/trunk per-row accent stripes (same hues
 * the rest of the app uses for symbolic / numerical / source roles).
 * Header has the pause button + MD source meta inline. The paused
 * overlay covers the whole grid when active and shows a buffered-
 * event count so the user sees what they froze.
 * ════════════════════════════════════════════════════════════════════════ */
.panel-streams {
  position: relative;
  overflow: hidden;
}

/* ════════════════════════════════════════════════════════════════════
 * SOURCE BAR · compact MD source identity strip · ~28 px tall
 * ════════════════════════════════════════════════════════════════════
 * Lives at the top of panel-streams; replaces the previous full
 * ① SOURCE section in the homoiconic console. Carries the same data
 * (filename + line/byte/anchor counts + status + 3 buttons) but as a
 * single horizontal strip rather than a 100+ px stacked section.
 *
 * Element IDs preserved (hc-source-fname, hc-source-meta, md-load,
 * md-clear, md-default, etc.) so existing JS handlers wire up
 * unchanged. ════════════════════════════════════════════════════════ */
.panel-streams .source-bar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 12px;
  background: color-mix(in srgb, var(--elev) 80%, var(--panel));
  border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
}
.panel-streams .source-bar-label {
  color: var(--a-violet-text);
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-size: 9px;
  flex-shrink: 0;
}
.panel-streams .source-bar-fname {
  color: var(--fg);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 10px;
}
.panel-streams .source-bar-meta {
  color: var(--fg-faint);
  font-size: 9px;
  flex-shrink: 0;
}
.panel-streams .source-bar-status {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg-faint);
  flex-shrink: 0;
}
.panel-streams .source-bar-status.pulse-ok {
  background: color-mix(in srgb, var(--a-teal) 80%, transparent);
  box-shadow: 0 0 4px color-mix(in srgb, var(--a-teal) 40%, transparent);
}
.panel-streams .source-bar-status.pulse-warn {
  background: color-mix(in srgb, var(--a-amber) 80%, transparent);
  box-shadow: 0 0 4px color-mix(in srgb, var(--a-amber) 40%, transparent);
}
.panel-streams .source-bar-status.pulse-fail {
  background: color-mix(in srgb, var(--a-rose) 80%, transparent);
  box-shadow: 0 0 4px color-mix(in srgb, var(--a-rose) 40%, transparent);
}
.panel-streams .source-bar-statustext {
  color: var(--fg-faint);
  font-size: 8.5px;
  font-style: italic;
  flex-shrink: 0;
}
.panel-streams .source-bar-actions {
  display: inline-flex;
  gap: 3px;
  margin-left: auto;
  flex-shrink: 0;
}
.panel-streams .source-bar-actions .hc-btn {
  padding: 2px 6px;
  font-size: 9.5px;
  border-radius: var(--r-2);
  height: 20px;
  line-height: 1;
}
/* Mobile · drop the meta text + status label, keep filename + buttons. */
@media (max-width: 600px) {
  .panel-streams .source-bar-meta,
  .panel-streams .source-bar-statustext { display: none; }
}

.panel-streams .streams-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  background: var(--elev);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.panel-streams .streams-title {
  display: flex;
  align-items: center;
  gap: 8px;
}
.panel-streams .streams-icon {
  font-family: var(--font-mono);
  font-size: 14px;
  color: var(--a-violet-text);
  font-weight: 700;
}
.panel-streams .streams-title h2 {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--a-violet-text);
  font-weight: 700;
}
/* The streams-panel pause button is hidden — its function moved
 * to the canvas top-bar where the global pause + speed slider live.
 * Kept in the DOM (existing JS still toggles its aria-pressed state)
 * to avoid breaking integration; just visually removed. */
.panel-streams .streams-pause { display: none !important; }
/* Legacy rule kept for any custom themes that still target it. */
.panel-streams .streams-pause-legacy {
  width: 28px;
  height: 22px;
  padding: 0;
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--fg);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  user-select: none;
  transition: background var(--duration-2) ease, border-color var(--duration-2) ease, transform var(--duration-3) ease;
}
.panel-streams .streams-pause:hover {
  background: color-mix(in srgb, var(--a-violet) 14%, var(--bg));
  border-color: color-mix(in srgb, var(--a-violet-text) 50%, var(--border));
  transform: scale(1.05);
}
.panel-streams .streams-pause[aria-pressed="true"] {
  background: color-mix(in srgb, var(--a-amber) 22%, var(--bg));
  border-color: color-mix(in srgb, var(--a-amber-text) 60%, var(--border));
  color: color-mix(in srgb, var(--a-amber-text) 90%, var(--fg));
  font-weight: 700;
}
.panel-streams .streams-meta {
  display: flex;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--fg-faint);
}
.panel-streams .streams-meta-md {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 30ch;
}

/* ════════════════════════════════════════════════════════════════════
 * UNIFIED STREAM · all 3 event types interleaved in one scroll
 * ════════════════════════════════════════════════════════════════════
 * The previous 3-row design ate ~66 px of chrome (3 title bars × ~22 px
 * each) before any actual stream content was visible. The new design
 * is a single scrollable column where every entry carries a 3-px type-
 * coded left stripe — your eye reads "what kind of event" by color
 * without separator chrome consuming vertical space.
 *
 * Visual cues:
 *   • per-entry left border  (3 px solid in the type's color)
 *   • per-entry icon column  (mono char §/⊢/λ in type color)
 *   • panel left-edge band   (4 px wide, color of MOST-RECENT event,
 *                              brief 600 ms pulse on each new entry)
 *
 * Type colors (consistent with the rest of the app):
 *   source       violet
 *   derivation   sky
 *   sympy        trunk-gold
 * ════════════════════════════════════════════════════════════════════ */

/* Active band · pulses on each new event, color = most recent type. */
.panel-streams .streams-active-band {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: transparent;
  z-index: 4;
  transition: background-color var(--duration-4) ease-out, box-shadow var(--duration-5) ease-out;
  pointer-events: none;
}
/* Set both `background` AND `color` per type so the pulse animation's
 * `box-shadow: ... currentColor` picks up the band's color exactly.
 * Without color set, currentColor inherits from the panel and the
 * shadow draws white-ish — which is the bug the user pointed out. */
.panel-streams .streams-active-band.type-source     { background: var(--a-violet); color: var(--a-violet-text); }
.panel-streams .streams-active-band.type-derivation { background: var(--a-sky);    color: var(--a-sky-text); }
.panel-streams .streams-active-band.type-sympy      { background: var(--a-trunk);  color: var(--a-trunk-text); }
.panel-streams .streams-active-band.pulsing {
  animation: streams-band-pulse calc(480ms / max(0.05, var(--engine-speed))) ease-out;
}
/* Colored pulse · uses currentColor for the box-shadow (matches the
 * band's own color exactly) and stays at the SAME brightness, so the
 * pulse looks like a brief outward bleed of the band's color rather
 * than a white wash. The previous brightness(1.6)+saturate(1.4)
 * filter pushed the color toward white, which the user correctly
 * called out as "takes away from the theme". */
@keyframes streams-band-pulse {
  0%   { box-shadow: 4px 0 8px -1px currentColor; }
  100% { box-shadow: none; }
}

/* Unified stream container · single scrollable area. Replaces the 3
 * separate row bodies. Existing CSS classes `.feed-stream` and
 * `.sympy-stream` co-exist on this container so old JS scroll/insert
 * logic continues to work without rewiring.
 *
 * PERF FIX (2026 stall hunt) · COLUMN-REVERSE TRICK
 * ─────────────────────────────────────────────────
 * Before: flex-direction:column, append-to-end, scrollTop=1e9 per flush
 *   ⇒ each scrollTop write forced full layout of the panel-console
 *     subtree (17+ hc-sections), measured at 51 ms avg / 85 ms max
 *     per call · 47% of all rAF blocking time.
 *
 * After: flex-direction:column-reverse, prepend-to-start, NO scroll
 *   manipulation. With column-reverse, scrollTop=0 corresponds to the
 *   visual bottom; new entries inserted at firstChild appear at the
 *   visual bottom WITHOUT changing scrollTop. The browser still does
 *   incremental layout for the new entry, but never has to clamp/recompute
 *   maxScroll — eliminating the dominant per-flush cost entirely.
 *
 * This is the chat-app trick (Slack, Discord) for live feeds.
 *
 * `overflow-anchor` keeps user-scrolled position stable when content
 * is added below their viewport. */
.panel-streams .stream-unified {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column-reverse;
  overflow-anchor: auto;
  padding: 6px 8px 6px 10px;     /* clears the active-band on the left */
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: 10.5px;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--fg-faint) 50%, transparent) transparent;
  /* Container-level paint isolation — sibling DOM (the 17+ hc-sections
   * in panel-console) is excluded from this scroller's layout pass. */
  contain: layout paint style;
}

/* Per-entry container · ALL types share this base. Type-coded left
 * border + icon column. Tight margins for high-velocity flow.
 *
 * Performance notes (this is the "wall of doom" hot path):
 *   • `contain: layout style paint` — the browser knows that style
 *     changes inside one entry never affect siblings, so it can skip
 *     re-rendering the whole stream on each insert. Critical at
 *     50+ events/sec.
 *   • Animation kept SHORT (120 ms fade only, no transform) — long
 *     animations queue up under burst load and produce visible jank.
 *   • Margin 0 between entries — the color stripe IS the separator;
 *     the eye doesn't need additional whitespace. */
.panel-streams .stream-entry {
  position: relative;
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 6px;
  align-items: start;
  margin: 0;
  padding: 3px 6px 3px 8px;
  border-left: 3px solid transparent;
  background: color-mix(in srgb, var(--bg) 60%, var(--panel));
  line-height: 1.4;
  contain: layout style paint;
  /* Animation duration scales inversely with --engine-speed so the
   * fade-in is faster when the slider is right of center, slower
   * when left. max(0.05, …) avoids divide-by-zero at the pause
   * extreme (body.engine-paused freezes via animation-play-state). */
  animation: stream-entry-in calc(var(--duration-2) / max(0.05, var(--engine-speed))) ease-out;
}
@keyframes stream-entry-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.panel-streams .stream-entry + .stream-entry {
  /* hairline divider between consecutive entries · cheaper than
   * margin (no layout flow change) and visually distinct */
  border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent);
}
.panel-streams .stream-entry:hover {
  background: color-mix(in srgb, var(--bg) 40%, var(--panel));
}
/* will-change:scroll-position removed — with column-reverse we never
 * mutate scrollTop, so the hint is misleading and forces the browser
 * to keep an extra compositor layer alive for nothing. */
/* Reduced-motion · respect users who've disabled animation. */
@media (prefers-reduced-motion: reduce) {
  .panel-streams .stream-entry { animation: none; }
}

/* Per-type accent: left border + icon color. The "wall of doom"
 * uses 8 distinct types; each gets its own color stripe + icon so
 * the eye reads the type by color alone in the high-velocity flow. */
.panel-streams .stream-entry.source       { border-left-color: var(--a-violet-text); }
.panel-streams .stream-entry.derivation   { border-left-color: var(--a-sky-text); }
.panel-streams .stream-entry.sympy        { border-left-color: var(--a-trunk-text); }
.panel-streams .stream-entry.bracket      { border-left-color: var(--a-amber-text); }
.panel-streams .stream-entry.orbit        { border-left-color: var(--a-mint-text); }
.panel-streams .stream-entry.prediction   { border-left-color: var(--a-rose-text); }
.panel-streams .stream-entry.signature    { border-left-color: var(--a-azure-text); }
.panel-streams .stream-entry.numerical    { border-left-color: var(--a-sage-text); }
.panel-streams .stream-entry.formal       { border-left-color: var(--a-rose-text); }
.panel-streams .stream-entry.leaf         { border-left-color: var(--a-teal-text); }
.panel-streams .stream-entry.placeholder {
  border-left-color: var(--fg-faint);
  font-style: italic;
  color: var(--fg-faint);
}

/* Icon column · mono character in the type's color. */
.panel-streams .stream-entry .entry-icon {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 11px;
  text-align: center;
  line-height: 1.5;
  user-select: none;
}
.panel-streams .stream-entry.source       .entry-icon { color: var(--a-violet-text); }
.panel-streams .stream-entry.derivation   .entry-icon { color: var(--a-sky-text); }
.panel-streams .stream-entry.sympy        .entry-icon { color: var(--a-trunk-text); }
.panel-streams .stream-entry.bracket      .entry-icon { color: var(--a-amber-text); }
.panel-streams .stream-entry.orbit        .entry-icon { color: var(--a-mint-text); font-weight: 800; }
.panel-streams .stream-entry.prediction   .entry-icon { color: var(--a-rose-text); }
.panel-streams .stream-entry.signature    .entry-icon { color: var(--a-azure-text); }
.panel-streams .stream-entry.numerical    .entry-icon { color: var(--a-sage-text); }
.panel-streams .stream-entry.formal       .entry-icon { color: var(--a-rose-text); }
.panel-streams .stream-entry.leaf         .entry-icon { color: var(--a-teal-text); }

/* Inline tag + meta · used by the new one-line event entries
 * (bracket, orbit, prediction, etc.). The tag highlights the
 * primary identifier; meta is dim accent text. */
.panel-streams .stream-entry .ev-tag {
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-right: 5px;
}
.panel-streams .stream-entry.bracket    .ev-tag { color: var(--a-amber-text); }
.panel-streams .stream-entry.orbit      .ev-tag { color: var(--a-mint-text); }
.panel-streams .stream-entry.prediction .ev-tag { color: var(--a-rose-text); }
.panel-streams .stream-entry.signature  .ev-tag { color: var(--a-azure-text); }
.panel-streams .stream-entry.numerical  .ev-tag { color: var(--a-sage-text); }
.panel-streams .stream-entry.formal     .ev-tag { color: var(--a-rose-text); }
.panel-streams .stream-entry.leaf       .ev-tag { color: var(--a-teal-text); }
.panel-streams .stream-entry .ev-meta {
  color: var(--fg-faint);
  font-size: 9.5px;
  margin-left: 4px;
}
.panel-streams .stream-entry .ev-val {
  color: var(--fg);
  font-weight: 700;
}

/* Active-band colors for the new types · same dual-set pattern so
 * the pulse animation's currentColor shadow matches per type. */
.panel-streams .streams-active-band.type-bracket    { background: var(--a-amber); color: var(--a-amber-text); }
.panel-streams .streams-active-band.type-orbit      { background: var(--a-mint);  color: var(--a-mint-text); }
.panel-streams .streams-active-band.type-prediction { background: var(--a-rose);  color: var(--a-rose-text); }
.panel-streams .streams-active-band.type-signature  { background: var(--a-azure); color: var(--a-azure-text); }
.panel-streams .streams-active-band.type-numerical  { background: var(--a-sage);  color: var(--a-sage-text); }
.panel-streams .streams-active-band.type-formal     { background: var(--a-rose); color: var(--a-rose-text); }
.panel-streams .streams-active-band.type-leaf       { background: var(--a-teal);  color: var(--a-teal-text); }

.panel-streams .stream-entry .entry-body {
  min-width: 0;            /* lets long content text-overflow + ellipsis */
  overflow: hidden;
}

/* ─── Source entries · §-anchor + via + tally header line + MD prose preview.
 * Header is always visible; the prose is collapsed to a 2-line preview
 * by default. Click the entry to expand to full prose. */
.panel-streams .stream-entry.source .entry-body {
  cursor: pointer;
}
.panel-streams .stream-entry.source .entry-meta {
  display: flex;
  gap: 6px;
  align-items: baseline;
  font-size: 9.5px;
  margin-bottom: 2px;
}
.panel-streams .stream-entry.source .entry-anchor {
  color: var(--a-violet-text);
  font-weight: 700;
}
.panel-streams .stream-entry.source .entry-via {
  color: var(--fg-dim);
  font-style: italic;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.panel-streams .stream-entry.source .entry-tally {
  color: var(--a-amber-text);
  font-weight: 600;
  font-size: 8.5px;
}
.panel-streams .stream-entry.source .entry-prose {
  margin: 0;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.5;
  color: var(--fg);
  background: transparent;
  white-space: pre-wrap;
  word-break: break-word;
  border: none;
  /* Default: collapsed to ~2 lines with fade-out + click to expand. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  position: relative;
}
.panel-streams .stream-entry.source.expanded .entry-prose {
  display: block;
  -webkit-line-clamp: unset;
  max-height: none;
  overflow: visible;
}

/* ─── Derivation entries · feed-block content, slim down padding. */
.panel-streams .stream-entry.derivation .entry-body {
  font-size: 10.5px;
}

/* ─── Sympy entries · keep the existing sym-line grid layout, just
 * tighter than before. */
.panel-streams .stream-entry.sympy .entry-body {
  font-size: 10px;
}

/* Hide the old hc-source-live-pre / hc-source-live-bar elements that
 * may still be referenced elsewhere in DOM. The unified-stream IS the
 * source viewer now; the old single-element source live block is gone. */
.panel-streams .hc-source-live-pre,
.panel-streams .hc-source-live-bar {
  display: none;
}

/* ── Paused state · the whole grid dims + an overlay shows the count
 *    of buffered events the user is currently freezing. */
.panel-streams.streams-paused .streams-grid {
  filter: grayscale(0.4) brightness(0.85);
  pointer-events: none;
}
/* ════════════════════════════════════════════════════════════════════
 * STREAMS-PAUSED OVERLAY · click-to-resume bar
 * ════════════════════════════════════════════════════════════════════
 * Anchored to the bottom of the streams panel; visible only when the
 * .streams-paused class is on the panel parent. The overlay is a
 * <button> — entire surface is clickable. Three regions:
 *   ⏵ glyph            primary affordance · resume on click
 *   "paused" + hint    state + how-to (click or Space)
 *   "N events held"    held-event counter (right-justified)
 * Theme-aware: --panel surface, --border separator, amber accent
 * limited to the LEFT EDGE stripe + the glyph color so the bar reads
 * as a structural section of the panel rather than a peach-colored
 * intrusion. Hover lifts the panel one tier; active depresses it.
 * ════════════════════════════════════════════════════════════════════ */
.panel-streams .streams-paused-overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  display: none;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-4);
  /* Match the panel chrome — flat, theme-aware, no amber wash. */
  background: var(--panel);
  border: none;
  border-top: 1px solid var(--border);
  /* Amber accent stripe on the LEFT edge only · visual rhyme with
   * the framework's paused-state hue without flooding the whole bar. */
  border-left: 3px solid var(--a-amber);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  z-index: 5;
  transition: background var(--duration-3) ease;
}
.panel-streams.streams-paused .streams-paused-overlay {
  display: flex;
}
.panel-streams .streams-paused-overlay:hover {
  background: var(--inset);
}
.panel-streams .streams-paused-overlay:active {
  background: var(--bg);
}
.panel-streams .streams-paused-overlay:focus-visible {
  outline: 2px solid var(--a-amber);
  outline-offset: -2px;
}
.panel-streams .streams-paused-glyph {
  font-size: 16px;
  line-height: 1;
  color: var(--a-amber-text);
  flex-shrink: 0;
}
.panel-streams .streams-paused-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1.2;
  /* Subtle uppercase to read as a state label, not a sentence. */
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 10px;
  color: var(--a-amber-text);
}
.panel-streams .streams-paused-hint {
  text-transform: none;
  letter-spacing: 0;
  font-size: 10px;
  color: var(--fg-faint);
  font-weight: 500;
}
.panel-streams .streams-paused-hint kbd {
  display: inline-block;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fg-dim);
  font-weight: 600;
}
.panel-streams .streams-paused-buffer {
  font-size: 10px;
  color: var(--fg-dim);
  font-weight: 700;
  flex-shrink: 0;
  /* Right-justify the held count regardless of the centre column. */
  margin-left: auto;
}

/* Reduced-motion overrides. */
@media (prefers-reduced-motion: reduce) {
  .panel-streams .streams-pause:hover { transform: none; }
  .panel-streams .streams-paused-overlay { transition: none; }
}

/* ════════════════════════════════════════════════════════════════════════
 * CANVAS MINI-PLAYER · floating spotify-style controls bottom-right
 * ════════════════════════════════════════════════════════════════════════
 * The previous full-width topbar was too obtrusive and ate ~50 px of the
 * canvas top edge. Replaced with a compact pill-shaped player that floats
 * over the bottom-right corner of the wheel canvas. Footprint: ~280 px ×
 * 36 px (collapsed) / ~280 px × 64 px (sound-on, modes row revealed).
 *
 * Carries the GLOBAL engine controls:
 *   • Pause / play (pauses the SSE event drain)
 *   • Speed slider (0..2× · truly throttles event throughput)
 *   • Sound toggle pill (♪ glyph · class .on when active)
 *   • Mode buttons (music / chaos · revealed only when sound on)
 * ════════════════════════════════════════════════════════════════════════ */
.panel-mandala { position: relative; }   /* anchor the floating player */

/* (Diagnostic overlay was here · moved to the page footer's HUD strip.
 * Removing it eliminated one absolute-positioned layer the compositor
 * was painting over the WebGL canvas every frame, AND collapsed two
 * separate writers into one. See the rx-hud-strip rules below for the
 * unified strip.)
 *
 * Color-code rules · jit / jit60 cells (the two stddev numbers) flip
 * class to .jit-good / .jit-mid / .jit-bad based on the same buckets
 * as before (< 1.5 / 1.5–4 / > 4 ms). lt cell gets .lt-bad when any
 * long task fires. mesh cell gets .mesh-bad if the writer detects a
 * climbing trend (steady-state + 20 % drift over 60 s).
 *
 * These rules apply to BOTH the old #px-jit ids (now gone) AND the
 * footer #rx-hud-jit / #rx-hud-jit60 / #rx-hud-lt / #rx-hud-mesh ids
 * — so even if any old reference remained they wouldn't break, and
 * the new footer cells get the same color cues. */
#rx-hud-jit.jit-good,
#rx-hud-jit60.jit-good { color: var(--a-teal-text); }
#rx-hud-jit.jit-mid,
#rx-hud-jit60.jit-mid  { color: var(--a-amber-text); }
#rx-hud-jit.jit-bad,
#rx-hud-jit60.jit-bad  { color: var(--a-rose-text); }
#rx-hud-lt.lt-bad      { color: var(--a-rose-text); }
#rx-hud-mesh.mesh-bad  { color: var(--a-amber-text); }

/* ════════════════════════════════════════════════════════════════════════
 * WHEEL FOOT CONTROLS · pause / speed / sound / volume
 * ════════════════════════════════════════════════════════════════════════
 *
 * Was a floating .canvas-mini-player pinned absolute at canvas bottom-
 * right. Now lives INLINE inside the wheel footer (.controls strip)
 * alongside the orbit/cells/A pills as one unified readout-and-controls
 * row. Mobile-first: wraps to a second row when the strip overflows.
 * Desktop: everything fits on one line via flex-wrap fall-through.
 *
 * Container is `<span class="wheel-foot-controls" id="wheel-foot-controls">`.
 * The .speed-paused state class lives on the container (set by app.js
 * _engineSetSpeed when v === 0).
 *
 * Per-control class names (.cmp-pause / .cmp-speed / .cmp-sound /
 * .cmp-vol etc.) are unchanged — the JS bindings address them by ID
 * and they were already namespaced enough to avoid collisions. */
.wheel-foot-controls {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  user-select: none;
  /* Allow the controls row to stretch and consume free space inside
   * the .controls strip, but never to the point of pushing the orbit
   * pills off — flex-wrap on the parent handles wrap-to-row-2. */
  flex: 1 1 auto;
  min-width: 0;
  /* On mobile (parent flex-wraps), this span lands on its own row
   * and stretches edge-to-edge so the speed slider has room to breathe.
   * Margin-left:auto keeps it right-aligned on a one-row desktop layout
   * (after the .spacer · keeps the visual order: stats … controls). */
  margin-left: auto;
}

/* ── Pause/Play button (round, ~26 px) ──────────────────────────────── */
.wheel-foot-controls .cmp-pause {
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--elev);
  color: var(--fg);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background var(--duration-2) ease, border-color var(--duration-2) ease, transform 0.10s ease;
}
.wheel-foot-controls .cmp-pause:hover {
  background: color-mix(in srgb, var(--a-violet) 28%, var(--bg));
  border-color: color-mix(in srgb, var(--a-violet-text) 65%, var(--border));
  transform: scale(1.06);
}
.wheel-foot-controls .cmp-pause[aria-pressed="true"] {
  background: color-mix(in srgb, var(--a-amber) 32%, var(--bg));
  border-color: color-mix(in srgb, var(--a-amber-text) 70%, var(--border));
  color: var(--a-amber-text);
}

/* ── Compact speed slider ──────────────────────────────────────────── */
.wheel-foot-controls .cmp-speed-wrap {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* The slider gets the lion's share of horizontal space — both pills
   * (pause + sound) are fixed-width, so the wrap takes the remainder.
   * min-width:0 is required for the inner <input> flex:1 to shrink
   * past its intrinsic width on narrow rows. */
  flex: 1 1 140px;
  min-width: 90px;
}
.wheel-foot-controls .cmp-label {
  font-family: var(--font-mono);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.10em;
  color: var(--fg-faint);
  text-transform: uppercase;
  flex-shrink: 0;
  line-height: 1;
}
.wheel-foot-controls .cmp-label-vol {
  color: color-mix(in srgb, var(--a-mint-text) 65%, var(--fg-faint));
}
.wheel-foot-controls .cmp-speed {
  flex: 1 1 auto;
  min-width: 0;
  height: 3px;
  -webkit-appearance: none;
  appearance: none;
  /* The input element's own background is invisible once -webkit-
   * appearance: none is set; the actual track is rendered by the
   * ::*-track pseudo-elements below. We still paint --fg-faint here
   * as a defensive baseline (in case a UA engine ignores the pseudo-
   * elements) — the rule's real job is just to be a backstop. */
  background: var(--fg-faint);
  border-radius: var(--r-1);
  outline: none;
  cursor: pointer;
}
/* Explicit track pseudo-elements · MANDATORY for a visible track on
 * dark theme. Symmetism's --border resolves to gray-perfect-d
 * (#404040) which equals --elev's channel — invisible on the footer.
 * --fg-faint (theme-symmetric ch 128) is the canonical "always-
 * legible neutral" per §IV; it sits one ladder step above --border
 * and ~70 channels above --elev on dark theme so the track reads
 * cleanly. The violet 33 % mix tints it toward the speed control's
 * accent for at-a-glance signal. */
.wheel-foot-controls .cmp-speed::-webkit-slider-runnable-track {
  height: 3px;
  background: color-mix(in srgb, var(--a-violet) 33%, var(--fg-faint));
  border-radius: var(--r-1);
}
.wheel-foot-controls .cmp-speed::-moz-range-track {
  height: 3px;
  background: color-mix(in srgb, var(--a-violet) 33%, var(--fg-faint));
  border-radius: var(--r-1);
}
.wheel-foot-controls .cmp-speed::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--a-violet);
  border: 2px solid var(--bg);
  /* Pull the thumb up so it stays vertically centered on the 3 px
   * track once the runnable-track is explicitly styled. Without
   * this, Webkit positions the thumb at the bottom of the input's
   * height, drifting off the track centerline. */
  margin-top: -4.5px;
  cursor: pointer;
}
.wheel-foot-controls .cmp-speed::-moz-range-thumb {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--a-violet);
  border: 2px solid var(--bg);
  cursor: pointer;
}
.wheel-foot-controls .cmp-speed-value {
  color: var(--a-violet-text);
  font-weight: 700;
  font-size: 10px;
  min-width: 36px;
  text-align: right;
  flex-shrink: 0;
}
.wheel-foot-controls.speed-paused .cmp-speed-value { color: var(--a-amber-text); }
.wheel-foot-controls.speed-paused .cmp-speed::-webkit-slider-thumb {
  background: var(--a-amber);
  box-shadow: 0 0 5px color-mix(in srgb, var(--a-amber) 60%, transparent);
}
.wheel-foot-controls.speed-paused .cmp-speed::-moz-range-thumb {
  background: var(--a-amber);
}

/* ── Sound pill (♪ glyph that fills with mint when active) ──────────── */
.wheel-foot-controls .cmp-sound {
  width: 28px;
  height: 26px;
  padding: 0;
  border-radius: var(--r-4);
  border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
  background: color-mix(in srgb, var(--panel) 80%, transparent);
  color: var(--fg-dim);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background var(--duration-3) ease, border-color var(--duration-3) ease, color var(--duration-3) ease;
}
.wheel-foot-controls .cmp-sound:hover {
  border-color: color-mix(in srgb, var(--a-mint-text) 55%, var(--border));
  color: var(--a-mint-text);
}
.wheel-foot-controls .cmp-sound.on {
  background: color-mix(in srgb, var(--a-mint) 30%, var(--bg));
  border-color: var(--a-mint-text);
  color: var(--a-mint-text);
  box-shadow: 0 0 8px color-mix(in srgb, var(--a-mint) 50%, transparent);
}
.wheel-foot-controls .cmp-sound.pending {
  background: color-mix(in srgb, var(--a-amber) 26%, var(--bg));
  border-color: color-mix(in srgb, var(--a-amber-text) 65%, var(--border));
  color: var(--a-amber-text);
}

/* ── Volume slider · revealed only when sound is ON ────────────────── */
.wheel-foot-controls .cmp-vol-wrap {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0 4px 0 6px;
  margin-left: 4px;
  border-left: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  /* Tighter than the speed wrap — volume is the least-likely-to-touch
   * control, so it gets a smaller slot and yields space to speed first
   * when the row is cramped. */
  flex: 0 1 110px;
  min-width: 70px;
}
.wheel-foot-controls .cmp-vol-wrap[hidden] { display: none; }
.wheel-foot-controls .cmp-vol-icon {
  color: var(--a-mint-text);
  font-size: 11px;
  line-height: 1;
  letter-spacing: -2px;
  flex-shrink: 0;
}
.wheel-foot-controls .cmp-vol {
  flex: 1 1 auto;
  min-width: 0;
  height: 3px;
  -webkit-appearance: none;
  appearance: none;
  /* Backstop · see .cmp-speed comment above. The ::*-track pseudo-
   * elements below are the actual visible track. */
  background: var(--fg-faint);
  border-radius: var(--r-1);
  outline: none;
  cursor: pointer;
}
.wheel-foot-controls .cmp-vol::-webkit-slider-runnable-track {
  height: 3px;
  background: color-mix(in srgb, var(--a-mint) 33%, var(--fg-faint));
  border-radius: var(--r-1);
}
.wheel-foot-controls .cmp-vol::-moz-range-track {
  height: 3px;
  background: color-mix(in srgb, var(--a-mint) 33%, var(--fg-faint));
  border-radius: var(--r-1);
}
.wheel-foot-controls .cmp-vol::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--a-mint);
  border: 2px solid var(--bg);
  box-shadow: 0 0 5px color-mix(in srgb, var(--a-mint) 50%, transparent);
  margin-top: -4px;
  cursor: pointer;
}
.wheel-foot-controls .cmp-vol::-moz-range-thumb {
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--a-mint);
  border: 2px solid var(--bg);
  cursor: pointer;
}
.wheel-foot-controls .cmp-vol-value {
  color: var(--a-mint-text);
  font-weight: 700;
  font-size: 9.5px;
  min-width: 30px;
  text-align: right;
  flex-shrink: 0;
}

/* Reduced motion fallback. */
@media (prefers-reduced-motion: reduce) {
  .wheel-foot-controls .cmp-pause:hover { transform: none; }
}

/* ── Mobile narrowing: hide numeric % labels so the controls fit on a
 *    single row even when wrapped below the orbit/cells/A pills.
 *    Slider thumbs themselves remain interactive (large touch targets). */
@media (max-width: 480px) {
  .wheel-foot-controls {
    /* Force its own row on mobile · the orbit/cells/A pills get row 1,
     * the controls get row 2 with the full strip width. */
    flex-basis: 100%;
    margin-left: 0;
    margin-top: 4px;
    padding-top: 6px;
    border-top: 1px dashed color-mix(in srgb, var(--border) 75%, transparent);
  }
  .wheel-foot-controls .cmp-speed-value,
  .wheel-foot-controls .cmp-vol-value { display: none; }
  /* Volume wrap collapses harder on phones (the slider stays usable
   * via the thumb without showing the full track length). */
  .wheel-foot-controls .cmp-vol-wrap { flex: 0 1 90px; min-width: 60px; }
}

/* ── TOUCH TAP-AREA EXPANSION · iOS-friendly hit boxes ───────────────
 * iOS/Android guidelines call for ≥ 44 px tappable. Visual size of the
 * pause/sound buttons stays at 26-28 px (so they fit inline in the
 * controls strip), but a transparent ::before overlay extends the
 * pointer-event surface to ~46 × 46, centered on the visual button.
 * Sliders are LEFT ALONE here · cross-browser <input type="range">
 * styling is fragile and the existing thumb is reachable because
 * users can also tap anywhere on the track to jump the thumb.
 * Cosmetics unchanged on desktop · this only kicks in on touch-only
 * devices via the (hover: none) and (pointer: coarse) media query. */
@media (hover: none) and (pointer: coarse) {
  .wheel-foot-controls .cmp-pause,
  .wheel-foot-controls .cmp-sound {
    position: relative;
  }
  .wheel-foot-controls .cmp-pause::before,
  .wheel-foot-controls .cmp-sound::before {
    content: "";
    position: absolute;
    /* ~10 px vertical, ~9 px horizontal extension · brings 26 × 26
     * → 46 × 44 visual-then-tappable. Doesn't show because the
     * pseudo-element has no background. pointer-events default
     * (auto) means the parent button still receives clicks routed
     * through this overlay. */
    inset: -10px -9px;
  }
}

/* ════════════════════════════════════════════════════════════════════
 * Engine-speed CSS var · scales animation durations globally
 * ════════════════════════════════════════════════════════════════════
 * Set on the body element from JS via _engineSetSpeed(value).
 * 0 = pause (animation-play-state: paused), 1 = real-time, 2 = double.
 * Used as a multiplier in any animation/transition that should
 * respect the global speed slider. Critical animations (the wheel
 * orbit rotation, stream entry slide-in, BHME pulse) all read from
 * --engine-speed. ════════════════════════════════════════════════════ */
:root { --engine-speed: 1; }
body.engine-paused .stream-entry,
body.engine-paused .panel-streams .streams-active-band {
  animation-play-state: paused;
}

/* ════════════════════════════════════════════════════════════════════════
 * SECTION GROUPS · ENGINE INTERNALS + VERIFICATION ROUTES
 * ════════════════════════════════════════════════════════════════════════
 * Stage B consolidation: 9 redundant sections (algebra, compact, split,
 * projection, fidelity, network, numerical, formal, computational) get
 * wrapped into 2 visual groups so the user reads them as 2 super-panels
 * instead of 9 stand-alone children. Each child preserves its existing
 * JS hooks and click-to-expand interactions.
 *
 * Visual hierarchy:
 *   .hc-group         · subtle inset background + accent border-left
 *   .hc-group-head    · sticky-ish title bar; click toggles collapse
 *   .hc-group.collapsed > .hc-section · hidden via display: none
 *   children get tighter top margin so they read as one unit. */
.homo-console .hc-group {
  margin: 14px 0 6px 0;
  padding: 2px 6px 2px 8px;
  border-left: 2px solid color-mix(in srgb, var(--a-azure) 50%, var(--border));
  background: color-mix(in srgb, var(--a-azure) 4%, transparent);
  border-radius: var(--r-2);
}
.homo-console .hc-group[data-group="verification"] {
  border-left-color: color-mix(in srgb, var(--a-mint-text) 50%, var(--border));
  background: color-mix(in srgb, var(--a-mint) 4%, transparent);
}
.homo-console .hc-group-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  padding: 4px 4px 6px 0;
  cursor: pointer;
  user-select: none;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
  margin-bottom: 4px;
}
.homo-console .hc-group-head:hover { opacity: 0.92; }
.homo-console .hc-group-label {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--a-azure-text);
}
.homo-console .hc-group[data-group="verification"] .hc-group-label {
  color: var(--a-mint-text);
}
.homo-console .hc-group-sub {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fg-faint);
  letter-spacing: 0.04em;
  flex: 1;
}
.homo-console .hc-group-toggle {
  font-size: 11px;
  color: var(--fg-faint);
  transition: transform var(--duration-3) ease;
  flex-shrink: 0;
}
.homo-console .hc-group.collapsed .hc-group-toggle {
  transform: rotate(-90deg);
}
.homo-console .hc-group.collapsed > .hc-section {
  display: none;
}
/* Tighten children inside the group so they feel like one unit. */
.homo-console .hc-group > .hc-section {
  margin-top: 6px;
  padding-left: 4px;
  border-left: none;            /* group's own border-left is enough */
}
.homo-console .hc-group > .hc-section:first-of-type {
  margin-top: 0;
}

/* ════════════════════════════════════════════════════════════════════════
 * FRAMEWORK META PANELS · CLAIM LEDGER, PHYSICS RECOVERY,
 *                          FALSIFICATION TABLE, HOMOICONIC AUDIT
 * ════════════════════════════════════════════════════════════════════════
 * Stages 1-5 of the framework-meta rollout. Each panel pulls from
 * session_start.machine_index (claim_index.json + falsifier_index.json
 * + equation_index.json + framework_index.json + reference_index.json)
 * or session_start.physics_recovery (curated physics_recovery.py).
 *
 * Visual style: one card per claim/row. Color coded by category
 * (ledger) or status (recovery / falsification). Filter chip row
 * controls visibility. No animations on entry — static, scannable. */

/* ── Filter chip row · shared by ledger and recovery ──────────────── */
.hc-section .hc-ledger-controls,
.hc-section .hc-recovery-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  margin: 6px 0 8px 0;
  padding: 0 2px;
}
.hc-section .hc-ledger-filter-label {
  color: var(--fg-faint);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  margin-right: 4px;
}
.hc-section .hc-ledger-chip,
.hc-section .hc-recovery-chip {
  padding: 2px 8px;
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  border-radius: var(--r-4);
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  color: var(--fg-dim);
  font-family: var(--font-mono);
  font-size: 9.5px;
  cursor: pointer;
  transition: background var(--duration-2) ease, color var(--duration-2) ease, border-color var(--duration-2) ease;
}
.hc-section .hc-ledger-chip:hover,
.hc-section .hc-recovery-chip:hover {
  background: color-mix(in srgb, var(--a-mint) 14%, var(--bg));
  color: var(--a-mint-text);
}
.hc-section .hc-ledger-chip.active,
.hc-section .hc-recovery-chip.active {
  background: color-mix(in srgb, var(--a-mint) 24%, var(--bg));
  border-color: color-mix(in srgb, var(--a-mint-text) 60%, var(--border));
  color: var(--a-mint-text);
  font-weight: 700;
}

/* ── ⑬ CLAIM LEDGER ────────────────────────────────────────────────── */
.hc-ledger-grid {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
}
.hc-ledger-cat-head {
  margin-top: 8px;
  padding: 3px 8px;
  border-left: 3px solid var(--cat-color);
  background: color-mix(in srgb, var(--cat-color) 10%, transparent);
  color: var(--cat-color);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}
.hc-ledger-cat-head:first-child { margin-top: 0; }
.hc-ledger-cat-count {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--cat-color) 20%, var(--bg));
  color: var(--cat-color);
  font-size: 9px;
  font-weight: 700;
}
.hc-ledger-card {
  border-left: 2px solid var(--cat-color);
  padding: 4px 8px;
  background: color-mix(in srgb, var(--bg) 70%, var(--panel));
  font-family: var(--font-mono);
  font-size: 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.hc-ledger-card-head {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.hc-ledger-id {
  color: var(--cat-color);
  font-weight: 700;
}
.hc-ledger-chapter {
  color: var(--fg-faint);
  font-size: 9px;
}
.hc-ledger-flag {
  margin-left: auto;
  padding: 0 6px;
  border-radius: var(--r-3);
  font-size: 8.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
}
.hc-flag-foundational { color: var(--a-violet-text); background: color-mix(in srgb, var(--a-violet) 16%, transparent); }
.hc-flag-verifiable   { color: var(--a-mint-text);   background: color-mix(in srgb, var(--a-mint) 18%, transparent); }
.hc-flag-derivable    { color: var(--a-azure-text);  background: color-mix(in srgb, var(--a-azure) 16%, transparent); }
.hc-ledger-card-form {
  color: var(--fg);
  font-size: 10px;
  line-height: 1.35;
  word-break: break-word;
}
.hc-ledger-value {
  color: var(--a-trunk-text);
  font-weight: 700;
}
.hc-ledger-card-foot {
  color: var(--fg-faint);
  font-size: 9px;
}
.hc-ledger-verify {
  color: var(--a-mint-text);
  font-weight: 600;
}

/* ── ⑭ PHYSICS RECOVERY MATRIX ─────────────────────────────────────── */
.hc-recovery-summary {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
  margin: 4px 0 8px;
}
.hc-recovery-summary-cell {
  padding: 4px 6px;
  border-left: 2px solid var(--st-color);
  background: color-mix(in srgb, var(--st-color) 10%, transparent);
  text-align: center;
  font-family: var(--font-mono);
}
.hc-recovery-summary-count {
  color: var(--st-color);
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
}
.hc-recovery-summary-label {
  color: var(--fg-dim);
  font-size: 8.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 2px;
}
.hc-recovery-grid {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.hc-recovery-domain {
  margin-top: 8px;
  padding: 3px 8px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  color: var(--fg-faint);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}
.hc-recovery-domain:first-child { margin-top: 0; }
.hc-recovery-row {
  border-left: 3px solid var(--st-color);
  padding: 5px 8px;
  background: color-mix(in srgb, var(--bg) 75%, var(--panel));
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.4;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.hc-recovery-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.hc-recovery-status-badge {
  padding: 1px 7px;
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--st-color) 20%, transparent);
  color: var(--st-color);
  font-size: 9px;
  text-transform: uppercase;
  font-weight: 700;
  letter-spacing: 0.06em;
}
.hc-recovery-artifact {
  color: var(--a-mint-text);
  font-weight: 700;
  padding: 0 5px;
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--a-mint) 14%, transparent);
}
.hc-recovery-artifact-none {
  color: var(--fg-faint);
  font-style: italic;
  font-size: 9px;
}
.hc-recovery-key {
  color: var(--fg-faint);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-right: 4px;
}
.hc-recovery-accepted { color: var(--fg); }
.hc-recovery-accepted-form,
.hc-recovery-fw-form {
  color: var(--a-trunk-text);
  font-size: 10px;
  padding-left: 12px;
  border-left: 1px dotted color-mix(in srgb, var(--a-trunk) 40%, transparent);
}
.hc-recovery-fw-form { color: var(--a-mint-text); border-left-color: color-mix(in srgb, var(--a-mint-text) 40%, transparent); }
.hc-recovery-evidence {
  color: var(--fg-dim);
  font-size: 9.5px;
  line-height: 1.4;
}
.hc-recovery-gap {
  color: var(--a-amber-text);
  font-size: 9.5px;
  line-height: 1.4;
  padding-left: 4px;
  border-left: 2px solid color-mix(in srgb, var(--a-amber) 40%, transparent);
}

/* ── ⑮ FALSIFICATION TABLE ─────────────────────────────────────────── */
.hc-falsif-grid {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.hc-falsif-row {
  border-left: 3px solid var(--st-color);
  padding: 5px 8px;
  background: color-mix(in srgb, var(--bg) 75%, var(--panel));
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.4;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.hc-falsif-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.hc-falsif-id {
  color: var(--st-color);
  font-weight: 700;
}
.hc-falsif-year {
  color: var(--fg-faint);
  font-size: 9px;
}
.hc-falsif-key {
  color: var(--fg-faint);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-right: 4px;
}
.hc-falsif-pred  { color: var(--fg); }
.hc-falsif-meas  { color: var(--a-trunk-text); }
.hc-falsif-stat  { color: var(--st-color); font-weight: 600; }
.hc-falsif-crit  { color: var(--a-amber-text); }
.hc-falsif-exp   { color: var(--a-azure-text); }
.hc-falsif-add   { color: var(--fg-dim); font-size: 9.5px; }

/* ── ⑯ HOMOICONIC AUDIT ────────────────────────────────────────────── */
.hc-audit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 6px;
  margin-top: 4px;
}
.hc-audit-cell {
  padding: 6px 8px;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: var(--r-2);
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  cursor: help;
  font-family: var(--font-mono);
}
.hc-audit-cell-label {
  color: var(--fg-faint);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.hc-audit-cell-value {
  color: var(--a-mint-text);
  font-size: 13px;
  font-weight: 700;
  margin-top: 2px;
  word-break: break-word;
}

/* Reduced motion · respect users who've disabled animation. */
@media (prefers-reduced-motion: reduce) {
  .hc-recovery-row, .hc-falsif-row, .hc-ledger-card { transition: none; }
}

/* ════════════════════════════════════════════════════════════════════
   ⑱ STRUCTURAL PROBE · anti-numerology engine · slider panel + MC
   ════════════════════════════════════════════════════════════════════
   Panel houses 5 sliders (one per leaf integer), 5 proposition rows
   that light ✓/✗ on each slider change, 5 ALPHA-U-route value badges,
   and a Monte Carlo histogram with a p-value verdict line. Visual goal:
   the canonical inputs → calm green 5/5; perturbation → cascading red
   ✗ marks. Histogram bar at score 5 stays visible even when empty so
   "the canonical position is rare" reads at a glance. */

.hc-section.hc-probe {
  /* slightly extra room so the histogram + propositions both fit */
  padding-bottom: 14px;
}
.hc-num-blurb {
  margin: 4px 0 10px 0;
  font-size: 11.5px;
  color: var(--fg-faint);
  font-style: italic;
  line-height: 1.4;
}
.hc-num-status-good {
  color: var(--a-mint-text) !important;
  font-weight: 600;
}
.hc-num-status-mid {
  color: var(--a-amber-text) !important;
  font-weight: 600;
}
.hc-num-status-bad {
  color: var(--a-rose-text) !important;
  font-weight: 600;
}

/* ── Sliders ──────────────────────────────────────────────────────── */
.hc-num-sliders {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  margin: 8px 0;
}
.hc-num-slider-row {
  display: grid;
  grid-template-columns: 130px 1fr 50px;
  align-items: center;
  gap: 8px;
  font-size: 11px;
}
.hc-num-slider-label {
  color: var(--fg);
  font-family: var(--font-mono);
  display: flex;
  align-items: center;
  gap: 4px;
}
.hc-num-slider-canonical {
  color: var(--fg-faint);
  font-size: 10px;
  font-style: italic;
}
.hc-num-slider-canonical::before { content: "(="; }
.hc-num-slider-canonical::after  { content: ")"; }
input.hc-num-slider {
  width: 100%;
  margin: 0;
  accent-color: var(--a-azure-text);
}
.hc-num-slider-value {
  color: var(--a-mint-text);
  font-family: var(--font-mono);
  font-weight: 600;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
}
.hc-num-reset {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-faint);
  padding: 3px 10px;
  font-size: 10.5px;
  border-radius: var(--r-2);
  cursor: pointer;
  margin: 4px 0 12px 0;
  font-family: inherit;
  transition: border-color var(--duration-3), color var(--duration-3);
}
.hc-num-reset:hover {
  border-color: var(--a-azure-text);
  color: var(--a-azure-text);
}

/* ── Propositions list ────────────────────────────────────────────── */
.hc-num-props {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3px;
  margin-bottom: 10px;
}
.hc-num-prop {
  display: grid;
  grid-template-columns: 22px 32px 1fr;
  align-items: center;
  padding: 4px 8px;
  font-size: 11px;
  border-radius: var(--r-2);
  border-left: 3px solid var(--border);
  background: color-mix(in srgb, var(--fg) 17%, transparent);
  transition: border-color var(--duration-3), background var(--duration-3);
}
.hc-num-prop-ok {
  border-left-color: var(--a-mint-text);
  background: color-mix(in srgb, var(--a-mint) 17%, transparent);
}
.hc-num-prop-fail {
  border-left-color: var(--a-rose-text);
  background: color-mix(in srgb, var(--a-rose) 17%, transparent);
}
.hc-num-prop-mark {
  font-weight: 700;
  font-size: 13px;
  text-align: center;
}
.hc-num-prop-ok .hc-num-prop-mark   { color: var(--a-mint-text); }
.hc-num-prop-fail .hc-num-prop-mark { color: var(--a-rose-text); }
.hc-num-prop-id {
  font-family: var(--font-mono);
  color: var(--fg-faint);
  font-weight: 600;
}
.hc-num-prop-text {
  color: var(--fg);
  font-size: 11px;
}

/* ── 5 ALPHA-U routes · live values shown side-by-side ───────────── */
.hc-num-routes {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 4px;
  margin: 8px 0 14px 0;
}
.hc-num-route {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 6px 4px;
  background: color-mix(in srgb, var(--fg) 17%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  transition: border-color var(--duration-3), background var(--duration-3);
}
.hc-num-route-name {
  font-size: 9.5px;
  color: var(--fg-faint);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.hc-num-route-val {
  font-size: 16px;
  font-weight: 700;
  font-family: var(--font-mono);
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}
.hc-num-route-converge {
  border-color: var(--a-mint-text);
  background: color-mix(in srgb, var(--a-mint) 17%, transparent);
}
.hc-num-route-converge .hc-num-route-val {
  color: var(--a-mint-text);
}

/* ── Monte Carlo block · button + histogram + p-value verdict ───── */
.hc-num-mc {
  border-top: 1px dashed var(--border);
  padding-top: 10px;
  margin-top: 4px;
}
/* Single-button + progress-bar layout. The button is the publication-
   grade 1M run — the primary call-to-action of the anti-numerology
   probe panel. Solid violet (the framework's lead accent) so the eye
   lands on it first; darker violet rim + soft purple glow give it
   "elevated, clickable" weight against the dashed-rule separator
   above it. While in flight the progress bar fills smoothly and the
   button shows "▶ sampling…"; after completion the bound table
   appears below with the rule-of-three "1 in N" headline.

   Color contract (text on violet across both themes):
     idle     #F4F4F4 wash-l on #8040BF  →  contrast 7.2:1 ✓ AAA
     hover    same text, halo intensifies, small lift
     active   #F4F4F4 wash-l on #552A7F  →  contrast 14.4:1 ✓ AAA
     disabled opacity 0.5, no halo, cursor: wait                       */
.hc-num-mc-run {
  background: var(--a-violet);
  /* Text is always light (gray-wash-l #F4F4F4) regardless of theme —
   * violet's luminance is low enough that light text is the only
   * AA-clear option on both themes. */
  color: var(--gray-wash-l);
  /* Darker violet rim defines the edge against bright violet body. */
  border: 1px solid var(--p-violet-dark);
  padding: 8px 16px;
  font-size: 11.5px;
  border-radius: var(--r-2);
  cursor: pointer;
  font-family: inherit;
  font-weight: 700;
  letter-spacing: 0.01em;
  /* Soft purple glow · advertises "click me, this is the action".
   * Two-layer: tight drop-shadow for depth + outer halo for presence. */
  box-shadow:
    0 2px 4px color-mix(in srgb, var(--a-violet) 35%, transparent),
    0 0 0 0  color-mix(in srgb, var(--a-violet) 18%, transparent);
  transition:
    background var(--duration-3) ease,
    box-shadow var(--duration-4) ease,
    transform  var(--duration-2) ease;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* Hover · halo intensifies + 1px lift. Background stays the same
 * violet so the text-on-violet contrast is unchanged; the visual cue
 * is the spread shadow + the lift, not a hue shift. */
.hc-num-mc-run:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow:
    0 4px 10px color-mix(in srgb, var(--a-violet) 50%, transparent),
    0 0 0 4px  color-mix(in srgb, var(--a-violet) 22%, transparent);
}
/* Active (pressed) · darker violet body + reverse the lift so the
 * button visually depresses under the click. Halo collapses tight. */
.hc-num-mc-run:active:not(:disabled) {
  background: var(--p-violet-dark);
  transform: translateY(1px);
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--a-violet) 40%, transparent),
    0 0 0 0  transparent;
}
/* Keyboard-nav focus · violet outline outside the button rim. */
.hc-num-mc-run:focus-visible {
  outline: 2px solid var(--a-violet);
  outline-offset: 3px;
}
.hc-num-mc-time {
  /* The "~4 s" runtime hint inside the button — slightly transparent
   * light text so it reads as secondary metadata against the primary
   * "Run Monte Carlo" label. */
  font-size: 10px;
  color: color-mix(in srgb, var(--gray-wash-l) 75%, transparent);
  font-style: italic;
  font-weight: 400;
  font-family: var(--font-mono);
}
.hc-num-mc-run:disabled {
  opacity: 0.5;
  cursor: wait;
  /* In-flight (sampling…) state · drop the halo so the user sees the
   * button is no longer "ready to click". */
  box-shadow: none;
  transform: none;
}
/* Reduced-motion contract · skip the lift transform, keep the
 * halo + bg shifts so the affordance is still legible. */
@media (prefers-reduced-motion: reduce) {
  .hc-num-mc-run:hover:not(:disabled),
  .hc-num-mc-run:active:not(:disabled) {
    transform: none;
  }
}

/* Progress bar · determinate fill driven from JS. Striped overlay
   animates left-to-right while the bar is filling so the user sees
   motion even when the fill width hasn't changed (e.g. while lingering
   at 95% waiting for the response). Hidden via the `hidden` attribute
   when the panel is idle. */
.hc-num-mc-progress {
  position: relative;
  height: 6px;
  margin-top: 8px;
  background: color-mix(in srgb, var(--fg) 17%, transparent);
  border-radius: var(--r-2);
  overflow: hidden;
}
.hc-num-mc-progress[hidden] { display: none; }
.hc-num-mc-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  /* Solid violet — was a violet→azure gradient. Per the canvas-only-
   * gradient rule, the MC progress fill is now a flat accent. The
   * shimmer animation that previously used background-position to
   * pan the gradient is now decoupled and applied as an opacity
   * pulse via the keyframes below. */
  background: var(--a-violet);
  border-radius: var(--r-2);
  transition: width var(--duration-3) ease-out;
  animation: hc-num-mc-shimmer 1.4s linear infinite;
}
@keyframes hc-num-mc-shimmer {
  /* Opacity-pulse shimmer — replaces the previous background-position
   * pan (which only worked when the fill carried a 200%-wide gradient).
   * Same 1.4s cadence; subtle 85→100% opacity envelope. */
  0%   { opacity: 0.85; }
  50%  { opacity: 1.00; }
  100% { opacity: 0.85; }
}
.hc-num-mc-progress-label {
  position: absolute;
  top: 8px;
  right: 0;
  font-size: 10px;
  color: var(--fg-faint);
  font-style: italic;
  font-family: var(--font-mono);
  pointer-events: none;
}

/* Bound result rows · the rule-of-three "1 in N" treatment. The
   headline (P(canonical 5/5 by chance)) renders large + violet; the
   secondary rows (≥4, ≥3) render at body size. Each row is a
   label-on-left + value-on-right pair. */
.hc-num-mc-bound-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 3px 0;
  font-size: 11px;
  font-family: var(--font-mono);
  border-bottom: 1px dashed color-mix(in srgb, var(--fg) 17%, transparent);
}
.hc-num-mc-bound-row:last-of-type {
  border-bottom: none;
}
.hc-num-mc-bound-label {
  color: var(--fg-faint);
  font-size: 10.5px;
  white-space: nowrap;
}
.hc-num-mc-bound-value {
  color: var(--fg);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.hc-num-mc-bound-headline {
  padding: 6px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 17%, transparent);
}
.hc-num-mc-bound-headline .hc-num-mc-bound-label {
  color: var(--fg);
  font-size: 11.5px;
  font-weight: 600;
}
.hc-num-mc-bound-headline .hc-num-mc-bound-value {
  color: var(--a-violet-text);
  font-size: 13px;
  font-weight: 700;
}
.hc-num-mc-status {
  margin: 6px 0;
  font-size: 10.5px;
  color: var(--fg-faint);
  font-style: italic;
}
.hc-num-mc-histogram {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 3px;
  height: 110px;
  align-items: end;
  padding: 4px 2px;
  background: color-mix(in srgb, var(--shadow) 17%, transparent);
  border-radius: var(--r-2);
}
.hc-num-mc-bar {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  justify-content: flex-start;
}
.hc-num-mc-bar-fill {
  width: 80%;
  background: var(--a-azure);
  height: 0%;
  transition: height var(--duration-5) cubic-bezier(0.34, 1.56, 0.64, 1);
  border-radius: 2px 2px 0 0;
  display: block;
}
.hc-num-mc-bar-canonical .hc-num-mc-bar-fill {
  background: var(--a-mint);
  outline: 1px dashed var(--a-mint);
  outline-offset: 1px;
}
.hc-num-mc-bar-label {
  position: absolute;
  bottom: -16px;
  font-size: 10px;
  color: var(--fg-faint);
  font-family: var(--font-mono);
}
.hc-num-mc-bar-canonical .hc-num-mc-bar-label {
  color: var(--a-mint-text);
  font-weight: 700;
}
.hc-num-mc-pvalue {
  margin-top: 22px;
  font-size: 11px;
  color: var(--fg);
  font-family: var(--font-mono);
  line-height: 1.5;
}
.hc-num-mc-verdict {
  display: inline-block;
  padding: 1px 6px;
  margin-left: 4px;
  background: color-mix(in srgb, var(--a-mint) 17%, transparent);
  color: var(--a-mint-text);
  border-radius: var(--r-1);
  font-size: 10.5px;
  font-style: italic;
}

/* Responsive: stack ALPHA-U routes 5-wide → 2-row 3-then-2 on narrow. */
@media (max-width: 520px) {
  .hc-num-routes { grid-template-columns: repeat(3, 1fr); }
  .hc-num-slider-row { grid-template-columns: 100px 1fr 44px; font-size: 10.5px; }
  .hc-num-slider-label { font-size: 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .hc-num-prop, .hc-num-route, .hc-num-mc-bar-fill { transition: none; }
}
