/* ============================================================================
   Meridian studio chrome — Scanline's design language, on Meridian's layout.

   The tokens, type ramp, and control idioms below are ported from Scanline's
   studio.css so the two applications read as one product. The rule that makes
   the palette work, carried over verbatim in intent:

     ONE ACCENT, AND IT MEANS "ACTIONABLE OR ACTIVE" — NEVER DECORATION.

   Everything else is greyscale over a near-black ground, and every border is a
   translucent white rather than a mixed colour, so the whole app re-themes by
   swapping --accent / --accent-on and nothing else. (The old amber #e0913f is
   gone from the chrome. If a warm hue is wanted in the *picture*, it belongs in
   the grade shader in src/post/, which is the thing being filmed — not here.)
   ============================================================================ */
:root {
  color-scheme: dark;

  --bg-app: #0a0b0a;
  --bg-panel: #0f110f;
  --bg-footer: #0c0e0c;
  --bg-overlay: rgba(13, 15, 13, 0.82);

  --surface-tile: rgba(255, 255, 255, 0.02);
  --surface-raised: rgba(255, 255, 255, 0.04);
  --surface-input: rgba(0, 0, 0, 0.28);

  --border: rgba(255, 255, 255, 0.08);
  --border-subtle: rgba(255, 255, 255, 0.06);
  --border-strong: rgba(255, 255, 255, 0.12);
  --track: rgba(255, 255, 255, 0.1);

  --text: #e9ece8;
  --text-2: #c6ccc5;
  --text-3: #9aa19a;
  --muted: #8d938c;
  --dim: #7d837d;
  --faint: #6b716b;

  --accent: #5fdd9c;
  --accent-on: #08130d;
  --accent-soft: rgba(95, 221, 156, 0.45);

  --bad: #ff8080;
  --warn: #f0c674;

  --sans: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;

  /* Short and mechanical. Value readouts are never animated. */
  --t-fast: 120ms ease-out;

  --panel-w: 360px;
}

* { box-sizing: border-box; }
html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg-app);
  color: var(--text);
  font: 400 12px/1.45 var(--sans);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { filter: brightness(1.08); }

/* `hidden` must actually hide. Any rule that sets display outranks the UA's
   [hidden]{display:none}, and the failure mode is a control that is supposed to
   be gone still sitting there. */
[hidden] { display: none !important; }

#app {
  display: grid;
  grid-template-columns: 1fr var(--panel-w);
  grid-template-rows: 1fr 190px;
  height: 100%;
}
#viewport { position: relative; grid-column: 1; grid-row: 1; background: #000; overflow: hidden; }
#map { position: absolute; inset: 0; }
#side {
  grid-column: 2;
  grid-row: 1 / span 2;
  background: var(--bg-panel);
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  /* Load-bearing: the panel body is a flex child that must be allowed to SHRINK,
     and a default min-height:auto pins it at its intrinsic content height. */
  min-height: 0;
  overflow: hidden;   /* the column itself never scrolls; the panel bodies do */
}
#timeline {
  grid-column: 1;
  grid-row: 2;
  background: var(--bg-footer);
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ============================================================================
   Form primitives — one shape for every control in the app
   ============================================================================ */
button, input, select, textarea { font: inherit; color: var(--text); }
button, input, select, textarea {
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  padding: 8px 10px;
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
input, textarea { font-family: var(--mono); font-size: 11px; }
input::placeholder, textarea::placeholder { color: var(--faint); }
select, option { color: var(--text); background-color: var(--bg-panel); }
select { font-size: 11px; }

/* The knob-stack sliders keep the native widget and take the accent through
   `accent-color`: the UA still draws a filled track, which is the readout that
   matters when you are aiming at one row in a long list. */
input[type=range] {
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  accent-color: var(--accent);
}
input[type=checkbox], input[type=radio] { accent-color: var(--accent); }

button { cursor: pointer; color: var(--text-2); }
button:hover { border-color: var(--accent-soft); color: var(--text); }
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
button:disabled { opacity: .45; cursor: default; }

/* A section label: mono, uppercase, wide tracking. */
.sec-label,
.mg-group-title {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ============================================================================
   Side panel
   ============================================================================ */
.side-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--border-subtle);
}
.side-head h1 {
  flex: 1;
  margin: 0;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--text);
}
.side-head select { padding: 5px 6px; border-radius: 5px; }

/* Tabs are an underline, not a box: the 2px accent rule is the only mark, and it
   is the same "accent means active" signal used everywhere else. */
.tabs {
  flex: none;
  display: flex;
  gap: 20px;
  padding: 0 18px;
  border-bottom: 1px solid var(--border-subtle);
}
.tab {
  position: relative;
  padding: 12px 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-size: 12px;
  color: var(--dim);
  cursor: pointer;
}
.tab:hover { color: var(--text-2); border-color: transparent; }
.tab.is-active { color: var(--text); font-weight: 500; }
.tab.is-active::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
}

#panel-body, #features-body, #elements-body, #project-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 18px;
  /* Leave room for the scrollbar so a long knob list does not sit under it. */
  scrollbar-width: thin;
}
#panel-body::-webkit-scrollbar,
#features-body::-webkit-scrollbar,
#elements-body::-webkit-scrollbar,
#project-body::-webkit-scrollbar { width: 5px; }
#panel-body::-webkit-scrollbar-thumb,
#features-body::-webkit-scrollbar-thumb,
#elements-body::-webkit-scrollbar-thumb,
#project-body::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }

/* ============================================================================
   Basemap picker — pinned above the scrolling knob stack
   ============================================================================ */
/* flex: 0 0 auto so it stays put while #panel-body (flex: 1) scrolls under it.
   The basemap is the one choice that changes every other knob's context, so it
   should not be something you have to scroll back up to find. */
#basemap-pick {
  flex: 0 0 auto;
  padding: 14px 18px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-tile);
}
.bm-row { display: flex; align-items: center; gap: 8px; }
.bm-label {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--faint);
}
#basemap-pick .hint { margin: 6px 0 0; }
/* Licence caveats read as warnings, not as body copy — see the Esri entry in
   params/basemaps.js for the case this exists for. */
.bm-notice { color: var(--warn) !important; }

/* ============================================================================
   The knob stack — one row per entry in params/params.js
   ============================================================================ */
.mg-group { border-bottom: 1px solid var(--border-subtle); padding: 2px 0 8px; }
.mg-group:last-child { border-bottom: 0; }
.mg-group-title { margin: 0 0 6px; padding: 7px 0 0; }
.mg-group-title::before { content: "▾ "; color: var(--accent); }

.mg-row {
  display: grid;
  /* The label column carries the hint badge too, so it is wider than the text
     needs. The last column is the ◆ key button — reserved on EVERY row, keyable
     or not, so the sliders stay on one x-axis instead of stepping in and out
     down the stack. */
  grid-template-columns: 100px 1fr 14px;
  align-items: center;
  gap: 6px;
  padding: 2px 0;
}
.mg-label {
  font-size: 11px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: help;
}
/* The hint marker is a badge in the label, not a line of copy under the row: no
   row grows and no row moves when you go looking for what a knob does. */
.mg-hint-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 12px;
  height: 12px;
  margin-left: 4px;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  color: var(--dim);
  font-size: 8px;
  line-height: 1;
  user-select: none;
}
.mg-hint-dot::after { content: "?"; }
.mg-label:hover .mg-hint-dot { color: var(--text); border-color: var(--accent-soft); }

.mg-control { display: flex; align-items: center; gap: 6px; min-width: 0; }
.mg-range { flex: 1; min-width: 0; height: 18px; }
/* The readout is also an input — a value can be typed exactly rather than hunted
   for with a slider. Mono and tabular so the digits do not dance while dragging. */
.mg-num {
  flex: none;
  /* Wide enough for the longest value the table can produce — the model scale runs to
     five digits, and a clipped readout is worse than a narrower slider. */
  width: 60px;
  padding: 2px 4px;
  border: 1px solid var(--border-subtle);
  border-radius: 5px;
  background: var(--surface-input);
  font-family: var(--mono);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text);
}
.mg-num:hover { border-color: var(--border-strong); }
.mg-color {
  flex: none;
  width: 34px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  background: none;
}
.mg-select, .mg-text {
  flex: 1;
  min-width: 0;
  padding: 3px 6px;
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  background: var(--surface-input);
  font-size: 11px;
  color: var(--text);
}
.mg-text { font-family: var(--mono); }

/* Per-knob keyframing. One accent, and it means "actionable or active": an
   unkeyed knob shows a hollow diamond in the muted ink, and the frame that
   actually carries the key fills it in. */
.mg-key {
  width: 14px;
  height: 14px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--muted);
  opacity: .35;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  /* Zeroed so the ::after glyph below is the only mark. controls.js writes "◆"
     as the button's text, and a filled diamond on every unkeyed row would spend
     the accent on decoration — hollow until the knob is actually keyed. */
  font-size: 0;
  transition: opacity 90ms ease, color 90ms ease;
}
.mg-key::after { content: "◇"; font-size: 9px; }
.mg-key:hover { opacity: 1; color: var(--text); border-color: transparent; }
.mg-key.is-keyed { opacity: 1; color: var(--accent); }
.mg-key.is-keyed::after { content: "◆"; }
/* Rows with nothing to key still reserve the column — see the .mg-row grid note. */
.mg-key-spacer { display: block; width: 14px; }

/* ============================================================================
   Transport + timeline
   ============================================================================ */
.tl-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border-subtle);
  flex-wrap: wrap;
}
button.btn {
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  color: var(--text-2);
  font-size: 11px;
  padding: 5px 10px;
  cursor: pointer;
}
button.btn:hover { border-color: var(--accent-soft); color: var(--text); }
button.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-on);
  font-weight: 600;
}
button.btn.primary:hover { filter: brightness(1.06); border-color: var(--accent); color: var(--accent-on); }
button.btn:disabled { opacity: .45; cursor: default; }

/* Play is the one round button in the app: it is the control you aim at without
   looking, so it gets a shape nothing else shares. */
#play.btn.primary {
  width: 26px;
  height: 26px;
  flex: none;
  padding: 0;
  border-radius: 50%;
  font-size: 10px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* .mg-select carries `flex: 1` for the knob stack, where it should fill the row.
   In the transport it must not — it would swallow the spacer and shove Export
   off the right edge. */
.tl-bar .mg-select { flex: none; padding: 5px 8px; border-radius: 5px; }
.frame-read {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.spacer { flex: 1; }

/* overflow-x is hidden, not auto: buildTimeline() lays ticks and keys out against
   clientWidth (which counts the padding), so the last frame's diamond sits a few px
   into the right gutter. That is deliberate — but with `auto` it raises a horizontal
   scrollbar across the whole track for the sake of one diamond. */
#track {
  flex: 1;
  position: relative;
  overflow-x: hidden;
  overflow-y: auto;
  padding: 0 18px 10px;
  min-height: 0;
  scrollbar-width: thin;
}
#track::-webkit-scrollbar { width: 5px; height: 5px; }
#track::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }

.tl-ruler {
  position: relative;
  height: 22px;
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: 4px;
  cursor: pointer;
}
.tl-tick { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--border-subtle); }
.tl-tick span {
  position: absolute;
  left: 3px;
  top: 4px;
  font-family: var(--mono);
  font-size: 9px;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}
.tl-playhead { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--accent); pointer-events: none; z-index: 3; }
.tl-playhead::after {
  content: '';
  position: absolute;
  top: 0;
  left: -4px;
  border: 4px solid transparent;
  border-top-color: var(--accent);
}
.tl-lane { position: relative; height: 18px; border-bottom: 1px solid var(--border-subtle); cursor: pointer; }
.tl-lane-name {
  position: absolute;
  left: 0;
  top: 2px;
  z-index: 2;
  pointer-events: none;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--faint);
  /* Fades into the lane rather than boxing the name, so a diamond sitting under
     the label is still legible. */
  background: linear-gradient(90deg, var(--bg-footer) 70%, transparent);
  padding-right: 10px;
}
.tl-key {
  position: absolute;
  top: 4px;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  background: var(--accent);
  border: 1px solid var(--accent-on);
  transform: rotate(45deg);
  cursor: grab;
}
.tl-key:hover { filter: brightness(1.12); }
.tl-key.is-sel { background: #d8fbe9; box-shadow: 0 0 0 2px var(--accent-soft); }

#status {
  flex: none;
  padding: 6px 18px;
  border-top: 1px solid var(--border-subtle);
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#status.err { color: var(--bad); }

/* ============================================================================
   Attribution — a licensing obligation, not a dismissible overlay
   ============================================================================ */
#attribution {
  position: absolute;
  right: 10px;
  bottom: 8px;
  z-index: 4;
  padding: 3px 8px;
  border: 1px solid var(--border-subtle);
  border-radius: 5px;
  background: var(--bg-overlay);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  font-size: 10px;
  color: var(--text-3);
  pointer-events: auto;
}
#attribution a { color: var(--text-2); }
#attribution a:hover { color: var(--accent); }

/* ============================================================================
   Labels + project panels
   ============================================================================ */
.elem-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.elem-row input[type=text] {
  flex: 1;
  min-width: 0;
  padding: 5px 7px;
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  background: var(--surface-input);
  font-size: 11px;
}
.elem-row select {
  flex: none;
  padding: 4px 6px;
  border-radius: 5px;
  font-size: 10px;
}
/* The delete button is a ghost until you reach for it: destructive, but not
   competing with the accent for attention on every row. */
.elem-row button.btn {
  flex: none;
  padding: 4px 8px;
  border-color: transparent;
  color: var(--dim);
}
.elem-row button.btn:hover { color: var(--bad); border-color: transparent; }

.elem-add { display: flex; gap: 8px; margin: 0 0 10px; }
.elem-add .btn { flex: 1; padding: 8px 0; }

.hint { margin: 4px 0; font-size: 10.5px; line-height: 1.45; color: var(--faint); }

/* ============================================================================
   Feature browser — uses the same tokens and action hierarchy as the studio
   ============================================================================ */
.feature-intro {
  margin: 0 0 14px;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.5;
}
.feature-search { display: flex; gap: 7px; margin-bottom: 7px; }
.feature-search input {
  flex: 1;
  min-width: 0;
  padding: 7px 9px;
  background: var(--surface-input);
  border-color: var(--border-strong);
}
.feature-search .btn { flex: none; }
.feature-search-note {
  margin: 0 0 14px;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 9px;
  line-height: 1.45;
}
.feature-search-note a { color: var(--dim); }
.feature-search-note a:hover { color: var(--accent); }
.feature-section-title {
  margin: 16px 0 4px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-subtle);
  color: var(--faint);
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.feature-empty { padding: 14px 4px; color: var(--faint); font-size: 10.5px; text-align: center; }
.feature-result { padding: 10px 0; border-bottom: 1px solid var(--border-subtle); }
.feature-result-main { display: flex; align-items: start; gap: 8px; }
.feature-result-copy { min-width: 0; flex: 1; }
.feature-result-name {
  overflow: hidden;
  color: var(--text);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.35;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.feature-result-meta {
  margin-top: 2px;
  color: var(--accent);
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.feature-result-sub {
  display: -webkit-box;
  margin-top: 4px;
  overflow: hidden;
  color: var(--dim);
  font-size: 10px;
  line-height: 1.4;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
.feature-actions { display: flex; gap: 5px; margin-top: 8px; }
.feature-actions .btn { flex: 1; padding: 4px 5px; font-size: 9.5px; }
.feature-actions .btn.is-added {
  border-color: var(--accent-soft);
  color: var(--accent);
}
.feature-source { flex: none; color: var(--dim); font-family: var(--mono); font-size: 11px; }
.feature-source:hover { color: var(--accent); }
.feature-added-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.feature-added-name {
  min-width: 0;
  overflow: hidden;
  color: var(--text-2);
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.feature-added-kind {
  margin-right: 7px;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.feature-added-row .btn {
  padding: 3px 7px;
  border-color: transparent;
  color: var(--dim);
}
.feature-added-row .btn:hover { border-color: transparent; color: var(--bad); }
.feature-spinner {
  display: inline-block;
  width: 10px;
  height: 10px;
  border: 1px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: feature-spin .7s linear infinite;
}
@keyframes feature-spin { to { transform: rotate(360deg); } }

@media (max-width: 850px) {
  :root { --panel-w: 320px; }
}
