/* css/models.css -- 3D Models browser (promoted from the _object-render POC).
 * All colors come from the project theme (--kb-* tokens in css/themes.css);
 * Geist is inherited from the shell. The page lives at models/index.html and
 * consumes data/models/ (geometry GLB + perf/HQ textures + thumbnails). */

/* ---------------- embed mode ----------------
 * When loaded inside an iframe (e.g. the ODF browser's Renders tab) with
 * ?embed=1, js/models.js adds body.embed; hide the page navbar + the viewer's
 * back-to-directory button so only the viewer chrome shows. */
body.embed .navbar,
body.embed #back-btn { display: none; }

/* ---------------- page layout lock ----------------
 * Lock the page to the viewport so the 3D viewer's stage always fits. Without
 * this, the body is content-sized (Bootstrap's .h-100 resolves to `auto` here)
 * and the canvas's fixed pixel height lets the flex column grow PAST the
 * viewport -- forcing page scroll and pushing the FPS counter + Sun-light panel
 * below the fold until a refresh. Pinning the body to the dynamic viewport
 * height + `overflow:hidden` makes the flex chain shrink the stage on every
 * resize; the directory grid scrolls internally instead of the whole page.
 * (Scoped to the Models page -- models.css is only loaded here.) */
html.h-100, body.h-100 {
  height: 100vh !important;        /* fallback for browsers without dvh */
  height: 100dvh !important;
  min-height: 0 !important;
}
body { overflow: hidden; }

/* ---------------- directory ---------------- */

.models-page { min-height: 0; }

.directory { padding: 1.25rem; overflow-y: auto; min-height: 0; }

.directory-hint {
  max-width: 1320px;
  margin: 0 auto 0.75rem;
  color: var(--kb-text-secondary);
  font-size: 0.82rem;
}

/* toolbar */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  max-width: 1320px;
  margin: 0 auto 0.5rem;
}
.search {
  flex: 1 1 240px;
  min-width: 200px;
  font: inherit;
  font-size: 0.9rem;
  color: var(--kb-text-primary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 8px;
  padding: 0.45rem 0.7rem;
}
.search:focus { outline: none; border-color: var(--kb-primary); }
.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.filter-chip {
  font: inherit;
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--kb-text-secondary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 999px;
  padding: 0.22rem 0.65rem;
  cursor: pointer;
}
.filter-chip:hover { border-color: var(--kb-primary); color: var(--kb-text-primary); }
.filter-chip.on {
  background: var(--kb-primary);
  border-color: var(--kb-primary);
  color: var(--kb-primary-fg);
}
.toolbar-right { display: flex; align-items: center; gap: 1rem; margin-left: auto; }
.sortwrap, .qualtoggle {
  font-size: 0.8rem;
  color: var(--kb-text-secondary);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.sort {
  font: inherit;
  font-size: 0.82rem;
  color: var(--kb-text-primary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 8px;
  padding: 0.35rem 0.5rem;
}
.qualtoggle { cursor: pointer; }
.count-label {
  max-width: 1320px;
  margin: 0 auto 0.75rem;
  color: var(--kb-text-secondary);
  font-size: 0.78rem;
}
.empty { text-align: center; color: var(--kb-text-secondary); padding: 3rem 1rem; }

.model-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
  max-width: 1320px;
  margin: 0 auto;
}

.model-card {
  display: flex;
  flex-direction: column;
  background: var(--kb-bg-card);
  border: 1px solid var(--kb-border-default);
  border-radius: 12px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}
.model-card:hover {
  transform: translateY(-3px);
  border-color: var(--kb-primary);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
}
.thumb {
  width: 100%;
  height: 180px;
  display: block;
  object-fit: contain;
  background:
    radial-gradient(circle at 50% 42%, var(--kb-bg-muted) 0%, var(--kb-bg-body) 72%);
}
.thumb-missing::after {
  content: "no preview";
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--kb-text-secondary);
  font-size: 0.75rem;
}
.card-body { padding: 0.7rem 0.8rem 0.85rem; }
.card-title { font-weight: 650; font-size: 0.98rem; }
.card-sub { display: flex; flex-wrap: wrap; gap: 0.4rem; row-gap: 0.35rem; margin: 0.4rem 0 0.5rem; }
.chip {
  font-size: 0.68rem;
  font-weight: 600;
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  color: var(--kb-primary-fg);
  background: var(--c, var(--kb-primary));
  white-space: nowrap;
  flex: 0 0 auto;
}
.chip-ghost {
  background: transparent;
  color: var(--kb-text-secondary);
  border: 1px solid var(--kb-border-default);
}
.chip-parts {
  background: transparent;
  color: var(--kb-primary);
  border: 1px solid color-mix(in srgb, var(--kb-primary) 55%, transparent);
}
.chip-colors {
  background: transparent;
  color: var(--kb-accent, var(--kb-secondary, var(--kb-primary)));
  border: 1px solid color-mix(in srgb, var(--kb-accent, var(--kb-secondary, var(--kb-primary))) 55%, transparent);
}
.chip-skins {
  background: transparent;
  color: var(--kb-secondary, var(--kb-primary));
  border: 1px dashed color-mix(in srgb, var(--kb-secondary, var(--kb-primary)) 55%, transparent);
}
.card-stats { color: var(--kb-text-secondary); font-size: 0.78rem; }
.card-odf {
  color: var(--kb-text-muted);
  font-size: 0.72rem;
  font-family: var(--bs-font-monospace, ui-monospace, "Cascadia Code", Consolas, monospace);
  margin-top: 0.25rem;
}

/* ---------------- viewer ---------------- */

.viewer { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.viewer-bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--kb-border-default);
  background: var(--kb-bg-card);
}
.viewer-titlewrap { display: flex; flex-direction: column; line-height: 1.25; flex: 1; min-width: 0; }
.viewer-title { font-weight: 700; }
.viewer-meta { color: var(--kb-text-secondary); font-size: 0.78rem; }
.viewer-actions { display: flex; gap: 0.5rem; }

.btn {
  font: inherit;
  font-size: 0.82rem;
  color: var(--kb-text-primary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 8px;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}
.btn:hover { border-color: var(--kb-primary); }
.btn:disabled { opacity: 0.6; cursor: default; }
.btn.toggle.on {
  background: var(--kb-primary);
  color: var(--kb-primary-fg);
  border-color: var(--kb-primary);
  font-weight: 650;
}
.back { color: var(--kb-text-secondary); }

/* segmented Performance | HQ control */
.seg {
  display: inline-flex;
  border: 1px solid var(--kb-border-default);
  border-radius: 8px;
  overflow: hidden;
}
.seg-btn {
  font: inherit;
  font-size: 0.8rem;
  color: var(--kb-text-secondary);
  background: var(--kb-bg-input);
  border: none;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
  white-space: nowrap;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--kb-border-default); }
.seg-btn:hover { color: var(--kb-text-primary); }
.seg-btn.on { background: var(--kb-primary); color: var(--kb-primary-fg); font-weight: 650; }

.stage { flex: 1; min-height: 0; position: relative; overflow: hidden; }
.stage canvas { display: block; touch-action: none; }
/* Free-spin grab affordance (set on .stage while Free spin is active). */
.stage.grabbable canvas { cursor: grab; }
.stage.grabbable canvas:active { cursor: grabbing; }

/* Unified control dock: a single left-edge column holding every settings pane.
   The container is click-through (pointer-events:none); each pane re-enables
   pointer events. Stacks all open panes on desktop and scrolls if tall. */
.viewer-dock {
  position: absolute;
  left: 14px;
  top: 14px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 240px;
  max-height: calc(100% - 28px);
  overflow-y: auto;
  pointer-events: none;
}

/* Control pane card. Flows inside .viewer-dock (no self-anchoring). */
.light-panel {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px;
  width: 100%;
  box-sizing: border-box;
  background: color-mix(in srgb, var(--kb-bg-card) 92%, transparent);
  border: 1px solid var(--kb-border-default);
  border-radius: 10px;
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.light-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  color: var(--kb-text-secondary);
}
.light-head {
  color: var(--kb-text-primary);
  font-weight: 600;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--kb-border-default);
}
/* Sun on/off lives in the Light pane head row (mirrors .scene-check input). */
.light-toggle {
  margin-left: auto;
  width: 15px;
  height: 15px;
  accent-color: var(--kb-primary);
  cursor: pointer;
}
label.light-head { cursor: pointer; }
.light-toggle:disabled { cursor: not-allowed; opacity: 0.5; }
.light-label { flex: 0 0 60px; }
.light-row input[type="range"] {
  flex: 1;
  min-width: 0;
  accent-color: var(--kb-primary);
}
.light-val {
  flex: 0 0 38px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--kb-text-primary);
}
/* Disabled (sun off) -> dim the angle sliders. */
.light-panel.off .light-row:not(.light-head) { opacity: 0.45; }
/* Ship lights are independent of the sun -- don't dim their row with it. */
.light-panel.off .shiplights-row { opacity: 1; }
/* Mood presets retint the fill + backdrop even with the sun off -- keep live. */
.light-panel.off .light-preset-row { opacity: 1; }

/* Lighting mood preset segment (Noon | Sunset | Overcast). */
.light-preset-seg { display: flex; width: 100%; }
.light-preset-seg .seg-btn { flex: 1; }

/* ---- Weapon loadout overlay (top-right of the stage) -------------------- */
.loadout-panel {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 5;
  width: 230px;
  max-width: calc(100% - 28px);
  background: color-mix(in srgb, var(--kb-bg-card) 92%, transparent);
  border: 1px solid var(--kb-border-default);
  border-radius: 10px;
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  overflow: hidden;
}
.loadout-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 14px;
  background: none;
  border: none;
  font-size: 12px;
  font-weight: 600;
  color: var(--kb-text-primary);
  cursor: pointer;
  text-align: left;
}
.loadout-head .bi-crosshair { color: var(--kb-primary); }
.loadout-head #loadout-chevron {
  margin-left: auto;
  color: var(--kb-text-secondary);
}
.loadout-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 14px 12px;
}
.loadout-variant {
  width: 100%;
  padding: 4px 8px;
  font-size: 11.5px;
  font-family: var(--kb-font-mono, monospace);
  color: var(--kb-text-primary);
  background: var(--kb-bg-elevated, var(--kb-bg-card));
  border: 1px solid var(--kb-border-default);
  border-radius: 6px;
}
.loadout-rows {
  display: flex;
  flex-direction: column;
}
.loadout-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 5px 6px;
  margin: 0 -6px;
  border-radius: 6px;
  font-size: 12px;
}
.loadout-row:hover { background: color-mix(in srgb, var(--kb-primary) 12%, transparent); }
.loadout-slot {
  flex: 0 0 64px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--kb-text-secondary);
}
.loadout-assault {
  font-size: 9px;
  color: var(--kb-warning, var(--kb-text-secondary));
}
.loadout-value {
  flex: 1;
  min-width: 0;
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 7px;
}
.loadout-weapon {
  color: var(--kb-text-primary);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px dotted color-mix(in srgb, var(--kb-primary) 55%, transparent);
}
.loadout-weapon:hover { color: var(--kb-primary); }
.loadout-odf {
  font-family: var(--kb-font-mono, monospace);
  font-size: 10.5px;
  color: var(--kb-text-secondary);
  word-break: break-all;
}
.loadout-count {
  font-size: 11px;
  font-weight: 700;
  color: var(--kb-primary);
}
.loadout-empty { color: var(--kb-text-secondary); opacity: 0.7; }

/* Animation panel: flows in the dock. */
.anim-clips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-height: 168px;
  overflow-y: auto;
}
.anim-chip {
  font: inherit;
  font-size: 11px;
  color: var(--kb-text-secondary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 6px;
  padding: 3px 8px;
  cursor: pointer;
  white-space: nowrap;
}
.anim-chip:hover { color: var(--kb-text-primary); border-color: var(--kb-primary); }
.anim-chip.on {
  background: var(--kb-primary);
  color: var(--kb-primary-fg);
  border-color: var(--kb-primary);
  font-weight: 650;
}
.anim-transport { display: flex; flex-wrap: wrap; gap: 6px; }
.btn.anim-mini { padding: 0.3rem 0.55rem; font-size: 0.78rem; }

/* Parts (articulation) panel: flows in the dock. */
.parts-section { display: flex; flex-direction: column; gap: 8px; }
.parts-section + .parts-section {
  border-top: 1px solid var(--kb-border-default);
  padding-top: 10px;
}
.parts-row { display: flex; flex-wrap: wrap; gap: 6px; }
.btn.parts-fire {
  background: var(--kb-primary);
  color: var(--kb-primary-fg);
  border-color: var(--kb-primary);
  font-weight: 650;
}
.btn.parts-fire:hover { filter: brightness(1.08); }
.parts-subhead {
  color: var(--kb-text-secondary);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.parts-visibility-row { cursor: pointer; }
/* Small inline "reset to 0" affordance at the end of a slider row (Drive). */
.parts-mini-reset {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--kb-border-default);
  border-radius: 6px;
  background: var(--kb-bg-input);
  color: var(--kb-text-secondary);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
}
.parts-mini-reset:hover {
  color: var(--kb-primary);
  border-color: var(--kb-primary);
}
/* Drive: label on the left, value + reset hugging the right, slider full-width
   on its own line below (so it isn't squeezed by the controls). */
.parts-drive-head .light-val { margin-left: auto; }
.parts-drive-slider {
  width: 100%;
  min-width: 0;
  accent-color: var(--kb-primary);
}

/* Team-color panel: flows in the dock. */
.color-swatches {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.color-swatch {
  appearance: none;
  border: 2px solid var(--kb-border-default);
  border-radius: 8px;
  height: 30px;
  padding: 0;
  cursor: pointer;
  background: var(--sw);
  transition: transform 0.08s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}
.color-swatch:hover { transform: translateY(-1px); border-color: var(--kb-text-secondary); }
.color-swatch.on {
  border-color: var(--kb-text-primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--sw) 60%, transparent);
}
/* Sub-section divider inside the colors panel (e.g. "Bold colors"). */
.color-section-head {
  color: var(--kb-text-secondary);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding-top: 8px;
  border-top: 1px solid var(--kb-border-default);
}
.color-custom-row { gap: 10px; }
.color-custom {
  flex: 1;
  min-width: 0;
  height: 30px;
  padding: 0;
  border: 1px solid var(--kb-border-default);
  border-radius: 8px;
  background: var(--kb-bg-input);
  cursor: pointer;
}
.colors-off.on {
  background: var(--kb-primary);
  color: var(--kb-primary-fg);
  border-color: var(--kb-primary);
  font-weight: 650;
}
.colors-external-row {
  padding-top: 8px;
  border-top: 1px solid var(--kb-border-default);
  justify-content: center;
}
.colors-external-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--kb-text-secondary);
  text-decoration: none;
}
.colors-external-link:hover {
  color: var(--kb-primary);
  text-decoration: underline;
}
.colors-external-link i { font-size: 10px; }

/* Textures panel (mod texture sets): flows in the dock. One row per set --
 * a full-width pick button (name + coverage) plus an optional Workshop credit
 * link-out beside it. */
.texset-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.texset-row {
  display: flex;
  align-items: stretch;
  gap: 6px;
}
.texset-pick {
  appearance: none;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  font-size: 12px;
  color: var(--kb-text-primary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.texset-pick:hover { border-color: var(--kb-text-secondary); }
.texset-pick.on {
  background: var(--kb-primary);
  color: var(--kb-primary-fg);
  border-color: var(--kb-primary);
  font-weight: 650;
}
.texset-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.texset-cov {
  flex: none;
  font-size: 10px;
  opacity: 0.75;
}
.texset-credit {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  border: 1px solid var(--kb-border-default);
  border-radius: 8px;
  color: var(--kb-text-secondary);
  text-decoration: none;
  font-size: 13px;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.texset-credit:hover {
  color: var(--kb-primary);
  border-color: var(--kb-primary);
}

/* Scene panel: flows in the dock. */
/* Background: label stacked ABOVE a full-width Dark/Light segmented control. */
.scene-bg-block { display: flex; flex-direction: column; gap: 6px; }
.scene-bg-label {
  color: var(--kb-text-primary);
  font-weight: 600;
  font-size: 12px;
}
.scene-bg-seg { display: flex; width: 100%; }
.scene-bg-seg .seg-btn { flex: 1; }
.scene-check { cursor: pointer; }
.scene-check input[type="checkbox"] {
  margin-left: auto;
  width: 15px;
  height: 15px;
  accent-color: var(--kb-primary);
  cursor: pointer;
}
.scene-ultra-head {
  color: var(--kb-text-secondary);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding-top: 8px;
  border-top: 1px solid var(--kb-border-default);
}
.scene-ultra { display: flex; flex-wrap: wrap; gap: 6px; }
.btn.scene-ultra-btn { padding: 0.3rem 0.55rem; font-size: 0.78rem; }
.btn.scene-ultra-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* Tiny FPS readout, bottom-right of the viewport. */
/* Mode-aware controls legend, stacked just above the fps counter (bottom-right)
   so it sits with the fps readout and clears the bottom-left light panel. */
.controls-hint {
  position: absolute;
  right: 10px;
  bottom: 34px;
  z-index: 6;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  max-width: 70%;
  pointer-events: none;   /* never intercept canvas drags */
}
.ctl {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--kb-bg-card) 80%, transparent);
  border: 1px solid var(--kb-border-default);
  color: var(--kb-text-secondary);
  font-size: 11px;
  white-space: nowrap;
}
.ctl-key {
  font-family: var(--kb-font-mono, ui-monospace, monospace);
  font-size: 10px;
  color: var(--kb-text-primary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 4px;
  padding: 0 5px;
}
.ctl-note { font-style: italic; }

/* WASD Drive Mode HUD: a single key-legend chip, top-center of the stage. */
.drive-hud {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 12px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--kb-bg-card) 85%, transparent);
  border: 1px solid var(--kb-border-default);
  color: var(--kb-text-secondary);
  font-size: 12px;
  white-space: nowrap;
  pointer-events: none;   /* never intercept canvas interaction */
}
.drive-hud-key {
  font-family: var(--kb-font-mono, ui-monospace, monospace);
  font-size: 11px;
  color: var(--kb-text-primary);
  background: var(--kb-bg-input);
  border: 1px solid var(--kb-border-default);
  border-radius: 4px;
  padding: 0 5px;
}
.drive-hud-sep { opacity: 0.5; }

.fps-counter {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 6;
  padding: 2px 7px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--kb-bg-card) 80%, transparent);
  border: 1px solid var(--kb-border-default);
  color: var(--kb-text-secondary);
  font-family: var(--kb-font-mono, ui-monospace, monospace);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  user-select: none;
}

/* Loading overlay (shown e.g. while the Ambient occlusion passes compile). */
.stage-loading {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: color-mix(in srgb, var(--kb-bg-base) 55%, transparent);
  backdrop-filter: blur(2px);
  color: var(--kb-text-primary);
  font-size: 13px;
  font-weight: 600;
  pointer-events: none;
}
.stage-loading .spinner-border { color: var(--kb-primary); }
/* Determinate progress track (shown for HQ texture loads). */
.stage-progress {
  width: 220px;
  max-width: 60%;
  height: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--kb-text-primary) 18%, transparent);
  overflow: hidden;
}
.stage-progress-fill {
  width: 0%;
  height: 100%;
  background: var(--kb-primary);
  border-radius: 999px;
  transition: width 0.15s ease;
}

.error {
  padding: 1.5rem;
  color: var(--kb-danger);
  font-size: 0.9rem;
}

/* ---------------- mobile ---------------- */

@media (max-width: 640px) {
  .directory { padding: 0.85rem; }
  /* Pointer-control legend is desktop-only (no mouse on touch). */
  .controls-hint { display: none; }

  /* Toolbar stacks; search takes the full width. */
  .toolbar { gap: 0.5rem; }
  .search { flex: 1 1 100%; }
  .toolbar-right { width: 100%; justify-content: space-between; margin-left: 0; }
  /* Chip rows scroll horizontally instead of wrapping into tall blocks. */
  .chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    width: 100%;
    padding-bottom: 2px;
  }
  .filter-chip { flex: 0 0 auto; padding: 0.4rem 0.7rem; }

  /* Stack the viewer bar: title row on top, actions row below. */
  .viewer-bar {
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
  }
  /* Control bar becomes a single horizontally-scrollable row (all buttons
     stay reachable by swipe -- chosen mobile pattern). */
  .viewer-actions {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
  }
  .viewer-actions > * { flex: 0 0 auto; }
  /* Larger tap targets. */
  .btn, .seg-btn { padding: 0.55rem 0.8rem; }

  /* Dock becomes a full-width top sheet; only one pane is open at a time
     (enforced in JS), so panes never stack/overlap on mobile. */
  .viewer-dock {
    left: 0;
    right: 0;
    top: 0;
    width: auto;
    max-height: 70vh;
    gap: 0;
  }
  .light-panel {
    width: 100%;
    border-radius: 0 0 12px 12px;
  }
  /* Loadout overlay shrinks to a corner chip under the dock sheet; the body
     starts collapsed on mobile (setupLoadoutUI) and expands over the canvas. */
  .loadout-panel {
    top: auto;
    bottom: 14px;
    right: 10px;
    width: 210px;
    z-index: 6;
  }
}

@media (max-width: 480px) {
  .model-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .thumb { height: 140px; }
}
