/* Atlas — Dark immersive theme
 *
 * Color organization (2026-05-10): every reusable color lives in
 * :root as a CSS variable. Raw rgba()/hex appear in downstream rules
 * ONLY for one-off accents that no other rule shares. To re-skin the
 * UI, tweak this block; downstream rules pick up the change everywhere.
 *
 * Variables are grouped by purpose; within each group, dimmer → brighter
 * generally reads top-to-bottom.
 *
 * Convention:
 *   --bg-*          surface colors (page, panel, input)
 *   --panel-glass-* the frosted-glass float-panel surface (filters etc.)
 *   --border-*      strokes
 *   --text-*        type colors
 *   --accent / --amber / --danger / --you   semantic accents
 *   --coherence-*   walker proximity-to-known-item palette
 *   --overlay-*    white-on-dark elevation tints
 *   --shadow-*      composite drop-shadow values (full box-shadow strings)
 */
:root {
    /* ── Background surfaces ─────────────────────────── */
    --bg:           #0a0a14;                       /* deepest fallback / extreme dock */
    --bg-deep:      #121214;                       /* page background */
    --bg-panel:     #1a1a1e;                       /* solid panel surface */
    --bg-panel-alt: #1a1a1e;                       /* slightly darker panel variant */
    --bg-input:     #1a1a1e;                       /* form fields */
    --bg-soft:      rgba(255, 255, 255, 0.06);     /* faint elevated surface (over dark bg) */

    /* Float panels (filters / probe / clusters / settings) wear a
       frosted-glass treatment: cool semi-transparent tint over a
       backdrop blur. Tweak these two to shift the whole UI feel
       (warmer/cooler/more-opaque/less-frosted). */
    --panel-glass-bg:        rgba(18, 18, 20, 0.3);   /* default float-panel surface */
    --panel-glass-bg-medium: rgba(18, 18, 20, 0.5);   /* toolbar / mid-opacity */
    --panel-glass-bg-deep:   rgba(18, 18, 20, 0.9);   /* modal / overlay near-opaque */
    --panel-glass-bg-deeper: rgba(18, 18, 20, 0.92);  /* tooltip near-solid */
    --panel-glass-bg-near:   rgba(18, 18, 20, 0.95);  /* nearly opaque dialog */
    --panel-glass-blur:      6px;
    /* Note: the medium/deep/deeper/near variants currently have a
       slightly different hue (12,12,26) than the base (18,18,20).
       That's pre-existing — when you tweak `--panel-glass-bg`'s hue,
       you may want to align the variants too so the family stays
       coherent. */

    /* ── Borders ──────────────────────────────────── */
    --border:     #2a2c33;                         /* default divider */
    --border-dim: rgba(255, 255, 255, 0.08);       /* subtler inner borders */

    /* ── Text ─────────────────────────────────────── */
    --text:         #d2d2d2;                       /* default body */
    --text-bright:  #e8e8ff;                       /* emphasized */
    --text-muted:   #aaa;                          /* secondary labels */
    --text-dimmer:  #888;                          /* faintest inline labels */
    --text-dim:     #727272;                       /* deprioritized */

    /* ── Accents ──────────────────────────────────── */
    --accent:       #5dbee1;                       /* blue-cyan — primary action */
    --accent-dim:   #4a7c8e;                       /* muted accent variant */
    --amber:        #ffd700;                       /* yellow — bookmark / standout */
    --amber-soft:   rgba(245, 196, 74, 0.4);       /* glow / halo */
    --danger:       #ff4466;                       /* error / refusal */
    --you:          #ff4444;                       /* the walker's current position */
    --card-accent:  #aaa;                          /* default card accent (cards override inline) */

    /* ── Coherence (proximity-to-known-item) ────────── */
    --coherence-high: #00ff88;                     /* on a known item */
    --coherence-mid:  #ffaa00;                     /* between items */
    --coherence-low:  #ff4466;                     /* in the void */
    --coherence-glow:        rgba(74, 124, 142, 0.04);  /* low-saturation halo */
    --coherence-glow-mid:    rgba(74, 124, 142, 0.06);  /* mid intensity glow */
    --coherence-glow-strong: rgba(74, 124, 142, 0.10);  /* selection / catalog hover */
    --coherence-border:      rgba(74, 124, 142, 0.5);   /* selected-row border */

    /* ── White overlays (hover / elevation tints on dark bg) ─ */
    --overlay-faint:    rgba(255, 255, 255, 0.02);
    --overlay-faintish: rgba(255, 255, 255, 0.025);
    --overlay-tint:     rgba(255, 255, 255, 0.03);
    --overlay-light:    rgba(255, 255, 255, 0.04);
    --overlay-light2:   rgba(255, 255, 255, 0.05);
    --overlay-medium:   rgba(255, 255, 255, 0.06);
    --overlay-strong:   rgba(255, 255, 255, 0.08);
    --overlay-stronger: rgba(255, 255, 255, 0.10);
    --overlay-12:       rgba(255, 255, 255, 0.12);
    --overlay-15:       rgba(255, 255, 255, 0.15);
    --overlay-18:       rgba(255, 255, 255, 0.18);
    --overlay-bright:   rgba(255, 255, 255, 0.45);     /* near-white text/icon */

    /* ── Shadow color tokens (for inline box-shadow expressions) ─ */
    --shadow-color-light:  rgba(0, 0, 0, 0.18);
    --shadow-color-medium: rgba(0, 0, 0, 0.3);
    --shadow-color-strong: rgba(0, 0, 0, 0.5);
    --shadow-color-deep:   rgba(0, 0, 0, 0.7);

    /* ── Shadows (full box-shadow values) ─────────── */
    --shadow-soft:   0 2px 8px  var(--shadow-color-light);
    --shadow-medium: 0 4px 16px var(--shadow-color-medium);
    --shadow-strong: 0 4px 24px var(--shadow-color-strong);

    /* ── Misc hue accents (base hues; alpha variants in next section) ─ */
    --bg-darker:   #121214;                  /* deeper than --bg, used in some overlays */
    --text-soft:   #ccc;                     /* between --text and --text-bright */
    --text-faint:  #999;                     /* between --text-muted and --text-dimmer */
    --accent-soft: rgb(140, 180, 210);       /* lighter cyan, secondary accent */
    --gold-soft:   #f5c44a;                  /* desaturated gold, distinct from --amber */
    --warm-soft:   rgb(220, 180, 100);       /* warm amber tint */
    --warm-gold:   rgb(220, 180, 60);        /* deeper warm gold */
    --danger-soft: rgb(255, 100, 100);       /* pinkish red — softer than --danger */
    --peach:       rgb(255, 210, 150);       /* warm beige-pink */
    --mint:        rgb(80, 200, 130);        /* pastel green, softer than --coherence-high */
    --gray-160:    rgb(160, 160, 160);       /* neutral gray, darker than --gray-mid */
    --sky:         rgb(150, 200, 255);       /* lighter sky-blue, brighter than --accent-soft */
    --gray-mid:    rgb(180, 180, 180);       /* neutral mid gray */
    --teal-soft:   rgba(74, 124, 142, 0.1);    /* dark teal, semi-transparent */

    /* ── Hue alpha variants (numerical suffix = alpha × 100) ─
       Naming: <base-name>-<alpha-pct>. So `--gold-soft-45` is
       `--gold-soft` at alpha 0.45. CSS doesn't compose easily, so the
       relationship is by name only — when you tweak a base hue, also
       update its variants here if you want the family to stay coherent. */
    --accent-soft-12: rgba(140, 180, 210, 0.12);
    --accent-soft-18: rgba(140, 180, 210, 0.18);
    --accent-soft-30: rgba(140, 180, 210, 0.3);
    --accent-soft-35: rgba(140, 180, 210, 0.35);
    --accent-soft-40: rgba(140, 180, 210, 0.4);

    --gold-soft-5:  rgba(245, 196, 74, 0.05);
    --gold-soft-10: rgba(245, 196, 74, 0.10);
    --gold-soft-20: rgba(245, 196, 74, 0.20);
    --gold-soft-45: rgba(245, 196, 74, 0.45);
    --gold-soft-70: rgba(245, 196, 74, 0.7);
    /* --amber-soft above is equivalent to --gold-soft-40 (same hue,
       0.4 alpha). Kept as a distinct name for back-compat. */

    --warm-soft-30: rgba(220, 180, 100, 0.3);
    --warm-soft-40: rgba(220, 180, 100, 0.4);

    --mauve-soft:    rgb(180, 150, 200);        /* regard layer accent */
    --mauve-soft-40: rgba(180, 150, 200, 0.4);

    --warm-gold-15: rgba(220, 180, 60, 0.15);
    --warm-gold-40: rgba(220, 180, 60, 0.4);

    --danger-soft-18: rgba(255, 100, 100, 0.18);
    --danger-soft-40: rgba(255, 100, 100, 0.4);
    --danger-soft-45: rgba(255, 100, 100, 0.45);

    --peach-15: rgba(255, 210, 150, 0.15);
    --peach-40: rgba(255, 210, 150, 0.4);

    --mint-15: rgba(80, 200, 130, 0.15);
    --mint-40: rgba(80, 200, 130, 0.4);

    --gray-160-15: rgba(160, 160, 160, 0.15);
    --gray-160-40: rgba(160, 160, 160, 0.4);

    --sky-12: rgba(150, 200, 255, 0.12);
    --sky-35: rgba(150, 200, 255, 0.35);

    /* coherence-high alpha variants (extending the existing --coherence-glow set) */
    --coherence-glow-3:  rgba(74, 124, 142, 0.03);
    --coherence-glow-25: rgba(74, 124, 142, 0.25);
    --coherence-glow-40: rgba(74, 124, 142, 0.4);

    /* black shadow color extended */
    --shadow-color-40: rgba(0, 0, 0, 0.4);
    --shadow-color-60: rgba(0, 0, 0, 0.6);
    --shadow-color-78: rgba(0, 0, 0, 0.78);

    /* ── Layout ───────────────────────────────────── */
    /* Width of the always-on left wiki pane. Set by JS from
       localStorage on load and updated on drag of the right-edge
       resize handle. The map area pads by this amount so the
       cloud's centroid centers in the map area, not the viewport. */
    --wiki-width: 420px;
}

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

body {
    background: var(--bg-deep);
    color: var(--text);
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 13px;
    line-height: 1.5;
    height: 100vh;
    overflow: hidden;
}

/* ── Inline info icon ──────────────────────────────────────────────
   Tiny "?" glyph appended next to confusing labels. The string itself
   lives in copy.js, bound via data-tip="path.key" — `applyTips()`
   resolves it into the native title attribute on boot. Use sparingly:
   migrate existing titles first; reach for the explicit icon only
   when a concept is genuinely jargon. */
.info-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    margin-left: 4px;
    border-radius: 50%;
    background: var(--bg-soft);
    color: var(--text-dim, rgba(255, 255, 255, 0.5));
    font-size: 9px;
    font-weight: 600;
    line-height: 1;
    cursor: help;
    user-select: none;
    transition: background 0.15s ease, color 0.15s ease;
}
.info-icon:hover {
    background: rgba(255, 255, 255, 0.14);
    color: var(--text, rgba(255, 255, 255, 0.9));
}

/* ── Loading veil ──────────────────────────────────────────────────
   Covers the page from first paint until the websocket connects (the
   "Connected" moment in app.js). Same color as --bg-deep so there's no
   flash when it fades — just the half-built UI revealing itself. */
#loading-veil {
    position: fixed;
    inset: 0;
    z-index: 10000;
    background: var(--bg-deep);
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 1;
    transition: opacity 0.6s ease;
    pointer-events: all;
}
#loading-veil.ready {
    opacity: 0;
    pointer-events: none;
}
#loading-veil .loading-mark {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    color: var(--text-dim, rgba(255, 255, 255, 0.4));
    user-select: none;
}
#loading-veil .loading-title {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 18px;
    font-weight: 300;
    letter-spacing: 0.32em;
    text-transform: lowercase;
    opacity: 0.7;
}
#loading-veil .loading-dots {
    display: inline-flex;
    gap: 6px;
}
#loading-veil .loading-dots i {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.3;
    animation: loading-dot-pulse 1.2s ease-in-out infinite;
}
#loading-veil .loading-dots i:nth-child(2) { animation-delay: 0.2s; }
#loading-veil .loading-dots i:nth-child(3) { animation-delay: 0.4s; }
@keyframes loading-dot-pulse {
    0%, 100% { opacity: 0.2; transform: scale(0.85); }
    50%      { opacity: 0.9; transform: scale(1.1);  }
}
/* Loading progress: a thin bar + % label so the (large) map-data download
   reads as progress, not a frozen spinner, on slow connections. */
#loading-veil .loading-bar {
    width: 180px;
    height: 2px;
    background: rgba(255, 255, 255, 0.08);
    border-radius: 2px;
    overflow: hidden;
}
#loading-veil .loading-bar i {
    display: block;
    height: 100%;
    width: 0%;
    background: currentColor;
    opacity: 0.55;
    transition: width 0.25s ease;
}
#loading-veil .loading-progress {
    font-size: 10px;
    letter-spacing: 0.18em;
    opacity: 0.45;
    min-height: 12px;
}

/* Corpus badge next to title — v1 / v2_plain / ... */
.corpus-badge {
    display: inline-block;
    margin-left: 0.4em;
    padding: 2px 7px;
    font-size: 0.6em;
    font-weight: 400;
    vertical-align: middle;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg-panel);
    letter-spacing: 0.02em;
}
.corpus-badge:empty { display: none; }

/* Connection status chip — hidden when healthy, a quiet red "DISCONNECTED"
   when the heartbeat (or the WS) says the server isn't answering. */
.conn-status {
    display: none;
    margin-left: 0.4em;
    padding: 2px 7px;
    font-size: 0.6em;
    font-weight: 600;
    vertical-align: middle;
    letter-spacing: 0.06em;
    border-radius: 4px;
    color: #ff8a7a;
    border: 1px solid #7a2e26;
    background: rgba(120, 30, 24, 0.25);
}
.conn-status.disconnected { display: inline-block; }

/* ── Layout ─────────────────────────────────────────── */

#app {
    display: grid;
    grid-template-rows: auto 1fr;
    height: 100vh;
}

header {
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    /* Left padding matches the docked panels' .panel-body padding (12px)
       so the Anima logo lines up with the back/forward buttons and the
       wiki column's content edge. Right padding kept at 16px to preserve
       breathing room for the catalog/bookmarks/subspace cluster. */
    padding: 8px 16px 8px 12px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

header h1 {
    font-size: 14px;
    font-weight: 400;
    color: var(--text-bright);
    letter-spacing: 2px;
    text-transform: uppercase;
    margin: 0;
}

/* ── Brand: Anima wordmark + Atlas title in a single flex row ────── */
.brand {
    display: flex;
    align-items: center;
    gap: 14px;
}
.brand-logo {
    display: inline-flex;
    align-items: center;
    line-height: 0;          /* kill the inline-image baseline gap */
}
.brand-logo img {
    height: 22px;
    width: auto;
    display: block;
}

.header-stats {
    display: flex;
    align-items: center;
    gap: 20px;
    font-size: 11px;
    color: var(--text-dim);
}

/* Embedding-lens toggle: a compact segmented control in the header. */
.lens-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.lens-toggle[hidden] { display: none; }
.lens-label {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-dim);
}
.lens-toggle .lens-opt {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-size: 10px;
    padding: 2px 8px;
    cursor: pointer;
}
.lens-toggle .lens-opt:first-of-type { border-radius: 4px 0 0 4px; }
.lens-toggle .lens-opt:last-of-type { border-radius: 0 4px 4px 0; margin-left: -1px; }
.lens-toggle .lens-opt:hover { color: var(--text); }
.lens-toggle .lens-opt.active {
    color: var(--text-bright);
    background: var(--overlay-medium);
    border-color: var(--text-dim);
}

.stat-value {
    color: var(--accent);
    font-weight: 600;
}

.stat-value.amber { color: var(--amber); }

.stat-model {
    color: var(--text-dim);
    font-weight: 400;
    font-size: 10px;
    margin-left: 4px;
    padding: 1px 5px;
    border: 1px solid var(--border);
    border-radius: 2px;
}

.handle-model {
    color: var(--text-dim);
    font-weight: 400;
    font-size: 9px;
    padding: 1px 5px;
    border: 1px solid var(--border);
    border-radius: 2px;
    margin-left: 2px;
}

.subspace-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px;
    border: 1px solid var(--border);
    border-radius: 12px;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
.subspace-pill:hover {
    border-color: var(--accent-dim);
    background: var(--coherence-glow);
}
.subspace-pill .subspace-label {
    color: var(--text-dim);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.subspace-pill .subspace-text {
    color: var(--text);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* ── Header buttons (Catalog / Bookmarks) ─────────────────────────── */
/* Rectangular, deliberately distinct from .subspace-pill (which is
   non-interactive readout, not a button). These are the entry points
   into the Catalog and Bookmarks views and want the affordance of a
   real button — squared corners, more weight, a hover surface. */
.header-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 16px;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.05em;
    text-transform: lowercase;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.header-btn:hover {
    border-color: var(--accent-dim);
    color: var(--text-bright);
    background: var(--coherence-glow);
}
.header-btn.active {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--coherence-glow-mid);
}
.header-btn-glyph {
    font-size: 13px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
}
/* Inline SVG variant (e.g. bookmark ribbon) — match the glyph slot.
   Outline-only by default; matches the wiki-bookmark icon style. */
svg.header-btn-glyph {
    width: 11px;
    height: 14px;
}
svg.header-btn-glyph path {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linejoin: miter;
}

/* ── Dormant: projection toggle ───────────────────────────────────── */
/* The chooser was removed from the header (UMAP-static is now the only
   surfaced projection). The CSS + the JS code paths for "umap" (live
   warping) and "pca" still exist — see static/app.js setupProjectionToggle
   and the projectionMode branches throughout. We may resurface this
   later via Settings → Advanced. For now: nothing references these
   selectors. Safe to delete with the JS if we decide PCA/live-UMAP are
   gone for good. */
.projection-toggle {
    display: inline-flex;
    gap: 2px;
    margin-left: auto;
    padding: 2px;
    border: 1px solid var(--text-dim);
    border-radius: 3px;
    opacity: 0.8;
}

.projection-toggle .proj-btn {
    background: transparent;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 10px;
    padding: 2px 8px;
    cursor: pointer;
    border-radius: 2px;
    transition: background 0.15s, color 0.15s;
}

.projection-toggle .proj-btn:hover {
    color: var(--accent);
}

.projection-toggle .proj-btn.active {
    background: var(--accent);
    color: var(--bg);
}

/* ── Map stage (full-bleed) ─────────────────────────── */

#map-stage {
    position: relative;
    overflow: hidden;
}

#map-3d {
    position: absolute;
    top: 0;
    bottom: 0;
    /* Map sits between the wiki (left dock) and the filters (right dock).
       Cloud's centroid centers in this area, not the viewport — so opening
       either panel reflows the map instead of covering it. */
    left: var(--wiki-width, 0px);
    right: var(--filters-width, 0px);
    /* No width transition: the renderer/camera are resized by a single
       onResize() when a dock opens/closes, which reads clientWidth
       synchronously. A CSS transition would animate the width AFTER that
       read, leaving the canvas (and the PCA axis stickers, which project
       against #map-3d's live rect each frame) sized for the pre-transition
       width — they'd sit offset until the next onResize. Instant resize
       keeps everything in sync (and makes wiki-drag track the cursor). */
}

/* ── Toolbar — vertical icon strip ──────────────────── */

#toolbar {
    position: absolute;
    top: 12px;
    /* Toolbar sits inside the map area, just past the wiki edge. */
    left: calc(var(--wiki-width, 0px) + 12px);
    transition: left 0.12s ease;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 4px;
    background: var(--panel-glass-bg-medium);
    backdrop-filter: blur(var(--panel-glass-blur));
    -webkit-backdrop-filter: blur(var(--panel-glass-blur));
    border: 1px solid var(--border);
    border-radius: 6px;
}

.tool-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 8px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
    user-select: none;
    width: 100%;
    text-align: left;
}
.tool-btn:hover {
    background: var(--overlay-light);
    color: var(--text-bright);
}
.tool-btn.active {
    background: var(--teal-soft);
    border-color: rgba(85, 162, 190, 0.25);
    color: var(--accent);
}
.tool-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}
.tool-btn .tool-glyph {
    font-size: 14px;
    line-height: 1;
    width: 14px;
    text-align: center;
    flex-shrink: 0;
}
.tool-btn .tool-label { /* shown only when toolbar wide enough */ }

.tool-divider {
    height: 1px;
    background: var(--border);
    margin: 4px 2px;
}

.action-btn { /* centroid / stop — visually distinct */ }
.action-btn.tool-btn {
    color: var(--text-dim);
}

/* ── Floating panels ────────────────────────────────── */

.float-panel {
    position: absolute;
    z-index: 20;
    background: var(--panel-glass-bg);
    backdrop-filter: blur(var(--panel-glass-blur));
    -webkit-backdrop-filter: blur(var(--panel-glass-blur));
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 4px 24px var(--shadow-color-40);
    display: none;
    flex-direction: column;
    max-height: calc(100vh - 100px);
}
.float-panel.open { display: flex; }

.float-panel header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 12px;
    border-bottom: 1px solid var(--border);
    background: transparent;
    cursor: move;
    user-select: none;
}
.float-panel .panel-title {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--text-dim);
}
.float-panel .panel-close {
    background: transparent;
    border: none;
    color: var(--text-dim);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    /* Comfortable ~32x30 hit area — a thin glyph-only target was easy to
       miss. (The deeper "near-miss lands on a map orb behind the panel" bug
       was the cursor-anchor #dot-hover-link sitting at z-index 50, above the
       panels; that's fixed at the z-index level now. This padding is just
       ergonomics.) */
    padding: 8px 11px;
    margin: -4px -5px -4px 0;   /* keep the header height tidy despite the bigger pad */
    border-radius: 4px;
}
.float-panel .panel-close:hover {
    color: var(--text-bright);
    background: var(--overlay-medium);
}
.float-panel .panel-body {
    overflow-y: auto;
    padding: 12px;
    flex: 1;
}

/* Default panel positions. "center" / "bottom-center" / etc. mean the
   center of the MAP AREA (right of the wiki dock), not the viewport. */
#panel-probe[data-default-pos="top-center"] {
    /* Thin strip at the top-center of the map area. Keeps the cloud
       fully visible below so the walker's motion is observable while
       probing — that's the whole point of the verb. */
    left: calc(50vw + var(--wiki-width, 0px) / 2);
    top: 80px;
    transform: translateX(-50%);
    width: 560px;
    max-width: calc(100vw - var(--wiki-width, 0px) - 100px);
    transition: left 0.12s ease;
}

/* Compact probe panel — input is the primary affordance; readouts +
   controls are an ambient strip below it. */
.panel-probe-compact .panel-body {
    padding: 10px 12px 12px;
}
.panel-probe-compact #walk-input {
    width: 100%;
    box-sizing: border-box;
    margin: 0 0 8px 0;
    /* Slight bump in font-size to read as the primary verb. */
    font-size: 13px;
    padding: 8px 10px;
}
.probe-strip {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    font-size: 11px;
    color: var(--text-dim);
    padding-top: 2px;
}
.probe-strip .probe-readout {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
}
.probe-strip .probe-readout-label {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-dim);
}
.probe-strip-sep {
    flex: 1;
    min-width: 0;
}
.probe-strip .alpha-control {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.probe-strip .alpha-control input[type="range"] {
    width: 70px;
}
.probe-strip .void-toggle {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    cursor: pointer;
    user-select: none;
}
.probe-strip .model-select {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.probe-strip .model-select select {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
    font-family: inherit;
    font-size: 10px;
    padding: 2px 6px;
    border-radius: 2px;
}
.probe-back {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 10px;
    padding: 3px 9px;
    border-radius: 2px;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s;
}
.probe-back:hover {
    border-color: var(--accent-dim);
    color: var(--text);
}
.probe-nearest {
    display: flex;
    align-items: baseline;
    gap: 6px;
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
    font-size: 11px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.probe-nearest .probe-readout-label {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-dim);
    flex-shrink: 0;
}
.probe-nearest .stat-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Main action bar (always-visible, bottom-center) ──────── */

#main-actions {
    position: absolute;
    /* Centered on the whole window (not the map area) — stays put when the
       wiki or filters docks open/close. */
    left: 50vw;
    bottom: 16px;
    transform: translateX(-50%);
    z-index: 28;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: var(--panel-glass-bg);
    backdrop-filter: blur(var(--panel-glass-blur));
    -webkit-backdrop-filter: blur(var(--panel-glass-blur));
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 24px var(--shadow-color-strong);
    max-width: calc(100vw - 200px);
    flex-wrap: nowrap;
    overflow: visible;
}

.main-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
    user-select: none;
    white-space: nowrap;
}
.main-btn:hover {
    background: var(--overlay-light);
    color: var(--text-bright);
    border-color: var(--overlay-12);
}
.main-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}
.main-btn.main-primary {
    color: var(--accent);
    border-color: var(--coherence-glow-25);
    background: var(--coherence-glow);
}
.main-btn.main-primary:hover {
    color: var(--bg-darker);
    background: var(--accent);
    border-color: var(--accent);
}
.main-glyph {
    font-size: 12px;
    line-height: 1;
}

.main-divider {
    width: 1px;
    height: 18px;
    background: var(--border);
    margin: 0 4px;
}

#contextual-actions {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
#contextual-actions:empty { display: none; }
/* The ::before divider used to separate the trio of always-available
   actions (centroid + stop + explore) from contextual-by-anchor ones.
   With centroid + stop moved into the Probe panel, the main bar holds
   only the primary "explore" button and the contextual actions, so the
   divider reads as orphaned. The 6px gap on the flex container is
   enough spacing between explore and the first contextual button. */

/* ── Context menus (e.g. scaffolding cousins list) ─────── */

.ctx-menu {
    position: fixed;
    z-index: 100;
    background: var(--panel-glass-bg-near);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 4px 24px var(--shadow-color-strong);
    padding: 4px;
    min-width: 220px;
    /* max-width caps the menu so creature names — which are now joined
       comma-lists from Sonnet 4.5 tagging and can be very long — get
       truncated with ellipsis (per .ctx-menu-name) instead of pushing
       the row's model badge off-screen. Without this, locations with
       short emoji handles fit fine but long-named creature cousins
       lose their model name. */
    max-width: 360px;
    max-height: 320px;
    overflow-y: auto;
    font-size: 11px;
}
.ctx-menu-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    padding: 6px 10px;
    background: transparent;
    border: none;
    border-radius: 3px;
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    text-align: left;
    cursor: pointer;
}
.ctx-menu-item:hover {
    background: var(--overlay-light2);
    color: var(--text-bright);
}
.ctx-menu-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ctx-menu-model {
    color: var(--text-dim);
    font-size: 9px;
    flex-shrink: 0;
}
#panel-journal[data-default-pos="right"] {
    right: 16px;
    top: 16px;
    width: 380px;
    max-height: calc(100vh - 100px);
}
#panel-filters[data-default-pos="right-side"] {
    /* Docked right column — mirrors the wiki's left dock. Toggled by the
       Filters button; the map area shrinks via --filters-width (set in JS
       to match this width) so the cloud centers in the remaining space
       instead of hiding under a floating panel. Not draggable. The width
       here MUST match FILTERS_DOCK_WIDTH in app.js. */
    right: 0;
    top: 0;
    bottom: 0;
    width: 320px;
    max-height: none;
    height: auto;
    border-radius: 0;
    border-right: none;
    border-top: none;
    border-bottom: none;
}
#panel-filters[data-default-pos="right-side"] header {
    cursor: default; /* docked, not a drag handle */
}
/* Reset + close grouped on the right of the filters title row. */
.panel-header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}
.panel-reset {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 2px 7px;
    border-radius: 3px;
    cursor: pointer;
}
.panel-reset:hover {
    color: var(--text-bright);
    border-color: var(--text-dim);
    background: var(--overlay-medium);
}
/* Lazy NEAREST: the Recalculate button glows when the panel is stale
   (filters changed since the last compute) — a nudge to recompute. */
#nearest-calc-btn.stale {
    color: var(--text-bright);
    border-color: var(--text-bright);
    background: var(--overlay-medium);
}
/* ── Docked filters: fixed bands + one scroll zone (the model list) ──────
   The panel body is a flex column that does NOT scroll as a whole. Every
   section keeps its natural height EXCEPT the models group, which grows to
   fill the leftover space and scrolls its rows internally. So search /
   types / LIVENESS above and the upstream sections below stay put while you
   play the model list — no scrolling the panel and losing your place. */
#panel-filters[data-default-pos="right-side"] .panel-body {
    display: flex;
    flex-direction: column;
    overflow: hidden;            /* the inner model zone scrolls, not this */
}
#panel-filters[data-default-pos="right-side"] #filters-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;               /* let the flex child actually shrink */
    overflow: hidden;
}
#panel-filters[data-default-pos="right-side"] #filters-body > * {
    flex: 0 0 auto;              /* fixed bands: keep natural height */
}
#panel-filters[data-default-pos="right-side"] #filters-body > #legend-models-group {
    flex: 1 1 0;                 /* the models group absorbs the leftover */
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
#panel-filters[data-default-pos="right-side"] .legend-models-scroll {
    flex: 1;
    overflow-y: auto;
    min-height: 120px;           /* never collapses to nothing */
}
/* Thin sub-label inside a merged section (e.g. "vector parameters" /
   "creature metadata" within "upstream parameters"). Quieter than a
   section header — it groups without claiming to be its own section. */
.filters-sublabel {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-dim);
    opacity: 0.7;
    margin: 9px 0 3px;
}
.filters-sublabel:first-child { margin-top: 2px; }

/* ── Model list as a lineage tree ─────────────────────────────────────
   Collapsible lineage parents over the per-model leaf rows. Collapsed by
   default; a thin swatch strip previews the family's colors. The dot is a
   tristate group toggle (full / partial / empty). */
.legend-lineage-head {
    display: flex;
    align-items: center;
    /* No flex gap: the chevron→dot spacing comes from the shared ::before
       margin-right (so it matches the filter chevron exactly); the dot→name
       gap is restored explicitly on .lineage-toggle below. */
    gap: 0;
    padding: 3px 0;
    cursor: pointer;
    user-select: none;
    border-radius: 3px;
}
.legend-lineage-head:hover { background: var(--overlay-medium); }
/* The model-group chevron is now a ::before on .legend-lineage-head, sharing
   the filter chevron's rule above (tune both there). Old span removed. */
.lineage-toggle {
    width: 9px;
    height: 9px;
    border-radius: 2px;
    border: 1px solid var(--text-dim);
    flex-shrink: 0;
    cursor: pointer;
    margin-right: 6px;   /* dot → name spacing (was the head's flex gap) */
}
.lineage-toggle.full   { background: var(--text); border-color: var(--text); }
.lineage-toggle.empty  { background: transparent; }
.lineage-toggle.partial {
    background: linear-gradient(135deg, var(--text) 0 50%, transparent 50% 100%);
}
.lineage-name {
    color: var(--text);
    font-weight: 600;
    font-size: 11px;
}
.lineage-mcount {
    font-size: 9px;
    color: var(--text-dim);
    margin-left: 3px;
    font-variant-numeric: tabular-nums;
}
.legend-lineage-head .legend-count { margin-left: auto; }
.legend-lineage-children { margin-left: 16px; }
/* Paint chip: the group's lens color. Transparent = a faint checkerboard so
   "see-through to per-model" reads at a glance. */
.lineage-paint {
    width: 11px;
    height: 11px;
    border-radius: 3px;
    flex-shrink: 0;
    margin-left: 8px;
    cursor: pointer;
    border: 1px solid var(--border);
}
.lineage-paint:hover { border-color: var(--text-dim); }
.lineage-paint.transparent {
    background-color: transparent;
    background-image:
        linear-gradient(45deg, var(--text-dim) 25%, transparent 25%, transparent 75%, var(--text-dim) 75%),
        linear-gradient(45deg, var(--text-dim) 25%, transparent 25%, transparent 75%, var(--text-dim) 75%);
    background-size: 6px 6px;
    background-position: 0 0, 3px 3px;
    opacity: 0.45;
}
.paint-popover {
    position: fixed;
    z-index: 60;
    display: grid;
    grid-template-columns: repeat(6, 18px);
    gap: 5px;
    padding: 8px;
    background: var(--panel-glass-bg);
    backdrop-filter: blur(var(--panel-glass-blur));
    -webkit-backdrop-filter: blur(var(--panel-glass-blur));
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 4px 20px var(--shadow-color-40);
}
.paint-swatch {
    width: 18px;
    height: 18px;
    border-radius: 4px;
    border: 1px solid var(--border);
    cursor: pointer;
    padding: 0;
}
.paint-swatch:hover { transform: scale(1.12); }
.paint-swatch.paint-transparent {
    background-color: transparent;
    background-image:
        linear-gradient(45deg, var(--text-dim) 25%, transparent 25%, transparent 75%, var(--text-dim) 75%),
        linear-gradient(45deg, var(--text-dim) 25%, transparent 25%, transparent 75%, var(--text-dim) 75%);
    background-size: 8px 8px;
    background-position: 0 0, 4px 4px;
}
.models-groupby {
    /* Drop the native OS chrome; draw our own caret so it matches the panel.
       Sized to normal-size text (not the 9px label) with real vertical room,
       so the value reads at a comfortable size in the closed state. */
    appearance: none;
    -webkit-appearance: none;
    background: transparent
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='9' height='9' viewBox='0 0 9 9'><path d='M1.5 3 4.5 6 7.5 3' fill='none' stroke='%239aa0a8' stroke-width='1.3' stroke-linecap='round'/></svg>")
        no-repeat right 7px center;
    border: 1px solid var(--border);
    color: var(--text);
    font-size: 11px;
    line-height: 1.5;
    border-radius: 5px;
    padding: 3px 22px 3px 9px;
    cursor: pointer;
}
.models-groupby:hover { border-color: var(--text-dim); }
.models-groupby:focus { outline: none; border-color: var(--text-dim); }
/* Open menu: Chrome honors option bg/color on Mac inconsistently, but this
   darkens it where supported. (A fully-custom popover is the next step if the
   native menu still reads as foreign.) */
.models-groupby option {
    background: #16161d;
    color: var(--text);
    font-size: 12px;
}
/* Left cluster (models · all · none) and right cluster (group by · select). */
.models-header-left,
.models-header-right {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.groupby-label {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-dim);
}
.legend-lineage.collapsed .legend-lineage-children { display: none; }
#panel-preview[data-default-pos="bottom-left"] {
    left: 16px;
    bottom: 24px;
    width: 380px;
    max-height: 60vh;
}
#panel-settings[data-default-pos="top-left-2"] {
    left: 80px;
    top: 16px;
    width: 280px;
}

/* ── Filters: param-min sliders ───────────────────────── */

#param-filters {
    margin-top: 8px;
    padding-top: 6px;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.param-row {
    display: grid;
    grid-template-columns: 64px 1fr 36px;
    align-items: center;
    column-gap: 8px;
    font-size: 11px;
    color: var(--text-dim);
    padding: 2px 0;
}
/* Two-column vector params: 14 dims → 7 rows. Each cell stacks the label over
   the slider+readout so names stay legible at half width. */
.param-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 12px;
    row-gap: 2px;
    /* Horizontal breathing room so the slider thumbs (which overflow their
       cell at min/max) aren't clipped by #filters-body's overflow:hidden. */
    padding: 0 8px;
}
.param-grid .param-row {
    grid-template-columns: 1fr auto;
    grid-template-areas:
        "label  readout"
        "slider slider";
    align-items: center;
    column-gap: 6px;
    row-gap: 0;
    padding: 1px 0;
}
.param-grid .param-label   { grid-area: label; }
.param-grid .param-readout { grid-area: readout; }
.param-grid .dual-range    { grid-area: slider; }
.param-row.active .param-label,
.param-row.active .param-readout {
    color: var(--accent);
}
.param-label {
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 9px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.param-readout {
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    text-align: right;
    font-size: 10px;
}
.param-min-val, .param-max-val { color: inherit; }

/* Multiplicity row — categorical chips instead of a numeric range.
   Sits in the same .param-row grid so it lines up with fortune /
   friendliness / sociality, but the middle column is filled with
   toggle-chips and the readout column is empty. */
.param-row.multiplicity-row {
    grid-template-columns: 64px 1fr;
    align-items: center;
}
.multiplicity-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.multiplicity-chip {
    background: transparent;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 1px 8px;
    font-size: 9px;
    text-transform: lowercase;
    letter-spacing: 0.04em;
    line-height: 1.5;
    cursor: pointer;
    font-family: inherit;
    transition: all 0.12s ease;
}
.multiplicity-chip:hover {
    color: var(--text);
    border-color: var(--text-dim);
}
.multiplicity-chip.off {
    opacity: 0.35;
    text-decoration: line-through;
}
.param-row.multiplicity-row.active .param-label {
    color: var(--accent);
}

/* Header above the param rows: title + reset-all */
.param-filters-header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 4px;
}
.param-reset {
    background: transparent;
    border: 1px solid var(--border-dim);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 1px 6px;
    border-radius: 2px;
    cursor: pointer;
}
.param-reset:hover {
    color: var(--text-bright);
    border-color: var(--text-dim);
}

/* Dual-handle range slider — two <input type="range"> overlaid on the
   same track, each only exposing its thumb to interaction. */
.dual-range {
    position: relative;
    display: block;
    height: 22px;
    width: 100%;
}
.dr-track {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 3px;
    margin-top: -1.5px;
    background: var(--bg-input, var(--overlay-strong));
    border-radius: 2px;
    pointer-events: none;
}
.dr-fill {
    position: absolute;
    top: 50%;
    height: 3px;
    margin-top: -1.5px;
    background: var(--accent-dim);
    border-radius: 2px;
    pointer-events: none;
    /* left + width set inline by JS */
}
.dr-input {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 22px;
    width: 100%;
    margin: 0;
    padding: 0;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    /* Track is non-interactive; only the thumb is. Lets both inputs
       overlay each other and both remain clickable. */
    pointer-events: none;
    z-index: 2;
}
.dr-input::-webkit-slider-thumb {
    -webkit-appearance: none;
    pointer-events: auto;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent);
    cursor: ew-resize;
    border: 1px solid var(--bg-deep);
    box-shadow: 0 0 0 1px var(--accent);
}
.dr-input::-moz-range-thumb {
    pointer-events: auto;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent);
    cursor: ew-resize;
    border: 1px solid var(--bg-deep);
    box-shadow: 0 0 0 1px var(--accent);
}
.dr-input::-webkit-slider-runnable-track { background: transparent; border: none; }
.dr-input::-moz-range-track { background: transparent; border: none; }

/* ── Settings panel rows ────────────────────────────── */

.setting-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    column-gap: 12px;
    row-gap: 4px;
    font-size: 11px;
    color: var(--text-dim);
    padding: 4px 0;
}
.setting-row label {
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 10px;
}
.setting-row input[type="range"] {
    grid-column: 1 / 3;
    width: 100%;
    accent-color: var(--accent);
}
.setting-row > span:last-child {
    color: var(--text);
    font-variant-numeric: tabular-nums;
    grid-column: 2;
    grid-row: 1;
}

/* Stack variant — for settings whose control wants the full width
   below its label (e.g. radio groups). */
.setting-row-stack {
    grid-template-columns: 1fr;
    row-gap: 8px;
}
.setting-radio-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.setting-radio {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 6px 8px;
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    transition: border-color 0.12s, background 0.12s;
}
.setting-radio:hover {
    border-color: var(--accent-dim);
    background: var(--coherence-glow-3);
}
.setting-radio input[type="radio"] {
    accent-color: var(--accent);
    margin-top: 2px;
    cursor: pointer;
}
.setting-radio-label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.3;
}
.setting-radio-label strong {
    color: var(--text-bright);
    font-weight: 600;
    text-transform: lowercase;
    letter-spacing: 0.04em;
    font-size: 11px;
}
.setting-radio-hint {
    color: var(--text-dim);
    font-size: 10px;
    text-transform: none;
    letter-spacing: 0;
}
/* Selected radio: subtle accent-tinted card, like the active TOC entry. */
.setting-radio:has(input[type="radio"]:checked) {
    border-color: var(--coherence-glow-40);
    background: var(--coherence-glow-mid);
}

/* ── Resize handle on every floating panel ──────────── */

.panel-resize-handle {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 14px;
    height: 14px;
    cursor: nwse-resize;
    z-index: 1;
    /* Subtle visual: two diagonal lines in the corner */
    background:
        linear-gradient(
            135deg,
            transparent 0,
            transparent 40%,
            var(--text-dim) 40%,
            var(--text-dim) 50%,
            transparent 50%,
            transparent 70%,
            var(--text-dim) 70%,
            var(--text-dim) 80%,
            transparent 80%
        );
    opacity: 0.35;
    transition: opacity 0.15s;
}
.panel-resize-handle:hover { opacity: 0.8; }
.float-panel.resizing .panel-resize-handle { opacity: 1; }
.float-panel.resizing .panel-body { user-select: none; }

/* Header is the drag handle for any .float-panel. While dragging,
   user-select is suppressed so text doesn't highlight. */
.float-panel header {
    cursor: grab;
}
.float-panel.dragging,
.float-panel.dragging header {
    cursor: grabbing;
}
.float-panel.dragging .panel-body {
    pointer-events: none;
    user-select: none;
}

/* ── Probe panel internals ──────────────────────────── */

#panel-probe .panel-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
#panel-probe #walk-input {
    width: 100%;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 8px 12px;
    color: var(--text-bright);
    font-family: inherit;
    font-size: 13px;
    outline: none;
    transition: border-color 0.15s;
}
#panel-probe #walk-input:focus {
    border-color: var(--accent-dim);
}
.probe-controls {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    font-size: 11px;
    color: var(--text-dim);
}
.probe-controls .model-select {
    display: flex;
    align-items: center;
    gap: 6px;
}
.probe-controls .model-select label {
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 9px;
}
.probe-controls .model-select select,
.probe-controls .void-toggle,
.probe-controls .alpha-control {
    color: var(--text);
}
.probe-actions {
    display: flex;
    gap: 6px;
    justify-content: flex-end;
}
.probe-actions .btn {
    padding: 6px 14px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.btn.btn-primary {
    background: var(--accent);
    color: var(--bg-darker);
    border-color: var(--accent);
}
.btn.btn-primary:hover {
    background: var(--accent-dim);
}

/* ── Model + type legend (rendered into floating #panel-filters) ──── */

#model-legend {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
    /* No left padding — panel-body padding handles the consistent
       left-edge inset for all section content. */
    padding: 3px 0;
    border-radius: 3px;
    cursor: pointer;
    font-size: 11px;
    transition: opacity 0.2s;
    user-select: none;
}

.legend-item:hover {
    background: var(--overlay-medium);
}

.legend-item.off {
    opacity: 0.3;
}
/* Partial-dim: model toggled ON but no points pass the other filters.
   Lighter than .off so the user can still see which models exist and
   read them as "currently empty under this filter" rather than "hidden". */
.legend-item.legend-empty {
    opacity: 0.55;
}
/* If both apply (toggled off AND empty), .off wins (deeper dim) — the
   off state is the more deliberate choice the user made. */
.legend-item.off.legend-empty {
    opacity: 0.3;
}

.legend-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
}

.legend-label {
    color: var(--text);
    font-weight: 500;
}

.legend-count {
    /* Match the label color so the count is readable both when the row
       is enabled (it was too dim against --text-dim before) and when
       the row is toggled off (opacity 0.3 on the parent now dims the
       count the same way it dims the label, instead of compounding
       on top of an already-dimmer base). Visual hierarchy between
       label and count is carried by font-size (10px vs default) and
       font-weight (label is 500, count inherits normal). */
    color: var(--text);
    font-size: 10px;
    margin-left: auto;
    font-variant-numeric: tabular-nums;
}

/* Model rows render counts inside a .legend-counts wrapper so the
   optional .legend-pct (shown when a text-search filter is active)
   sits just to the left of the X/Y count, with the whole group pinned
   to the right edge. Other legend rows (types, derivatives, etc.)
   keep using .legend-count directly with its own margin-left:auto. */
.legend-counts {
    margin-left: auto;
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
}
.legend-counts .legend-count {
    margin-left: 0;
}
.legend-pct {
    color: var(--accent);
    font-size: 10px;
    font-variant-numeric: tabular-nums;
    opacity: 0.85;
}

/* Group-name register: bolder + brighter than the labels inside
   sections, so the eye reads the section hierarchy from font weight
   alone (no x-position trickery — every section starts at the same
   left edge, set by the panel-body's padding). */
.legend-section-header {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-bright);
    font-weight: 600;
    padding: 6px 0 4px;
    margin: 0;
}

/* Collapsible filter sections — header is clickable, chevron sits at
   the right end so the group name starts at the same left edge as
   every other piece of content in the panel. Models is the one
   section expanded by default; everything else collapses on first
   load. */
.legend-section-header.collapsible {
    cursor: pointer;
    user-select: none;
    display: flex;
    align-items: center;
}
.legend-section-header.collapsible:hover {
    color: var(--accent);
}
/* Disclosure marker at the START of the header — a plain ">" at text size,
   so a first-time visitor immediately reads the section as openable. Points
   right when collapsed, rotates down (90°) when open. */
/* Shared by filter-section headers AND model-group (lineage) heads, so tuning
   the glyph / size / color / spacing here updates BOTH chevrons at once. */
.legend-section-header.collapsible::before,
.legend-lineage-head::before {
    content: "›";
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    transform: rotate(0deg);   /* explicit (not `none`) so the rule cascades cleanly */
    display: inline-block;
    width: 11px;
    text-align: center;
    margin-right: 2px;
    color: var(--text-dim);
}
.legend-section-header.collapsible:not(.collapsed)::before,
.legend-lineage:not(.collapsed) .legend-lineage-head::before {
    transform: rotate(90deg);
}
.section-collapsed > *:not(.legend-section-header) {
    display: none !important;
}

/* Divider between every top-level filter group. Using a top border on
   each group except the first gives consistent spacing without
   doubling separators. The name-filter (always visible) is first by
   construction; subsequent groups draw the line above themselves. */
#filters-body > * + * {
    border-top: 1px solid var(--border);
    padding-top: 4px;
    margin-top: 4px;
}

.legend-separator {
    height: 1px;
    background: var(--border);
    margin: 4px 0 2px;
    opacity: 0.5;
}

.legend-recalc-btn[hidden] { display: none; }
.legend-recalc-btn {
    display: block;
    width: 100%;
    margin: 4px 0;
    padding: 5px 8px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    background: transparent;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 3px;
    cursor: pointer;
    font-family: inherit;
    text-align: left;
}
.legend-recalc-btn:hover {
    color: var(--text);
    border-color: var(--text-dim);
    background: var(--overlay-tint);
}

.hover-handle {
    cursor: help;
    border-bottom: 1px dotted var(--overlay-18);
}
.hover-handle:hover {
    border-bottom-color: var(--overlay-bright);
}

.btn-open-handle {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 6px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-dim);
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 2px;
    cursor: pointer;
    vertical-align: baseline;
    line-height: 1.4;
    font-family: inherit;
}
.btn-open-handle:hover {
    color: var(--text-bright);
    border-color: var(--overlay-bright);
}

.between-handle {
    color: var(--text-dim);
    font-style: italic;
    font-family: monospace;
}

/* Base type-badge shape (shared chrome). Color comes from the
   per-type modifier class so each kind keeps its own visual register
   matching TYPE_REGISTRY in app.js. The base rule is intentionally
   color-neutral — every emit site appends a `-<type>` modifier. */
.neighbor-type-badge {
    display: inline-block;
    margin-left: 4px;
    padding: 0 4px;
    font-size: 8px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 2px;
    line-height: 1.4;
    vertical-align: middle;
}

/* Per-type colors — match the TYPE_REGISTRY accent values exactly so
   the badge in the journal/tooltip/neighbor-list reads the same
   register as the legend dot, the card accent, and the wiki type pill. */
.neighbor-type-badge-creature {
    color: var(--warm-soft);
    border-color: var(--warm-soft-30);
}
.neighbor-type-badge-advisory {
    color: var(--accent-soft);
    border-color: var(--accent-soft-30);
}

.detail-parent {
    font-size: 11px;
    color: var(--text-dim);
    margin: 4px 0 6px;
}

.detail-image-block {
    margin: 8px 0;
}
.detail-image {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 4px;
    border: 1px solid var(--border);
}
.detail-image-attr {
    margin-top: 4px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-dim);
    text-align: right;
}

/* Empty-image CTA: compact row with button + small hint, NOT an
   aspect-ratio-reserved placeholder. A reader who doesn't want an image
   shouldn't pay for a full image-sized hole in their reading column;
   the prose can flow normally below. The button stays visible and
   discoverable; the frame appears only when an actual image exists. */
.detail-image-empty {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 6px 0;
}
.detail-image-empty .btn-generate-image {
    background: var(--gold-soft-10);
    border: 1px solid var(--amber-soft);
    color: var(--gold-soft);
    padding: 5px 10px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 10px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    transition: background 0.15s, border-color 0.15s;
    flex-shrink: 0;
}
.detail-image-empty .btn-generate-image:hover {
    background: var(--gold-soft-20);
    border-color: var(--gold-soft-70);
}
.detail-image-empty-hint {
    font-size: 10px;
    color: var(--text-dim);
    opacity: 0.6;
    font-style: italic;
}

/* ── Wiki skeletons (lazy prose + image) ───────────────────────────
   The wiki paints its instant scaffold (name, model, params, links)
   from in-memory map data, then lazy-fetches the heavy parts — the
   prose body and the canonical image. On a fast link the gap is a
   blink; on a slow one the panel sat with empty holes, then text and
   image *popped* in with a layout jump and no signal that anything was
   pending. These placeholders reserve the footprint and shimmer, so
   the wait reads as loading and the real content settles in over the
   same space instead of shoving everything below it. */
@keyframes wiki-shimmer {
    0%   { background-position: 180% 0; }
    100% { background-position: -180% 0; }
}
.skeleton-shimmer {
    background-color: var(--overlay-faint);
    background-image: linear-gradient(
        100deg,
        transparent 18%,
        var(--overlay-medium) 50%,
        transparent 82%);
    background-size: 200% 100%;
    background-repeat: no-repeat;
    animation: wiki-shimmer 1.4s ease-in-out infinite;
    border-radius: 3px;
}
.desc-skeleton {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 3px 0 2px;
}
.desc-skeleton .skeleton-line {
    display: block;
    height: 10px;
}
/* Image slot still waiting on its image to paint: hold the 1:1 canonical
   frame so the prose below it doesn't jump when the picture lands.
   Principle — keep the <img> in normal flow in BOTH states and let it
   reserve its own square via aspect-ratio, so the image element never
   changes position or box: only its pixels fill in. That avoids the
   double-border (the img keeps its single border throughout) and the
   positional snap (nothing moves when `loading` drops). The shimmer is
   the block background, visible through the not-yet-painted (transparent)
   image and in the empty frame before the <img> is inserted. */
.detail-image-block.img-loading {
    width: 100%;
    aspect-ratio: 1 / 1;   /* holds the square while the <img> is absent/empty */
    border-radius: 4px;
}
.detail-image-block.img-loading > .detail-image {
    aspect-ratio: 1 / 1;   /* reserve the square before pixels arrive, in normal flow */
    width: 100%;
    height: auto;
}
/* Attribution + thumbnail strip are meaningless until the image is up;
   keep them out of the holding frame so they don't flash inside it. */
.detail-image-block.img-loading > .detail-image-attr,
.detail-image-block.img-loading > .detail-thumbs {
    display: none;
}
/* Fade the real image in over the (now-removed) skeleton, but only the
   first time a genuinely-late image lands — JS adds .img-fade-in only
   when the slot had been in the loading state, so warm re-hovers don't
   pulse. */
.detail-image.img-fade-in {
    animation: wiki-img-fade 0.35s ease-out;
}
@keyframes wiki-img-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton-shimmer { animation: none; }
    .detail-image.img-fade-in { animation: none; }
}

.detail-thumbs {
    display: flex;
    gap: 4px;
    margin-top: 6px;
    flex-wrap: wrap;
}
.detail-thumb {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: transparent;
    cursor: pointer;
    overflow: hidden;
    transition: border-color 0.15s;
}
.detail-thumb:hover { border-color: var(--accent-dim); }
.detail-thumb.active { border-color: var(--accent); }
.detail-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.detail-type {
    display: inline-block;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 2px;
    padding: 1px 5px;
    /* margin-left dropped — parent .detail-name (flex) handles spacing
       via column-gap, which collapses correctly when the leading name
       text is empty. */
    vertical-align: middle;
}

.detail-type-creature {
    color: var(--warm-soft);
    border-color: var(--warm-soft-40);
}

.detail-type-advisory {
    color: var(--accent-soft);
    border-color: var(--accent-soft-40);
}

.detail-type-regard {
    color: var(--mauve-soft);
    border-color: var(--mauve-soft-40);
}

/* Advisories-for-this-place block on a location's wiki entry. Sits
   below the description; one clickable chip per writer model. */
.advisories-block {
    margin-top: 12px;
    padding: 8px 10px;
    border-top: 1px solid var(--accent-soft-18);
}
.advisories-block-label {
    font-size: 0.75em;
    color: var(--text-dim);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    margin-bottom: 6px;
}
.advisories-block-list {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.advisory-handle {
    background: transparent;
    border: 1px solid var(--accent-soft-35);
    color: var(--accent-soft);
    font-size: 0.8em;
    padding: 2px 8px;
    border-radius: 3px;
    cursor: pointer;
    font-family: inherit;
}
.advisory-handle:hover {
    background: var(--accent-soft-12);
}
.advisory-writer {
    text-transform: none;
}

/* (Removed old #resize-handle and #neighbors-panel layout — now lives
   inside floating panels.) */

#item-detail,
#wiki-body {
    line-height: 1.5;
    font-size: 11px;
}

.detail-name {
    color: var(--text-bright);
    font-weight: 600;
    margin-bottom: 4px;
    /* Flex layout with gap (not per-child margin-left) so the type-badge
       and model-name sit at consistent 6px spacing whether or not the
       entry has a visible name. With margin-left on the badge, an
       empty/whitespace name (placements often have one) would leave the
       badge indented 6px from the column edge instead of flush with it.
       The flex `gap` only applies BETWEEN children — when the leading
       anonymous text item is whitespace-only it collapses (per spec),
       and the badge becomes the first visible item at column-left. */
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 6px;
    row-gap: 2px;
}

.detail-model {
    font-weight: 400;
    font-size: 11px;
    color: var(--text-dim);
    /* margin-left dropped — parent .detail-name's column-gap handles it. */
}
/* Clickable author chip (item → model hop). Native <button> reset, then
   inherit the .detail-model look with a hover affordance. */
.detail-model-link {
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font: inherit;
    line-height: inherit;
    cursor: pointer;
    border-bottom: 1px dashed transparent;
    transition: color 0.12s ease, border-color 0.12s ease;
}
.detail-model-link:hover {
    color: var(--text-bright);
    border-bottom-color: var(--text-dim);
}
/* Model page → filter chain tail. */
.model-wiki-filterbar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
}
.model-wiki-filter {
    background: var(--overlay-medium);
    border: 1px solid var(--border);
    color: var(--text-bright);
    font-size: 11px;
    padding: 5px 10px;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.model-wiki-filter:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: var(--text-dim);
}
.model-wiki-soloed {
    font-size: 11px;
    color: var(--text-bright);
}
.model-wiki-unfilter {
    background: none;
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-size: 10px;
    padding: 3px 8px;
    border-radius: 6px;
    cursor: pointer;
    transition: color 0.12s ease, border-color 0.12s ease;
}
.model-wiki-unfilter:hover {
    color: var(--text-bright);
    border-color: var(--text-dim);
}

/* Model wiki — the panel content when the visitor clicks a centroid
   in PCA view. Uses the same outer wiki frame as item entries; the
   inner layout is a stack of key/value fact rows. */
.model-wiki-pill {
    display: inline-block;
    margin-bottom: 8px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--overlay-medium);
    color: var(--text-dim);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.model-wiki-row {
    display: grid;
    grid-template-columns: 96px 1fr;
    gap: 8px;
    align-items: baseline;
    margin-bottom: 3px;
    font-size: 12px;
}
.model-wiki-key {
    color: var(--text-dim);
    text-transform: lowercase;
    font-variant: small-caps;
    letter-spacing: 0.03em;
}
.model-wiki-val {
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.model-wiki-desc {
    margin-top: 10px;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.5;
}
.model-wiki-footer {
    margin-top: 14px;
    padding-top: 8px;
    border-top: 1px solid var(--overlay-medium);
    font-size: 10px;
}
.model-wiki-conf {
    color: var(--text-dim);
}
.model-wiki-conf.conf-epoch    { color: var(--text-muted); }
.model-wiki-conf.conf-manual   { color: var(--accent); }
.model-wiki-conf.conf-review   { color: #c79c4a; }   /* warm caution */

.detail-params {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 8px;
    margin-bottom: 4px;
    font-size: 10px;
}

.pv-dim {
    display: inline-flex;
    gap: 3px;
    align-items: baseline;
}

.pv-name {
    color: var(--text-dim);
    min-width: 38px;
}

.pv-bar {
    color: var(--accent-dim);
    letter-spacing: -1px;
    font-size: 9px;
}

.pv-val {
    color: var(--text);
    min-width: 18px;
    text-align: right;
}

.detail-desc {
    color: var(--text);
    white-space: pre-wrap;
    font-size: 13px;
}

.neighbor-item {
    display: flex;
    align-items: center;
    gap: 8px;
    /* Rows pack tight: zero margin AND zero padding so thumbnails touch
       each other vertically and the panel's left edge horizontally —
       the list reads as a contiguous filmstrip rather than a list of
       cards. The 8px gap survives because that's intra-row spacing
       (thumb → rank → name → badge → model → distance), not row
       breathing. Hit area = thumbnail height (~36px), still ample. */
    margin: 0;
    padding: 0;
    line-height: 1.4;
    min-height: 28px;
    border-radius: 3px;
    transition: background 0.1s;
}
.neighbor-item.hover-handle {
    cursor: pointer;
}
.neighbor-item.hover-handle:hover {
    background: var(--overlay-light);
}

/* Thumb sits to the left of the rank when present. Items without an
   image still align cleanly: the thumb slot keeps a fixed width via the
   placeholder. The visual rate of "imaged vs not" is intentional —
   helps us see, at a glance, where image coverage is sparse. */
.neighbor-thumb {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--overlay-faintish);
    border: 1px solid var(--overlay-light);
    cursor: pointer;
}
.neighbor-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.neighbor-thumb.placeholder {
    /* Empty slot: a faint diagonal cross-hatch so the row layout
       doesn't collapse on un-imaged items, and the absence reads
       as absence rather than as a render bug. */
    background:
        repeating-linear-gradient(
            45deg,
            var(--overlay-faint) 0,
            var(--overlay-faint) 3px,
            transparent 3px,
            transparent 6px);
}

/* .neighbor-rank is no longer rendered (rank dropped from row layout —
   visual order is implicit). Keeping the rule dormant in case some
   legacy code path still emits it, but new rows don't include it. */
.neighbor-rank {
    color: var(--text-dim);
    min-width: 16px;
}

/* Wraps name + type-badge + model into a single inline-flowing column
   so a long multi-name comma list can use the row's full width before
   the model spills onto its own line. flex:1 takes whatever space is
   left after thumb + distance; min-width:0 is the standard incantation
   to let flex children shrink past their intrinsic content size, which
   is what allows the inner text to wrap. */
.neighbor-content {
    flex: 1;
    min-width: 0;
    line-height: 1.35;
}

.neighbor-bar {
    display: inline-block;
    height: 6px;
    background: var(--accent);
    border-radius: 3px;
    opacity: 0.6;
    min-width: 4px;
    flex-shrink: 0;
    align-self: center;
}

.neighbor-name {
    color: var(--text-bright);
    font-weight: 400;
}

.neighbor-name.clickable {
    cursor: pointer;
}

.neighbor-name.clickable:hover {
    color: var(--accent);
}

.neighbor-pv {
    color: var(--text-dim);
    font-size: 9px;
    opacity: 0.6;
    letter-spacing: -0.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 180px;
}

.neighbor-model {
    color: var(--text-dim);
    font-size: 9px;
    opacity: 0.6;
    flex-shrink: 0;
}

.neighbor-dist {
    color: var(--text-dim);
    margin-left: auto;
    margin-right: 8px;
    flex-shrink: 0;
}

#map-tooltip {
    position: absolute;
    background: var(--panel-glass-bg-deeper);
    border: 1px solid var(--border);
    padding: 5px 9px;
    font-size: 11px;
    pointer-events: none;
    display: none;
    z-index: 10;
    color: var(--text-bright);
    border-radius: 4px;
    white-space: normal;
    max-width: 260px;
    line-height: 1.3;
}
#map-tooltip .tt-name {
    display: block;
    font-weight: 600;
    color: var(--text-bright);
}
#map-tooltip .tt-meta {
    display: block;
    margin-top: 3px;
    font-size: 10px;
    color: var(--text-dim);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    letter-spacing: 0.02em;
}
#map-tooltip .tt-type-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    margin-right: 5px;
    vertical-align: middle;
}

/* ── Journal entries (live inside floating #panel-journal) ──── */

#journal-scroll {
    overflow-y: auto;
    padding: 8px 0;
    scroll-behavior: smooth;
}

#neighbors-list {
    border-bottom: 1px solid var(--border);
    padding: 0;
    margin: 0;
    font-size: 11px;
    max-height: 40vh;
    overflow-y: auto;
}

#journal-scroll::-webkit-scrollbar { width: 6px; }
#journal-scroll::-webkit-scrollbar-track { background: var(--bg-deep); }
#journal-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

.journal-entry {
    margin-bottom: 16px;
    animation: fadeIn 0.3s ease-in;
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

.entry-walk {
    color: var(--accent);
    font-weight: 600;
    margin-bottom: 4px;
}

.entry-walk .steps {
    color: var(--text-dim);
    font-weight: 400;
}

.entry-neighborhood {
    font-size: 11px;
    color: var(--text-dim);
    margin-bottom: 8px;
}

.entry-neighborhood .nearest {
    color: var(--text);
}

.entry-description {
    color: var(--text-bright);
    line-height: 1.7;
    margin-bottom: 8px;
    padding: 8px 12px;
    background: var(--overlay-tint);
    border-left: 2px solid var(--accent-dim);
    border-radius: 0 4px 4px 0;
}

.entry-image-prompt {
    font-size: 11px;
    color: var(--text-dim);
    font-style: italic;
    margin-bottom: 8px;
}

.entry-image {
    margin-bottom: 8px;
}

.entry-image img {
    max-width: 100%;
    border-radius: 6px;
    border: 1px solid var(--border);
}

.entry-system {
    color: var(--text-dim);
    font-size: 11px;
    font-style: italic;
}

.entry-system.started {
    color: var(--amber);
}

/* Loading spinner */
.loading {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--text-dim);
    font-size: 12px;
}

.loading::before {
    content: '';
    width: 12px;
    height: 12px;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* (Old #input-bar removed — input lives inside floating #panel-probe.) */

#walk-input::placeholder {
    color: var(--text-dim);
}

.alpha-control {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: var(--text-dim);
}

.alpha-control label {
    color: var(--accent-dim);
    font-weight: 600;
}

#alpha-slider {
    width: 60px;
    accent-color: var(--accent);
}

#alpha-value {
    min-width: 32px;
    color: var(--text);
}

.btn {
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 8px 12px;
    font-family: inherit;
    font-size: 12px;
    cursor: pointer;
    border-radius: 4px;
    transition: all 0.15s;
}

.btn:hover {
    border-color: var(--accent);
    color: var(--text-bright);
}

.btn.active {
    background: var(--accent);
    color: var(--bg-deep);
    border-color: var(--accent);
}

/* ── Coherence meter ────────────────────────────────── */

.coherence-bar {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
}

.coherence-track {
    width: 60px;
    height: 4px;
    background: var(--bg-input);
    border-radius: 2px;
    overflow: hidden;
}

.coherence-fill {
    height: 100%;
    border-radius: 2px;
    transition: width 0.5s, background 0.5s;
}

/* (Old #model-bar removed — model selectors live inside #panel-probe.) */

.model-select {
    display: flex;
    align-items: center;
    gap: 4px;
}

.model-select label {
    color: var(--text-dim);
    font-weight: 600;
    min-width: 48px;
}

.model-select select {
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 2px 6px;
    font-family: inherit;
    font-size: 11px;
    border-radius: 3px;
    cursor: pointer;
}

.model-select select:hover {
    border-color: var(--accent-dim);
}

.void-toggle {
    display: flex;
    align-items: center;
    gap: 4px;
    color: var(--text-dim);
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    margin-right: 4px;
}

.void-toggle input {
    accent-color: var(--accent);
    cursor: pointer;
}

/* ── Entry Action Bars (regen) ─────────────────────── */

.entry-description,
.entry-image-prompt,
.entry-image {
    position: relative;
}

.entry-actions {
    position: absolute;
    top: 4px;
    right: 4px;
    display: none;
    align-items: center;
    gap: 4px;
    background: var(--panel-glass-bg-deep);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 2px 4px;
}

.entry-description:hover .entry-actions,
.entry-image-prompt:hover .entry-actions,
.entry-image:hover .entry-actions {
    display: flex;
}

.regen-model {
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 1px 4px;
    font-family: inherit;
    font-size: 10px;
    border-radius: 2px;
    cursor: pointer;
}

.btn-regen {
    background: none;
    border: 1px solid var(--border);
    color: var(--text-dim);
    padding: 1px 6px;
    font-size: 12px;
    cursor: pointer;
    border-radius: 2px;
    transition: all 0.15s;
}

.btn-regen:hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* ── Journal Tabs ──────────────────────────────────── */

#journal-tabs {
    display: flex;
    gap: 0;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.tab-btn {
    flex: 1;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    padding: 8px 16px;
    cursor: pointer;
    transition: all 0.15s;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.tab-btn:hover {
    color: var(--text);
    background: var(--overlay-tint);
}

.tab-btn.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* (History scroll, journal cards, visited tooltip, visited legend
   diamond — all removed 2026-05-09 with the visited pipeline retirement.
   Bookmarks / catalog / cards cover the "browse what I've seen" need.) */

/* ── Responsive ─────────────────────────────────────── */

@media (max-width: 900px) {
    #main {
        grid-template-columns: 1fr;
        grid-template-rows: 40vh 1fr;
    }

    #map-panel {
        border-right: none;
        border-bottom: 1px solid var(--border);
    }
}


/* ── Wiki panel (replaces preview + journal) ─────────────────── */

/* Wiki panel: fixed footprint anchored top + bottom so the visual
   anchor (top edge — where the eye reads from) stays put as content
   changes. The body scrolls inside. Draggable + resizable like the
   other floating panels. */
/* Wiki is a full-height left dock now (two-pane layout). The wiki and
   the map coexist in their own zones instead of competing for screen
   center. Width is controlled by the --wiki-width CSS variable, which
   the user adjusts via the right-edge resize handle. No drag, no
   border-radius on the right (it meets the map seam). */
#panel-wiki[data-default-pos="bottom-left"] {
    left: 0;
    top: 0;
    bottom: 0;
    width: var(--wiki-width, 420px);
    height: auto;
    max-height: none;
    border-radius: 0;
    border-left: none;
    border-top: none;
    border-bottom: none;
}

.float-panel-wiki {
    /* Always open, no close button — primary content surface. */
    display: flex !important;
    flex-direction: column;
    /* Fill the top..bottom box; body grows / shrinks inside. */
    overflow: hidden;
}

.float-panel-wiki header {
    /* Docked, not draggable. */
    cursor: default;
}

/* Vertical resize handle on the wiki's right edge. Drag to change the
   pane split. */
#wiki-resize-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    right: -3px;       /* slight overhang so it's easy to grab */
    width: 6px;
    cursor: ew-resize;
    z-index: 21;
    background: transparent;
}
#wiki-resize-handle:hover,
#wiki-resize-handle.resizing {
    background: var(--accent-dim);
    opacity: 0.5;
}

.float-panel-wiki .wiki-body {
    flex: 1 1 auto;
    min-height: 0; /* essential for flex children to scroll */
}

.float-panel-wiki header {
    gap: 8px;
}

.wiki-nav {
    display: inline-flex;
    gap: 2px;
}

.wiki-arrow {
    background: transparent;
    border: 1px solid var(--border-dim);
    color: var(--text-dim);
    /* Auto-width now that the button carries text ("← back" / "forward →")
       instead of a bare arrow glyph. The bare arrows were misread as
       close-this-panel affordances; the worded labels make the back/forward
       semantics explicit. Height stays compact. */
    height: 22px;
    line-height: 18px;
    padding: 0 8px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 11px;
    font-family: inherit;
    white-space: nowrap;
}
.wiki-arrow:hover:not(:disabled) {
    color: var(--text-bright);
    border-color: var(--text-dim);
}
.wiki-arrow:disabled {
    opacity: 0.3;
    cursor: default;
}

.wiki-title {
    flex: 1;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.wiki-loading {
    margin-left: 8px;
    color: var(--accent-dim);
    font-size: 10px;
    font-style: italic;
    animation: wiki-loading-pulse 1.4s ease-in-out infinite;
}
@keyframes wiki-loading-pulse {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 1; }
}

.wiki-actions {
    display: inline-flex;
    gap: 4px;
}

.wiki-icon {
    background: transparent;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    font-size: 14px;
    padding: 2px 6px;
    border-radius: 3px;
}
.wiki-icon:hover { color: var(--text-bright); background: var(--overlay-light); }
.wiki-icon:disabled,
.wiki-icon[aria-disabled="true"] {
    /* :disabled covers <button>; aria-disabled covers <a> (no native
       disabled attr on anchors). wiki-readview is the latter — needs
       the matching aria selector or it stays full-opacity / clickable
       when there's no current item. */
    opacity: 0.35;
    cursor: default;
    background: transparent !important;
    color: var(--text-dim) !important;
    pointer-events: none;
}
/* Same dim treatment on the bookmark + readview anchor when no item */
.wiki-icon[aria-disabled="true"]:hover {
    background: transparent;
    color: var(--text-dim);
}
.wiki-icon.wiki-bookmark {
    /* Empty (un-bookmarked) state: muted outline ribbon. */
    color: var(--text-dim);
    transition: color 0.15s, transform 0.15s, filter 0.15s;
    /* Tighten so the SVG's bbox doesn't introduce extra padding. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px 5px;
}
.wiki-bookmark-glyph {
    width: 12px;
    height: 16px;
    display: block;
}
.wiki-bookmark-glyph path {
    /* Outline by default; filled via the .bookmarked toggle below. */
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linejoin: miter;
}
.wiki-icon.wiki-bookmark.bookmarked:not(:disabled) {
    /* Saved state: warm gold, slightly lit. The fill comes from
       currentColor so the path inherits this color. */
    color: var(--gold-soft);
    filter: drop-shadow(0 0 4px var(--gold-soft-45));
}
.wiki-icon.wiki-bookmark.bookmarked:not(:disabled) .wiki-bookmark-glyph path {
    fill: currentColor;
}
.wiki-icon.wiki-bookmark:not(:disabled):hover {
    transform: scale(1.08);
}

.wiki-body {
    overflow-y: auto;
    padding-bottom: 6px;
}

.wiki-empty {
    color: var(--text-dim);
    font-style: italic;
    font-size: 11px;
    padding: 8px 0;
}

/* Amber intro page (ATLAS_COPY.wiki.amber.body). Rendered inside
   .wiki-empty, which is dim+italic+small for the placeholder case —
   .amber-page resets that to a readable content page. */
#wiki-body .amber-page {
    color: var(--text);
    font-style: normal;
    font-size: 12px;
    line-height: 1.5;
}
#wiki-body .amber-page p {
    margin: 0 0 10px;
}
#wiki-body .amber-h {
    margin: 16px 0 6px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-dim);
}
#wiki-body .amber-list {
    margin: 0 0 10px;
    padding-left: 20px;
}
#wiki-body .amber-list li {
    margin-bottom: 5px;
    line-height: 1.45;
}
#wiki-body .amber-page code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--overlay-medium, rgba(255, 255, 255, 0.08));
    color: var(--text-bright);
}
#wiki-body .amber-aside {
    color: var(--text-dim);
    font-style: italic;
    margin: 6px 0 12px;
}
#wiki-body .amber-seq {
    width: 100%;
    border-collapse: collapse;
    margin: 4px 0 8px;
    font-size: 12px;
}
#wiki-body .amber-seq th,
#wiki-body .amber-seq td {
    text-align: left;
    padding: 5px 8px;
    border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.12));
    vertical-align: top;
}
#wiki-body .amber-seq th {
    color: var(--text-dim);
    font-weight: 600;
    font-size: 10px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
#wiki-body .amber-tbd {
    color: var(--text-dim);
    font-size: 10px;
}
#wiki-body .amber-page a {
    color: var(--accent);
    text-decoration: none;
    border-bottom: 1px solid var(--accent-dim);
    transition: color 0.12s ease, border-color 0.12s ease;
}
#wiki-body .amber-page a:hover {
    color: var(--sky);
    border-bottom-color: var(--accent);
}

/* "The Atlas" header title doubles as a home link → Amber. */
.atlas-home {
    cursor: pointer;
    transition: color 0.12s ease;
}
.atlas-home:hover {
    color: var(--accent);
}

.wiki-image {
    display: block;
    width: 100%;
    border-radius: 3px;
    margin: 6px 0;
}

/* In-place placeholder shown while live image generation is in flight
   (server has sent `image_started`, no `image` arrived yet). Reserves
   the same slot the eventual image will fill so the layout doesn't
   reflow when the image lands. Same diagonal-hatch background as
   .neighbor-thumb.placeholder / .catalog-card-art.placeholder so the
   "this is an empty image slot" vocabulary stays consistent across
   the UI.

   The progress bar fills to 95% over `expected_duration_s` (a CSS
   animation tied to the `style="animation-duration: Xs"` set inline
   per element), then asymptotes — never visually completes before
   the image actually arrives, since hitting 100% with no image yet
   feels worse than crawling the last 5%. */
.image-placeholder {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 3px;
    margin: 6px 0;
    background:
        repeating-linear-gradient(
            45deg,
            var(--overlay-faint) 0,
            var(--overlay-faint) 4px,
            transparent 4px,
            transparent 8px);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}
.image-placeholder-meta {
    position: absolute;
    top: 8px;
    left: 8px;
    right: 8px;
    display: flex;
    justify-content: space-between;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-dim);
    opacity: 0.55;
    pointer-events: none;
}
.image-placeholder-bar {
    height: 3px;
    background: var(--overlay-medium);
    overflow: hidden;
}
.image-placeholder-bar-fill {
    height: 100%;
    width: 0%;
    background: var(--accent, var(--overlay-bright));
    animation-name: image-placeholder-progress;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
}
@keyframes image-placeholder-progress {
    /* Honest linear fill 0% → 100% over the calibrated
       expected_duration_s the server announced. If the image arrives
       early, the placeholder gets replaced (bar disappears with it).
       If the image is late, the bar sits at 100% — that's information,
       not embarrassment. Tells the user the estimate was wrong rather
       than pretending the wait is shorter than it is. */
    0%   { width: 0%; }
    100% { width: 100%; }
}

.wiki-regen-bar {
    display: flex;
    gap: 6px;
    margin-top: 8px;
    flex-wrap: wrap;
}

/* "prompt" + "card" + "copy" buttons — small uppercase pills in the
   regen-bar. All reference/info actions, not generation actions. */
.btn-prompt-origin, .btn-card-export, .btn-copy-text {
    background: transparent;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 3px 9px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    font-family: inherit;
}
.btn-prompt-origin:hover, .btn-card-export:hover, .btn-copy-text:hover {
    color: var(--text);
    border-color: var(--text-dim);
    background: var(--overlay-light);
}
/* Disabled state during the brief "copied!" confirmation so a second
   click doesn't fire while the label is in the post-action flash. */
.btn-copy-text:disabled {
    cursor: default;
    color: var(--accent);
    border-color: var(--accent);
}

/* ── Collectable card modal ─────────────────────────────────────────
   MTG-style card view. The card itself uses the model's assigned color
   as a glowing accent border; everything else is dark and quiet so the
   image and text dominate. The card is exported to PNG via html2canvas,
   so its layout has to be self-contained — no surrounding chrome leaks
   into the snapshot. */
.card-modal {
    position: fixed;
    inset: 0;
    background: var(--shadow-color-78);
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    box-sizing: border-box;
}
.card-modal-inner {
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 6px;
    width: min(560px, 100%);
    max-height: 92vh;
    display: flex;
    flex-direction: column;
    box-shadow: 0 16px 50px var(--shadow-color-deep);
}
.card-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
}
.card-modal-title {
    color: var(--text);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.card-modal-close {
    background: transparent;
    color: var(--text-dim);
    border: none;
    font-size: 22px;
    line-height: 1;
    padding: 0 6px;
    cursor: pointer;
}
.card-modal-close:hover { color: var(--text); }
.card-modal-body {
    overflow: auto;
    padding: 24px;
    flex: 1;
    min-height: 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
}
.card-stage {
    width: 100%;
    display: flex;
    justify-content: center;
}
.card-modal-footer {
    display: flex;
    justify-content: flex-end;
    padding: 10px 14px;
    border-top: 1px solid var(--border);
}
.card-modal-save {
    background: transparent;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 6px 14px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    cursor: pointer;
    font-family: inherit;
}
.card-modal-save:hover:not(:disabled) {
    color: var(--text);
    border-color: var(--card-accent);
    background: var(--overlay-light);
}
.card-modal-save:disabled {
    opacity: 0.5;
    cursor: default;
}
.card-error {
    color: var(--text-dim);
    padding: 30px;
    font-style: italic;
}

/* The card itself — fixed aspect ratio so the snapshot is consistent.
   Uses --card-accent (set inline on .atlas-card) for the model-color
   border and accents. Background slightly off-black so the model's
   color reads as a glow. */
.atlas-card {
    width: 480px;
    background: #15151a;
    /* Quiet outer border — the type accent (orange for creatures, grey
       for locations) lives on the inner elements (type pill, model
       name, param bars) where it functions as a vocabulary tag rather
       than a frame. */
    border: 1px solid var(--overlay-stronger);
    /* Square corners — the saved PNG is a clean rectangle. Rounded
       corners would clip the background out and leave transparent
       gaps in the export that look like artifacts when the card is
       pasted into another document. */
    border-radius: 0;
    padding: 0;
    box-shadow: 0 6px 24px var(--shadow-color-strong);
    color: #e8e8ec;
    font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
}
.card-frame {
    padding: 14px 16px 12px;
}
.card-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--overlay-strong);
    margin-bottom: 12px;
}
.card-title {
    font-size: 16px;
    font-weight: 600;
    line-height: 1.2;
    color: #f4f4f8;
    flex: 1;
    word-break: break-word;
}
.card-type {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--card-accent);
    padding: 2px 8px;
    border: 1px solid var(--card-accent);
    border-radius: 999px;
    white-space: nowrap;
    flex-shrink: 0;
}
.card-art {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: #050507;
    border: 1px solid var(--overlay-strong);
    /* Square frame to match the squared card outer. */
    border-radius: 0;
    overflow: hidden;
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.card-art img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.card-art-empty {
    color: var(--text-dim);
    font-style: italic;
    font-size: 11px;
    aspect-ratio: 1 / 1;
}
.card-params {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 10px;
    row-gap: 3px;
    margin-bottom: 12px;
}
.card-param {
    display: grid;
    grid-template-columns: 60px 1fr 22px;
    column-gap: 5px;
    align-items: center;
    font-size: 9px;
    color: var(--text-muted);
}
.card-param-name {
    text-transform: lowercase;
    letter-spacing: 0.02em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.card-param-bar {
    display: block;
    height: 4px;
    background: var(--overlay-strong);
    border-radius: 1px;
    overflow: hidden;
    position: relative;
}
.card-param-bar > span {
    display: block;
    height: 100%;
    background: var(--card-accent);
    opacity: 0.85;
}
.card-param-val {
    text-align: right;
    color: var(--text-soft);
    font-variant-numeric: tabular-nums;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
}
.card-prose {
    font-size: 11px;
    line-height: 1.5;
    color: #d0d0d8;
    padding: 8px 0 12px;
    border-top: 1px solid var(--overlay-medium);
    margin-bottom: 0;
    white-space: pre-wrap;
}

/* "also-names" line — when the tagger surfaces multiple named beings
   (an ecosystem with several distinct creatures: Elara + The Keeper +
   Copper + Tomás), the first sits in the card title and the rest land
   here as a smaller subtitle row, dotted-separator style. */
.card-also-names {
    font-size: 10px;
    line-height: 1.45;
    color: var(--card-accent);
    opacity: 0.75;
    margin-top: -8px;
    margin-bottom: 10px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--overlay-medium);
    font-style: italic;
}
.card-also-names .dot {
    opacity: 0.5;
    margin: 0 2px;
}
.card-also-prefix {
    opacity: 0.6;
    font-style: normal;
    text-transform: lowercase;
    letter-spacing: 0.04em;
    margin-right: 2px;
}

/* Standout quote — Sonnet-4.5 surfaced this sentence as the one that
   surprised the tagger. Renders as MTG-style flavor text: italicized,
   set apart by a divider, tinted in the type accent so the eye reads
   it as a distinct layer of meaning from the prose. */
.card-flavor {
    border-top: 1px dashed var(--overlay-stronger);
    margin-bottom: 10px;
    padding: 10px 0 8px;
    text-align: center;
}
.card-flavor-quote {
    font-size: 11px;
    line-height: 1.45;
    font-style: italic;
    color: var(--card-accent);
    opacity: 0.9;
    padding: 0 6px;
}
.card-flavor-quote::before { content: "\201C"; margin-right: 1px; }
.card-flavor-quote::after  { content: "\201D"; margin-left: 1px; }

/* Creature stats block — replaces the param-bars block when the card
   is for a creature. Top row: chips (sentient / multiplicity / name
   status). Bottom row: friendliness + fortune as 0-5 bars. */
.card-stats {
    margin-bottom: 12px;
}
.card-stats-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin-bottom: 6px;
}
.card-stat-chip {
    /* inline-flex + align-items:center + line-height:1 = pixel-perfect
       vertical centering of the text inside the pill, regardless of
       font-internal metrics. The earlier `line-height: 1.4` combined
       with asymmetric default font baselines made the text sit a few
       pixels above the visual center. */
    display: inline-flex;
    align-items: center;
    font-size: 9px;
    text-transform: lowercase;
    letter-spacing: 0.04em;
    color: var(--card-accent);
    border: 1px solid var(--card-accent);
    border-radius: 999px;
    padding: 3px 9px;
    line-height: 1;
}
.card-stat-chip-dim {
    color: var(--text-dimmer);
    border-color: var(--overlay-15);
}
.card-stats-bars {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 14px;
    row-gap: 3px;
}
.card-stat-bar {
    display: grid;
    grid-template-columns: 80px 1fr 26px;
    column-gap: 6px;
    align-items: center;
    font-size: 9px;
}
.card-stat-bar-label {
    text-transform: lowercase;
    letter-spacing: 0.02em;
    color: var(--text-muted);
}
.card-stat-bar-track {
    display: block;
    height: 4px;
    background: var(--overlay-strong);
    border-radius: 1px;
    overflow: hidden;
}
.card-stat-bar-track > span {
    display: block;
    height: 100%;
    background: var(--card-accent);
    opacity: 0.9;
}
.card-stat-bar-val {
    text-align: right;
    color: var(--text-soft);
    font-variant-numeric: tabular-nums;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 9px;
}
.card-footer {
    font-size: 9px;
    color: var(--text-dimmer);
}
.card-meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 4px;
}
.card-model {
    color: var(--card-accent);
    font-weight: 600;
    text-transform: lowercase;
    letter-spacing: 0.02em;
}
.card-id {
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.card-host {
    color: var(--text-faint);
    margin-bottom: 4px;
    font-style: italic;
}
.card-url {
    color: #666;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 8px;
    letter-spacing: 0.02em;
    text-align: center;
    padding-top: 2px;
    border-top: 1px solid var(--overlay-light2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Origin / "prompt" modal — overlays the whole viewport. */
.origin-modal {
    position: fixed;
    inset: 0;
    background: var(--shadow-color-deep);
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    box-sizing: border-box;
}
.origin-modal-inner {
    background: var(--bg-panel-alt);
    border: 1px solid var(--border);
    border-radius: 6px;
    width: min(900px, 100%);
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    box-shadow: 0 12px 40px var(--shadow-color-60);
}
.origin-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
}
.origin-modal-title {
    color: var(--text);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.origin-modal-close {
    background: transparent;
    color: var(--text-dim);
    border: none;
    font-size: 22px;
    line-height: 1;
    padding: 0 6px;
    cursor: pointer;
}
.origin-modal-close:hover { color: var(--text); }
.origin-modal-meta {
    padding: 10px 14px;
    border-bottom: 1px solid var(--border);
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 12px;
    row-gap: 4px;
    font-size: 11px;
}
.origin-row {
    display: contents;
}
.origin-key {
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 9px;
    align-self: center;
}
.origin-val { color: var(--text); }
.origin-val-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px;
    word-break: break-all;
}
.origin-source-badge {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 3px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.origin-source-log {
    background: var(--mint-15);
    color: rgb(120, 230, 160);
    border: 1px solid var(--mint-40);
}
.origin-source-reconstructed {
    background: var(--warm-gold-15);
    color: rgb(240, 200, 100);
    border: 1px solid var(--warm-gold-40);
}
.origin-source-unknown {
    background: var(--gray-160-15);
    color: var(--gray-mid);
    border: 1px solid var(--gray-160-40);
}
.origin-source-help {
    color: var(--text-dim);
    font-size: 10px;
    margin-left: 6px;
    font-style: italic;
}

/* ── Public mode ────────────────────────────────────────────────────
   Hide every UI affordance that would trigger a server-side LLM /
   image call. The server enforces the same set at the WS / HTTP
   boundary independently, so this is purely cosmetic — but it keeps
   the interface honest about what's available.
*/
body.public-mode #btn-see { display: none !important; }
body.public-mode .btn-open-handle { display: none !important; }
body.public-mode .entry-actions { display: none !important; }
.origin-modal-body {
    overflow: auto;
    padding: 0;
    flex: 1;
    min-height: 0;
}
.origin-modal-pre {
    margin: 0;
    padding: 14px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    line-height: 1.5;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
}
.origin-modal-footer {
    display: flex;
    justify-content: flex-end;
    padding: 10px 14px;
    border-top: 1px solid var(--border);
}
.origin-modal-copy {
    background: transparent;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 5px 12px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    font-family: inherit;
}
.origin-modal-copy:hover {
    color: var(--text);
    border-color: var(--text-dim);
}

/* Peek overlay — wiki body shows a different entry while hovering. */
.float-panel-wiki.peeking .wiki-body {
    background: rgba(80, 70, 100, 0.06);
}
.wiki-peek-badge {
    color: var(--accent);
    font-size: 9px;
    font-style: italic;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 4px;
    opacity: 0.8;
}

/* History strip — collapsed by default. */
.wiki-history {
    border-top: 1px solid var(--border-dim);
    flex-shrink: 0;
    transition: max-height 0.2s ease;
}
.wiki-history.collapsed {
    max-height: 0;
    overflow: hidden;
}
.wiki-history:not(.collapsed) {
    max-height: 220px;
    overflow-y: auto;
}

.wiki-history-header {
    color: var(--text-dim);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 6px 0 4px;
}

.wiki-history-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.wiki-history-item {
    display: grid;
    grid-template-columns: 1fr auto auto;
    gap: 8px;
    align-items: baseline;
    background: transparent;
    border: none;
    color: inherit;
    font-family: inherit;
    font-size: 10px;
    padding: 4px 6px;
    text-align: left;
    cursor: pointer;
    border-radius: 2px;
}
.wiki-history-item:hover {
    background: var(--overlay-light);
}
.wiki-history-item.active {
    background: rgba(180, 160, 220, 0.08);
    color: var(--text-bright);
}
.hist-name {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.hist-model { color: var(--text-dim); font-size: 9px; }
.hist-time { color: var(--text-dim); font-size: 9px; }

/* ── Toast stack ──────────────────────────────────────────────── */

#toast-stack {
    position: fixed;
    top: 70px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    gap: 6px;
    z-index: 1500;
    pointer-events: none;
    align-items: center;
    max-width: 70vw;
}

.toast {
    background: rgba(20, 20, 28, 0.92);
    border: 1px solid var(--border-dim);
    color: var(--text);
    padding: 8px 14px;
    border-radius: 4px;
    font-size: 11px;
    line-height: 1.4;
    box-shadow: 0 4px 16px var(--shadow-color-strong);
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 0.3s ease, transform 0.3s ease;
    pointer-events: auto;
    max-width: 100%;
}
.toast.visible {
    opacity: 1;
    transform: translateY(0);
}
.toast-error {
    border-color: var(--danger-soft-40);
    color: #ffd1d1;
}
.toast a { color: var(--accent); }

/* ── Nearest panel (was a fixed tray; now a real toggleable panel) ── */

#panel-neighbors[data-default-pos="center-wide"] {
    /* Wider default + positioned center-left so it overlaps the wiki
       dock by ~160px and sits well below the top toolbar so it
       doesn't crowd the menu. The width buys room for long joined-
       name creature rows to render comma-separated names without
       harsh truncation. User can drag if they want it elsewhere. */
    left: 240px;
    top: 380px;
    width: 720px;
    max-height: calc(100vh - 440px);
}

/* Clusters panel docks just under the subspace pill in the top-right
   corner of the header. This visually completes the "we're working
   with the visible subset" frame: subspace count above, the operations
   you can do on that subset (recompute, name clusters) immediately
   below. Right-aligned by `right: 16px` so the panel grows leftward
   and doesn't shift as the user resizes. The `top: 44px` sits ~6px
   below the header (which is ~38px tall: 8px padding × 2 + content). */
#panel-clusters[data-default-pos="top-right-under-subspace"] {
    right: 16px;
    top: 44px;
    width: 260px;
}

#panel-neighbors .panel-body {
    font-size: 10px;
    /* Override the default .float-panel .panel-body padding so the
       neighbors list runs flush to the panel edges — the contiguous
       thumbnails read as a filmstrip, not a card list. */
    padding: 0;
}


/* ── Name filter (in filters panel) ──────────────────────────── */

#name-filter-section {
    margin-top: 12px;
    padding-top: 8px;
    border-top: 1px dashed var(--border-dim);
}

/* Standalone "with image" toggle. Without an override it inherits the
   near-invisible solid border-top from `#filters-body > * + *`, which
   makes it visually merge with the name-filter row above. Match the
   dashed style name-filter uses against the panel edge so the boundary
   is legible. */
#image-filter-section {
    border-top: 1px dashed var(--border-dim);
    padding-top: 8px;
    margin-top: 8px;
}

.name-filter-row {
    display: flex;
    gap: 4px;
    align-items: center;
}

.name-filter-input {
    flex: 1;
    background: var(--bg-input, var(--shadow-color-medium));
    border: 1px solid var(--border-dim);
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    padding: 4px 8px;
    border-radius: 3px;
    min-width: 0;
}
.name-filter-input:focus {
    outline: none;
    border-color: var(--accent-dim);
}
.name-filter-input::placeholder {
    color: var(--text-dim);
    font-style: italic;
}

.name-filter-clear,
.name-filter-go {
    background: transparent;
    border: 1px solid var(--border-dim);
    color: var(--text-dim);
    width: 22px;
    height: 22px;
    line-height: 18px;
    padding: 0;
    border-radius: 3px;
    cursor: pointer;
    font-family: inherit;
    font-size: 14px;
    flex-shrink: 0;
}
.name-filter-clear:hover {
    color: var(--text-bright);
    border-color: var(--text-dim);
}
.name-filter-go {
    color: var(--accent, var(--text-bright));
    border-color: var(--accent-dim, var(--border-dim));
}
.name-filter-go:hover {
    color: var(--text-bright);
    border-color: var(--accent, var(--text-dim));
}


/* ── Host location block (creature wiki page) ────────────── */

.host-block {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px dashed var(--border-dim);
}

.host-divider {
    color: var(--text-dim);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 8px;
    text-align: center;
    font-style: italic;
}

.host-block .detail-image-block {
    margin-top: 4px;
}

.host-block .detail-desc {
    color: var(--text);
    opacity: 0.92;  /* very subtly recessed vs the creature's own desc */
}


/* ── Tag-attributes block (Sonnet-4.5-tagged derivatives) ─── */

.tag-block {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px dashed var(--border-dim);
    font-size: 11px;
    line-height: 1.4;
}

.tag-block-header {
    color: var(--text-dim);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 6px;
}

.tag-attribution {
    opacity: 0.7;
}

.tag-pills-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 6px;
}

.tag-pill {
    display: inline-block;
    padding: 2px 7px;
    border-radius: 10px;
    background: var(--bg-soft);
    border: 1px solid var(--border-dim);
    color: var(--text);
    font-size: 10px;
    white-space: nowrap;
}

.tag-pill.tag-sentient {
    background: var(--peach-15);
    border-color: var(--peach-40);
    color: rgb(255, 220, 170);
}

.tag-pill.tag-non-sentient {
    opacity: 0.6;
}

.tag-pill.tag-fortune {
    background: var(--sky-12);
    border-color: var(--sky-35);
}

.tag-pill.tag-na {
    opacity: 0.55;
    font-style: italic;
}

.tag-names-row {
    margin-bottom: 6px;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: baseline;
}

.tag-label {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-dim);
}

.tag-name {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 8px;
    background: var(--overlay-tint);
    color: var(--text);
    font-size: 11px;
}

.tag-anti, .tag-untitled {
    color: var(--text-dim);
    font-size: 11px;
}

.tag-standout {
    margin-top: 8px;
    padding: 8px 10px;
    border-left: 2px solid var(--border);
    background: var(--overlay-faint);
}

.tag-standout-quote {
    font-style: italic;
    color: var(--text);
    margin-bottom: 4px;
}

.tag-standout-why {
    font-size: 10px;
    color: var(--text-dim);
}

/* ── Derivative filters section ────────────────────────────── */

.legend-sentient .legend-dot {
    /* Use slightly amber color to distinguish from "with image" blue */
    background: var(--peach);
}


/* ── Models-section quick selectors (all / none) ─────────── */

.models-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.models-header-actions {
    display: inline-flex;
    gap: 4px;
}

.models-quick-btn {
    background: transparent;
    border: 1px solid var(--border-dim);
    color: var(--text-dim);
    border-radius: 8px;
    padding: 0px 6px;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    line-height: 1.5;
}

.models-quick-btn:hover {
    color: var(--text);
    border-color: var(--text-dim);
}

/* ── Bad-text warning pill (text_status != ok) ─────────────── */

.tag-pill.tag-bad-text {
    background: var(--danger-soft-18);
    border-color: var(--danger-soft-45);
    color: rgb(255, 180, 180);
    font-weight: 500;
}

.legend-ok-text .legend-dot {
    background: var(--gray-mid);
}

/* ── Cousins drop-up thumbnails ───────────────────────────────────── */
/* The cousins (and host-cousins) menu pops up from a contextual button.
   With thumbnails the menu becomes a quick visual triage: same place,
   different mind, side-by-side. Same placeholder hatching as the
   nearest panel so empties look intentional. */
.ctx-menu-item .ctx-menu-thumb {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--overlay-faintish);
    border: 1px solid var(--overlay-light);
}
.ctx-menu-item .ctx-menu-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.ctx-menu-item .ctx-menu-thumb.placeholder {
    background:
        repeating-linear-gradient(
            45deg,
            var(--overlay-faint) 0,
            var(--overlay-faint) 3px,
            transparent 3px,
            transparent 6px);
}

/* ── Probe panel — readouts ──────────────────────────────────────── */
/* The three readouts (coherence, distance from origin, nearest) used
   to live in the top header bar. They moved here when the header was
   slimmed to make room for Catalog / Bookmarks. The layout is
   placeholder — the Probe panel will get a proper rework later. */
.probe-readouts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 14px;
    padding: 6px 0 10px 0;
    border-bottom: 1px solid var(--border);
    margin-bottom: 10px;
    font-size: 11px;
    color: var(--text-dim);
}
.probe-readout {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
}
.probe-readout-label {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-dim);
}
.probe-readout-nearest {
    /* Nearest can be long. Let it take the rest of the line. */
    flex: 1 1 100%;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Full-view overlays (Catalog / Bookmarks) ───────────────────────── */
/* Both views cover the entire #map-stage area when open. The header bar
   above #map-stage stays visible so the visitor can swap views or
   close back to the map by clicking the active header button again or
   pressing Esc. Only one view is open at a time. */
.full-view {
    position: absolute;
    inset: 0;
    /* Fully opaque — earlier 0.97 let the wiki text show through faintly,
       which read as a render bug. Catalog and Bookmarks are takeover
       views; the visitor expects the map and wiki to be hidden. */
    background: var(--bg);
    z-index: 200;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.full-view[hidden] {
    display: none;
}
.full-view-header {
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 14px 24px;
    border-bottom: 1px solid var(--border);
    background: var(--shadow-color-medium);
    flex-shrink: 0;
}
.full-view-title {
    font-size: 14px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--text-bright);
}
.full-view-controls {
    display: flex;
    align-items: center;
    gap: 18px;
    flex: 1;
    flex-wrap: wrap;
}
.full-view-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    color: var(--text-dim);
    font-size: 11px;
    user-select: none;
}
.full-view-toggle input { cursor: pointer; }
.full-view-select {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--text-dim);
    font-size: 11px;
}
.full-view-select label {
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.full-view-select select {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    padding: 3px 8px;
    border-radius: 3px;
    cursor: pointer;
}
.full-view-count {
    color: var(--text-dim);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.full-view-close {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-size: 18px;
    line-height: 1;
    padding: 4px 12px;
    border-radius: 3px;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.full-view-close:hover {
    border-color: var(--accent-dim);
    color: var(--text-bright);
    background: var(--coherence-glow);
}
.full-view-body {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 24px;
}

/* ── Catalog ─────────────────────────────────────────────────────────── */
/* Two-pane layout inside the catalog body: a sticky TOC on the left,
   the scrollable grid on the right. Only #catalog-body uses the flex
   row — bookmarks-body keeps the default single-column layout from
   .full-view-body above. */
#catalog-body {
    display: flex;
    padding: 0;
    overflow: hidden;
    /* Disable the inherited padding from .full-view-body — each pane
       has its own. */
}

.catalog-toc {
    flex: 0 0 240px;
    overflow-y: auto;
    padding: 18px 14px 24px;
    border-right: 1px solid var(--border);
    background: var(--shadow-color-light);
    /* Smooth auto-scroll when the active entry follows the visitor's
       content scroll. block:"nearest" only fires when needed, so this
       doesn't fight manual TOC scrolling. */
    scroll-behavior: smooth;
}

.catalog-content {
    flex: 1;
    overflow-y: auto;
    padding: 24px;
    /* Smooth jumps when a TOC entry is clicked. */
    scroll-behavior: smooth;
    /* Make sure scrollIntoView lands the heading clear of the top
       edge — applied per-group below. */
}

.catalog-toc-corpus {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--text-dim);
    padding: 0 4px 8px 4px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--border);
}

.catalog-toc-model-group {
    margin-bottom: 8px;
}

.catalog-toc-model-name {
    font-size: 11px;
    color: var(--text-bright);
    font-weight: 600;
    padding: 4px 4px 2px;
}

.catalog-toc-entry {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    width: 100%;
    background: transparent;
    border: 1px solid transparent;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11px;
    padding: 3px 8px 3px 16px;
    cursor: pointer;
    border-radius: 2px;
    text-align: left;
    transition: background 0.1s, color 0.1s, border-color 0.1s;
}
.catalog-toc-entry:hover {
    background: var(--overlay-light);
    color: var(--text);
}
.catalog-toc-entry.active {
    color: var(--accent);
    background: var(--coherence-glow-strong);
    border-color: var(--coherence-border);
    font-weight: 600;
}
.catalog-toc-entry.active .catalog-toc-count {
    color: var(--accent);
    opacity: 1;
}
.catalog-toc-label {
    flex: 1;
}
.catalog-toc-count {
    color: var(--text-dim);
    font-size: 10px;
    font-variant-numeric: tabular-nums;
    opacity: 0.7;
    margin-left: 8px;
}

.catalog-section {
    margin-bottom: 36px;
}
.catalog-corpus-heading {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--text-dim);
    padding-bottom: 8px;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--border);
}
.catalog-group {
    margin-bottom: 28px;
    /* When the TOC jumps to this group, give the heading a little air
       above so it doesn't sit right at the top edge of the content
       pane. The catalog-content's padding adds 24px already; this
       extra margin makes the landing read as deliberate. */
    scroll-margin-top: 12px;
}
.catalog-group-heading {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 8px 0;
    margin-bottom: 10px;
    font-size: 12px;
    color: var(--text);
    border-bottom: 1px dashed var(--overlay-medium);
}
.catalog-group-model {
    font-weight: 600;
    color: var(--text-bright);
}
.catalog-group-type {
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 10px;
    padding: 1px 6px;
    border: 1px solid var(--border);
    border-radius: 2px;
}
.catalog-group-count {
    color: var(--text-dim);
    font-size: 10px;
    font-variant-numeric: tabular-nums;
}
.catalog-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
}
.catalog-card {
    display: block;
    text-decoration: none;
    color: inherit;
    border: 1px solid var(--border);
    border-radius: 3px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.015);
    transition: border-color 0.15s, transform 0.1s, background 0.15s;
}
.catalog-card:hover {
    border-color: var(--accent-dim);
    background: var(--coherence-glow);
    transform: translateY(-1px);
}
.catalog-card-art {
    aspect-ratio: 1;
    background: var(--overlay-faint);
    overflow: hidden;
    position: relative;
}
.catalog-card-art img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.catalog-card-art.placeholder {
    background:
        repeating-linear-gradient(
            45deg,
            var(--overlay-faint) 0,
            var(--overlay-faint) 4px,
            transparent 4px,
            transparent 8px);
    display: flex;
    align-items: center;
    justify-content: center;
}
.catalog-card-art.placeholder::after {
    content: "no image";
    color: var(--text-dim);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    opacity: 0.5;
}
.catalog-card-meta {
    padding: 8px 10px;
    border-top: 1px solid var(--overlay-light);
}
.catalog-card-name {
    font-size: 11px;
    line-height: 1.3;
    color: var(--text);
    /* Clamp to 2 lines so cards stay uniform height. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}
.catalog-card-handle {
    margin-top: 4px;
    font-size: 11px;
    color: var(--text-dim);
    /* Emoji handles are short, single-line. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Bookmarks list ──────────────────────────────────────────────────── */
.bookmarks-empty {
    color: var(--text-dim);
    font-style: italic;
    padding: 40px 0;
    text-align: center;
}
.bookmark-row {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--overlay-light);
    cursor: pointer;
    transition: background 0.1s;
}
.bookmark-row:hover {
    background: var(--overlay-tint);
}
.bookmark-thumb {
    flex-shrink: 0;
    width: 64px;
    height: 64px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--overlay-faintish);
    border: 1px solid var(--overlay-light);
}
.bookmark-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.bookmark-thumb.placeholder {
    background:
        repeating-linear-gradient(
            45deg,
            var(--overlay-faint) 0,
            var(--overlay-faint) 3px,
            transparent 3px,
            transparent 6px);
}
.bookmark-meta {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.bookmark-name {
    color: var(--text-bright);
    font-size: 13px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bookmark-tags {
    display: flex;
    gap: 8px;
    align-items: center;
    color: var(--text-dim);
    font-size: 10px;
}
.bookmark-tag {
    padding: 1px 6px;
    border: 1px solid var(--border);
    border-radius: 2px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.bookmark-tag-type {
    color: var(--text);
}
.bookmark-tag-creature {
    color: var(--warm-soft);
    border-color: var(--warm-soft-40);
}
.bookmark-tag-advisory {
    color: var(--accent-soft);
    border-color: var(--accent-soft-40);
}
.bookmark-tag-regard {
    color: var(--mauve-soft);
    border-color: var(--mauve-soft-40);
}
.bookmark-tag-placement {
    color: rgb(150, 200, 170);
    border-color: rgba(150, 200, 170, 0.4);
}
.bookmark-tag-model {
    color: var(--text-dim);
    text-transform: none;
    letter-spacing: 0;
}
.bookmark-added {
    color: var(--text-dim);
    font-size: 10px;
    margin-left: auto;
}
.bookmark-remove {
    background: transparent;
    border: 1px solid transparent;
    color: var(--text-dim);
    font-size: 16px;
    line-height: 1;
    padding: 6px 10px;
    border-radius: 3px;
    cursor: pointer;
    flex-shrink: 0;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
    /* Use the same SVG-ribbon glyph as the wiki bookmark icon, but
       always rendered as filled-gold so the un-bookmark gesture is
       legible: clicking turns it grey-outline as it disappears. */
}
.bookmark-remove:hover {
    border-color: var(--amber-soft);
    color: var(--gold-soft);
    background: var(--gold-soft-5);
}
.bookmark-remove svg {
    width: 12px;
    height: 16px;
    display: block;
}
.bookmark-remove svg path {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linejoin: miter;
}

/* ── Nearest panel: "to selected / to here" segmented toggle ───────── */
.nearest-segmented {
    display: inline-flex;
    gap: 0;
    margin-left: 8px;
    border: 1px solid var(--border);
    border-radius: 3px;
    overflow: hidden;
    flex-shrink: 0;
}
.nearest-seg-btn {
    background: transparent;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 3px 8px;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.nearest-seg-btn:hover {
    color: var(--text-bright);
    background: var(--overlay-light);
}
.nearest-seg-btn.active {
    color: var(--accent);
    background: var(--coherence-glow-strong);
}
.nearest-seg-btn + .nearest-seg-btn {
    border-left: 1px solid var(--border);
}

/* Filters panel — tighter inset than the default .float-panel body
   so that section content sits at one consistent left edge with
   ~8px breathing room from the panel border. */
#panel-filters .panel-body {
    padding: 6px 8px 8px;
}

/* All groups inside filters-body share the same flush-left geometry —
   no per-section padding or left margin. Top spacing + the divider
   between consecutive groups is handled by the `> * + *` rule below
   (after the collapsible-header CSS). */
#filters-body > * {
    margin: 0;
    padding: 0;
}

/* ── Filters panel: layout-recompute action buttons ───────────────── */
.filters-actions {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 0 0 6px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--border);
}
.filters-action-btn {
    width: 100%;
    text-align: left;
    padding: 5px 8px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: 3px;
    cursor: pointer;
    font-family: inherit;
    transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.filters-action-btn:hover {
    border-color: var(--accent-dim);
    background: var(--coherence-glow);
    color: var(--text-bright);
}
.filters-action-btn:disabled {
    opacity: 0.5;
    cursor: default;
    background: transparent;
}

/* ── Top-bar view-mode pill (Map / Concepts) ──────────────────────── */
.view-mode-pill {
    display: inline-flex;
    margin-left: 24px;
    border: 1px solid var(--border);
    border-radius: 14px;
    overflow: hidden;
    flex-shrink: 0;
    background: var(--shadow-color-light);
}
.view-mode-btn {
    background: transparent;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11px;
    /* No text-transform: HTML controls casing. "concepts" is lowercase
       in the markup; "PCA" and "UMAP" are acronyms that should render
       as written. A blanket text-transform: lowercase would clobber
       both. */
    letter-spacing: 0.05em;
    padding: 4px 14px;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.view-mode-btn:hover {
    color: var(--text-bright);
    background: var(--overlay-light);
}
.view-mode-btn.active {
    color: var(--accent);
    background: var(--teal-soft);
}
.view-mode-btn + .view-mode-btn {
    border-left: 1px solid var(--border);
}

/* ── View overlay (Concepts placeholder for now) ──────────────────── */
.view-overlay {
    position: absolute;
    inset: 0;
    background: var(--bg);
    /* Background layer, NOT a takeover. The wiki, toolbar, nearest /
       filters / settings panels (all at z-index 20+) float on top so
       they stay reachable across view modes. Above #map-3d (default
       z-index 0) but below the floating-panel surface. */
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: auto;
    padding: 40px;
}
.view-overlay[hidden] { display: none; }
.view-placeholder {
    max-width: 540px;
    color: var(--text);
}
.view-placeholder h2 {
    margin: 0 0 18px 0;
    font-size: 18px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    color: var(--text-bright);
}
.view-placeholder p {
    margin: 0 0 14px 0;
    font-size: 13px;
    line-height: 1.55;
    color: var(--text);
}
.view-placeholder-hint {
    color: var(--text-dim);
    font-size: 12px;
}

/* ── Cluster detail slider (HDBSCAN min size) ─────────────────────── */
.cluster-detail-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: 8px;
    padding: 4px 0;
    font-size: 10px;
    color: var(--text-dim);
}
.cluster-detail-row label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
    /* Quieter than the section header so the visual hierarchy reads
       header → label, not header = label. */
    color: var(--text-dim);
    font-weight: 400;
}
.cluster-detail-row input[type="range"] {
    width: 100%;
    accent-color: var(--accent);
    margin: 0;
}
.cluster-detail-row > span:last-child {
    color: var(--text);
    font-variant-numeric: tabular-nums;
    min-width: 28px;
    text-align: right;
}

/* ── PCA view ────────────────────────────────────────────────────── */
/* View-settings section rows (centroid toggle, axes toggle, ...).
   This is the place to restyle if you want a different look. */
.view-setting-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 0;
    cursor: pointer;
    font-size: 13px;
    color: var(--text);
}
.view-setting-row input[type="checkbox"] {
    accent-color: var(--accent);
}
.view-setting-hint {
    color: var(--text-dim);
    font-size: 11px;
    margin: 4px 0 8px;
    line-height: 1.4;
}

/* Axis tip labels (HTML overlay positioned per-frame by app.js). */
.pca-axis-label {
    position: absolute;
    pointer-events: none;
    transform: translate(-50%, -50%);
    padding: 4px 8px;
    border: 1px solid;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.55);
    color: var(--text);
    font: 11px/1.3 -apple-system, system-ui, sans-serif;
    white-space: nowrap;
    display: grid;
    gap: 1px;
    z-index: 30;
}
.pca-axis-name {
    font-weight: 600;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    opacity: 0.7;
}
.pca-axis-tokens {
    font-style: italic;
    color: var(--accent);
}
.pca-axis-stats {
    font-size: 10px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}

/* PCA pole sphere hover tooltip. Shared single div repositioned per hover. */
.pca-pole-tooltip {
    position: absolute;
    pointer-events: none;
    padding: 8px 10px;
    border: 1px solid;
    border-radius: 4px;
    background: rgba(8, 8, 12, 0.88);
    color: var(--text);
    font: 12px/1.4 -apple-system, system-ui, sans-serif;
    max-width: 280px;
    display: grid;
    gap: 3px;
    z-index: 35;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
.pca-pole-axis {
    font-weight: 600;
    font-size: 11px;
    letter-spacing: 0.04em;
    color: var(--text-dim);
}
.pca-pole-sign {
    margin-left: 4px;
    font-variant-numeric: tabular-nums;
    color: var(--accent);
}
.pca-pole-tokens {
    font-style: italic;
    color: var(--accent);
    line-height: 1.5;
}
.pca-pole-stats {
    font-size: 10px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.pca-pole-hint {
    margin-top: 3px;
    font-size: 10px;
    color: var(--text-dim);
    opacity: 0.65;
}

/* `display: grid` above wins over the UA `[hidden]` rule. Restore it. */
.pca-pole-tooltip[hidden],
.pca-pole-sticker[hidden] {
    display: none !important;
}

/* Centroid hover tooltip — same shape as .pca-pole-tooltip but
   slightly wider since model descriptions can be sentence-length. */
.centroid-tooltip {
    position: absolute;
    pointer-events: none;
    padding: 8px 10px;
    border: 1px solid;
    border-radius: 4px;
    background: rgba(8, 8, 12, 0.92);
    color: var(--text);
    font: 12px/1.4 -apple-system, system-ui, sans-serif;
    max-width: 340px;
    display: grid;
    gap: 3px;
    z-index: 35;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
.centroid-tt-name {
    font-weight: 600;
    font-size: 13px;
    color: var(--text);
}
.centroid-tt-meta {
    font-size: 11px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.centroid-tt-desc {
    margin-top: 2px;
    font-size: 11px;
    line-height: 1.45;
    color: var(--text-muted);
}
.centroid-tt-hint {
    font-size: 10px;
    color: var(--text-dim);
    opacity: 0.6;
}
.centroid-tooltip[hidden] {
    display: none !important;
}

/* Tiny always-on labels at each pole sphere (mPC tag + top BoW word).
   Lives in the same layer as .view-overlay (z-index 5): above the
   canvas, BELOW the floating panels. Logically the sticker is part of
   the 3D scene — a label attached to a sphere — so it should be
   occluded by panels the same way the canvas is. (Earlier z-index 28
   placed it above panels at z-index 20, which made axis tip labels
   bleed across the Wiki + filter surfaces.) The hover tooltip variant
   (.pca-pole-tooltip, z-index 35) stays above panels on purpose: it's
   only present during active canvas interaction and acts as an
   affordance for that interaction. */
.pca-pole-sticker {
    position: absolute;
    pointer-events: none;
    transform: translate(-50%, -50%);
    display: grid;
    gap: 1px;
    padding: 2px 6px;
    background: rgba(0, 0, 0, 0.45);
    border-radius: 3px;
    font: 10px/1.2 -apple-system, system-ui, sans-serif;
    white-space: nowrap;
    text-align: center;
    z-index: 5;
    text-shadow: 0 0 6px rgba(0,0,0,0.6);
}
.pca-sticker-tag {
    font-size: 9px;
    letter-spacing: 0.04em;
    font-weight: 600;
    opacity: 0.85;
}
.pca-sticker-word {
    font-style: italic;
    font-size: 11px;
}
.pca-sticker-second {
    font-size: 9px;
    opacity: 0.6;
}

/* Type-filter row marked as "not in PCA" — the type still toggles for
   map mode visibility, but its rows project nothing in the PCA view. */
.legend-item.legend-type.not-in-pca {
    opacity: 0.55;
}
.legend-pca-tag {
    margin-left: 6px;
    font-size: 9px;
    font-style: italic;
    color: var(--text-dim);
    text-transform: lowercase;
    letter-spacing: 0.03em;
    padding: 1px 5px;
    border: 1px solid var(--border, rgba(255,255,255,0.15));
    border-radius: 8px;
}

/* ── Click sprite (haptic confirmation on dot-click) ──────────────
   Small colored orb that launches from the click point and flies
   toward the wiki panel, where the clicked item's content has
   already been loaded by the hover-update path. Solves cursor-
   occlusion ("can't see a halo under the cursor") by becoming
   visible the moment it clears the cursor footprint, and gives
   gaze handoff for free — your eye follows the moving thing to
   the panel where you were about to read.
   See spawnClickSprite() in app.js. */
.click-sprite {
    position: fixed;
    left: 0;
    top: 0;
    width: 12px;
    height: 12px;
    margin-left: -6px;
    margin-top: -6px;
    border-radius: 50%;
    pointer-events: none;
    z-index: 9999;
    will-change: transform, opacity;
    /* background + box-shadow set inline by spawnClickSprite() so the
       color carries the writer-model identity. */
}

/* Wiki header arrival glow — the sprite dissolves at the panel's
   right boundary and the header (where back/forward/title/bookmark
   live) lights up in the sprite's color, then settles back. The
   color is passed in as --pulse-rgb (set by _pulseWikiPanel in
   app.js) so each writer-model paints the header in its own hue.
   Fast rise (~120ms to peak), brief hold, slow fade (~600ms) — the
   "settle back" beat.

   No layout shift: the glow lives in inset box-shadow + a tinted
   background overlay, neither of which moves anything. */
@keyframes wiki-header-arrival {
    0%   {
        box-shadow: inset 0 0 0 0 rgba(var(--pulse-rgb, 255, 255, 255), 0);
        background-color: transparent;
    }
    17%  {
        box-shadow: inset 0 0 26px 2px rgba(var(--pulse-rgb, 255, 255, 255), 0.55);
        background-color: rgba(var(--pulse-rgb, 255, 255, 255), 0.18);
    }
    27%  {
        box-shadow: inset 0 0 22px 1px rgba(var(--pulse-rgb, 255, 255, 255), 0.45);
        background-color: rgba(var(--pulse-rgb, 255, 255, 255), 0.14);
    }
    100% {
        box-shadow: inset 0 0 0 0 rgba(var(--pulse-rgb, 255, 255, 255), 0);
        background-color: transparent;
    }
}
#panel-wiki.click-arrival > header {
    animation: wiki-header-arrival 800ms ease-out;
}
