/* ============================================================================
 * phantasm-ui.css — PhantasmUI (visual half) · v2.31.0
 * Versioned as a whole with phantasm-ui.js — see CHANGELOG.md beside this
 * file; bump the version + changelog on every kit change, and retag every
 * consumer's ?v= to match.
 * ----------------------------------------------------------------------------
 * Design tokens + every
 * reusable component's styling. Self-contained, no build step. Load BEFORE the
 * app's own stylesheet so the app can compose/override:
 *     <link rel="stylesheet" href="ui/phantasm-ui.css">
 * Behaviours live in ui/phantasm-ui.js (window.PhantasmUI); conventions and
 * component docs in ui/docs/STYLE_GUIDE.md. Accent tints derive from the tokens
 * via color-mix(), so re-theming = editing :root.
 * The host app defines --pw (side-panel width) or overrides .disp-pop's anchor.
 * ==========================================================================*/
:root{
    --void:#070a12;
    --void2:#0a0f1d;
    --panel:#0d1322;               /* pop-over surface — SOLID as of 2.11.0 (was rgba(13,19,34,0.88), itself raised from 0.72 because --muted/--faint text was hard to read over busy stages; the same colour, now fully opaque). A solid surface also lets a sticky .sec-head simply paint it — see .glass. */
    --ink:#e9eefb;
    --muted:#7e8aab;
    --faint:#4a5577;
    --line:rgba(120,150,210,0.12);
    --accent:#f5b441;
    --cool:#5ad1ff;
    --edge:rgba(120,150,210,0.16);
    --sel:#3dffa0;                 /* THE selection colour (one meaning only); JS twin: SEL_GREEN */
    --ctl-fill:rgba(10,14,24,0.6); /* ONE form-control fill on a CARD — .fld / .dd / checkbox / number / switch-OFF all share it; .subcard redefines it (below) for controls on a sub-card. Two fills, no per-control variation. */
    /* THE SLIDER RAIL — one OPAQUE colour, shared by the track and a snap point's bezel. Opaque is the point:
       both used to be the same hue at different alphas (track .22, bezel .6), so the bezel COMPOSITED over the
       track wherever they overlap and that band read brighter than either — the snap point looked stuck ON the
       rail instead of set INTO it. Same solid colour on both ⇒ the overlap is invisible and the bezel reads as
       the rail closing around the dot. Value = what rgba(120,150,210,0.22) resolves to over --card. */
    --rail: color-mix(in srgb, rgb(120,150,210) 22%, var(--card));
    /* ---- SURFACE ELEVATION (opaque/flat family) — each step is LIGHTER than the one below. The header
       carries no fill of its own, so it shows --base; a card sits ONE step above the base; a sub-card one
       step above the card. Use these roles for surfaces, not the raw --void/--void2 (which stay the palette,
       + dropdown-menu/tooltip fills). The pop-over surface is --panel/.glass — a separate family. */
    --base:  var(--void);    /* app background — what the header shows */
    --card:  var(--void2);   /* THE card / side-panel back-colour — one elevation step lighter than --base */
    --subcard: color-mix(in srgb, var(--cool) 3.5%, transparent);   /* sub-card fill: a cool wash composited OVER --card -> lighter still (a card nested in a card) */
    /* ---- full-window POP-OUT layout (see the .pop-* block near the end + STYLE_GUIDE §12): a slim header
       over a left picker rail + a responsive card grid. Override these in your page to resize. */
    --pop-header-h:44px;     /* the slim pop-out header's height */
    --pop-rail-w:236px;      /* the left picker rail's width */

    /* ---- floating POP-OVER WINDOWS (.hud / .disp-pop — the registerPopWin framework, STYLE_GUIDE §5).
       NOT the pop-out tokens above: that is the full-window layout, this is the draggable little windows.
       ONE width for all of them, so they match by default. Widen them all from your page's :root, or just
       ONE window by setting the token on it —  .disp-pop{ --popwin-w:416px }  /  el.style.setProperty(…). */
    --popwin-w:320px;
}

.mono{
    font-family:ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;
    font-variant-numeric:tabular-nums;
    font-feature-settings:"tnum"
  }
/* gfx + sim rate readout: lives in the HEADER after the model name (the header is an opaque bar above the stage, so it can never collide with canvas content — axis ticks/labels, HUD, clouds — in any view/mode/panel state) */
.obs-cat{ font-size:11px;font-weight:600;color:var(--ink);letter-spacing:.02em; }
/* Observables sub-card: category header */
.obs-sublist{ margin-top:4px;font-size:11px;color:var(--muted);line-height:1.75;display:grid;grid-template-columns:1fr 48px 56px;align-items:baseline; }
/* the observables matrix under a category: name + a check column per variant (global | per type) */
.obs-sublist.off{ opacity:0.4; }
/* group toggled off -> its observables are unavailable */
.obs-colh{ font-size:8.5px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--cool);opacity:.85;text-align:center;align-self:end; }
/* matrix column headers: per type / system-wide */
.obs-chk{ color:var(--cool);font-weight:600;text-align:center; }
/* variant exists */
.obs-dash{ color:var(--muted);opacity:.45;text-align:center; }
.dd{                                               /* dropdown / select trigger — SAME box as .fld: radius 6, 30px control height (7px pad + 14px line-height), --edge border. Font stays 11.5 sans (labels), unlike .fld's 12 mono (values). */
    font-weight:400;
    appearance:none;
    -webkit-appearance:none;
    background:var(--ctl-fill);
    color:var(--ink);
    border:1px solid var(--edge);
    border-radius:6px;
    padding:7px 30px 7px 12px;
    font-size:11.5px;
    line-height:14px;                              /* pins the height to .fld's 30px (whose 12px 'normal' line box ≈ 14px), independent of the smaller label font */
    font-family:inherit;
    letter-spacing:.02em;
    cursor:pointer;
    transition:border-color .15s;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 3.5 L5 6.5 L8 3.5' fill='none' stroke='%237e8aab' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/></svg>");
    background-repeat:no-repeat;
    background-position:right 11px center
  }
.dd:hover{border-color:rgba(120,150,210,0.4)}
.dd:focus{
    outline:none;
    border-color:var(--cool)
  }
.dd option{
    background:#0a0f1d;
    color:var(--ink)
  }
.dd optgroup{
    background:#0a0f1d;
    color:var(--faint);
    font-style:normal;
    font-weight:700;
    letter-spacing:.08em
  }
/* .fld = text inputs / textareas: same dark-edge treatment + electric-blue focus as .dd (no yellow UA ring) */
.fld{                                              /* THE text input. Complete recipe — style, height, font — lives HERE:
                                                      never re-pad or re-font .fld inline. 7px/12px padding = the same 30px
                                                      control height as .dd; the 12px mono face is the design system's
                                                      "values are mono" rule (labels stay sans). Code/JSON areas: .fld-code. */
    background:var(--ctl-fill);
    color:var(--ink);
    border:1px solid var(--edge);
    border-radius:6px;
    padding:7px 12px;
    font:12px ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;
    transition:border-color .15s;
  }
.fld:hover{ border-color:rgba(120,150,210,0.4); }
.fld:focus{
    outline:none;
    border-color:var(--cool);
  }
/* checkboxes: themed box matching .dd/.fld, with an electric-blue check when ticked */
input[type=checkbox]{
    -webkit-appearance:none;
    appearance:none;
    width:15px;
    height:15px;
    flex:none;
    margin:0;
    border:1px solid var(--edge);
    border-radius:4px;
    background:var(--ctl-fill);
    cursor:pointer;
    position:relative;
    transition:border-color .15s, background .15s;
  }
/* the hover ring stands down on a disabled box — and it must be said here, not left to
   pointer-events: a LABEL's hover is forwarded to its control (Chromium, WebKit), so a dead
   box inside a .chk-row lit up whenever the row was hovered (2.27.0) */
input[type=checkbox]:hover:not(:disabled){ border-color:rgba(120,150,210,0.4); }
input[type=checkbox]:checked{
    border-color:var(--cool);
    background:color-mix(in srgb, var(--cool) 14%, transparent);
  }
input[type=checkbox]:checked::after{
    content:'';
    position:absolute;
    left:3.5px;
    top:0px;
    width:3.5px;
    height:8px;
    border:solid var(--cool);
    border-width:0 2px 2px 0;
    transform:rotate(45deg);
  }
input[type=checkbox]:focus-visible{
    outline:none;
    border-color:var(--cool);
    box-shadow:0 0 0 3px color-mix(in srgb, var(--cool) 14%, transparent);
  }
/* DISABLED (2.27.0) — the .btn:disabled convention on the box: dimmed and desaturated (a cyan tick on a dead
   box must stop signalling), default cursor, pointer-events off (no hover ring, no press). Driven by the
   native `disabled` attribute alone — nothing for a consumer to toggle, nothing to fall out of step. On a
   .chk-row the ROW is the unit: a label at full strength beside a dead box reads as a live control with a
   dead tick, so the row dims BY STATE (`:has(input:disabled)`) and the box inside it is left at full
   strength — one dim, never two stacked. The row keeps its pointer events, so a `title` on it can still say
   WHY the box is dead (the .btn rule's "put the reason on a wrapper": the row IS the wrapper); the box alone
   goes inert. A bare box (a matrix cell, a list row) dims on its own. A .switch's carrier input is invisible
   by design — the pill is the switch — so the box rule does not reach it: `:where()` carries no specificity,
   which is what lets the .chk-row override below still win. */
input[type=checkbox]:disabled:where(:not(.switch>input)){
    opacity:.42;
    filter:saturate(.45);
    cursor:default;
    pointer-events:none;
  }
.chk-row:has(input:disabled){
    opacity:.42;
    filter:saturate(.45);
    cursor:default;
  }
.chk-row input[type=checkbox]:disabled{ opacity:1; filter:none; }   /* the row carries the dim — not the box again */
/* iOS-style on/off switch (a themed checkbox): used for per-rule enable in Simulation */
.switch{
    position:relative;
    display:inline-block;
    width:34px;
    height:20px;
    flex:none;
    cursor:pointer;
  }
.switch>input{
    position:absolute;
    inset:0;
    width:100%;
    height:100%;
    margin:0;
    opacity:0;
    cursor:pointer;
    z-index:1;
  }
.switch>.sw-bg{
    position:absolute;
    inset:0;
    box-sizing:border-box;
    border-radius:999px;
    border:1px solid var(--cool);
    background:var(--ctl-fill);                      /* OFF = the shared control fill (was --edge); ON = --cool below */
    transition:background .15s;
  }
/* electric-blue border kept in both states */
.switch>input:checked + .sw-bg{ background:var(--cool); }
.switch>.sw-bg::after{
    content:'';
    position:absolute;
    top:1px;
    left:1px;
    width:16px;
    height:16px;
    border-radius:50%;
    background:#fff;
    box-shadow:0 1px 2px rgba(0,0,0,0.35);
    transition:transform .15s;
  }
/* top/left:1px centres the knob inside the 1px border (box-sizing:border-box) */
.switch>input:checked + .sw-bg::after{ transform:translateX(14px); }
.seg button:disabled{
    opacity:.4;
    cursor:not-allowed;
  }
.hud{
    position:absolute;
    top:14px;
    left:14px;
    width:var(--popwin-w);                          /* the shared pop-over window width (was 222 -> 290 -> 320, 2026-07-06; a token since 2.14.0) */
    border-radius:12px;
    padding:0;
    overflow:hidden;                                /* clips the window bar to the rounded corners */
    pointer-events:none;
  }
.row{
    font-weight:400;
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    margin:5px 0;
    font-size:12px
  }
.row .k{color:var(--muted)}
.row .v{
    font-weight:400;font-size:13px}
.seg{                                              /* segmented control: full-width by default, buttons SPLIT the width evenly (a horizontal .seg must never leave its options left-clustered). Opt out with .seg.pill for a compact, fit-to-content header rendition. */
    display:flex;
    border:1px solid var(--edge);
    border-radius:9px;
    overflow:hidden
  }
.seg button{
    appearance:none;
    border:0;
    background:transparent;
    color:var(--muted);
    padding:8px 14px;
    font-size:12px;
    font-weight:600;
    cursor:pointer;
    letter-spacing:.01em;
    transition:background .15s,color .15s;
    flex:1 1 0;                                     /* each option takes an EQUAL share of the control's width (min-width:0 lets long labels shrink) */
    min-width:0;
  }
.seg button + button{border-left:1px solid var(--edge)}
.seg.vert{ flex-direction:column }                 /* stacked variant: options fill the width, sit at their natural height (no vertical stretch), left-aligned */
.seg.vert button{ flex:0 0 auto; text-align:left }
.seg.vert button + button{ border-left:0; border-top:1px solid var(--edge) }
.seg.vert button.on{ box-shadow:inset 2px 0 0 var(--cool) }
.seg.pill{ display:inline-flex; border-color:rgba(120,150,210,0.28); }   /* the GLOBAL/header rendition: joined pill, FIT-to-content (not full-width), selected = cool tint fill (no underline). Use for top-chrome selectors (e.g. the view switcher); the default .seg stays the full-width in-panel rendition. */
.seg.pill button{
    color:var(--ink);
    padding:7px 15px;
    font-size:11.5px;
    letter-spacing:.02em;
    flex:0 0 auto;                                  /* compact: buttons fit their label, the pill sizes to content */
  }
.seg.pill button.on{
    background:color-mix(in srgb, var(--cool) 10%, transparent);
    color:var(--cool);
    box-shadow:none;
  }
.seg.pill button.on:hover{ background:color-mix(in srgb, var(--cool) 16%, transparent); }
.seg button.on{
    background:transparent;
    color:var(--cool);
    box-shadow:inset 0 -2px 0 var(--cool)
  }
.seg button:not(.on):hover{
    color:var(--ink);
    background:rgba(120,150,210,0.07)
  }
.seg button:disabled{
    opacity:.35;
    cursor:not-allowed
  }
.slider{
    display:flex;
    flex-direction:column;
    gap:5px;
    min-width:130px
  }
.slider .top{                                      /* THE slider header row: name LEFT-justified (the label), live value RIGHT-justified (.num, 12px mono) on the same line — space-between does the justification; the track sits below. Markup: .slider > .top(label + .num) + input[type=range]. */
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    margin-bottom:6px;                             /* + the .slider flex gap = 11px of air above the bar. The header row used to sit hard against the track, which read as cramped; this separates them without detaching the row from its slider. Deliberately NOT a bigger .slider gap — that would also push a following .hint.sub-readout away from the bar it annotates. */
  }
.slider label{
    font-weight:400;
    font-size:10.5px;
    letter-spacing:.16em;
    text-transform:uppercase;
    color:var(--faint)
  }
.slider .num{
    font-weight:400;
    font-size:12px;
    color:var(--ink)
  }
/* READOUT (2.28.0) — a READ-ONLY line in a control card: the slider's own header row with no
   track beneath it. Same grammar (.readout > .top(label + .num) [+ .hint.sub-readout]) and the
   same voice (10.5px uppercase label, 12px mono value), so a reported value sits among the
   controls beside it and reads as one of them — a card's Grid, Threads or Deformation radius
   line. The value may run to several lines (pre-line, right-aligned, the label on its first).
   The stats listing (.row › .k/.v, 12/13px) is for the dense listings of a floating window,
   never for a card's lines; and a .slider authored without a track is this component
   misnamed. conform's `readout-row` holds both. The header's 6px margin is kept so a readout
   and a slider fall on the same rhythm in one card. */
.readout{
    display:flex;
    flex-direction:column;
    gap:5px;
    min-width:130px
  }
.readout .top{
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    gap:12px;
    margin-bottom:6px
  }
.readout label{
    font-weight:400;
    font-size:10.5px;
    letter-spacing:.16em;
    text-transform:uppercase;
    color:var(--faint)
  }
.readout .num{
    font-weight:400;
    font-size:12px;
    color:var(--ink);
    white-space:pre-line;
    text-align:right;
    line-height:1.35
  }
input[type=range]{
    -webkit-appearance:none;
    appearance:none;
    display:block;
    width:100%;
    height:3px;
    border-radius:3px;
    background:var(--rail);
    outline:none;
    cursor:pointer;
  }
input[type=range]::-webkit-slider-thumb{
    -webkit-appearance:none;
    box-sizing:border-box;
    width:14px;
    height:14px;
    border-radius:50%;
    background:var(--ink);
    border:2px solid var(--cool);
    box-shadow:0 0 0 3px color-mix(in srgb, var(--cool) 12%, transparent)
  }
input[type=range]::-moz-range-thumb{
    box-sizing:border-box;
    width:14px;
    height:14px;
    border-radius:50%;
    background:var(--ink);
    border:2px solid var(--cool);
    box-shadow:0 0 0 3px color-mix(in srgb, var(--cool) 12%, transparent)
  }
/* DISABLED (2.30.0) — the checkbox convention on the range. The native attribute already makes a range
   inert (no drag, no `input`), but the rail and thumb are drawn by hand, so a dead slider looked exactly
   like a live one (a consumer's offset slider, disabled while its season is fixed, noticed by eye). As
   with the box: dimmed and desaturated, default cursor, pointer-events off, from `disabled` alone —
   nothing for a consumer to toggle. Inside a .slider the ROW is the unit (label and readout recede with
   the track, `:has(input:disabled)`) and the input inside is left at full strength — one dim, never two;
   a bare range dims on its own. The thumb's cool ring is a "live" signal and stands down, and the snap
   points lose their pointer (addSmartPoints's click checks `disabled` too — a snap on a dead slider does
   nothing). The has-sp thumb rule below hands the thumb its pointer back at a higher specificity, so the
   dead thumb is named there as well. */
input[type=range]:disabled{
    opacity:.42;
    filter:saturate(.45);
    cursor:default;
    pointer-events:none;
  }
input[type=range]:disabled::-webkit-slider-thumb{ box-shadow:none; }
input[type=range]:disabled::-moz-range-thumb{ box-shadow:none; }
.slider:has(input[type=range]:disabled){
    opacity:.42;
    filter:saturate(.45);
    cursor:default;
  }
.slider input[type=range]:disabled{ opacity:1; filter:none; }   /* the row carries the dim — not the track again */
.trackwrap:has(input[type=range]:disabled) .smartpt{ pointer-events:none; cursor:default; }
.trackwrap.has-sp input[type=range]:disabled::-webkit-slider-thumb{ pointer-events:none; }
.trackwrap.has-sp input[type=range]:disabled::-moz-range-thumb{ pointer-events:none; }
.trackwrap{position:relative}
.trackwrap.has-sp{
    margin-bottom:20px;                             /* room for the snap labels below — tracks .sp-label's top (was 26/21) */
    isolation:isolate;
  }
/* room below for labels; isolate so the dot z-index stays scoped to this track */
.trackwrap.has-sp::before{
    content:'';
    position:absolute;
    left:0; right:0; top:50%;
    transform:translateY(-50%);
    height:3px;
    border-radius:3px;
    background:var(--rail);
    z-index:0;
    pointer-events:none;
  }
/* THE RAIL, drawn on the WRAPPER so it can sit BEHIND the snap points. It used to be the input's own
   background, and the input is lifted to z-index 2 (below) so its THUMB always wins a click over the dots —
   which meant the rail painted across the FACE of every snap point. That was survivable while the rail was
   translucent (a veil over the dot), but not once it went opaque for the seamless bezel: it became a solid
   bar cutting the jewel in half. Rail behind, dot above it, thumb above both — each layer where it belongs. */
.trackwrap.has-sp input[type=range]{
    position:relative;
    z-index:2;
    pointer-events:none;
    background:transparent;                          /* ...so the input does not paint a SECOND rail over the dots; ::before above is the rail now */
  }
/* lift the track above the dots; the bare track ignores clicks... */
.trackwrap.has-sp input[type=range]::-webkit-slider-thumb{ pointer-events:auto; }
/* ...but the THUMB stays grabbable and sits above the dots -> thumb wins any overlap */
.trackwrap.has-sp input[type=range]::-moz-range-thumb{ pointer-events:auto; }
.smartpt{
    position:absolute;
    top:50%;
    transform:translate(-50%,-50%);
    width:12px;
    height:12px;
    border-radius:50%;
    background:var(--cool);
    background-clip:padding-box;
    opacity:.92;
    /* clip fill inside the border so the cyan doesn't bleed into/under the bezel */
    border:3px solid var(--rail);                    /* the collar is EXACTLY the rail's own 3px line weight, so the rail runs into it without a step. Grown 10->12 (border-box) to pay for the thicker border out of the OUTER radius: the cyan core stays 6px, which also keeps .sp-label put — it is positioned from the PADDING box, whose top is core-radius above the centre either way. Keep width == core + 2*border if you retune this. */
    /* bezel = the RAIL ITSELF, opaque and identical to the track, so the ring reads as the rail clasping the
       dot: seamless where it crosses the track, and a solid rail-coloured collar above/below it. (Was the
       same hue at .6 alpha, chosen to stay visible against the dot — but that composited over the .22 track
       and brightened the overlap. Contrast against the cyan dot comes from the hue difference, not alpha.) */
    margin:0;
    padding:0;
    z-index:1;
    cursor:pointer;
    /* clickable to snap, but sits below the lifted track + thumb */
    -webkit-appearance:none;
    appearance:none;
    transition:box-shadow .12s, opacity .12s;
  }
.smartpt .sp-label{
    font-weight:400;
    position:absolute;
    left:50%;
    top:15px;                                      /* the snap label sat too far under the rail to read as its annotation; 6px closer (the wrap's margin-bottom above drops by the same 6, so no dead space is left) */
    transform:translateX(-50%);
    font-size:11px;
    letter-spacing:.05em;
    color:var(--cool);
    opacity:.9;
    white-space:nowrap;
    cursor:pointer;
    transition:opacity .12s;
  }
.smartpt:hover{
    opacity:1;
    box-shadow:0 0 0 3px color-mix(in srgb, var(--cool) 22%, transparent), 0 0 8px color-mix(in srgb, var(--cool) 60%, transparent);
  }
.smartpt:hover .sp-label{ opacity:1; }

.btn{
    appearance:none;
    border:1px solid var(--edge);
    background:transparent;
    color:var(--ink);
    padding:8px 16px;
    border-radius:9px;
    font-size:12px;
    font-weight:600;
    cursor:pointer;
    letter-spacing:.04em;
    --press-glow:var(--cool);                      /* the press-flash ring colour; each COLOURED variant overrides it to match itself */
    transition:background .15s,border-color .15s,transform .09s ease,filter .09s ease,box-shadow .12s ease;
  }
.btn:hover{
    background:rgba(120,150,210,0.08);
    border-color:var(--muted)
  }
/* PRESS FEEDBACK ("electric pop") — a pressed ACTION button brightens, flashes a 3px ring in its OWN
   colour (`--press-glow`, set per variant/control), and dips a hair of scale, springing back on release.
   SHARED by every action button: `.btn` (+ variants), the header icon buttons (`.view-gear`), a section
   head's help toggle (`.head-help`), the window close (`.pop-x`), and the card delete (`.subcard-x`). Deliberately NOT on selectors (`.seg` buttons,
   `.cp-tab`) — their meaning is "chosen state", not "fired action". */
.btn:active, .view-gear:active, .pop-x:active, .subcard-x:active, .head-help:active{
    transform:scale(0.97);
    filter:brightness(1.15);
    box-shadow:0 0 0 3px color-mix(in srgb, var(--press-glow) 42%, transparent);
  }
@media (prefers-reduced-motion:reduce){
    .btn:active, .view-gear:active, .pop-x:active, .subcard-x:active, .head-help:active{ transform:none; }   /* keep the brighten + ring, drop the motion */
  }
.btn.is-green{
    background:#34c06a;
    border-color:#34c06a;
    color:#06210f;
    --press-glow:#34c06a                            /* the glow matches the button — a green pop, not a cyan one */
  }
.btn.is-green:hover{
    background:#3ed079;
    border-color:#3ed079
  }
.btn.on{
    background:var(--accent);
    border-color:var(--accent);
    color:#1a1205;
    --press-glow:var(--accent)
  }
.btn.on:hover{
    background:#f7c45f;
    border-color:#f7c45f
  }
.btn.is-yellow{
    background:#f1c84b;
    border-color:#f1c84b;
    color:#2a1f00;
    --press-glow:#f1c84b
  }
.btn.is-yellow:hover{
    background:#f7d566;
    border-color:#f7d566
  }
.btn.is-red{
    background:#dc4a4a;
    border-color:#dc4a4a;
    color:#fff;
    --press-glow:#dc4a4a                            /* a red pop for destructive buttons */
  }
.btn.is-red:hover{
    background:#e85d5d;
    border-color:#e85d5d
  }
/* DISABLED — an action that exists but is unavailable right now. It must read INERT: dimmed (so both the
   fill and the label recede), desaturated (so a coloured variant stops signalling "go" while it cannot be
   pressed), and pointer-events off so the mouse gets NO response at all — no hover wash, no press pop, no
   pointer cursor. The .view-gear:disabled convention, applied to the button family. Note this also drops
   `title` tooltips on the element; put the reason in the label, or on a wrapper. */
.btn:disabled{
    opacity:.42;
    filter:saturate(.45);
    cursor:default;
    pointer-events:none;
  }
/* LABEL PLACEMENT — the system's default: the field NAME (a .vis-label) sits on its own line ABOVE
   the widget (text box, dropdown, seg, …). .vis-group is the canonical stacked wrapper; .slider is the
   same shape with the value readout sharing the label line. Inline LEFT labels (64px) are reserved for
   dense editor-card rows; checkboxes/switches put their label AFTER the control (.chk-row). */
.vis-group{
    display:flex;
    flex-direction:column;
    gap:6px
  }
.dd-full{width:100%}
.vis-label{
    font-weight:400;
    font-size:10.5px;
    letter-spacing:.16em;
    text-transform:uppercase;
    color:var(--faint)
  }
input[type=color]{
    -webkit-appearance:none;
    appearance:none;
    width:46px;
    height:26px;
    padding:2px;
    border:1px solid var(--edge);
    border-radius:7px;
    background:transparent;
    cursor:pointer;
  }
input[type=color]::-webkit-color-swatch-wrapper{padding:0}
input[type=color]::-webkit-color-swatch{
    border:none;
    border-radius:5px
  }
input[type=color]::-moz-color-swatch{
    border:none;
    border-radius:5px
  }
.btn.primary{min-width:74px}
/* control-panel tab bar: floats over the section flow at the panel's bottom (post-launch only) */
.cp-tabs{
    position:absolute;
    left:0; right:0; bottom:0;
    z-index:6;
    display:flex;
    flex-direction:column;                           /* icon row on top; an optional status line (e.g. the fps readout) may sit under it */
    align-items:stretch;
    padding:9px 10px 8px;
    background:var(--card);                           /* shares the card back-colour it floats over */
    border-top:1px solid var(--line);
    box-shadow:0 -12px 22px rgba(0,0,0,0.45);
  }
.cp-tab-row{
    display:flex;
    justify-content:space-evenly;
    align-items:center;
  }
.cp-tab{
    background:none;
    border:1px solid transparent;
    border-radius:9px;
    padding:7px 12px;
    cursor:pointer;
    color:var(--muted);
    display:flex;
    transition:color .15s, background .15s, border-color .15s;
  }
.cp-tab:hover{ color:var(--ink); background:rgba(120,150,210,0.10); }
.cp-tab.on{ color:var(--cool); border-color:var(--cool); background:color-mix(in srgb, var(--cool) 10%, transparent); }
.cp-tab svg{ width:22px; height:22px; display:block; }
.cp-tab.pulse{ animation:cpTabPulse 1.1s ease-out 2; }
/* view switched -> the eye tab flashes twice: "this view's render options live here" */
@keyframes cpTabPulse{
    0%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--cool) 55%, transparent); color:var(--cool); }
    70%{ box-shadow:0 0 0 9px rgba(90,209,255,0); color:var(--cool); }
    100%{ box-shadow:0 0 0 0 rgba(90,209,255,0); }
  }
.view-gear{                                                           /* header view selector's Display-settings shortcut */
    background:none;
    border:1px solid rgba(120,150,210,0.28);
    border-radius:7px;
    width:26px; height:26px;
    display:flex; align-items:center; justify-content:center;
    color:var(--muted);
    cursor:pointer;
    padding:0;
    flex:none;
    --press-glow:var(--cool);                                          /* press feedback (shared .btn:active rule) — cool glow */
    transition:color .15s, border-color .15s, background .15s, transform .09s ease, filter .09s ease, box-shadow .12s ease;
  }
.view-gear:hover{ color:var(--cool); border-color:var(--cool); background:color-mix(in srgb, var(--cool) 8%, transparent); }
.view-gear.on{ color:var(--cool); border-color:var(--cool); background:color-mix(in srgb, var(--cool) 12%, transparent); }
.view-gear:disabled{ opacity:.35; cursor:default; pointer-events:none; }   /* unavailable tool (e.g. the active view offers none): stays visible so the header never reflows, but reads clearly inert — no hover ring, no press feedback */
.view-gear.pulse{ animation:cpTabPulse 1.1s ease-out 2; }
.view-gear svg{ width:15px; height:15px; display:block; }
.disp-pop{                                                            /* compact floating Display panel (view-gear popover): holds the ACTIVE view's Display sections, reparented in while open. Persistent: the gear or its ✕ closes it. */
    position:absolute;
    top:14px;
    right:calc(var(--pw) + 14px);                                       /* upper-right of the VISIBLE viewport — mirrors the Statistics HUD's upper-left anchor. The loop OVERRIDES this inline, riding the panel's slide tween (panelW*(1-panelP)+14) so the popover glides with the panel edge. */
    z-index:6;
    width:var(--popwin-w);                                              /* shared with .hud; set --popwin-w ON this element (or a [data-pop] variant of it) to widen just this window */
    max-height:min(72vh, 640px);
    display:flex;
    flex-direction:column;
    overflow:hidden;                                                    /* the BODY scrolls, not the box -> the scrollbar stops below the header and the rounded corners clip it */
    border-radius:12px;
    box-shadow:0 18px 48px rgba(0,0,0,.6);
  }
.disp-pop-body{ flex:1 1 auto; min-height:0; overflow-y:auto; padding-top:8px; }
/* the scroll region: starts BELOW the window bar. padding-top clears the bar the way .hud-body's 11px does
   — the reparented .sec's own header is hidden here, so its 3px sec-inner top alone reads too tight (8+3≈11). */
.disp-pop-body::-webkit-scrollbar{ width:9px; }
.disp-pop-body::-webkit-scrollbar-thumb{ background:rgba(120,150,210,0.18); border-radius:5px; }
.pop-head, .disp-pop-head, .hud-head{                                             /* the shared pop-over window bar (title + ✕); sits OUTSIDE the scroll region so it is always in front */
    flex:0 0 auto;
    display:flex; justify-content:space-between; align-items:center;
    padding:10px 12px 8px 14px;
    background:rgba(13,19,34,0.96);                                      /* a step above --panel */
    border-bottom:1px solid var(--edge);
  }
.pop-head .t, .disp-pop-head .t, .hud-head .t{
    font-weight:400; font-size:9.5px; letter-spacing:.22em; text-transform:uppercase; color:var(--faint); }
.pop-head, .disp-pop-head, .hud-head{ cursor:grab; user-select:none; -webkit-user-select:none; }
/* CONTROLS ARE NOT TEXT (2.26.0). A quick second press on a control is the browser's
   double-click, and double-click means "select the nearest word" — in every engine — so a
   rapid pair of presses on a stepper left its label or readout highlighted. Non-selectable
   chrome starts no selection, on a double-click or on a press that slips into a drag. A
   DISABLED control hands its presses to its parent, so a consumer marks the container of
   its own control clusters too (a dock's step row, say). Readouts (a slider's .num included —
   the System card quotes its facts through slider headers), fields and hints are deliberately
   absent: copying a value keeps working. One line, by design: a red run lifts it with one
   deletion. A JS multi-click guard was tried beside this rule and dropped: in Chromium it
   changed nothing the rule had not already settled. */
button, .seg, .switch, .slider label, .slider input, .trackwrap, .smartpt, .chk-row, .cp-tab, .view-gear, .vis-label, .dock-step, .subcard-title{ user-select:none; -webkit-user-select:none; }   /* .dock-step (its readout included) and .subcard-title since 2.30.1: rapid clicks on a stepper's arrows selected the text beside them */
.pop-head:active, .disp-pop-head:active, .hud-head:active{ cursor:grabbing; }
.hud-head{ pointer-events:auto; }
/* drag handle inside the otherwise click-transparent HUD */
.pop-x{                                                                /* shared ✕ close (the lattice-preview pk-close look) */
    background:none; border:none;
    color:var(--muted,#8b97b4);
    cursor:pointer;
    font-size:14px; line-height:1;
    padding:0 2px;
    pointer-events:auto;
    border-radius:5px;                                                  /* so the press ring/scale reads as a tidy rounded flash */
    --press-glow:var(--cool);                                          /* press feedback (shared .btn:active rule) — window buttons pop too */
    transition:color .15s, transform .09s ease, filter .09s ease, box-shadow .12s ease;
  }
.pop-x:hover{ color:var(--ink); }
.hud-body{ padding:11px 14px 12px; }
/* the HUD's content (the box itself is padding-less so the window bar spans edge-to-edge) */
.hud-sys{ font-size:10px; color:var(--muted); margin:-2px 0 9px; }
/* ensemble: the shown system's index, under the window bar */
.disp-pop .vis-label, .disp-pop .slider label, .disp-pop .readout label{ color:var(--muted); }
/* contrast over the glass: lift the --faint control labels to the HUD's --muted key colour — a readout's label is a widget label too (2.29.1: it sat a notch dimmer than the slider label beside it) */
.disp-pop .hint{ color:var(--muted); }
.tip{                                                /* floating tooltip (see JS attachTip): body-appended + position:fixed so it ESCAPES a card's overflow:hidden; JS sets left/top (centred above the anchor, viewport-clamped). Was .cp-tab-tip (an absolutely-positioned child, clipped by the panel). */
    position:fixed;
    z-index:1100;                                    /* on top of everything: above pop-overs (880), the fullscreen panel peek (900), and dd menus (1000) */
    font-weight:400;
    background:var(--void2);                          /* a floating menu/tooltip surface — raw palette, not a card */
    border:1px solid var(--line);
    border-radius:7px;
    padding:5px 10px;
    font-size:10.5px;
    letter-spacing:.02em;
    color:var(--ink);
    white-space:nowrap;
    pointer-events:none;
    opacity:0;
    transition:opacity .12s;
    box-shadow:0 8px 22px rgba(0,0,0,0.5);
  }
.tip.show{ opacity:1; }
/* room for the floating tab bar: the last section can scroll fully above it */
.sec{ border-bottom:1px solid var(--line); }
.sec-head{                                          /* STICKY (2.10.0): the section you are reading pins to the top of the scrolling card and floats over its own content; it rides out as the next section arrives (sticky is bounded by the parent .sec, so the headers hand off rather than stack). Applies to collapsible AND .fixed heads alike. */
    position:sticky;
    top:0;
    z-index:3;                                       /* the layer stack: in-section controls (1-2) < a stuck head (3) < the overlay scrollbar (.ovscroll-bar, 4) < floating footer bars (6). The head floats over its content but must NOT cover the scroll thumb */
    width:100%;
    display:flex;
    align-items:center;
    gap:9px;                                         /* the chevron belongs to the TITLE, so it sits just after it (2.20.0). Flung to the far right it read as a separate control — and once a .sec-headrow put a [?] out there, two unrelated glyphs shared that edge and the turn-symbol looked like it belonged to the help button rather than to the words it opens */
    justify-content:flex-start;
    background:var(--sec-head-bg, var(--card));      /* it must be OPAQUE to float over content; the token lets a non-card surface opt out (see .glass below) */
    border:0;
    cursor:pointer;
    color:var(--accent);
    padding:14px 18px 13px;
    font-family:inherit;
    font-size:11px;
    font-weight:700;
    letter-spacing:.18em;
    text-transform:uppercase;
    transition:color .15s;
  }
.sec-head:hover{ color:var(--accent); }
.sec.fixed .sec-head{ cursor:default; }
.sec.fixed .sec-head:hover{ color:var(--accent); }

/* A SECTION HEAD THAT CARRIES A [?]. `.sec-head` is a <button>, so the help
   toggle cannot live inside it — a button in a button is invalid HTML, and its
   clicks would fight the collapse. Wrap the two as siblings instead:

     <div class="sec-headrow">
       <button class="sec-head" type="button"><span>Backdrops</span><i class="chev"></i></button>
       <button class="head-help" type="button" aria-expanded="false" aria-label="How this section works">?</button>
     </div>
     <div class="sec-body"><div class="sec-inner"> …controls… </div></div>

   Only a section WITH help needs the wrapper; every existing `.sec` is
   untouched. STICKY MOVES TO THE ROW — a head that pinned on its own would
   leave its help button behind to scroll away — so the row takes the sticky box
   and the head inside it goes static. The row paints `--sec-head-bg` for the
   same reason the head does: it must be opaque to float over its own content. */
.sec-headrow{
    position:sticky;
    top:0;
    z-index:3;                                       /* the head's own layer, unchanged — see .sec-head */
    display:flex;
    align-items:center;
    background:var(--sec-head-bg, var(--card));
    padding-right:18px;                              /* the head's right padding, moved out to the row so the [?] sits at the panel's gutter */
  }
.sec-headrow .sec-head{
    position:static;                                 /* the ROW sticks now */
    width:auto;
    flex:1 1 auto;
    min-width:0;
    padding-right:12px;                              /* the chevron stops short of the [?] rather than crowding it */
  }

/* THE [?] HELP TOGGLE — `.view-gear`'s little sibling, carrying a glyph instead
   of a stroke icon, right-justified on its heading's own line (`margin-left:auto`,
   so it needs no `justify-content` from the host and works in any flex heading
   row a consumer already has). TWO KIT HOSTS: a control-panel section head, via
   the `.sec-headrow` wrapper above, and a pop-out `.pop-card`'s `.card-title`,
   which is a heading rather than a button and so takes the [?] directly.

   WHAT IT DOES IS THE HOST'S BUSINESS. The kit styles the affordance and its two
   states and stops there — reveal a panel under the head, open a pop-over, start
   a tour, nothing at all. Mark the open state with `aria-expanded="true"` (a
   disclosure) or `.on` (anything else); both get the same cool treatment, and
   `aria-expanded` is the one to reach for because a screen reader reads it. */
.head-help{
    margin-left:auto;
    flex:0 0 auto;
    width:21px; height:21px;                         /* a glyph button, smaller than the 26px .view-gear: it rides a heading's line, not a header bar */
    display:flex; align-items:center; justify-content:center;
    padding:0;
    background:none;
    border:1px solid rgba(120,150,210,0.28);         /* the .view-gear edge — this is that family, sized down */
    border-radius:6px;
    color:var(--muted);
    font-family:inherit;
    font-size:12px;
    font-weight:700;
    line-height:1;
    letter-spacing:0;                                /* a heading's .18em tracking would push the glyph off-centre */
    text-transform:none;
    cursor:pointer;
    --press-glow:var(--cool);                        /* press feedback (the shared :active rule) — cool glow */
    transition:color .15s, border-color .15s, background .15s, transform .09s ease, filter .09s ease, box-shadow .12s ease;
  }
.head-help:hover{ color:var(--cool); border-color:var(--cool); background:color-mix(in srgb, var(--cool) 8%, transparent); }
.head-help[aria-expanded="true"], .head-help.on{ color:var(--cool); border-color:var(--cool); background:color-mix(in srgb, var(--cool) 12%, transparent); }
.head-help:disabled{ opacity:.35; cursor:default; pointer-events:none; }
.sec-head .chev{
    width:7px;
    height:7px;
    border-right:1.5px solid currentColor;
    border-bottom:1.5px solid currentColor;
    flex:0 0 auto;
    transform:rotate(45deg);
    transition:transform .25s;
    opacity:.8;
    margin-bottom:3px;
  }
.sec.collapsed .sec-head .chev{
    transform:rotate(-45deg);
    margin-bottom:0;
  }
.sec-body{
    overflow:hidden;
    transition:height .28s ease;
  }
.sec.collapsed .sec-body{ height:0; }              /* CSS backstop: a collapsed section stays shut even if its inline height (set by toggleSec's animation) is later cleared. Inline height wins DURING the animation, so this never fights the transition. */
.sec-inner{
    display:flex;
    flex-direction:column;
    gap:15px;
    padding:3px 18px 20px;
  }
/* SECTION GROUP (2.11.0): a .sec whose body holds other .secs — one amber head standing over several
   sections (e.g. a single settings window covering two panes). The children already carry the 18px
   .sec-inner gutter, so the group must NOT add a second one: without this, grouped content sits 36px in
   while the group's own head sits at 18px. The group is the visual unit, so the children's dividers go too. */
.sec-group > .sec-body > .sec-inner{ padding:0; gap:0; }
.sec-group > .sec-body > .sec-inner > .sec{ border-bottom:none; }
/* A stuck head (top:0) pins to the scrollport's PADDING edge, so a scroll container that pads its top holds
   grouped heads that far below the window bar instead of flush against it. */
.sec-group-scroll{ padding-top:0; }
.sec-stack{ display:flex; flex-direction:column; gap:15px; }
/* CONVENTION: a wrapper div nested inside a .sec-inner (grouping sliders/rows so they can be toggled
   together) is a plain block by default, which COLLAPSES the section's inter-control rhythm to 0. Give it
   .sec-stack so its children keep the same 15px vertical gap as the control panel. Applies everywhere,
   pop-out windows included (their sections reparent the same .sec-inner markup). */
/* Overlay scrollbar (PhantasmUI.overlayScroll): a floating thumb over the scroller's right edge —
   never steals layout width, so content doesn't reflow/jitter when it starts or stops overflowing.
   .ovscroll hides the scroller's native bar; the thumb is drawn into the scroller's positioned parent
   and rides over its right-hand padding gutter (keep >= the bar's width of right padding on content). */
.ovscroll{ scrollbar-width:none; }                         /* Firefox: hide native */
.ovscroll::-webkit-scrollbar{ width:0; height:0; }         /* WebKit: hide native (the overlay thumb replaces it) */
.ovscroll-bar{
    position:absolute;
    right:3px;
    width:8px;
    z-index:4;                                             /* above the scroller's in-flow content AND any stuck .sec-head (3), below floating footer bars (e.g. a tab/action bar at z:6) */
    pointer-events:none;                                   /* the track is transparent; only the thumb catches the pointer */
  }
.ovscroll-thumb{
    position:absolute;
    top:0; right:0;
    width:100%; height:0;
    border-radius:5px;
    background:rgba(120,150,210,0.18);                     /* the --line/--edge blue-grey family; mirrors the native thumb it replaces */
    pointer-events:auto;
    cursor:default;
    transition:background .15s;
  }
.ovscroll-thumb:hover{ background:rgba(120,150,210,0.32); }
/* Solid scrollbars (PhantasmUI.solidScroll): the overlay bar's counterpart for a VIEWPORT — a pane
   showing oversized content (a zoomed sheet, a map, a canvas) rather than a column of controls.
   Two differences that decide which one you want:
     · BOTH AXES. Oversized content overflows sideways too; the overlay bar is vertical-only.
     · SOLID track + thumb. The overlay thumb is translucent so it can sit over a padding gutter;
       over busy artwork it disappears. These float over the CONTENT with a track behind them, so
       they stay legible on anything.
   Like the overlay bar they take no layout width, so the viewport never reflows. `.solidscroll` on
   the scroller hides its native bars; the bars mount in the scroller's positioned parent. */
.solidscroll{ scrollbar-width:none; }                      /* Firefox: hide native */
.solidscroll::-webkit-scrollbar{ width:0; height:0; }      /* WebKit: hide native */
.solidscroll-bar{
    position:absolute;
    z-index:4;                                             /* same rung as .ovscroll-bar: over content, under floating footer bars */
    background:var(--void2);
    border:1px solid var(--edge);
    border-radius:5px;
  }
.solidscroll-h{ left:3px; right:3px; bottom:3px; height:10px; }
.solidscroll-v{ top:3px; right:3px; bottom:3px; width:10px; }
.solidscroll-bar.both.solidscroll-h{ right:16px; }         /* when both bars show, each stops clear of the other's corner */
.solidscroll-bar.both.solidscroll-v{ bottom:16px; }
.solidscroll-thumb{
    position:absolute;
    background:var(--muted);
    border-radius:3px;
    cursor:grab;
    transition:background .12s;
  }
.solidscroll-thumb:hover,
.solidscroll-thumb:active{ background:var(--cool); }
.solidscroll-thumb:active{ cursor:grabbing; }
.solidscroll-h .solidscroll-thumb{ top:0; bottom:0; }
.solidscroll-v .solidscroll-thumb{ left:0; right:0; }
.sec-inner .slider{ min-width:0; }
/* the LM-launcher source-toggle wrappers would otherwise collapse the gap between their sliders/hints; match .sec-inner's 15px */
.sec-inner .seg button{ padding:8px 6px; }   /* in-panel: tighter horizontal padding (the base .seg already handles full-width + even split + the .vert stack) */
.sec-inner .btn{ width:100%; }
/* the anchored action bar's STEP RETREAT (Back): extra clearance above the bar's own
   12px gap, so the retreat stands visually apart from the primary action it undoes */
.bar-retreat{ margin-top:14px; }
.subcard{
    border:1px solid var(--edge);
    border-radius:11px;
    background:var(--subcard);                       /* sub-card surface role (a cool wash over the --card behind it — one elevation step lighter than the card) */
    padding:13px 13px 15px;
    display:flex;
    flex-direction:column;
    gap:13px;
    --ctl-fill:rgba(13,21,32,0.6);                  /* controls on a sub-card = the card fill shifted by the sub-card's own 3.5% cool wash: lighter than the card fill (no dark hole on the brighter surface) yet still a hair DARKER than the sub-card (a subtle recess, never raised). Same surface-relationship a card control has to the card. */
    --rail: color-mix(in srgb, rgb(120,150,210) 22%, color-mix(in srgb, var(--cool) 3.5%, var(--card)));   /* the rail is OPAQUE, so it has to be re-mixed over the sub-card's own solid colour — otherwise a slider on a sub-card wears the card's rail and reads a shade too dark. Track and bezel both read this, so they stay identical to each other either way. */
  }
.subcard-x{
    font-weight:400;                               /* THE reusable delete button: a red-tinted square ×. Un-scoped from .subcard so it works anywhere a
                                                      remove control is needed (sub-card corners, editor/list rows, pop-out rows). In a .subcard's first row
                                                      `margin-left:auto` pins it upper-right; in a grid/flex row it sits in its cell. Pairs with the optional
                                                      .subcard-title — a card may carry either, both, or neither. */
    width:26px;
    height:26px;
    flex:none;
    margin-left:auto;
    border-radius:6px;
    border:1px solid var(--edge);
    background:rgba(255,90,90,0.12);
    color:#ff8a8a;
    cursor:pointer;
    font-size:15px;
    line-height:1;
    --press-glow:#ff5a5a;                          /* press feedback (shared .btn:active rule) — a RED pop for the delete × */
    transition:background .15s,border-color .15s,transform .09s ease,filter .09s ease,box-shadow .12s ease;
  }
.subcard-x:hover{ background:rgba(255,90,90,0.22); border-color:rgba(255,90,90,0.45); }
.subcard .subcard-title{
    font-weight:400;
    font-size:9.5px;
    letter-spacing:.2em;
    text-transform:uppercase;
    color:var(--cool);
    opacity:.9;
  }

/* ---- one-line selectable LIST-BOX — a titled, scrollable roster of one-line rows ---------------------
   The horizontal sibling of the pop-out rail (§12): where a `.rail-item` STACKS a label over a sub for a
   full-height picker, a `.list-box` packs a name + a trailing value on ONE line for a dense in-panel roster
   (live monsters, connected players, search hits). Selection is the SAME --cool language as `.rail-item.on`
   — a reserved transparent→--cool BORDER, never an `outline` (an outline paints OUTSIDE the box, so a
   scroller's overflow shears it). Build the rows in JS; wire overlayScroll() on the `.list-scroll`. */
.list-box{ display:flex; flex-direction:column; gap:7px; }                       /* head + scroll host, tighter than the section's own 15px rhythm (the header hugs its list) */
.list-head{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.list-title{ font-weight:400; font-size:9.5px; letter-spacing:.2em; text-transform:uppercase; color:var(--cool); opacity:.9; }   /* the `.subcard-title` voice: a COOL widget label (a list is a titled container like a sub-card), NOT the amber `.sec-head` */
.list-count{ font-size:10.5px; color:var(--faint); }                             /* optional right-aligned tally ("8 live"); add `mono` in markup for the digits */
.list-host{ position:relative; background:var(--ctl-fill); border:1px solid var(--edge); border-radius:6px; padding:4px; }   /* the overlayScroll mount point — its floating thumb rides this positioned parent. Wears the form-control fill (2.12.0) so a roster reads as a text-box-style well, distinct from its card; --ctl-fill tracks card vs sub-card automatically, exactly like .fld */
.list-scroll{ display:flex; flex-direction:column; gap:2px; overflow-y:auto; }   /* the scroller: give it a `max-height` (context-specific) so it scrolls */
.list-row{ display:flex; align-items:center; gap:9px; width:100%; text-align:left; padding:6px 10px; font:inherit; font-size:12px; color:var(--ink); cursor:pointer; background:transparent; border:1px solid transparent; border-radius:8px; }   /* one entry = a <button> (keyboard-reachable). The 1px transparent border is RESERVED so `.on` adds no layout shift */
.list-row:hover{ background:color-mix(in srgb, var(--cool) 7%, transparent); }
.list-row.on{ border-color:var(--cool); background:color-mix(in srgb, var(--cool) 12%, transparent); }   /* SELECTED = the active --cool language, exactly like `.rail-item.on` — the border lives INSIDE the box, so a scroller can never clip it */
.list-dot{ width:7px; height:7px; border-radius:50%; flex:none; }                /* optional leading status pip; colour it by data (e.g. --sel awake, --accent asleep) */
.list-name{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }   /* the primary label — takes the row, ellipsises when long */
.list-meta{ flex:none; color:var(--muted); }                                     /* the trailing value/state; add `mono` in markup for data */
/* TEXT TIP — THE explanatory-prose style for cards and sub-cards: small, faint, sans, quiet. It is the
   default voice for "what this control does" copy; do not reach for `.mono` or a custom opacity to say the
   same thing (mono is for VALUES, not sentences). SELF-SPACING by design: every container that holds one
   (.sec-inner, .subcard, .pop-card) lays out with a flex gap, so the <p> default margin is pure noise —
   authors were resetting it inline on nearly every instance. It is reset here instead. */
.hint{
    font-weight:400;
    font-size:10.5px;
    color:var(--faint);
    line-height:1.5;
    margin:0;
  }
/* SUB-READOUT — an ALTERNATIVE reading of the control directly above it: the same setting in another unit,
   or what it works out to in practice ("38 systems / measurement · 100% of systems per τₛ"). Right-justified
   so it hangs under the widget's own value, mono + tabular figures so digits don't jitter as it updates, and
   a notch quieter than tip prose so it reads as secondary. Compose on .hint; needs no inline styling. */
.hint.sub-readout{
    font-family:ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;
    font-variant-numeric:tabular-nums;
    font-feature-settings:"tnum";
    text-align:right;
    margin-top:3px;
    opacity:.85;
  }
.hint.warn-amber{ color:#e0b341; }
.hint.warn-red{ color:#ff6b6b; }
.hint.q-filling{ color:#f5b441; }
.hint.q-full{ color:#34c06a; }

/* ---- statistics listing (label->value rows use .row/.k/.v; these add the grouped per-item chips) ---- */
.statchips{
    font-weight:400;                                         /* a wrapping line of per-type value chips under a group header */
    display:flex;
    flex-wrap:wrap;
    font-size:11px;
    color:var(--ink);
  }
.statchips .chip{
    display:inline-flex;
    align-items:center;
    gap:4px;
    margin:1px 8px 1px 0;
  }
.swatch{                                            /* the small colour-key square (stats chips, legend rows); set its background inline — colour is data */
    display:inline-block;
    width:8px;
    height:8px;
    border-radius:2px;
    flex:none;
  }

/* ---- numeric stepper (upgradeNumber wraps an <input type=number>) ---- */
.numfld{
    position:relative;
    display:inline-flex;
    align-items:stretch;
  }
.numfld input.fld{
    padding-right:26px;                              /* room for the stepper column; everything else = the standard .fld recipe (30px, 12px mono) */
    width:100%;
    -moz-appearance:textfield;
  }
.numfld input.fld::-webkit-outer-spin-button,
.numfld input.fld::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
.numfld .steps{
    position:absolute;
    top:1px; right:1px; bottom:1px;
    width:22px;
    display:flex;
    flex-direction:column;
    border-left:1px solid var(--edge);
  }
.numfld .steps button{
    font-weight:400;
    flex:1;
    appearance:none;
    border:0;
    padding:0;
    background:transparent;
    color:var(--muted);
    cursor:pointer;
    font-size:8px;
    line-height:1;
    transition:color .12s, background .12s;
  }
.numfld .steps button:hover{ color:var(--cool); background:color-mix(in srgb, var(--cool) 10%, transparent); }
.numfld .steps button:first-child{ border-bottom:1px solid var(--edge); border-radius:0 5px 0 0; }
.numfld .steps button:last-child{ border-radius:0 0 5px 0; }

/* ---- kit additions (2026-07): shared utilities ---- */
.glass{                                             /* the pop-over surface (compose with a window class). SOLID since 2.11.0 — see --panel: it used to be translucent + backdrop-blurred, but text over a busy, moving stage was the recurring cost and the blur bought nothing a solid surface doesn't. */
  background:var(--panel);
  border:1px solid var(--edge);
  --sec-head-bg:var(--panel);                       /* a sticky head paints the window's OWN surface (not --card, which is a different shade): now that the surface is solid this is safe, so content genuinely cannot read through an anchored header */
}
.dd-menu::-webkit-scrollbar{ width:9px; }
.dd-menu::-webkit-scrollbar-thumb{ background:color-mix(in srgb, var(--cool) 14%, transparent); border-radius:5px; }
.fld-code{                                          /* code/JSON textarea (Import / Export) */
  width:100%;
  height:150px;
  resize:vertical;
  font:11px ui-monospace,Menlo,monospace;
  padding:8px;
  box-sizing:border-box;
}
.chk-row{
    font-weight:400;                                           /* checkbox + wrapped label line */
  display:flex;
  align-items:flex-start;
  gap:7px;
  font-size:11.5px;
  line-height:15px;
  color:var(--muted,#8b97b4);
  cursor:pointer;
}

/* ============================================================================================
   FULL-WINDOW POP-OUT LAYOUT (STYLE_GUIDE §12) — a self-contained editor that opens in its OWN
   browser tab/window (launched by a main app), NOT an in-app floating window (.disp-pop / .hud).
   Anatomy:
       <header class="pop-header"> brand · divider · page-name · spacer · unsaved </header>
       <div class="pop-main">                          a grid:  [rail] [content]
         <div class="pop-col"><nav class="pop-rail"> …picker items… </nav></div>
         <div class="pop-col"><div class="pop-content"><div class="pop-cards"> …cards… </div></div></div>
       </div>
   The PAGE supplies html/body height + base font (host chrome — see STYLE_GUIDE §2); the classes below
   own the layout. Wire overlayScroll() on .pop-rail and .pop-content (their .pop-col parent hosts the
   floating thumb). Content inside cards uses the SAME kit widgets/rows as everywhere else.
   ============================================================================================ */
/* the slim header — a deliberately lighter variant of the main app header (a pop-out is compact) */
.pop-header{ display:flex; align-items:center; gap:10px; height:var(--pop-header-h); padding:0 12px; border-bottom:1px solid var(--line); background:var(--base); }
.pop-header .brand{ font-size:13px; font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:var(--ink); white-space:nowrap; }   /* == the app header brand (h1) — the ONLY thing that differs from the main header is the bar HEIGHT */
.pop-header .brand b{ font-weight:inherit; color:inherit; }                       /* keep the wordmark MONOCHROME like the main-header brand (no --cool accent word) */
.pop-header .page-name{ font-size:12px; font-weight:500; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); white-space:nowrap; }   /* == the app header's .model-name (muted uppercase) */
.pop-header .divider{ width:1px; height:18px; align-self:center; flex:none; border-radius:1px; background:linear-gradient(180deg,transparent,var(--accent) 22%,var(--accent) 78%,transparent); opacity:.85; }   /* the amber header divider (as the main header's .head-divider), sized for the slim bar */
.pop-header .spacer{ flex:1; }
.pop-header .unsaved{ font-size:10px; font-weight:400; letter-spacing:.18em; text-transform:uppercase; color:var(--accent); white-space:nowrap; }   /* amber = attention (unsaved changes) */
/* the body grid: rail | content, filling the space under the header */
.pop-main{ display:grid; grid-template-columns:var(--pop-rail-w) 1fr; height:calc(100% - var(--pop-header-h)); }
.pop-main.no-rail{ grid-template-columns:1fr; }                                   /* a RAILLESS pop-out (a settings sheet / single-card tool): drop the picker column so the cards span the full width */
.pop-col{ position:relative; overflow:hidden; min-height:0; }                     /* a positioned, non-scrolling host for overlayScroll's floating thumb */
/* the LEFT PICKER RAIL — a vertical list of objects; click one to load it into the cards */
.pop-rail{ height:100%; overflow-y:auto; border-right:1px solid var(--line); background:var(--card); padding:8px 12px 8px 8px; display:flex; flex-direction:column; gap:4px; }
.rail-item{ display:flex; flex-direction:column; align-items:flex-start; gap:1px; width:100%; padding:7px 10px; border:1px solid transparent; border-radius:8px; background:transparent; color:var(--ink); font:inherit; text-align:left; cursor:pointer; }
.rail-item:hover{ background:color-mix(in srgb, var(--cool) 7%, transparent); }
.rail-item.on{ border-color:var(--cool); background:color-mix(in srgb, var(--cool) 12%, transparent); }   /* the SELECTED object = the kit's active language (--cool), like an active tab — the one open in the editor (--sel green stays a transient "inspect this" marker) */
.rail-label{ font-size:13px; }                                                    /* the object name (primary) */
.rail-sub{ font-size:10.5px; color:var(--muted); }                               /* a secondary id/stats line — add `mono` for data */
/* the RIGHT CONTENT + responsive CARD GRID */
.pop-content{ height:100%; overflow-y:auto; padding:14px 16px 40px; background:var(--base); }
.pop-cards{ display:grid; grid-template-columns:repeat(auto-fit, minmax(280px, 1fr)); gap:12px; max-width:1100px; }   /* cards in a row match the tallest (grid stretch) */
.pop-card{ border:1px solid var(--edge); border-radius:10px; background:var(--card); padding:14px; display:flex; flex-direction:column; gap:15px; align-items:stretch; }   /* a SECTION rendered as a card: --card surface (one step up from --base), accent title, the 15px .sec-inner rhythm */
.pop-card .card-title{ font-size:11px; font-weight:700; letter-spacing:.18em; text-transform:uppercase; color:var(--accent); margin:0; display:flex; align-items:center; gap:8px; min-height:21px; }   /* the kit section-heading voice (== .sec-head), un-collapsable. A ROW, so a `.head-help` [?] can right-justify on it — a title on its own is unchanged (one flex child lays out exactly as the text did), and the min-height reserves the [?]'s box so cards WITH and WITHOUT one keep the same title band and their first rows still line up across a grid row. No `.sec-headrow` needed here: a card title is a heading, not a button */
.card-stack{ display:flex; flex-direction:column; gap:12px; }                    /* two cards stacked in one grid cell keep the grid's gap */
/* a dense EDITOR ROW — the inline-left label placement (STYLE_GUIDE §1): fixed label + control + an
   optional trailing action (e.g. a .subcard-x remove). `--ed-label-w` tunes the label column. */
.ed-row{ display:grid; grid-template-columns:var(--ed-label-w, 96px) minmax(0, 1fr) auto; align-items:center; gap:8px; }
.ed-row + .ed-row{ margin-top:6px; }
/* A SUB-CARD sitting in a row flow. `.ed-row + .ed-row` set the tight 6px
   rhythm for consecutive rows and nothing said what happens when a .subcard
   joins them, so a group butted straight against the row or the group above it
   — two bordered blocks touching, reading as one badly drawn box. A sub-card
   is a GROUP, so it takes the card's own 15px rhythm on whichever side meets a
   sibling. (.sec-stack is the other half of this: use it on a wrapper whose
   children are all groups. It cannot serve here, because it would give the
   consecutive rows 15px too and lose their 6px.) */
.ed-row + .subcard,
.subcard + .ed-row,
.subcard + .subcard{ margin-top:15px; }
/* A READ-ONLY value in an .ed-row — a derived total, a size, a computed second
   reading. The kit's rule is that read-only facts are .ed-rows too (§ the row
   recipe), but a bare <span> is text-height while every control is 30px, so a
   card mixing the two loses the rhythm it was built on: the derived row sits
   tight between neighbours that breathe. .ed-val gives the value the SAME
   vertical box as .fld and .dd with none of the chrome — no fill, no border,
   nothing suggesting it can be typed into. */
.ed-val{
    display:flex;
    align-items:center;
    min-width:0;
    min-height:30px;
    font:12px ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;
    font-variant-numeric:tabular-nums;
    color:var(--ink);
  }
.ed-row>label{ color:var(--faint); font-size:10.5px; font-weight:400; letter-spacing:.16em; text-transform:uppercase; white-space:normal; overflow-wrap:anywhere; }   /* EXACTLY the .vis-label voice (10.5px · 400 · .16em · uppercase · faint) so every widget label matches.
                                                      A label WRAPS rather than ellipsising: the label column is a fixed
                                                      width, so clipping silently renamed any widget whose title outgrew
                                                      it — "Spawn grace lasts" reading as "Spawn grace la…" — and a
                                                      control whose name is a guess is worse than a taller row. Widen
                                                      --ed-label-w where two lines look wrong; never let the name go. */
