/* MaruDeeJay — amber phosphor archive terminal.
   Everything is one CRT: a tube (.tube) holding a screen (.screen) under three
   stacked overlays (roll, scanlines, vignette). Nothing scrolls but list bodies. */

@font-face {
  font-family: 'Fixedsys Excelsior';
  src: url('/static/fonts/FSEX300.ttf') format('truetype');
  font-display: swap;
}
@font-face {
  font-family: 'VT323';
  src: url('/static/fonts/vt323.woff2') format('woff2');
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/static/fonts/jetbrainsmono.woff2') format('woff2');
  font-display: swap;
}

:root {
  --amber: #ffb52e;
  --amber-hi: #ffd27a;
  --amber-70: rgba(255, 181, 46, .7);
  --amber-62: rgba(255, 181, 46, .62);
  --amber-55: rgba(255, 181, 46, .55);
  --amber-45: rgba(255, 181, 46, .45);
  --amber-35: rgba(255, 181, 46, .35);
  --amber-20: rgba(255, 181, 46, .2);
  --amber-12: rgba(255, 181, 46, .12);
  --amber-06: rgba(255, 181, 46, .06);
  --tube: #150d03;
  --case: #241c10;
  /* Phosphor bloom. Real amber phosphor spreads in two lobes — a tight bright
     halo around the glyph and a wide dim one further out — so every glow here
     is two shadows, never one blurrier one. Text takes --glow (--glow-hi where
     the tube is meant to be shouting), rules take --glow-rule, and anything
     lit solid amber takes --glow-lit. Inverted blocks keep text-shadow: none —
     dark ink does not glow. */
  --glow: 0 0 3px rgba(255, 176, 60, .55), 0 0 10px rgba(255, 140, 10, .28);
  --glow-hi: 0 0 3px rgba(255, 214, 140, .85), 0 0 16px rgba(255, 150, 20, .5);
  --glow-rule: 0 0 5px rgba(255, 160, 20, .3), 0 0 16px rgba(255, 140, 0, .14);
  --glow-lit: 0 0 8px rgba(255, 181, 46, .62), 0 0 21px rgba(255, 150, 10, .3);
  --row-h: 19px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%;
  background: var(--tube);
  overflow: hidden;
  font-family: 'JetBrains Mono', 'VT323', 'Fixedsys Excelsior', 'Courier New', ui-monospace, monospace;
  letter-spacing: .02em;
  color: var(--amber);
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
}

a { color: var(--amber); text-decoration: none; }
a:hover { color: var(--amber-hi); }

/* <b> is used purely as a value/glyph hook (status text, meter readouts, row
   controls). Only one cut of each face ships (JBM regular; Fixedsys has no
   bold at all), so synthetic bold just smears it and makes the value louder
   than its own label — keep it inline with its line. */
b { font-weight: inherit; color: inherit; }

input {
  background: transparent; border: none; outline: none;
  padding: 0; margin: 0; line-height: 1.25;
  color: var(--amber); font: inherit; letter-spacing: inherit;
  text-shadow: var(--glow); caret-color: var(--amber);
}
input::placeholder { color: rgba(255, 181, 46, .35); }
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; display: none; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--amber-35); border: 2px solid var(--tube); }
::-webkit-scrollbar-track { background: var(--amber-06); }
* { scrollbar-color: var(--amber-35) var(--amber-06); scrollbar-width: thin; }

@keyframes crtFlicker { 0%,100%{opacity:1} 52%{opacity:.985} 54%{opacity:.997} 78%{opacity:.98} 80%{opacity:1} }
@keyframes crtRoll    { 0%{background-position:0 0} 100%{background-position:0 10000%} }
@keyframes blinkCursor{ 0%,49%{opacity:1} 50%,100%{opacity:0} }

/* ── Tube ────────────────────────────────────────────────────────── */

/* No case: the tube is the window. Padding and the corner radius went with
   it — a rounded glass edge over a browser's square one reads as a border,
   which is the one thing the bezel was there to be. */
.bezel { height: 100vh; padding: 0; background: var(--tube); }

.tube {
  position: relative; height: 100%;
  background: var(--tube); overflow: hidden;
  box-shadow: inset 0 0 180px rgba(0, 0, 0, .78);
  animation: crtFlicker 9s infinite steps(1);
}

.screen {
  position: relative; height: 100%;
  display: flex; flex-direction: column;
  padding: 14px 22px 16px;
  text-shadow: var(--glow);
  /* 18px/1.4 is the hermes-crt tuning for JetBrains Mono: JBM renders full
     where Fixedsys ran small, so the old 19px/1.25 bitmap sizing is backed
     out with the face. */
  font-size: 18px; line-height: 1.4;
  /* Defocus — the survivor of the barrel experiment. The bow is gone
     (Blink's feDisplacementMap resamples with no subpixel interpolation, so
     1px rules broke at every step) but the softness it forced stays: a CRT
     never drew a hard edge. 0.28px, not the bitmap-era 0.5px — JBM is an
     outline face with its own antialiasing and goes mushy under half a
     pixel of blur. */
  filter: blur(0.28px);
}

.fx { position: absolute; inset: 0; pointer-events: none; }
.fx-roll {
  background-image: linear-gradient(0deg,
    rgba(255, 214, 140, .025) 0%, rgba(255, 214, 140, .072) 45%, rgba(255, 214, 140, .025) 100%);
  background-size: 100% 80%;
  animation: crtRoll 300s linear infinite;
}
.fx-scan { background: repeating-linear-gradient(0deg, rgba(0,0,0,.14) 0 1px, transparent 1px 3px); }

body.flat .screen { filter: none; }

/* Three stops, not two: a single ramp from 62% reads as a dark band with an
   edge, while an early low stop lets the falloff start mid-screen and only
   bite in the corners — which is where a real tube loses its beam. */
.fx-vig {
  background: radial-gradient(ellipse at center,
    transparent 33%, rgba(0,0,0,.27) 66%, rgba(0,0,0,.76) 100%);
}
/* Phosphor haze at the glass edge, all of it on the pseudo so one opacity can
   drive it. Riding the top overlay so the vignette cannot swallow it. Two dim
   lobes all round and a hotter offset pair along y (the negative spread keeps
   it off the sides); no hairline — a 1px line here reads as a border, and
   that has been tried and removed twice. The shadows stay square so the glow
   hugs the window edge into the corners; the corner radials pool a quarter-
   circle of haze in each one, which rounds the glow's INNER contour only —
   rounding the layer instead pulls the glow off the corner entirely and reads
   as a rounded screen.
   The pulse is a tired tube breathing, not a starter sputtering: the level
   wanders between swells with one deeper sag per cycle, eased both ways so
   nothing snaps. Irregular keyframe spacing is what keeps it organic — evenly
   spaced dips read as a metronome. Animating opacity keeps it on the
   compositor; the shadows and radials themselves are never re-painted. 10.7s
   against the flicker's 9s so the two never fall into step. */
.fx-vig::after {
  content: ''; position: absolute; inset: 0;
  box-shadow: inset 0 0 13px rgba(255, 181, 46, .11),
              inset 0 0 44px rgba(255, 150, 10, .06),
              inset 0 14px 30px -10px rgba(255, 181, 46, .35),
              inset 0 -14px 30px -10px rgba(255, 181, 46, .22);
  background:
    radial-gradient(circle 110px at 0 0,        rgba(255, 181, 46, .06), transparent 68%),
    radial-gradient(circle 110px at 100% 0,     rgba(255, 181, 46, .06), transparent 68%),
    radial-gradient(circle 110px at 0 100%,     rgba(255, 181, 46, .06), transparent 68%),
    radial-gradient(circle 110px at 100% 100%,  rgba(255, 181, 46, .06), transparent 68%);
  animation: tubePulse 10.7s ease-in-out infinite;
}
@keyframes tubePulse {
  0%, 100% { opacity: .96; }
  13% { opacity: .87; }
  24% { opacity: 1; }
  37% { opacity: .84; }
  46% { opacity: .95; }
  57% { opacity: .88; }
  65% { opacity: 1; }
  76% { opacity: .74; }
  86% { opacity: .93; }
  93% { opacity: .85; }
}
body.flat .fx-vig::after { animation: none; }
@media (prefers-reduced-motion: reduce) { .fx-vig::after { animation: none; } }

body.flat .fx-roll, body.flat .fx-scan { animation: none; display: none; }
body.flat .tube { animation: none; }

/* ── Chrome ──────────────────────────────────────────────────────── */

.topline {
  flex: none; display: flex; justify-content: space-between; gap: 16px;
  font-size: 16px; line-height: 20px; height: 20px; align-items: center;
  color: rgba(255, 181, 46, .6);
}
.topline span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The auth control sits in the topline like a word, not a button — the
   brighter hover is the whole affordance. */
#authctl { cursor: pointer; }
#authctl:hover { color: var(--amber-hi); text-shadow: var(--glow-hi); }
.topline .right { flex: 1 1 320px; text-align: right; }
.blip { animation: blinkCursor 1.6s steps(1) infinite; }
/* A dead link stops the cursor blinking and says so in the brighter ink the
   rest of the terminal reserves for trouble. */
.blip.dead { animation: none; opacity: .2; }
#linkstate.down { color: var(--amber-hi); }

.pathbar {
  flex: none; margin-top: 10px;
  border: 1px solid var(--amber-45); background: var(--amber-06);
  box-shadow: var(--glow-rule);
  padding: 3px 12px; display: flex; gap: 10px; align-items: center; white-space: nowrap;
}
.pathbar .k { flex: none; color: rgba(255, 181, 46, .6); }
.pathbar .v { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pathbar .meta { flex: none; color: var(--amber-45); font-size: 15px; }
.pathbar .pathbtn { flex: none; font-size: 13px; padding: 1px 8px; }
/* Desktop F1 shows records in its dedicated side panel on every row click —
   no mode to toggle, so no button. F2 (float) and stack (drawer) keep it. */
html:not(.stack) body[data-panel="F1"] .recbtn { display: none; }
/* Stack keeps one chip only — the pathbar's; the track-list copy is
   redundant on a phone and the button row is already tight there. */
html.stack #tracks .recbtn { display: none; }

/* ── Panels ──────────────────────────────────────────────────────── */

.panel { display: none; flex: 1 1 0; height: 0; min-height: 0; gap: 12px; margin-top: 12px; }
body[data-panel="F1"] #panel-F1,
body[data-panel="F2"] #panel-F2 { display: grid; }
#panel-F1 { grid-template-columns: minmax(0, 23fr) minmax(0, 10fr); }
#panel-F2 { grid-template-columns: minmax(0, 10fr) minmax(0, 23fr); }

/* Double rule. The second line is two inset shadows, not a second element: a
   tube-coloured ring 3px wide with an amber ring 4px wide painted under it, so
   only the 3–4px band shows. Shadows take no layout, so the panel grid, the
   10:23 split and every row count are exactly what they were with one line. */
.box {
  border: 1px solid var(--amber-35); padding: 12px;
  /* Bloom stays inside. An outer lobe here lands in the 12px gutters, where two
     panels' halos meet and read as haze between them rather than lit glass. */
  box-shadow: inset 0 0 0 3px var(--tube), inset 0 0 0 4px var(--amber-35),
              inset 0 0 18px rgba(255, 150, 10, .13);
  display: flex; flex-direction: column; min-height: 0;
}
.box.scroll { overflow: auto; }

.legend {
  color: var(--amber-55); font-size: 15px; letter-spacing: 1px;
  margin-bottom: 8px; flex: none; white-space: nowrap; overflow: hidden;
}
.dash { border-top: 1px dashed var(--amber-35); }

/* ── Case ────────────────────────────────────────────────────────── */

/* Data prints in its real casing (v2.10.1 — the uppercase transforms were
   removed with the bitmap face; the chrome labels are still authored in
   caps). `.keepcase` hooks remain in the templates, inert. */

/* ── Track tables ────────────────────────────────────────────────── */

.thead, .trow {
  display: grid; gap: 18px; align-items: center;
  grid-template-columns: minmax(0, 376px) minmax(90px, 1fr) 110px 58px 50px;
}
.thead.numbered, .trow.numbered {
  grid-template-columns: 34px minmax(0, 376px) minmax(90px, 1fr) 110px 58px 50px;
}
.thead {
  flex: none; padding: 0 10px 4px 4px; margin-bottom: 4px;
  border-bottom: 1px solid var(--amber-20);
  font-size: 13px; line-height: 1; letter-spacing: .06em;
  color: var(--amber-45); white-space: nowrap;
}
.tbody { flex: 1; min-height: 0; overflow: hidden; padding-right: 6px; }
.tbody.scroll { overflow-y: auto; }

.trow {
  padding: 0 4px; overflow: hidden; height: var(--row-h); line-height: var(--row-h);
  cursor: pointer;
}
/* Playing and selected are different facts and get different languages:
   playing fills the row (and carries the ▸ marker), selected marks its left
   edge. They were two washes 6% apart, which read as one state — with a record
   open and a track streaming you could not tell which row was which. */
.trow:hover { background: var(--amber-12); }
.trow.sel { background: var(--amber-06); box-shadow: inset 3px 0 0 var(--amber); }
.trow.playing { background: rgba(255, 181, 46, .16); }
.trow:hover.sel, .trow:hover.playing { background: rgba(255, 181, 46, .26); }
.trow > span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.trow .num { color: rgba(255, 181, 46, .5); }
.trow .title { color: var(--amber-62); }
.trow .fmt { color: var(--amber-55); font-size: 14px; }
.trow .acts {
  display: flex; gap: 8px; align-items: center; line-height: 1;
  color: var(--amber-55); overflow: visible;
}
.trow .acts b { width: 14px; text-align: center; cursor: pointer; font-weight: normal; }
.trow .acts b:hover { color: var(--amber-hi); }
/* An armed row asks in place: it lights up, its own action cell becomes Y N,
   and the rest of the list is left exactly where it was. */
.trow.arming { background: rgba(255, 181, 46, .26); box-shadow: inset 3px 0 0 var(--amber-hi); }
.trow.arming .acts b { color: var(--amber-hi); }
.trow.going { opacity: .4; }
.trow.drag-over { box-shadow: inset 0 -2px 0 var(--amber); }
.trow.dragging { opacity: .35; }

.rowmark { color: var(--amber); }
.none { color: var(--amber-45); font-size: 17px; padding: 2px 4px; }

.pager {
  flex: none; display: flex; justify-content: flex-end; gap: 6px; align-items: center;
  font-size: 14px; letter-spacing: .06em; color: var(--amber-55);
  margin-top: 6px; white-space: nowrap;
}
.pager .left { margin-right: auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pager .right { display: flex; gap: 6px; align-items: center; }
.pager .arrow { cursor: pointer; padding: 0 6px; }
.pager .arrow:hover { color: var(--amber-hi); }

/* ── Filter / entry lines ────────────────────────────────────────── */

.entry { border-top: 1px dashed var(--amber-35); padding-top: 14px; margin-top: 14px; flex: none; }
/* The add-file stack is three stacked sections (input / hits / footer); they
   share one rhythm so the rules read as a set rather than three guesses. */
.entry.tight { padding-top: 8px; margin-top: 8px; }
.entryline {
  font-size: 18px; line-height: 1.4;
  display: flex; gap: 8px; align-items: center; color: rgba(255, 181, 46, .85);
}
.entryline input { font-size: 18px; flex: 1; min-width: 0; }
.entryline .count { flex: none; font-size: 14px; color: rgba(255, 181, 46, .5); white-space: nowrap; }
.entryline .lit { white-space: nowrap; }

.seg { flex: none; display: flex; font-size: 13px; letter-spacing: .06em; white-space: nowrap; }
.seg span {
  border: 1px solid var(--amber-45); padding: 2px 8px; cursor: pointer;
}
.seg span + span { border-left: none; }
.seg span:hover { background: rgba(255, 181, 46, .16); }
.seg span.on { background: var(--amber); color: var(--tube); text-shadow: none; box-shadow: var(--glow-lit); }

/* ── Buttons ─────────────────────────────────────────────────────── */

.btn {
  border: 1px solid var(--amber-45);
  display: flex; align-items: center; justify-content: center;
  line-height: 1; letter-spacing: .08em; padding: 4px 10px;
  cursor: pointer; white-space: nowrap; font: inherit;
  color: var(--amber); background: transparent; text-shadow: var(--glow);
}
.btn:hover { background: rgba(255, 181, 46, .16); color: var(--amber-hi); text-shadow: var(--glow-hi); }
.btn.on { background: var(--amber); color: var(--tube); text-shadow: none; box-shadow: var(--glow-lit); }
.btnrow { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; margin-top: 10px; font-size: 14px; flex: none; }
.btnrow.two { grid-template-columns: repeat(2, 1fr); }
.btnrow.one { grid-template-columns: 1fr; }
.btnrow.four { grid-template-columns: repeat(4, 1fr); }
.btnpair { display: flex; gap: 8px; margin-top: 14px; padding-top: 14px; font-size: 16px; }

/* ── Playlist list (F2 left) ─────────────────────────────────────── */

/* The wrapper takes the free height and holds the pager; the list pages to fit
   it rather than growing a scrollbar, like every other list in the tube. */
#playlist-list { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.pls { overflow: hidden; min-height: 0; flex: 1; display: flex; flex-direction: column; gap: 3px; }
.pls a {
  padding: 1px 6px; display: flex; gap: 10px; justify-content: space-between;
  cursor: pointer; color: var(--amber);
}
.pls a:hover { background: var(--amber-12); }
.pls a.sel { background: rgba(255, 181, 46, .22); }
.pls a span:first-child { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pls a .c { flex: none; color: var(--amber-55); }

.newline { border: 1px solid var(--amber-45); padding: 2px 8px; margin-top: 10px; font-size: 18px; flex: none; }
.newline input { font-size: 18px; width: 200px; }
/* Rename starts prefilled, and playlist names run long. */
.newline input.wide { width: calc(100% - 22px); }
/* The button border is the affordance; the [ ] brackets it used to carry were
   framing the same thing a second time. */
.confirm {
  margin-top: 10px; font-size: 16px; color: var(--amber-hi); flex: none;
  display: flex; gap: 6px; align-items: center;
}
.confirm .btn { display: inline-flex; padding: 2px 10px; }
/* The duplicate question lands inside a list panel, so it wraps rather than
   forcing the panel wider. */
.confirm.dup { margin: 0; padding: 4px 6px; flex-wrap: wrap; font-size: 15px; }
/* Answered, waiting on the write that will replace this whole panel. */
.confirm.dup.busy { color: var(--amber-55); }

/* ── File record / status side panel ─────────────────────────────── */

.rec-title { font-size: 20px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-artist { font-size: 17px; color: var(--amber-70); margin-bottom: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-fields { display: flex; flex-direction: column; gap: 3px; font-size: 16px; border-top: 1px dashed var(--amber-35); padding-top: 8px; }
.rec-fields > div { display: flex; gap: 8px; }
/* 108px: RESOLUTION is 10 chars and JBM runs ~9.9px/char at this size —
   the bitmap-era 88px left the label colliding with its value. */
.rec-fields .k { color: rgba(255, 181, 46, .5); width: 108px; flex: none; }
.rec-fields .v { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.statustext { font-size: 17px; color: var(--amber-70); line-height: 1.5; }
.statustext .dim { color: var(--amber-45); }

/* The menu owns the side panel: the list takes the free height (and pages to
   fit it), the footer sits on the floor rather than trailing the last entry. */
.addmenu { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-height: 0; overflow: hidden; }
.addmenu .opt { padding: 0 6px; cursor: pointer; }
.addmenu .opt:hover { background: rgba(255, 181, 46, .16); }
.addfoot { flex: none; margin-top: auto; padding-top: 8px; }
.addfoot .pager { margin-top: 0; }

/* ── Search results (F2 add line) ────────────────────────────────── */

/* Type-ahead results float above the ADD FILE line rather than pushing into
   the track list — the list must not resize (or clip a row) while you type. */
.entry.tight { position: relative; }
#search-results:not(:empty) {
  position: absolute; left: 0; right: 0; bottom: 100%;
  margin-bottom: 8px; padding: 8px 10px;
  background: var(--tube);
  border: 1px solid var(--amber-45);
  box-shadow: 0 -10px 28px rgba(0, 0, 0, .7);
  max-height: 60vh; overflow: auto;
  z-index: 40;
}
/* The FILE RECORD slides in from the right edge over both panels — a
   drawer, not a swap, so no list ever resizes or clips a row under it. It
   sits inside .screen (under the scanline/vignette overlays: part of the
   picture) and quotes the .box frame on its leading edge. */
#recdrawer {
  position: absolute; top: 44px; right: 0; bottom: 66px; z-index: 45;
  width: min(480px, 92%);
  background: var(--tube);
  border: 1px solid var(--amber-45); border-right: none;
  box-shadow: inset 0 0 0 3px var(--tube), inset 0 0 0 4px var(--amber-35),
              -14px 0 34px rgba(0, 0, 0, .6);
  padding: 14px 16px;
  transform: translateX(103%); transition: transform .22s ease-out;
  display: flex; flex-direction: column;
}
#recdrawer.open { transform: translateX(0); }
#recdrawer .dbody { display: flex; flex-direction: column; min-height: 0; overflow-y: auto; }

.hits { padding-right: 6px; font-size: 18px; }
.hits .trow { cursor: pointer; }
.hits .trow:hover { background: rgba(255, 181, 46, .16); }
.hits .acts { justify-content: center; }
.hits .acts b { color: var(--amber-70); }
.hint { font-size: 14px; color: rgba(255, 181, 46, .5); margin-top: 2px; }
.hint.hitfoot {
  display: flex; gap: 12px; align-items: center; justify-content: space-between;
  border-top: 1px dashed var(--amber-35); margin-top: 8px; padding-top: 8px;
}
.hint.hitfoot .right { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
.hint.hitfoot .arrow { cursor: pointer; padding: 0 6px; }
.hint.hitfoot .arrow:hover { color: var(--amber-hi); }

/* ── Signal cells ────────────────────────────────────────────────── */

/* Three rows in the same body height the VU stack uses, so the cluster does
   not grow. Bars and axis are one glyph grid at two sizes — nothing here may
   wrap, or a column stops meaning a frequency. */
.specrow {
  font-size: 15px; line-height: 17px; height: 17px;
  white-space: pre; overflow: hidden;
}
.specrow.live { color: rgba(255, 199, 102, .9); }
.specrow.hold { color: rgba(255, 181, 46, .4); }
.specaxis {
  font-size: 12px; line-height: 15px; height: 15px;
  white-space: pre; overflow: hidden; color: var(--amber-45);
}

/* The verdict rides the cell's own caption rather than taking a row. */
.cap.sig.wait { color: var(--amber-45); }
.cap.sig.ok { color: var(--amber-55); }
.cap.sig.warn { color: var(--amber-hi); }
/* A lossy file in a lossless archive is the one reading worth shouting. */
.cap.sig.bad { background: var(--amber); color: var(--tube); text-shadow: none; box-shadow: var(--glow-lit); }

.chainline {
  font-size: 13px; line-height: 17px; height: 17px;
  color: var(--amber-55); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.sigrow {
  /* 38px key cells: CRST/CLIP/LUFS/CEIL are 4 chars ≈ 35px in JBM 14px —
     the Fixedsys-era 30/34px let the key run into its value. */
  display: grid; grid-template-columns: 38px minmax(0, 1fr) 38px minmax(0, 1fr);
  gap: 6px; font-size: 14px; line-height: 17px; height: 17px; white-space: nowrap;
}
.sigrow .k { color: rgba(255, 181, 46, .5); }
.sigrow .v { overflow: hidden; text-overflow: ellipsis; }

/* ── Player cluster ──────────────────────────────────────────────── */

/* The cluster lines up with the panel above it. Both panels split 10:23 with a
   12px gutter, but mirrored — F1 is wide-left, F2 is wide-right — so a single
   seam can only ever match one of them. The cluster is therefore cut at BOTH
   boundaries and each panel finds its divider continued below.

   The widths are percentages of the cluster's own box rather than `fr`,
   because `fr` divides what is left after THIS grid's two gutters while the
   panel divides what is left after its one: the same ratio would land a few
   pixels off. Written out, with u = (W - 12) / 33:

     group 1 = 10u          → gutter at [10u, 10u+12]   = F2's divider
     group 2 = 13u - 12px   → gutter at [23u, 23u+12]   = F1's divider
     group 3 = 10u          → ends at 33u + 12 = W

   Change the panels' 10:23 and these three lines change with them. */
.cluster {
  display: grid;
  grid-template-columns:
    calc((100% - 12px) * 10 / 33)
    calc((100% - 12px) * 13 / 33 - 12px)
    calc((100% - 12px) * 10 / 33);
  gap: 12px; margin-top: 12px; flex: none;
}
.cgroup { display: grid; gap: 12px; min-width: 0; }
.cgroup.one { grid-template-columns: 172px minmax(0, 1fr); }        /* TRANSPORT · TRACK */
.cgroup.two { grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); } /* SCOPE · SPECTRUM */
.cgroup.three { grid-template-columns: minmax(0, 1fr) 205px; }      /* LEVELS · VU */
.cell {
  border: 1px solid var(--amber-35); padding: 5px 10px; min-width: 0;
  /* Same as .box: bloom inward only. Six cells in a row with outer lobes fill
     every gutter with haze. No double rule here — the body is 51px and the
     inner line would sit on the readouts. */
  box-shadow: inset 0 0 14px rgba(255, 150, 10, .13);
  display: flex; flex-direction: column;
}
.cell .cap {
  font-size: 14px; line-height: 17px; height: 17px;
  color: var(--amber-55); letter-spacing: 1px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.cell .cap.mid { text-align: center; }
.cell .body { flex: 1; display: flex; flex-direction: column; justify-content: center; }

.transport { flex: 1; display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 1fr; gap: 4px; padding: 3px 0 1px; }
.transport .btn { font-size: 13px; padding: 0 2px; }
.transport .btn.main { background: var(--amber); color: var(--tube); text-shadow: none; border-color: var(--amber); box-shadow: var(--glow-lit); }

/* The line is the window; the track inside it is what moves. One copy sweeps in
   from the right edge (--mq-from) and out past the left (--mq-shift = its own
   width), so the title is never on screen twice. .roll is set only while a
   track is loaded — see setNow. */
.nowline { font-size: 18px; line-height: 17px; text-shadow: var(--glow-hi); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nowline .mq { display: inline-flex; }
.nowline .mq > span { flex: none; white-space: pre; }
.nowline.roll { text-overflow: clip; }
.nowline.roll .mq { animation: marquee var(--mq-dur, 12s) linear infinite; }
body.flat .nowline.roll .mq { animation: none; }
@media (prefers-reduced-motion: reduce) { .nowline.roll .mq { animation: none; } }
@keyframes marquee {
  from { transform: translateX(var(--mq-from, 100%)); }
  to { transform: translateX(calc(-1 * var(--mq-shift, 100%))); }
}
.timeline { font-size: 15px; line-height: 17px; white-space: nowrap; color: var(--amber-70); display: flex; gap: 8px; align-items: baseline; }
.timeline .t { flex: none; }
.seek { cursor: pointer; flex: 1; min-width: 0; height: 9px; align-self: center; border: 1px solid var(--amber-45); display: block; position: relative; }
.seek i { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: rgba(255, 181, 46, .85); display: block; box-shadow: var(--glow-lit); }

/* The trace is the brightest thing the tube draws — it gets the loud lobe. */
.scope { font-size: 26px; line-height: 34px; height: 34px; color: rgba(255, 199, 102, .9); text-shadow: var(--glow-hi); overflow: hidden; white-space: nowrap; text-align: center; }

.vu { font-size: 13px; line-height: 17px; height: 17px; white-space: nowrap; display: flex; gap: 8px; align-items: center; }
.vu .lbl { width: 26px; flex: none; color: var(--amber-55); }
.vu .bars { flex: 1; min-width: 0; display: flex; gap: 3px; }
.vu .bars i { flex: 1; height: 8px; border: 1px solid rgba(255, 181, 46, .4); display: block; }
/* One lobe, not two: these repaint with the meter, and a lit segment is 8px
   tall — the wide lobe would cost paint nobody can see. */
.vu .bars i.on { background: rgba(255, 181, 46, .85); box-shadow: 0 0 7px rgba(255, 181, 46, .58); }
.vu.volrow { color: rgba(255, 181, 46, .6); }
.vu .bars.vol i { cursor: pointer; }
/* Numbers carry no tracking. Every glyph in this font advances exactly half
   the font size, so a run of digits is even by construction — until tracking
   makes each cell fractional under display scaling (14px + 1px = 8.00 at
   100%, 8.75 + 1.25 at 125%), and the rasterizer spends the remainder as one
   extra pixel inside a single pair: "9 0%". Labels keep their letterspacing;
   the readouts they carry do not. */
.num, #curtime, #durtime, #pagelabel, #filtercount,
.pathbar .meta, .sigrow .v, .timeline .t, .chainline {
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.cell .cap .mute { cursor: pointer; letter-spacing: .08em; }
.cell .cap .mute:hover { color: var(--amber-hi); }
.cell .cap.split { display: flex; justify-content: space-between; gap: 8px; }
.muted { color: var(--amber-hi) !important; }

/* ── Function keys ───────────────────────────────────────────────── */

.fkeys {
  display: flex; gap: 12px; margin-top: 12px; align-items: center;
  font-size: 16px; line-height: 20px; height: 20px; color: rgba(255, 181, 46, .75); flex: none;
}
.fkeys .fk { white-space: nowrap; cursor: pointer; }
.fkeys .fk:hover { color: var(--amber-hi); }
.fkeys .fk b { padding: 0 6px; font-weight: normal; }
.fkeys .fk.on b { background: var(--amber); color: var(--tube); text-shadow: none; box-shadow: var(--glow-lit); }
.fkeys .hintline { margin-left: auto; white-space: nowrap; color: rgba(255, 181, 46, .5); }

/* ── Login ───────────────────────────────────────────────────────── */

.login-wrap { height: 100%; display: flex; align-items: center; justify-content: center; }
.login { width: 480px; max-width: 88%; border: 1px solid var(--amber-45); padding: 22px 26px; background: var(--amber-06); box-shadow: var(--glow-rule); }
.login h1 { margin: 0 0 4px; font-size: 26px; font-weight: normal; letter-spacing: .08em; text-shadow: var(--glow-hi); }
.login .sub { color: var(--amber-55); font-size: 15px; margin-bottom: 18px; }
.login .field { border: 1px solid var(--amber-45); padding: 4px 10px; display: flex; gap: 10px; align-items: center; font-size: 18px; }
.login .field input { flex: 1; min-width: 0; font-size: 18px; }
.login .btn { margin-top: 14px; padding: 6px; }
.login .err { color: var(--amber-hi); font-size: 15px; margin-top: 10px; }
.login .boot { color: var(--amber-45); font-size: 14px; line-height: 1.5; margin-top: 18px; }

/* ── Toast / status ──────────────────────────────────────────────── */

/* The message reads at the toast's full brightness rather than the topline's
   dimmed .6, and errors keep the brighter ink the toast border used to carry. */
#statusmsg { color: var(--amber); }
#statusmsg.err { color: var(--amber-hi); }

.htmx-request .pathbar .meta::after { content: ' ·· BUSY'; }
audio#player { display: none; }

/* ── stack (phone) layout ─────────────────────────────────────────
   Entered once at load via html.stack (coarse pointer + short screen
   side ≤700, hermes-crt's rule — a fold is not a reflow). Same tube,
   same fx, same amber: fitRube maps the SHORT side to 580 virtual px,
   so glyphs land ~12 real px either orientation. One panel column;
   the F1 FILE RECORD/add-menu pops over the archive (the F2 float
   pattern); the cluster keeps TRANSPORT · TRACK only. */
html.stack .bezel { height: auto; }
html.stack .tube { transform-origin: top left; }
html.stack .screen { padding: 10px 12px 12px; }
html.stack .topline { font-size: 14px; }
html.stack .topline .op { display: none; }
html.stack .pathbar .meta { display: none; }

html.stack #panel-F1, html.stack #panel-F2 { grid-template-columns: minmax(0, 1fr); }
html.stack .panel { position: relative; }
html.stack #panel-F2 { grid-template-rows: minmax(0, 2fr) minmax(0, 3fr); }

/* F1's side box becomes a pop-over: hidden while it only holds ARCHIVE
   STATUS, floated over the list once a record or the add menu lands in
   it (syncSidePop). Same frame as #trackrec. */
html.stack #side { display: none; }
html.stack #side.pop {
  display: flex; position: absolute; z-index: 35;
  left: 6%; right: 6%; top: 5%; max-height: 85%; overflow: auto;
  background: var(--tube);
  border: 1px solid var(--amber-45); padding: 12px;
  box-shadow: inset 0 0 0 3px var(--tube), inset 0 0 0 4px var(--amber-35),
              0 10px 28px rgba(0, 0, 0, .7);
}

/* FORMAT column folds away; TITLE inherits its room. */
html.stack .thead, html.stack .trow { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 58px 50px; gap: 8px; }
html.stack .thead.numbered, html.stack .trow.numbered { grid-template-columns: 26px minmax(0, 1fr) minmax(0, 1fr) 58px 50px; }
html.stack .thead:not(.numbered) > span:nth-child(3),
html.stack .thead.numbered > span:nth-child(4),
html.stack .trow .fmt { display: none; }

/* Cluster, phone edition: transport and the now-playing line. The
   meters need width the phone does not have, and its volume is the
   phone's own. */
html.stack .cluster { display: block; }
html.stack .cgroup.two, html.stack .cgroup.three { display: none; }

html.stack #hintline { display: none; }
/* The lists own their touch gestures (pointer-event row scrolling) — the
   browser must not claim the drag for page scroll first. */
html.stack #archrows, html.stack #tracks, html.stack #playlist-list,
html.stack #search-results, html.stack #side .addmenu { touch-action: none; }

/* Finger-sized touches: taller rows (fitRows follows --row-h), fatter
   pager arrows, row glyphs and buttons. Everything is virtual px — real
   size ≈ ×0.67 on a 390px-wide phone. */
html.stack { --row-h: 26px; }
html.stack .pager .arrow { padding: 0 10px; font-size: 20px; }
html.stack .trow .acts { gap: 12px; }
html.stack .trow .acts b { width: 24px; font-size: 18px; }
html.stack .btn { padding: 5px 8px; }
html.stack .fkeys { gap: 16px; font-size: 18px; line-height: 24px; height: 24px; }
/* Real input size must stay ≥16px or iOS focus-zoom fires (hermes rule):
   24 virtual px × ~0.67 scale ≈ 16 real. */
html.stack input { font-size: 24px; }
