/* Phantom Space — app styles on top of moth-ds.
 *
 * Written as ONE ordered file. An earlier version grew by appending override blocks and ended up
 * fighting its own specificity — margin resets silently killing gap rules, and so on. Every rule
 * appears once, in layout order.
 *
 * Two things to know before editing:
 *
 * 1. moth-ds declares its tokens on `html[data-theme="dark"]`, NOT on :root, so a :root override
 *    loses. Everything below sits on `html[data-theme][data-color]` — two attribute selectors, so
 *    it outranks the theme without needing !important.
 *
 * 2. Containers own all spacing, via `gap`. Children contribute none. A <details> is display:block
 *    and cannot use gap, so the folds are made grids. This is why component and browser margins
 *    are zeroed: they are a second, competing source of spacing.
 *
 * Colourway: grey surface, black hairlines, green highlight. Nothing else.
 */

html[data-theme][data-color] {
  /* ---- the three colours ---- */
  --g-surface:   #a3a3a3;
  --g-line:      #000;
  --g-text:      #111;
  --g-muted:     #454545;
  /* Klein blue, pushed brighter: #002FA7 is the classic value but reads almost black against the
     panel. This keeps the hue and raises the intensity -- white on it is still 6.6:1, and the
     title on the black canvas goes from 1.96:1 (illegible) to 3.18:1. */
  --g-accent:     #0040ff;
  --g-accent-dim: #2b5cff;
  --g-on-accent:  #fff;

  /* ---- metrics ---- */
  --gap:     10px;   /* between one thing and the next */
  --gap-sm:   6px;   /* between parts of a single control */
  --pad:     10px;   /* inside a box */
  --pad-ctl: 7px 10px;
  --size:    13px;

  /* Every moth-ds type step flattened. A select takes its size from --t04 at a specificity
     `.panel *` cannot reach, so this is the only way to size everything at once. The LINE HEIGHTS
     matter as much as the sizes: leaving them on the original scale makes a select taller than the
     wrapper it sits in, so it overflows downward and swallows the gap below it. */
  --t01-font-size: var(--size); --t02-font-size: var(--size); --t03-font-size: var(--size);
  --t04-font-size: var(--size); --t05-font-size: var(--size); --t06-font-size: var(--size);
  --t07-font-size: var(--size); --t08-font-size: var(--size); --t09-font-size: var(--size);
  --t10-font-size: var(--size); --t11-font-size: var(--size); --t12-font-size: var(--size);
  --t01-line-height: 1.45; --t02-line-height: 1.45; --t03-line-height: 1.45;
  --t04-line-height: 1.45; --t05-line-height: 1.45; --t06-line-height: 1.45;
  --t07-line-height: 1.45; --t08-line-height: 1.45; --t09-line-height: 1.45;
  --t10-line-height: 1.45; --t11-line-height: 1.45; --t12-line-height: 1.45;

  /* ---- moth-ds tokens, re-seated onto the colourway ---- */
  --c-page: var(--g-surface);
  --c-page-above: var(--g-surface);
  --c-page-below: var(--g-surface);
  --c-page-muted: var(--g-surface);
  --c-text: var(--g-text);
  --c-text-emphasis: #000;
  --c-text-muted: var(--g-muted);
  --c-disabled-text: #6d6d6d;
  --c-accent: var(--g-accent);
  --c-cta: var(--g-accent);
  --c-cta-hover: var(--g-accent-dim);
  --c-cta-text: var(--g-on-accent);
  --c-inversion: var(--g-on-accent);
  --c-input-frame: var(--g-line);
  --c-input-frame-hover: var(--g-line);
  --c-progress: var(--g-accent);
  --c-progress-disabled: #8d8d8d;
  --c-thumb: #000;
  --c-thumb-hover: #000;
  --c-engaged: var(--g-accent);
  --c-engaged-hover: var(--g-accent-dim);
  --c-engaged-text: var(--g-on-accent);
  --c-disengaged: transparent;
  --c-disengaged-text: var(--g-muted);
  --c-error: #7a1414;
  --c-danger: #7a1414;
}

/* Light mode is a render change as much as a repaint: the canvas goes white and the shader reads
   the volume as absorbing rather than emitting (see uBg / uInvert in shader.js). The panel grey
   lifts so it still separates from the page behind it. */
html[data-theme="light"] { --g-surface: #d6d6d6; }
html[data-theme="light"], html[data-theme="light"] body { background: #fff; }
html[data-theme="light"] .brand .by { color: #5a5a5a; }

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: #000; overflow: hidden; }
canvas#gl { position: fixed; inset: 0; width: 100%; height: 100%; display: block;
  cursor: grab; touch-action: none; }
canvas#gl.drag { cursor: grabbing; }

/* ---------- brand, footer, drop target ---------- */
/* inline-block so the block shrinks to its widest line -- the title -- and the fold below can
   then be exactly as wide as "phantom space" */
.brand { position: fixed; top: 18px; left: 20px; pointer-events: none; display: inline-block; }
.brand h1 { margin: 0; font-size: 20px; line-height: 1.3; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--g-accent); }
/* the byline sits under the title, at body size so it reads as a credit */
.brand h1 .by { display: block; margin-top: 4px; font-size: var(--size); font-weight: 400;
  letter-spacing: .04em; text-transform: none; color: #6a6a6a; }

/* ---------- "what is this?" ---------- */
/* position:relative + an absolutely positioned body: out of flow, the body's long lines cannot
   widen the inline-block .brand, so the fold stays exactly as wide as the title */
.brand .about { pointer-events: auto; display: block; position: relative; width: 100%;
  margin-top: 10px; font-size: var(--size); line-height: 19px; }
.brand .about > summary { list-style: none; cursor: pointer; user-select: none;
  padding: 7px 10px; border: 1px solid #fff; border-radius: 7px;
  background: var(--g-surface); color: var(--g-text); }
.brand .about > summary::-webkit-details-marker { display: none; }
.brand .about > summary::before { content: '▸ '; }
.brand .about[open] > summary::before { content: '▾ '; }
.brand .about-body { position: absolute; z-index: 6; left: 0; top: calc(100% + 10px); width: 100%;
  padding: 10px; border: 1px solid #fff; border-radius: 8px;
  background: var(--g-surface); color: var(--g-text);
  max-height: calc(100vh - 190px); overflow-y: auto; scrollbar-gutter: stable; }
.brand .about-body p { margin: 0; }
.brand .about-body * + p { margin-top: 8px; }
.brand .about-body h2 { margin: 12px 0 0; font-size: var(--size); font-weight: 700;
  text-transform: uppercase; letter-spacing: .16em; }
.brand .about-body b { font-weight: 700; }
.drop { position: fixed; inset: 0; display: grid; place-items: center; background: rgba(0, 0, 0, .7);
  border: 1px dashed var(--g-accent); color: #ddd; margin: 14px; border-radius: 12px; z-index: 5; }
.drop[hidden] { display: none; }

/* ---------- panel ---------- */
/* The panel itself is only a container: no background, no border, no padding. Each box below
   carries the grey, so they read as separate blocks floating over the canvas rather than as
   panes inside one big slab. */
html[data-theme][data-color] .panel {
  position: fixed; right: 14px; top: 14px; width: 294px;
  max-height: calc(100vh - 28px); overflow-y: auto; scrollbar-width: thin;
  /* The scrollbar is drawn inside the panel, so without a reserved gutter it sits on top of the
     boxes' right edge. The extra width keeps the boxes the size they were. */
  /* overflow-y clips at the padding box. With zero padding on the left, top and bottom, each
     box's outermost border pixel sits exactly on that clip edge, and on a fractionally-scaled
     display the antialiased corner arcs get shaved -- it showed as a notch in the top-left of the
     first box and the bottom-left of the last. A couple of pixels of slack keeps every corner
     inside the clip. */
  scrollbar-gutter: stable;
  display: grid; align-content: start; gap: var(--gap);
  padding: 2px var(--gap) 2px 2px;
  background: transparent; border: none;
  color: var(--g-text); transition: opacity .3s, transform .3s;
}
html[data-theme][data-color] .panel.collapsed {
  opacity: 0; transform: translateX(20px); pointer-events: none; }

/* One size for everything in the panel. The line height is an exact pixel value, not a ratio:
   13px x 1.45 = 18.85px, which puts every control on a fractional boundary and antialiases its
   top and bottom borders across two device pixels, so a 1px line looks fainter horizontally than
   vertically. An integer keeps every hairline crisp on all four sides. */
html[data-theme][data-color] .panel,
html[data-theme][data-color] .panel *,
.tipbox { font-size: var(--size); line-height: 19px; }

/* Containers own all spacing; children contribute none. moth-ds components and the browser both
   add their own margins, which is what made gaps uneven. */
html[data-theme][data-color] .panel > *,
html[data-theme][data-color] .panel p,
html[data-theme][data-color] .panel label,
html[data-theme][data-color] .panel select,
html[data-theme][data-color] .panel summary,
html[data-theme][data-color] .panel details,
html[data-theme][data-color] .panel .button,
html[data-theme][data-color] .panel .slider,
html[data-theme][data-color] .panel .checkbox,
html[data-theme][data-color] .panel .field-select { margin: 0; }

/* Every box the full width of the panel's inner edge. min-width:0 is not optional: grid and flex
   children default to min-width:auto, so a long <option> or a nowrap label refuses to shrink and
   pushes the whole panel's contents out past its right edge. */
html[data-theme][data-color] .panel { grid-template-columns: minmax(0, 1fr); }
html[data-theme][data-color] .panel > *,
html[data-theme][data-color] .group > *,
html[data-theme][data-color] .eng > * { width: 100%; min-width: 0; }
html[data-theme][data-color] div.group,
html[data-theme][data-color] .eng { grid-template-columns: minmax(0, 1fr); }

/* ---------- sections ---------- */
/* A <details> CANNOT use gap for its contents, and making it a grid does not help: the browser
   wraps every non-summary child in one anonymous box, so the element has just two grid items --
   the summary and that box -- and the gap lands between them while everything inside stays flush.
   Measured: summary -> first child 10px, every pair after it 0px. So the folds space their
   children with margin-top instead, at a specificity above the margin reset further up. */
html[data-theme][data-color] div.group,
html[data-theme][data-color] .eng {
  display: grid; align-content: start; gap: var(--gap); }
/* The folds are left as plain blocks on purpose: a hidden first child (the technical-mode
   description, display:none in vibe) would otherwise absorb the grid's summary gap and the next
   visible element would get that gap plus its own margin. With margins alone, a display:none
   child contributes nothing and every pair measures the same. */
html[data-theme][data-color] .panel details.group > * + *,
html[data-theme][data-color] .panel details.sub > * + * { margin-top: var(--gap); }
/* One white hairline all the way round each box that sits directly on the canvas -- it replaces
   the black edge rather than sitting outside it, so there is a single line, not two. Controls
   nested inside a box keep their black lines, which is what separates them from the box. */
html[data-theme][data-color] .group,
html[data-theme][data-color] .panel > .switch,
html[data-theme][data-color] .panel > .row > .button {
  border: 1px solid #fff; }
html[data-theme][data-color] .group {
  padding: var(--pad); border-radius: 8px; background: var(--g-surface); }
html[data-theme][data-color] details.sub {
  padding: var(--gap) 0 0; border-top: 1px solid var(--g-line); }

html[data-theme][data-color] .panel .tag {
  font-weight: 700; text-align: left; letter-spacing: .16em; text-transform: uppercase;
  color: var(--g-text); }
html[data-theme][data-color] .panel .sub > summary.tag { font-weight: 400; }
summary.tag { cursor: pointer; list-style: none; user-select: none; }
summary.tag::-webkit-details-marker { display: none; }
summary.tag::before { content: '▸ '; }
details[open] > summary.tag::before { content: '▾ '; }

/* ---------- rows ---------- */
/* Buttons size from content and share the slack, so a long label keeps to one line and a short
   neighbour gives up the width. Equal halves made "↓ shader pack" wrap beside "remove". */
/* The row wraps: two long labels side by side (say "second image…" and "generate morph") are
   wider than the panel, and without wrapping they would push its contents out instead. */
html[data-theme][data-color] .panel .row {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap); }
html[data-theme][data-color] .panel .row > .button {
  flex: 1 1 auto; width: auto; min-width: 0; white-space: nowrap; }
html[data-theme][data-color] .panel .row > .field-select {
  flex: 1 1 auto; width: auto; min-width: 0; }

/* ---------- buttons ---------- */
/* appearance:none matters on macOS: a <button> left at `auto` keeps its native decoration
   underneath, which draws over the corner of our own border -- it showed as a notch missing from
   the top-left of the vibe/technical switch. */
html[data-theme][data-color] .panel .button {
  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--pad-ctl); border: 1px solid var(--g-line); border-radius: 7px;
  background: var(--g-surface); background-image: none; color: var(--g-text); text-align: center;
  --bloom-base: transparent; --bloom-fill: rgba(0, 0, 0, .10);
  --bloom-text: var(--g-text); --bloom-hover-label: var(--g-text);
}
html[data-theme][data-color] .panel .button:hover {
  background: #8f8f8f; color: var(--g-text); border-color: var(--g-line); }
html[data-theme][data-color] .panel .button:disabled {
  background: var(--g-surface); color: #6d6d6d; border-color: rgba(0, 0, 0, .35); cursor: not-allowed; }
/* a selected state is a value, not an action, so it takes the green — as the switch pip does */
html[data-theme][data-color] .panel .button.on,
html[data-theme][data-color] .panel .button.on:hover {
  background: var(--g-accent); color: var(--g-on-accent); border-color: var(--g-line); }
html[data-theme][data-color] .panel .button.recording {
  color: #7a1414; animation: blink 1.1s infinite; }
@keyframes blink { 50% { opacity: .55; } }

/* ---------- selects & text fields ---------- */
/* moth-ds draws the select's frame as an outline and jumps it to 3px on focus. One hairline in
   every state instead, with no border underneath it, or the two read as a single thick line. */
html[data-theme][data-color] .panel .field-select { display: block; }
html[data-theme][data-color] .panel .field-select select,
html[data-theme][data-color] .panel input[type=password] {
  display: block; width: 100%; min-width: 0; text-overflow: ellipsis;
  padding: var(--pad-ctl); border-radius: 7px; background: var(--g-surface);
  color: var(--g-text); border: none; outline: 1px solid var(--g-line); outline-offset: -1px; }
html[data-theme][data-color] .panel .field-select select:hover,
html[data-theme][data-color] .panel .field-select select:focus,
html[data-theme][data-color] .panel .field-select select:focus-visible,
html[data-theme][data-color] .panel input[type=password]:focus {
  outline: 1px solid var(--g-line); outline-offset: -1px; }
html[data-theme][data-color] .panel input[type=password]::placeholder { color: var(--g-muted); }
html[data-theme][data-color] .panel .keyfield { display: grid; gap: var(--gap-sm); }
html[data-theme][data-color] .panel .keyfield .keylabel { color: var(--g-text); }
html[data-theme][data-color] .panel .keyfield.ready .keylabel { color: var(--g-text); }
html[data-theme][data-color] .panel .keyfield.ready input[type=password] {
  outline-color: var(--g-line); }

/* ---------- sliders ---------- */
html[data-theme][data-color] .panel .slider { padding: 0; width: 100%; }
html[data-theme][data-color] .panel .slider .slider-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0 var(--gap-sm); margin: 0; }
html[data-theme][data-color] .panel .slider .slider-label {
  order: 1; flex: 1 1 auto; color: var(--g-text); font-weight: 400; letter-spacing: .02em; }
html[data-theme][data-color] .panel .slider .slider-value {
  order: 2; color: var(--g-text); font-variant-numeric: tabular-nums; }
html[data-theme][data-color] .panel .slider .slider-row input[type=range] {
  order: 3; flex: 1 1 100%; margin-top: var(--gap-sm);
  height: var(--track-height, 1rlh); }
/* the unplayed half of the track is the panel grey; the whole bar gets a hairline */
html[data-theme][data-color] .panel input[type=range] { --c-page-below: var(--g-surface); }
.panel input[type=range]::-webkit-slider-runnable-track { border: 1px solid var(--g-line); }
.panel input[type=range]::-moz-range-track { border: 1px solid var(--g-line); }
html[data-theme][data-color] .panel input:disabled { opacity: .45; }

/* Transport row: a scrub bar with a play toggle, so it matches the other sliders' height rather
   than the buttons' — the pause button comes down to the track, not the track up to the button. */
html[data-theme][data-color] .panel .slider.tau { flex: 1 1 auto; min-width: 0; width: auto; }
html[data-theme][data-color] .panel .slider.tau .slider-row input[type=range] {
  order: 0; flex: 1 1 auto; margin-top: 0; }
html[data-theme][data-color] .panel .row.time > .button {
  flex: 0 0 auto; height: 1rlh; min-height: 0; padding: 0 10px; line-height: 1; }

/* ---------- text ---------- */
html[data-theme][data-color] .panel .info {
  display: none; color: var(--g-muted); white-space: pre-line; text-transform: none; }
/* must match the specificity of the `display: none` above, or technical mode shows nothing */
html[data-theme][data-color][data-ui="technical"] .panel .info { display: block; }
html[data-theme][data-color][data-ui="technical"] .panel .info[hidden] { display: none; }
html[data-theme][data-color] .panel .info.note,
html[data-theme][data-color] .panel .info.card {
  padding: var(--pad); border: 1px solid var(--g-line); border-radius: 8px;
  background: var(--g-surface); }
html[data-theme][data-color] .panel .mini { color: var(--g-muted); word-break: break-word; }
/* In the default interface a section's description is a plain line, not a boxed card. */
html[data-theme][data-color][data-ui="vibe"] #panel .info.simple {
  display: block; padding: 0; border: 0; background: none; text-align: left; }
html[data-theme][data-color][data-ui="vibe"] #panel .info.simple[hidden] { display: none; }

/* ---------- readout: bottom left of the canvas, always in view ---------- */
/* Job progress and errors used to sit in the middle of the panel, often scrolled out of sight, so a
   failed run looked like nothing happened. They live here now, above the line saying what in the
   current view came from Moth. */
.readout { position: fixed; left: 20px; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); z-index: 5;
  max-width: calc(100vw - 420px); pointer-events: none; display: grid; gap: 4px; }
.readout p { margin: 0; font-size: 12px; line-height: 17px; word-break: break-word; }
.readout .status { color: #e8e8e8; }
html[data-theme="light"] .readout .status { color: #1a1a1a; }
.readout .status:empty { display: none; }
.readout .status.err { color: #e08585; }
.readout .provenance { color: #9a9a9a; }
html[data-theme="light"] .readout .provenance { color: #555; }
html[data-theme="light"] .readout .status.err { color: #b3261e; }
@media (max-width: 640px) { .readout { left: 12px; right: 12px; max-width: none; bottom: calc(55vh + 24px); } }

/* ---------- key field ---------- */
html[data-theme][data-color] .panel .keyfield .keyerr { color: #e08585; }
html[data-theme][data-color] .panel .keyfield .keyerr:empty { display: none; }
html[data-theme="light"][data-color] .panel .keyfield .keyerr { color: #b3261e; }
html[data-theme][data-color] .panel .keyfield.err input { border-color: #e08585; }
html[data-theme][data-color] .panel kbd {
  margin-left: auto; font: inherit; border: 1px solid var(--g-line);
  border-radius: 4px; padding: 0 5px; }

/* ---------- vibe / technical switch ---------- */
html[data-theme][data-color] .switch {
  appearance: none; -webkit-appearance: none;
  display: flex; align-items: center; justify-content: center; gap: var(--gap);
  width: 100%; padding: var(--pad-ctl); border: 1px solid var(--g-line); border-radius: 7px;
  background: var(--g-surface); color: var(--g-muted); font: inherit; cursor: pointer; }
.switch i { position: relative; width: 30px; height: 16px; flex: none;
  border: 1px solid var(--g-line); border-radius: 9px; background: transparent; }
.switch i::after { content: ''; position: absolute; top: 2px; left: 2px; width: 10px; height: 10px;
  border-radius: 50%; background: var(--g-accent); transition: transform .2s; }
.switch[aria-checked="true"] i::after { transform: translateX(14px); }
.switch .lv, .switch .lt { color: var(--g-muted); }
.switch[aria-checked="true"] .lt, .switch:not([aria-checked="true"]) .lv { color: var(--g-text); }

/* ---------- mode-dependent visibility ---------- */
/* Superposition, tunnelling and depth describe how copies are stacked in SPACE. The shader returns
   before reading any of them in spacetime mode, so they are hidden there rather than left on the
   panel doing nothing. */
/* These must outrank the `.eng { display: grid }` and slider/label display rules above, which carry
   the html[data-theme][data-color] prefix — otherwise every engine's panel shows at once. */
html[data-theme][data-color] .panel.mode-time .space-only { display: none; }
html[data-theme][data-color] .panel .eng[hidden] { display: none; }
html[data-theme][data-color] .panel [hidden] { display: none; }

/* ---------- hover tip ---------- */
/* The native title attribute waits ~1s and is drawn by the OS, which reads as lag. Sits to the
   left of the right-docked panel so it never covers the control being pointed at. */
.tipbox { position: fixed; z-index: 20; padding: var(--pad); border-radius: 8px; pointer-events: none;
  background: var(--g-surface); border: 1px solid var(--g-line); color: var(--g-text);
  text-transform: none; box-shadow: 0 6px 24px rgba(0, 0, 0, .5); }
.tipbox[hidden] { display: none; }
@media (max-width: 860px) { .tipbox { display: none; } }

/* ---------- technical mode & small screens ---------- */
html[data-ui="technical"] .panel { width: 382px; }
@media (max-width: 640px) {
  html[data-theme][data-color] .panel {
    top: auto; bottom: 12px; left: 12px; right: 12px; width: auto; max-height: 55vh; }
  html[data-ui="technical"] .panel { width: auto; }

}

/* ---------- sound ---------- */
html[data-theme][data-color] .panel .row.transport { align-items: center; }
html[data-theme][data-color] .panel #stime { font-variant-numeric: tabular-nums; }
/* the live spectrum: blue bars (a value) on the panel's own surface, one hairline like every control */
html[data-theme][data-color] .panel #smeter {
  display: block; width: 100%; height: 32px; border: 1px solid var(--g-line); border-radius: 7px; background: var(--g-surface); }

/* ---------- moving image ---------- */
html[data-theme][data-color] .panel #vthumb {
  display: block; width: 100%; height: auto; border: 1px solid var(--g-line); border-radius: 7px; background: #000; }
html[data-theme][data-color] .panel #vbody .row > .field-select { flex: 0 0 auto; }
html[data-theme][data-color] .panel #vbody > * + * { margin-top: var(--gap); }

/* ---------- the platform note at the top ---------- */
html[data-theme][data-color] .panel .platform { display: grid; gap: var(--gap); }
html[data-theme][data-color] .panel .platform p { margin: 0; }
html[data-theme][data-color] .panel .platform a.button { text-decoration: none; }

/* ---------- picture: what front to back means ---------- */
html[data-theme][data-color] .panel .viewrow { display: grid; gap: var(--gap-sm); }
html[data-theme][data-color] .panel .button.wide { width: 100%; }

/* ---------- settings: switches whose label is always readable (the switch itself shows on/off) ---------- */
html[data-theme][data-color] .panel .settings .switch { justify-content: space-between; }
html[data-theme][data-color][data-ui] .panel .settings .switch .lv { color: var(--tc-emph); }

/* Greek and physics symbols keep their case inside capitalised labels (see ui-mode.js). */
.panel .sym { text-transform: none; }
