/* Gentree shared chrome:
   - .gtopbar-title            — Gentree wordmark
   - .g-mode-chip              — live | static | site indicator
   - .g-xlinks                 — cross-link nav (sibling pages)
   - .ts-* (TreeSelect)        — flyout picker (was inline in 5 files)
   - #group-chip.g-group-chip — output-group chip (mount()'s 'group' picker)
   - #gt-grid.gt-grid-btn, .gt-grid-icon — visual-picker open button
   - #grid-modal, #grid-footer — visual run picker grid + multi-select Apply bar */

/* Hardcoded sans-serif stack so chrome stays consistent regardless of any
   page-level --font override (some viewers redefine --font to a mono stack
   for their own UI). Also covers each page's own #toolbar / #topbar so the
   whole title bar reads as one unit. The body-prefixed selectors bump
   specificity above the page's #toolbar h1 / .tb-btn rules. */
body .ts-trigger, body .ts-panel, body .ts-panel *,
body .g-mode-chip, body .g-xlinks-select,
body #grid-modal, body #grid-modal *,
body #toolbar, body #toolbar *,
body #topbar,  body #topbar *,
body #topbar-chrome, body #topbar-chrome * {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', system-ui, sans-serif;
}
.gtopbar-title {
  font-weight: 700; letter-spacing: .04em; color: var(--text);
}

/* Mode chip ----------------------------------------------------------------*/
.g-mode-chip {
  font-family: var(--mono); font-size: 10px;
  padding: 2px 7px; border-radius: 10px;
  border: 1px solid; opacity: 0.85; letter-spacing: .04em;
}
.g-mode-chip.live   { color: var(--green); border-color: var(--green);  background: rgba(74,222,128,.10); }
.g-mode-chip.static { color: var(--amber); border-color: #92400e;       background: rgba(251,191,36,.10); }
.g-mode-chip.site   { color: var(--blue);  border-color: #1e40af;       background: rgba(96,165,250,.10); }

/* Cross-link nav -----------------------------------------------------------*/
.g-xlinks {
  display: flex; gap: 8px; align-items: center;
  font-size: 11px;
}
.g-xlinks { display: inline-flex; align-items: center; }
.g-xlinks-select {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border);
  padding: 3px 24px 3px 8px;
  border-radius: 4px;
  font: 11px var(--font); cursor: pointer;
  min-width: 110px;
  appearance: none; -webkit-appearance: none;
  background-image:
    linear-gradient(45deg,  transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 12px) 50%, calc(100% - 7px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.g-xlinks-select:hover  { border-color: var(--accent); color: var(--accent); }
.g-xlinks-select:focus  { outline: none; border-color: var(--accent); }
.g-xlinks-select option { background: var(--surface); color: var(--text); }

/* Number-input spinner arrows — invert + dim so they read as dark-theme controls.
   WebKit lets us tint the native chrome via filter; Firefox renders no spinners
   when we set appearance:textfield, but we DO want them visible so we leave
   Firefox at its default and accept a slight inconsistency. Most users are
   on Chromium/Safari. */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
  filter: invert(1);
  opacity: .45;
  cursor: pointer;
}
input[type=number]::-webkit-inner-spin-button:hover,
input[type=number]::-webkit-outer-spin-button:hover { opacity: 1; }

/* TreeSelect flyout (was inline in 5 files; kept name-compatible) ----------*/
.ts-trigger {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 26px 4px 8px; border-radius: 4px;
  font-size: 11px; cursor: pointer;
  min-width: 180px; text-align: left; position: relative;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: inline-block; box-sizing: border-box; vertical-align: middle;
}
.ts-trigger::after {
  content: "▾"; position: absolute; right: 7px; top: 50%;
  transform: translateY(-50%); font-size: 10px;
  color: var(--text); pointer-events: none; opacity: .6;
}
.ts-trigger:hover { border-color: var(--accent); }

/* .gt-btn and .ts-trigger are both single-class selectors — (0,1,0) each —
   so on an element carrying both (`class="gt-btn ts-trigger"`, a page's own
   picker-trigger button, e.g. controller's #config-select-btn, orch_ui's
   #sel-config-btn, meta-editor's #btn-config-trigger, audio's #runPickerBtn)
   whichever is declared later in the cascade wins outright, property by
   property. `.gt-btn` is declared later in this file, so it washes the
   trigger's brighter var(--text)/var(--border) look down to .gt-btn's dimmer
   var(--muted)/var(--border2), and its symmetric `padding: 4px 10px` erases
   the asymmetric right padding `.ts-trigger::after`'s caret needs — the
   caret and the label text overlap. Four pages hit this and patched it four
   different ad-hoc ways (a compound class selector, two separate id rules,
   one unpatched). A two-class selector is (0,2,0), which unconditionally
   outranks either single-class rule regardless of source order or file —
   fixing it here removes the need for any page-local workaround. */
.gt-btn.ts-trigger {
  color: var(--text);
  border-color: var(--border);
  padding: 4px 26px 4px 8px;
}

/* Output-group chip — sits immediately left of the run picker and reads as
   the same control family. Styled by ID: several pages carry their own
   `#toolbar button` rules, which outrank a bare class selector and would
   otherwise repaint the chip. Every colour has a literal fallback so the chip
   can never render as an unstyled white button if a token is missing, and no
   color-mix() is used — one undefined custom property inside it invalidates
   the whole declaration, which is exactly how a background goes transparent.
   Narrower than the run picker so the pair reads "group > run" rather than
   two equal dropdowns. */
#group-chip.g-group-chip {
  min-width: 96px;
  max-width: 190px;
  padding: 4px 26px 4px 8px;
  border-radius: 4px;
  font: inherit;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
  position: relative;
  display: inline-block;
  box-sizing: border-box;
  vertical-align: middle;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--accent, #7dd3fc);
  background: var(--surface, #0e0e0e);
  border: 1px solid var(--accent2, #0e7490);
}
#group-chip.g-group-chip:hover { border-color: var(--accent, #7dd3fc); }
/* The dropdown caret, in case TreeSelect has not wired the element yet. */
#group-chip.g-group-chip::after {
  content: "\25BE"; position: absolute; right: 7px; top: 50%;
  transform: translateY(-50%); font-size: 10px;
  color: var(--accent, #7dd3fc); pointer-events: none; opacity: .7;
}

/* Grid (visual picker) button — mount()'s equivalent of #group-chip above.
   A `.gtopbar button, .gtopbar .tb-btn` rule used to sit here to cover this
   (and every other button mount() renders), on the theory that mount()'s
   chrome block carried a `.gtopbar` class — it never did (confirmed by
   grepping every *.html and assets/*.js for `class="gtopbar`), so that rule
   never matched anything and was deleted in Task 12. mesh3d and p5viz only
   looked correctly styled before that because of an unrelated accident:
   both happen to define their own page-level `.tb-btn` class for
   unrelated, page-owned buttons (Spin/Fit/etc.), and mount()'s grid button
   also carries the class `tb-btn`, so it rode along for free on those two
   pages. splatviz has no such page-level `.tb-btn`, so it needed this
   dedicated, ID-scoped rule from the start (this task's own, correct,
   scoping fix) to avoid an unstyled browser-default button. Scoped by ID,
   same precedent as #group-chip.g-group-chip above, so it wins regardless
   of what a page happens to define. */
#gt-grid.gt-grid-btn {
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--border2);
  padding: 4px 10px; border-radius: 4px;
  font: 11px var(--font); cursor: pointer;
}
#gt-grid.gt-grid-btn:hover {
  background: #1a1a1a; color: var(--text); border-color: #555;
}

/* Loading / empty states. Every picker is fed by a fetch; without an explicit
   state an in-flight list is indistinguishable from an empty one. */
.ts-row.ts-empty {
  color: var(--muted, #888); font-style: italic;
  pointer-events: none;
}
.grid-loading {
  grid-column: 1 / -1;
  padding: 28px 12px; text-align: center;
  color: var(--muted, #888); font-style: italic; font-size: 12px;
}

.ts-panel {
  position: fixed; background: var(--surface);
  border: 1px solid var(--border); border-radius: 4px;
  min-width: 240px; max-height: 480px;
  z-index: 9999; box-shadow: 0 4px 20px rgba(0,0,0,.6);
  padding: 4px 0;
  display: flex; flex-direction: column;
  overflow: hidden;
}
.ts-filter {
  flex-shrink: 0; padding: 4px 8px 6px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 4px;
}
.ts-filter input {
  flex: 1; min-width: 0; box-sizing: border-box;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 3px;
  padding: 4px 8px;
  font: 11px var(--font);
  outline: none;
}
.ts-filter input:focus { border-color: var(--accent); }
.ts-sort-btn, .ts-content-btn, .ts-sort-sel {
  flex-shrink: 0;
  background: var(--bg); color: var(--muted);
  border: 1px solid var(--border); border-radius: 3px;
  padding: 3px 7px; cursor: pointer;
  font: 11px var(--font); line-height: 1;
}
.ts-sort-btn:hover, .ts-content-btn:hover, .ts-sort-sel:hover {
  color: var(--accent); border-color: var(--accent);
}
.ts-sort-sel { padding-right: 18px; }
.ts-content-btn.on {
  color: var(--accent); border-color: var(--accent);
  background: rgba(125, 211, 252, .08);
}
.ts-list { flex: 1 1 auto; overflow-y: auto; padding: 4px 0; }
.ts-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 5px 10px; cursor: pointer;
  color: var(--text); font-size: 11px;
  white-space: nowrap; user-select: none; line-height: 1.4;
}
.ts-row:hover, .ts-row.ts-active { background: rgba(128,128,128,.15); }
.ts-row.ts-selected { color: var(--accent); }
.ts-arrow { color: var(--muted); font-size: 9px; flex-shrink: 0; opacity: .6; }
.ts-dir-name { color: var(--text); opacity: .8; }
.ts-sep { height: 1px; background: var(--border); margin: 4px 0; }
.ts-star { display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:6px; background:transparent; }
.ts-label { flex: 1; }
.ts-count { color: var(--muted); font-size: 10px; opacity: .8; }

/* Grid (visual) picker -----------------------------------------------------*/
#grid-modal {
  display: none; position: fixed; inset: 0; z-index: 10000;
  background: rgba(0,0,0,.85); flex-direction: column;
}
#grid-modal[data-open="1"] { display: flex; }
#grid-modal header {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px; background: var(--surface);
  border-bottom: 1px solid var(--border);
  font-size: 11px; color: var(--text);
}
#grid-modal #grid-search {
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  padding: 4px 8px; font-size: 11px; min-width: 240px;
}
/* The rest of the header's controls, found by the same "audit every element
   mount() renders" pass that found #gt-grid's gap: _gridModalEl() creates
   these unconditionally on every page, but nothing in this file styled them
   before now — #grid-close rendered as a genuinely naked browser-default
   button (grey background, black outset border) on ALL THREE migrated
   pages, not just splatviz, since the modal is appended straight to
   <body> and so never sat inside any page's #toolbar/.tb-btn scope to ride
   along on by accident the way #gt-grid did on mesh3d/p5viz. Values below
   are carried over from splatviz's own deleted `#grid-modal header button`
   rule (the modal's markup and behaviour predate mount() — the CSS just
   never migrated with it). */
#grid-modal .lbl {
  font-size: 10px; color: var(--dim);
  text-transform: uppercase; letter-spacing: .05em;
}
#grid-modal input[type="range"] { width: 120px; }
#grid-close {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  padding: 4px 10px; cursor: pointer; font: 12px var(--mono);
}
#grid-close:hover { border-color: var(--accent); }
/* Square cells driven by explicit grid-auto-rows + fixed-size columns
   (both keyed off --cell-size). aspect-ratio + minmax(1fr) and
   padding-bottom:100% pseudo-elements both produced cut-off rows because
   the grid's row track sized to in-flow content (image height leaks into
   auto row sizing → shorter cells in upper rows, only the last row
   settles to a square). Absolute-positioning the image keeps the row
   track bound to --cell-size. */
#grid-body {
  flex: 1 1 auto;
  /* min-height: 0 lets this flex item shrink below content size so its own
     `overflow-y: auto` can engage (default `min-height: auto` would make the
     modal grow and the page scrolls behind it). */
  min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  padding: 12px;
  display: grid;
  grid-template-columns: repeat(auto-fill, var(--cell-size, 160px));
  grid-auto-rows: var(--cell-size, 160px);
  justify-content: center;
  align-content: start;
  gap: 10px;
}
.grid-cell {
  position: relative;
  background: #050505; border: 1px solid var(--border);
  border-radius: 4px; overflow: hidden; cursor: pointer;
}
.grid-cell:hover { border-color: var(--accent); }
.grid-cell img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block; background: #0e0e0e;
}
.grid-cell .cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 4px 6px;
  background: linear-gradient(transparent, rgba(0,0,0,.85) 60%);
  font: 10px var(--font); color: #d8d8d8;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.grid-cell .cap .id    { color: #888; font-size: 9px; }
.grid-cell .cap .count { color: var(--accent); font-size: 9px; margin-left: 6px; }

/* Multi-select grid: selection is not navigation, so the modal stays open
   and the caller commits with Apply. */
#grid-body .grid-cell.selected { outline: 2px solid var(--accent); }
#grid-footer {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; border-top: 1px solid var(--border);
}
#grid-selected { flex: 1; color: var(--muted); font-size: 11px; }
#grid-apply {
  background: var(--surface); color: var(--accent);
  border: 1px solid var(--accent2); border-radius: 4px;
  padding: 4px 12px; font-size: 11px; cursor: pointer;
}
#grid-apply[disabled] { opacity: .45; cursor: default; }

/* Grid button icon — was inline markup repeated in three pages. */
.gt-grid-icon {
  display: inline-grid; grid-template: 1fr 1fr / 1fr 1fr; gap: 2px;
  width: 12px; height: 12px; vertical-align: middle;
}

/* ─── Stage 2: the toolbar shell and control primitives ────────────────────
   .gt-bar is a CLASS, not an id: four pages (controller, orch_ui,
   meta-editor, audio_controller) stack two bars and a duplicate id
   is invalid. Density sets a min-height FLOOR, never a height — splatviz's
   colour-swatch row legitimately wraps to a second line, and a fixed height
   would clip it. Sizing matches #gt-grid.gt-grid-btn above so page controls
   move to meet the chrome rather than the reverse. */
.gt-bar {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px; box-sizing: border-box;
  background: var(--surface); border-bottom: 1px solid var(--border);
  font-family: var(--font);
  /* Compact's own values, repeated below as the explicit attribute rule.
     Every bar in the codebase carries a data-gt-density attribute today, so
     this default is currently unreachable in practice — but a bar that
     omits the attribute should still get a sane floor rather than
     collapsing to zero padding and no min-height. */
  min-height: 40px; padding: 4px 14px; font-size: 11px;
}
.gt-bar[data-gt-density="compact"] { min-height: 40px; padding: 4px 14px; font-size: 11px; }
.gt-bar[data-gt-density="roomy"]   { min-height: 48px; padding: 6px 16px; font-size: 12px; }

.gt-btn {
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--border2);
  padding: 4px 10px; border-radius: 4px;
  font: 11px var(--font); cursor: pointer;
  white-space: nowrap;
  /* .gt-btn is applied to <a> as well as <button> (gallery's/mesh3d's/
     splatviz's "?" help link, p5viz's "splat 3D" link) — an anchor's UA
     stylesheet underlines it by default and .gt-btn must reset that itself
     rather than relying on a page-local `#toolbar a { text-decoration: none }`,
     which is exactly what gallery never had and p5viz never had either. */
  text-decoration: none;
}
.gt-btn:hover:not(:disabled) {
  background: var(--surface2); color: var(--text); border-color: var(--dim);
}
.gt-btn:disabled { opacity: .45; cursor: default; }
.gt-btn.is-active { color: var(--accent); border-color: var(--accent); }
/* Beats .gt-btn:hover:not(:disabled) (0,3,0) with an explicit (0,4,0) rule
   rather than relying on source order, which a later edit could reshuffle.
   Keeps the accent identity (colour + border) on hover — only the
   background shifts — so an active button still reads as active while
   still feeling hoverable. */
.gt-btn.is-active:hover:not(:disabled) {
  background: var(--surface2); color: var(--accent); border-color: var(--accent);
}
.gt-btn--primary {
  color: var(--text); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent, #22d3ee) 18%, var(--surface, #101017));
}
.gt-btn--primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent, #22d3ee) 30%, var(--surface, #101017));
}
.gt-btn--danger { color: var(--red); border-color: var(--red); }

.gt-select {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border2);
  padding: 4px 22px 4px 8px; border-radius: 4px;
  font: 11px var(--font); cursor: pointer;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 10px center, right 6px center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
.gt-select:hover { border-color: var(--accent); }
.gt-select:focus { outline: none; border-color: var(--accent); }
.gt-select option { background: var(--surface); color: var(--text); }

.gt-sep {
  width: 1px; align-self: stretch; margin: 0 2px;
  background: var(--border2); flex: 0 0 auto;
}

.gt-label {
  color: var(--muted); font: 11px var(--font);
  display: inline-flex; align-items: center; gap: 5px;
  white-space: nowrap;
}

.gt-range { accent-color: var(--accent); height: 18px; cursor: pointer; }
.gt-bar input[type="checkbox"] { accent-color: var(--accent); }
.gt-readout {
  color: var(--text); font: 11px var(--mono);
  min-width: 2.5ch; text-align: right;
}

/* State is an attribute, not four bespoke class names (the pages used
   #status-pill, #file-status, .nodes-hint and plain #status for one idea). */
.gt-status { font: 11px var(--font); color: var(--muted); white-space: nowrap; }
.gt-status[data-gt-state="ok"]   { color: var(--green); }
.gt-status[data-gt-state="warn"] { color: var(--amber); }
.gt-status[data-gt-state="err"]  { color: var(--red); }
.gt-status[data-gt-state="idle"] { color: var(--dim); }

/* Number/text inputs inside a bar. Pages declared these ~6 separate times. */
.gt-bar input[type="number"], .gt-bar input[type="text"], .gt-bar input[type="search"] {
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border2);
  padding: 4px 6px; border-radius: 4px;
  font: 11px var(--font);
}
.gt-bar input:focus { outline: none; border-color: var(--accent); }

/* Pushes everything after it to the right edge. */
.gt-spacer { flex: 1 1 auto; }
.gt-grid-icon > span { background: currentColor; border-radius: 1px; }
