/* ---------------------------------------------------------------------------
   "Latent Instrument" — see the direction contract in index.html.
   Chrome is strictly achromatic; the only chroma on the surface comes from the
   six language-cluster colors, set at runtime via the --lang custom property.
   --------------------------------------------------------------------------- */
:root {
  --ground: #0a0b0d;          /* near-black observatory ground (neutral, not blue-black) */
  --ground-hi: #14161b;       /* radial lift at the top-right */
  --ink: #eceef1;             /* primary text */
  --ink-dim: #8a9099;         /* secondary text / labels */
  --ink-faint: #6f7783;       /* tertiary / decorative only (dot) — not for words */
  --line: rgba(255, 255, 255, 0.09);   /* hairline borders */
  --line-strong: rgba(255, 255, 255, 0.16);
  --fill-1: rgba(255, 255, 255, 0.05); /* subtle control surface */
  --fill-2: rgba(255, 255, 255, 0.10); /* active / hover control surface */
  --scrim: rgba(10, 11, 13, 0.66);      /* console legibility scrim (strong stop) */
  --scrim-2: rgba(10, 11, 13, 0.42);    /* console scrim (soft stop) */
  --track: rgba(255, 255, 255, 0.12);   /* fader track */
  --lang: #eceef1;            /* current nearest-language color (JS overrides) */

  --buf-ok:   #4ade80;        /* buffer traffic light: healthy (600–1000 ms) */
  --buf-warn: #fbbf24;        /* buffer traffic light: caution (300–600 / 1000–1300 ms) */
  --buf-bad:  #f87171;        /* buffer traffic light: danger (< 300 / > 1300 ms) */

  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Menlo", "Consolas", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Light mode — an instrument on a bright bench. Language colors are unchanged (data). */
:root[data-theme="light"] {
  --ground: #eceef2;
  --ground-hi: #f8f9fb;
  --ink: #14171c;
  --ink-dim: #525a66;
  --ink-faint: #8b93a0;
  --line: rgba(0, 0, 0, 0.11);
  --line-strong: rgba(0, 0, 0, 0.20);
  --fill-1: rgba(0, 0, 0, 0.04);
  --fill-2: rgba(0, 0, 0, 0.08);
  --scrim: rgba(236, 238, 242, 0.78);
  --scrim-2: rgba(236, 238, 242, 0.5);
  --track: rgba(0, 0, 0, 0.14);

  --buf-ok:   #16a34a;        /* darker green for light background */
  --buf-warn: #d97706;        /* darker amber for light background */
  --buf-bad:  #dc2626;        /* darker red for light background */
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
}

body {
  font-family: var(--mono);
  color: var(--ink);
  background:
    radial-gradient(140vmax 100vmax at 70% -25%, var(--ground-hi) 0%, var(--ground) 62%);
  -webkit-font-smoothing: antialiased;
  user-select: none;
  transition: background 0.3s ease;
}

/* --- the instrument surface ------------------------------------------------ */
#map {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: grab;
  touch-action: none;
}
#map.dragging { cursor: grabbing; }
#map:focus-visible { outline: none; box-shadow: inset 0 0 0 1.5px var(--line-strong); }

/* --- loading overlay ------------------------------------------------------- */
.overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  color: var(--ink-dim);
  font-size: 13px;
  letter-spacing: 0.04em;
  background: var(--ground);
  z-index: 30;
  transition: opacity 0.5s ease;
}
.overlay.hidden { opacity: 0; pointer-events: none; }
.overlay-mark {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--ink-dim);
  animation: breathe 1.4s ease-in-out infinite;
}
@keyframes breathe {
  0%, 100% { opacity: 0.25; transform: scale(0.7); }
  50%      { opacity: 1;    transform: scale(1); }
}

/* --- identity (heads the rail) --------------------------------------------- */
.rail-brand {
  flex: 0 0 auto;
  padding: clamp(16px, 2.2vh, 24px) var(--rail-pad) 16px;
}
.wordmark {
  font-family: var(--sans);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.34em;
  color: var(--ink);
}
.tagline {
  margin-top: 3px;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  color: var(--ink-dim);
  text-transform: lowercase;
}
.status-line {
  margin-top: 12px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  letter-spacing: 0.03em;
  color: var(--ink-dim);
}
.dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ink-faint);
  flex: 0 0 auto;
  transition: background 0.3s, box-shadow 0.3s;
}
.dot.ok  { background: #46d39a; box-shadow: 0 0 10px rgba(70, 211, 154, 0.55); }
.dot.bad { background: #e5555b; box-shadow: 0 0 10px rgba(229, 85, 91, 0.5); }

/* --- top-right theme toggle + legend --------------------------------------- */
.theme-toggle {
  position: fixed;
  top: clamp(16px, 2.4vh, 26px);
  right: clamp(16px, 2.4vw, 30px);
  z-index: 21;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; line-height: 1;
  color: var(--ink-dim);
  background: var(--fill-1);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.theme-toggle:hover { color: var(--ink); background: var(--fill-2); }
.theme-toggle:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--line-strong); }

.legend {
  position: fixed;
  top: calc(clamp(16px, 2.4vh, 26px) + 42px);
  right: clamp(16px, 2.4vw, 30px);
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 7px;
  align-items: flex-end;
  pointer-events: none;
}
.legend .item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--ink-dim);
}
.legend .swatch {
  width: 8px; height: 8px; border-radius: 2px;
  flex: 0 0 auto;
}

/* --- the rail --------------------------------------------------------------
   A channel strip, not a panel: scrim + one hairline edge, no card, no radius, no
   chroma. The map bleeds underneath it. Width is set by the widest head it must
   hold ("VOICE HOLD 100% · 1.92s" ~ 162px) plus padding — not chosen for looks.
   Three bands: brand (fixed) · groups (scrolls if the viewport is short) · foot
   (fixed), so PLAY can never scroll out of reach. */
.rail {
  --rail-pad: 18px;
  position: fixed;
  top: 0; bottom: 0; left: 0;
  z-index: 20;
  width: clamp(228px, 17vw, 264px);
  display: flex;
  flex-direction: column;
  background: linear-gradient(to right, var(--scrim) 0%, var(--scrim) 72%, var(--scrim-2) 100%);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
  border-right: 1px solid var(--line);
}
/* Only this band scrolls, and only when it has to. `min-height: 0` is what lets a
   flex child actually shrink enough to become scrollable. */
.rail-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 var(--rail-pad) 8px;
}
.rail-scroll::-webkit-scrollbar { width: 6px; }
.rail-scroll::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 3px; }

/* --- control groups --------------------------------------------------------
   Read top to bottom as the signal path: model -> map -> voice -> mix. A hairline
   rule and a quiet title is the whole treatment; anything more and the rail starts
   looking like the stack of cards the direction refuses. */
.group {
  padding: 14px 0;
  border-top: 1px solid var(--line);
}
.group-title {
  margin: 0 0 12px;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.group .seg { margin-bottom: 8px; }
.group .ctl-hint { margin-bottom: 12px; }
.group .voice-row { margin-bottom: 8px; }
/* An empty group would leave a stray rule floating in the rail. */
.group:has(> .faders:empty) { display: none; }

/* readout */
.readout {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 4px 0 14px;
}
.readout-lang { display: flex; align-items: center; gap: 10px; }
.readout-lang .swatch {
  width: 12px; height: 12px; border-radius: 3px;
  background: var(--lang);
  transition: background 0.25s;
}
.nearest {
  font-family: var(--sans);
  font-size: 30px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.coords {
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* faders */
/* GRID, so every fader is exactly as wide as every other one and that width never depends on what
   a head currently says. A wrapped flex row could not do this: free space is distributed per LINE,
   so a last row holding fewer items stretches them wider than the row above. Grid columns are
   shared by every row. In the rail this is a single column, which makes uniformity automatic; the
   `auto-fit` track only earns its keep in the stacked layout at the bottom of this file. */
.faders {
  display: grid;
  grid-template-columns: 1fr;
  align-content: start;
  gap: 13px clamp(14px, 2vw, 26px);
  min-width: 0;
}
/* The grid column owns the width; the fader must not have an opinion of its own, or a long head
   would widen its own track and the row would go ragged again. `min-width: 0` lets a grid item
   shrink below its content so the head's ellipsis can do its job. */
.fader {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* One line, ALWAYS. A two-word label ("voice hold") or a two-part readout ("0% · 2.5s") would
   otherwise wrap, making that one head taller and pushing its track out of line with every other
   fader — the row stops reading as a set. So nothing wraps: the label truncates under pressure,
   the value never does, because the value is the data. `min-height` is the belt to that braces. */
.fader-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  min-height: 13px;
  white-space: nowrap;
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.fader-head label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* `tabular-nums` equalises digit GLYPHS; it does nothing about how many digits there are. Without
   a floor the label would still twitch left and right as a readout crosses 9 -> 10 -> 100, since
   the head is `space-between`. 4ch holds "100%" — the widest plain readout — so the label stays
   put. Right-aligned, so the value's last character is the fixed edge. */
.fader-head .val {
  flex: 0 0 auto;
  min-width: 4ch;
  text-align: right;
  color: var(--ink);
  letter-spacing: 0.02em;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}
.fader.disabled { opacity: 0.32; }
/* `display: flex` above would otherwise beat the UA's [hidden] rule, so the voice-only faders
   would show with no voice loaded. Same for the clear button next to the record chip. */
.fader[hidden], .voice-mini[hidden] { display: none; }

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  /* `width: 0; min-width: 100%` rather than `width: 100%`. A range input carries a chunky UA
     intrinsic width (~160px), and with `flex-basis: auto` on .fader that — not the label — would
     decide how wide each fader wants to be, wrapping the row far earlier than the text needs.
     Percentages are ignored when computing intrinsic size, so this leaves the head as the only
     thing that sizes a fader, while the used width still fills its column. */
  width: 0;
  min-width: 100%;
  height: 14px;               /* hit target */
  background: transparent;
  cursor: pointer;
  margin: 0;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 2px;
  border-radius: 2px;
  background:
    repeating-linear-gradient(90deg, var(--line-strong) 0 1px, transparent 1px 20%),
    var(--track);
}
input[type="range"]::-moz-range-track {
  height: 2px; border-radius: 2px; background: var(--track);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 3px;
  height: 15px;
  margin-top: -6.5px;
  border-radius: 1px;
  background: var(--ink);
  box-shadow: 0 0 0 4px var(--ground);   /* carves the fader out of the track */
}
input[type="range"]::-moz-range-thumb {
  width: 3px; height: 15px; border: none; border-radius: 1px;
  background: var(--ink); box-shadow: 0 0 0 4px var(--ground);
}
input[type="range"]:focus-visible { outline: none; }
input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--ground), 0 0 0 6px var(--line-strong); }
input[type="range"]:disabled { cursor: not-allowed; }

/* mode toggle — lives in the MAP group, because that is what it switches */
.seg {
  display: flex;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}
.seg-btn { flex: 1 1 0; }     /* halves, so the toggle spans the rail like the faders do */
.seg-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink-dim);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  white-space: nowrap;        /* "inv-UMAP" must not split across two lines on a phone */
  padding: 8px 13px;
  cursor: pointer;
  transition: background 0.14s, color 0.14s;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--line); }
.seg-btn:hover:not(.active):not(:disabled) { color: var(--ink); background: var(--fill-1); }
.seg-btn.active { background: var(--fill-2); color: var(--ink); }
.seg-btn:disabled { opacity: 0.32; cursor: not-allowed; }
.ctl-hint {
  font-size: 10px;
  line-height: 1.4;
  letter-spacing: 0.02em;
  color: var(--ink-dim);
}

/* voice prompt (record) — chrome stays achromatic; the only accent is --lang, as everywhere.
   The recording state is carried by the ring + the dot filling with --lang, never by a fixed red. */
.voice-row { display: flex; align-items: center; gap: 6px; }
.voice-row .rec { flex: 1 1 auto; justify-content: center; }
.rec {
  appearance: none;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  color: var(--ink-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 13px;
  cursor: pointer;
  overflow: hidden;
  transition: background 0.14s, color 0.14s, border-color 0.2s;
}
.rec:hover:not(:disabled) { color: var(--ink); background: var(--fill-1); }
.rec:disabled { opacity: 0.4; cursor: not-allowed; }
.rec:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--lang) 40%, transparent); }
.rec-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  border: 1px solid var(--ink-faint);
  flex: 0 0 auto;
  transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
}
.rec-label { font-variant-numeric: tabular-nums; }
/* recording: the dot lights and the ring sweeps the 6 s cap left to right */
.rec.recording { border-color: var(--lang); color: var(--ink); }
.rec.recording .rec-dot {
  background: var(--lang);
  border-color: var(--lang);
  box-shadow: 0 0 10px -1px var(--lang);
}
.rec-ring {
  position: absolute;
  left: 0; bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--lang);
  transform: scaleX(0);
  transform-origin: left center;
  opacity: 0;
}
.rec.recording .rec-ring { opacity: 1; }
/* loaded: the chip reads as filled, showing the take's duration in its label */
.rec.loaded { border-color: var(--line-strong); color: var(--ink); background: var(--fill-1); }
.rec.loaded .rec-dot { background: var(--ink); border-color: var(--ink); }
.rec.busy { opacity: 0.6; cursor: progress; }
/* review row: listen / use / discard — square hairline buttons, same register as the seg toggle */
.voice-mini {
  appearance: none;
  width: 28px; height: 28px;
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: 10px;
  line-height: 1;
  color: var(--ink-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 7px;
  cursor: pointer;
  transition: color 0.14s, background 0.14s, border-color 0.2s;
}
.voice-mini:hover { color: var(--ink); background: var(--fill-1); }
.voice-mini:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--lang) 40%, transparent); }
.voice-mini.use { color: var(--ink); border-color: var(--lang); }
.voice-mini.use:hover { background: color-mix(in srgb, var(--lang) 16%, transparent); }
.voice-mini.playing { color: var(--ink); border-color: var(--lang); }

/* transport foot — pinned, so the primary action never scrolls away */
.rail-foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px var(--rail-pad) max(14px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
}
.rail-foot .play { flex: 1 1 auto; justify-content: center; }
.buffer { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
.buffer-track {
  width: 66px; height: 3px;
  background: var(--track);
  border-radius: 2px;
  overflow: hidden;
}
.buffer-fill {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: var(--buf-color, var(--lang));   /* colour set by JS traffic-light; falls back to lang colour */
  border-radius: 2px;
  transition: transform 0.25s ease, background 0.4s;
}
.buffer-ms {
  font-size: 10.5px;
  letter-spacing: 0.03em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.play {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink);
  background: var(--fill-1);
  border: 1px solid var(--lang);
  border-radius: 10px;
  padding: 12px 22px;
  cursor: pointer;
  transition: background 0.16s, box-shadow 0.2s, opacity 0.16s, transform 0.05s;
}
.play .play-glyph { font-size: 11px; color: var(--lang); transition: color 0.25s; }
.play:hover:not(:disabled) {
  background: var(--fill-2);
  box-shadow: 0 0 22px -6px var(--lang);
}
.play:active:not(:disabled) { transform: translateY(1px); }
.play:disabled { opacity: 0.4; cursor: not-allowed; border-color: var(--line); }
.play.playing { background: color-mix(in srgb, var(--lang) 16%, transparent); }
.play:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--lang) 45%, transparent); }

/* --- responsive -------------------------------------------------------------
   Below 900px a left rail would eat the only axis a phone has. It re-docks to the
   bottom and the groups flow as columns instead of a stack — same DOM, same order,
   same grouping, just laid on its side. */
@media (max-width: 900px) {
  .rail {
    top: auto; right: 0;
    width: auto;
    max-height: 52vh;
    border-right: 0;
    border-top: 1px solid var(--line);
    background: linear-gradient(to top, var(--scrim) 0%, var(--scrim) 72%, var(--scrim-2) 100%);
  }
  /* Identity, status and position collapse onto one line: docked, every row costs the map
     directly, and the wordmark's stack is the first thing that can afford to go. */
  .rail-brand {
    display: flex;
    align-items: baseline;
    gap: 14px;
    padding: 10px var(--rail-pad) 6px;
  }
  .rail-brand .tagline { display: none; }
  .status-line { margin-top: 0; }
  /* The GROUPS become the columns — one row of four where it fits. The old `minmax(240px)`
     let MIX wrap to a second row on its own, which stranded a hairline rule at a different
     height from its neighbours and wasted a whole band of the dock. */
  .rail-scroll {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    align-items: start;
    gap: 0 22px;
    padding-bottom: 4px;
  }
  .readout {
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: baseline;
    gap: 14px;
    padding: 0 0 6px;
  }
  .nearest { font-size: 24px; }
  .legend { gap: 5px; }
  .group { padding: 10px 0 12px; }
  .group-title { margin-bottom: 9px; }
  .group .ctl-hint { margin-bottom: 9px; }
  .faders { gap: 11px; }
  .rail-foot { padding: 8px var(--rail-pad) max(10px, env(safe-area-inset-bottom)); }
}
/* Short windows (landscape phones): the dock and the map are fighting over the only axis
   either of them has. Give the map the bigger half and let the dock scroll. */
@media (max-width: 900px) and (max-height: 560px) {
  .rail { max-height: 46vh; }
  .group { padding: 8px 0 10px; }
  .play { padding: 9px 18px; }
}
/* One column of groups, one fader per row: below this width a second column would put
   "VOICE HOLD 100% · 1.92s" (~162px) under real pressure and start truncating labels. */
@media (max-width: 560px) {
  .rail-scroll { grid-template-columns: 1fr; gap: 0; }
  .faders { grid-template-columns: 1fr; }
}
