/* TEXT·STREAM — VS Code Dark terminal theme (§2.1) */

@font-face {
  font-family: "TS Mono";
  src: url("fonts/ts-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "TS Mono";
  src: url("fonts/ts-mono-700.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

:root {
  --bg: #1e1e1e; /* page background */
  --fg: #cccccc; /* normal text */
  --dim: #858585; /* row numbers, artist, hints */
  --line: #424242; /* separators */
  --blue: #3b8eea; /* headings, paths — bold */
  --yellow: #f5f543; /* now playing, times — bold */
  --green: #23d18b; /* ❯, liked ♥, played part of the bar, "on" modes */
  --red: #f14c4c; /* errors */
  --sel: #264f78; /* selected row */
  --hover: #2a2d2e; /* row under the mouse */
  --rule: #2a2a2a; /* thin row separators */
  --font: "TS Mono", ui-monospace, Menlo, Consolas, "Noto Sans Mono",
    "Apple SD Gothic Neo", "Malgun Gothic", monospace;
  --sp-w: 300px; /* width of the side player (§7.3, from 1080 px) */
  /* Sizes. Three layouts only: a phone up to 700 px, the bottom bar from
     701 px, the side player from 1080 px. A phone redefines these in its
     media query; every rule reads them instead of its own number. */
  --gutter: 18px; /* left and right edge of the view */
  --hit: 44px; /* the smallest touch target */
  --fs-s: 11px; /* secondary text: artist, times, counts. Never smaller. */
  --bar-h: 53px; /* the bottom bar, without the safe area */
  color-scheme: dark;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

html,
body {
  background: var(--bg);
  color: var(--fg);
  font: 12px/1.45 var(--font);
  margin: 0;
}

b,
.hl {
  font-weight: 700;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 10px;
  min-width: 44px;
  min-height: 44px;
  touch-action: manipulation;
  cursor: pointer;
}

input {
  font: inherit;
}
/* 16 px stops iOS zoom on focus; only touch screens need it */
@media (pointer: coarse) {
  input {
    font-size: 16px;
  }
}

.time {
  font-variant-numeric: tabular-nums;
}

.row {
  content-visibility: auto;
  contain-intrinsic-size: auto 2.4em;
}

@media (hover: hover) {
  .row:hover {
    background: var(--hover);
  }
}

:focus-visible {
  outline: 1px solid var(--blue);
  outline-offset: -1px;
}

/* ---------- login and sign-up pages (§4.2) ---------- */
.auth-page {
  min-height: 100dvh;
  box-sizing: border-box;
  padding: calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom));
  display: flex;
  justify-content: flex-start;
}
.auth {
  width: 100%;
  max-width: 40em;
  padding-top: 8vh;
}
.auth-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 2.5em;
}
.brand {
  font-weight: 700;
}
.langs a,
.auth-actions a {
  color: var(--blue);
  text-decoration: none;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.auth-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--blue);
  margin: 0 0 1.2em;
}
.prompt {
  color: var(--green);
}
.auth-form {
  display: grid;
  gap: 2px;
}
.field {
  display: grid;
  grid-template-columns: 9em minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  padding: 2px 0;
}
.field > span {
  color: var(--dim);
}
.field input {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 0;
  padding: 10px;
  min-height: 44px;
  box-sizing: border-box;
  width: 100%;
}
.field input:focus-visible {
  border-color: var(--blue);
  outline: none;
}
.auth-actions {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 0 0 9em;
}
.auth-actions .primary {
  border: 1px solid var(--line);
  color: var(--fg);
}
.auth-actions .primary:hover {
  border-color: var(--blue);
}
.error {
  color: var(--red);
  margin: 0 0 1em;
}
.hint {
  color: var(--dim);
}

/* ---------- app shell (§7.1) ---------- */
body.app {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  -webkit-tap-highlight-color: transparent;
}
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--bg);
  color: var(--blue);
  padding: 8px;
  z-index: 100;
}
.skip:focus {
  left: 8px;
  top: 8px;
}
.brand {
  color: var(--blue);
  font-weight: 700;
  letter-spacing: 0.04em;
}
.wrap {
  flex: 1;
  display: flex;
  min-height: 0;
  padding-bottom: calc(var(--bar-h) + env(safe-area-inset-bottom) + var(--gutter));
}
.grip {
  flex: 0 0 7px;
  cursor: col-resize;
  border-right: 1px solid var(--line);
  touch-action: none;
}
.grip:hover,
.grip:focus-visible {
  background: var(--blue);
  outline: none;
}
body.resizing {
  cursor: col-resize;
  user-select: none;
}
.side {
  flex: 0 0 var(--side-w, 180px);
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 8px 0;
  font-size: 12px;
}
/* The symbol is a text character like the `+` in `+ upload`: the word's own
   size and line, one space before the word. Not a bigger icon column. */
.side .nav-glyph {
  flex: 0 0 auto;
}
.side .nav-word {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.side a,
.side button.side-head {
  display: flex;
  justify-content: flex-start;
  gap: 8px;
  align-items: center;
  padding: 5px 14px;
  min-height: 34px;
  color: var(--fg);
  text-decoration: none;
}
.side a[aria-current="page"] {
  background: var(--sel);
}
.side a .n,
.side button.side-head .n {
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
/* From 701 px the playlists are listed by name under the views, so the
   `playlists` cell would repeat the group head. The column keeps to the screen
   height and scrolls on its own when the list is long. `.wrap >` keeps this
   off the side player, which is `.bar.side` and fixed to the full height. */
.wrap > .side {
  position: sticky;
  top: 0;
  align-self: flex-start;
  max-height: 100dvh;
  box-sizing: border-box;
  overflow-y: auto;
  /* It still scrolls with the wheel and touch; no bar is drawn. */
  scrollbar-width: none;
}
.wrap > .side::-webkit-scrollbar {
  display: none;
}
.side .nav-pl {
  display: none;
}
.side-pls {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.side-pls .side-head {
  color: var(--dim);
}
button.side-head {
  font: inherit;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.side-pls.folded a:not(.side-head) {
  display: none;
}
.side-pls a:not(.side-head) {
  padding-left: 24px;
}
.view {
  flex: 1 1 0;
  min-width: 0;
  padding: 12px max(var(--gutter), env(safe-area-inset-right)) 24px max(var(--gutter), env(safe-area-inset-left));
}
.list-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin: -12px 0 10px;
  padding: 12px 0 8px;
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--bg);
}
.path {
  color: var(--blue);
  font-weight: 700;
  white-space: nowrap;
}
.count {
  color: var(--dim);
}
.search {
  flex: 1 1 12em;
  min-width: 8em;
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 0;
  padding: 7px 8px;
  min-height: 40px;
}
/* The library toolbar: search on the left, then sync, upload, download and
   select. Every button is a `.tool`: a symbol and a label. */
.lib-head {
  align-items: center;
  gap: 8px;
}
.tool {
  border: 1px solid var(--line);
  padding: 7px 12px;
  min-height: 40px;
  white-space: nowrap;
  font-weight: 700;
}
.tool:hover,
.tool:focus-visible {
  border-color: currentColor;
}
.up-btn,
.dl-btn {
  color: var(--green);
}
/* Sync, upload and download behind one `+` (list.ts `transferMenu`): a
   dropdown under it. A button with a running state stays beside `+` while
   it is shut, so `↑ 3/5` still shows at a glance. */
.xfer {
  position: relative;
  display: flex;
  gap: 8px;
  flex: 0 0 auto;
}
.xfer-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  order: 2;
  color: var(--green);
}
.xfer.open .xfer-btn {
  border-color: currentColor;
}
.xfer-menu {
  display: flex;
  gap: 8px;
  order: 1;
}
.xfer:not(.open) .xfer-menu > .tool:not(.noted) {
  display: none;
}
.xfer.open .xfer-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 20;
  flex-direction: column;
  gap: 0;
  min-width: 13em;
  padding: 4px 10px;
  background: var(--bg);
  border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgb(0 0 0 / 45%);
}
.xfer.open .xfer-menu > .tool {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: flex-start;
  width: 100%;
  padding: 0 6px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  text-align: left;
}
.xfer.open .xfer-menu > .tool:last-child {
  border-bottom: 0;
}
.lib-head .xfer.open .tool .tool-label {
  display: inline;
}
/* sync button (§12), left of the upload button */
.sync-btn {
  color: var(--blue);
}
.sync-btn.err {
  color: var(--red);
}
.search:focus-visible {
  border-color: var(--blue);
  outline: none;
}
ul.rows {
  list-style: none;
  margin: 0;
  padding: 0;
}
.row {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--rule);
}
.row .pick {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  /* The label track is fixed. An `auto` track let the label text change the
     1fr split, so the artist column started at a different x on each row.
     10em (120 px at 12 px) covers the widest label at 10 px. */
  grid-template-columns: 1em minmax(0, 1fr) minmax(0, 1.1fr) 10em 4.6em;
  gap: 10px;
  align-items: baseline;
  text-align: left;
  padding: 9px 6px;
}
.row .num {
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.row .title,
.row .artist {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row .artist {
  color: var(--dim);
}
.row .artist .prod {
  opacity: 0.7;
}
.row .dur {
  color: var(--dim);
  text-align: right;
}
/* File label and tempo (§7.14). One line, dim, never wraps. */
.row .q {
  color: var(--dim);
  font-size: 10px;
  letter-spacing: 0.04em;
  white-space: nowrap;
  text-align: right;
  opacity: 0.85;
}
.row.playing .num,
.row.playing .title {
  color: var(--yellow);
  font-weight: 700;
}
.row.sel {
  background: var(--sel);
}
.row .dots {
  color: var(--dim);
  padding: 0 4px;
  min-width: 34px;
}
.row-acts {
  display: flex;
}
.row-acts button {
  color: var(--dim);
  min-width: 28px;
  padding: 4px;
  font-size: 11px;
}
.row-acts button:disabled,
button:disabled {
  opacity: 0.35;
  cursor: default;
}
.empty {
  color: var(--dim);
  list-style: none;
  padding: 14px 6px;
}
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin: 4px 0 10px;
}
.actions button {
  border: 1px solid var(--line);
  padding: 6px 10px;
  color: var(--fg);
}
.danger {
  color: var(--red);
}
.on {
  color: var(--green);
}
.off {
  color: var(--dim);
}
.confirm {
  color: var(--red);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* multi-select and removal (§7.13) */
.pick-btn {
  color: var(--dim);
  font-weight: 400;
}
.pick-btn:hover,
.pick-btn:focus-visible {
  border-color: var(--green);
}
.pick-btn.on {
  color: var(--green);
  border-color: var(--green);
}
/* The download strip between the library toolbar and the list (§9): one
   line per job that still works, or that this page watched finish. */
.dl-strip {
  border: 1px solid var(--line);
  margin: 0 0 10px;
  padding: 4px 10px;
}
.dl-strip[hidden] { display: none; }
.dl-line {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 32px;
}
.dl-line + .dl-line { border-top: 1px solid var(--line); }
.dl-name {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  min-width: 0;
  text-align: left;
}
.dl-name .num { color: var(--green); font-weight: 700; }
.dl-name .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dl-line .bar-mini { color: var(--green); font-size: 11px; white-space: nowrap; }
.dl-line .state { color: var(--dim); white-space: nowrap; font-variant-numeric: tabular-nums; }
.dl-line.done .state, .dl-line.done .num { color: var(--green); }
.dl-line.error .state, .dl-line.error .num { color: var(--red); }
.dl-line.error .state { overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
.dl-x { color: var(--dim); padding: 0 4px; }
.dl-x:hover { color: var(--fg); }

.bulk {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  border: 1px solid var(--green);
  background: var(--hover);
  padding: 6px 8px;
  margin: 0 0 10px;
}
.bulk-n {
  color: var(--green);
  font-weight: 700;
  margin-right: 4px;
  white-space: nowrap;
}
.bulk button {
  border: 1px solid var(--line);
  padding: 6px 10px;
  min-height: 34px;
  color: var(--fg);
}
.bulk button:hover,
.bulk button:focus-visible {
  border-color: var(--fg);
}
.bulk button.danger {
  color: var(--red);
}
.row.picked {
  background: var(--sel);
}
.row.picked .num {
  color: var(--green);
}
.row.picked .title {
  font-weight: 700;
}
.warn {
  color: var(--red);
  margin: 0 0 10px;
}
.kept {
  color: var(--dim);
  font: inherit;
  margin: 0 0 10px;
  white-space: pre-wrap;
}
.prow a {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: baseline;
  padding: 10px 6px;
  min-height: 44px;
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
}
.prow .pname {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prow .prod {
  color: var(--dim);
}
.to-artists {
  margin: 16px 0 0;
}
.to-artists a {
  color: var(--fg);
}
.to-artists a + a {
  margin-left: 16px;
}
.newpl {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 16px;
}
.newpl .field {
  flex: 1 1 14em;
  grid-template-columns: auto minmax(0, 1fr);
}
.menu-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}
.menu-actions .primary,
.newpl .primary {
  border: 1px solid var(--line);
  color: var(--fg);
}

/* ---------- status bar (§7.3) ---------- */
.bar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 30;
  display: grid;
  grid-template-columns: auto auto auto minmax(6em, 1fr) auto auto minmax(7em, 16em) auto;
  grid-template-areas: "transport modes cur prog dur vol now acts";
  align-items: center;
  gap: 4px;
  padding: 4px max(8px, env(safe-area-inset-right)) calc(4px + env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));
  border-top: 1px solid var(--line);
  background: var(--bg);
  font-size: 11px;
  box-sizing: border-box;
  min-height: calc(var(--bar-h) + env(safe-area-inset-bottom));
}
.bar button {
  padding: 4px 6px;
  min-width: 36px;
  min-height: 36px;
  color: var(--fg);
}
.bar .transport {
  grid-area: transport;
  display: flex;
  gap: 2px;
}
.bar .transport button {
  min-width: 40px;
}
.bar .toggle {
  min-width: 44px;
  color: var(--yellow);
  font-weight: 700;
}
.bar .modes {
  grid-area: modes;
  display: flex;
  gap: 2px;
}
/* Shuffle and repeat: off is dim, on is green with a bar under the symbol.
   `.bar button` sets the text color, so the state needs this weight. */
.bar .modes button.off {
  color: var(--dim);
}
.bar .modes button.on {
  color: var(--green);
  text-decoration: underline 2px;
  text-underline-offset: 5px;
}
.bar .cur {
  grid-area: cur;
  color: var(--dim);
}
.bar .prog {
  grid-area: prog;
  display: flex;
  align-items: center;
  overflow: hidden;
  white-space: nowrap;
  height: 44px;
  touch-action: none;
  cursor: pointer;
  user-select: none;
}
.bar .prog .on,
.bar .prog .dot {
  color: var(--green);
}
.bar .prog .off {
  color: var(--line);
  overflow: hidden;
  text-overflow: clip;
}
.bar .dur {
  grid-area: dur;
  color: var(--dim);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.bar .vol {
  grid-area: vol;
  display: flex;
  align-items: center;
}
.bar .vol .blocks {
  color: var(--dim);
  letter-spacing: 0.5px;
  padding: 4px 2px;
}
.blocks .on {
  color: var(--green);
}
.blocks .off {
  color: var(--line);
}
.bar .now {
  grid-area: now;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  min-width: 0;
  overflow: hidden;
  text-align: left;
  padding: 2px 6px;
}
.bar .now .artist,
.bar .now .title {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar .now .artist {
  color: var(--dim);
  font-size: 10px;
}
.bar .now .title {
  font-weight: 700;
}
.bar .now .title-in {
  display: inline-block;
  transition: transform 0.4s ease-out;
}
.bar .now .title.sliding {
  text-overflow: clip;
}
.bar .now .title.sliding .title-in {
  transform: translateX(var(--slide));
  transition: transform var(--slide-time) linear 0.3s;
}
.bar .now .title.dim {
  color: var(--dim);
  font-weight: 400;
}
.bar .now .title.err {
  color: var(--red);
  font-weight: 400;
}
.bar .now .q {
  color: var(--dim);
  font-size: 10px;
  letter-spacing: 0.04em;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar .acts {
  grid-area: acts;
  display: flex;
}
.bar .acts button {
  min-width: 38px;
}

/* ---------- side player (§7.3, from 1080 px) ---------- */
/*
 * The same markup as the bar, in one column on the right of the list from
 * 1080 px, or over the whole screen on a phone (`.bar.side.full`, below).
 * `.bar.side` is set only by `mountBar()`, which reads the same queries.
 */
@media (min-width: 1080px) {
  .wrap {
    padding-bottom: 0;
    padding-right: var(--sp-w);
  }
}
.bar.side {
  inset: 0 0 0 auto;
  width: var(--sp-w);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  overflow: hidden auto;
  padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
  border-top: 0;
  border-left: 1px solid var(--line);
}
.bar.side .sp-head {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  text-align: center;
}
.bar.side .sp-head .sp-from {
  flex: 0 0 auto;
  white-space: nowrap;
}
.bar.side .sp-from {
  color: var(--dim);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.bar.side .sp-source {
  padding: 2px 4px;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 700;
  color: var(--blue);
}
.bar.side .sp-source[disabled] {
  color: var(--dim);
  cursor: default;
}
.bar.side .sp-art {
  position: relative;
  aspect-ratio: 1;
  align-self: center;
  display: block;
  width: 100%;
  overflow: hidden;
  /* 20 cells per row at about 0.6 em each: this keeps the art inside the
     panel at every `--sp-w`, with no sideways crop. */
  font-size: calc((var(--sp-w) - 40px) / 12.4);
  line-height: 1.18;
  color: var(--green);
  border: 1px solid var(--line);
  background: var(--hover);
  padding: 6px 0;
}
.bar.side .sp-art-real {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--bg);
  opacity: 0;
}
.bar.side .sp-art.has-art .sp-art-real {
  opacity: 1;
}
/* statusbar.ts writes the image shape into `aspect-ratio`; this cap keeps a
   very tall cover from pushing the transport off the bottom. */
.bar.side .sp-art.has-art {
  max-height: min(38vh, 380px);
}
.bar.side .sp-art.has-art .sp-art-row {
  visibility: hidden;
}
/* The music video (§7.18): a 16:9 picture over the cover, muted, in time
   with the audio. video.ts adds `has-video` once the player shows a frame. */
.bar.side .sp-video {
  position: absolute;
  inset: 0;
  opacity: 0;
  background: #000;
  pointer-events: none;
}
.bar.side .sp-video iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}
.bar.side .sp-art.has-video {
  aspect-ratio: 16 / 9 !important;
  padding: 0;
}
.bar.side .sp-art.has-video .sp-video {
  opacity: 1;
}
.bar.side .sp-art.has-video .sp-art-row,
.bar.side .sp-art.has-video .sp-art-real {
  visibility: hidden;
}
.bar.side .sp-art.hue-1 {
  color: var(--blue);
}
.bar.side .sp-art.hue-2 {
  color: var(--yellow);
}
.bar.side .sp-art.idle {
  color: var(--line);
}
.bar.side .sp-art-row {
  display: block;
  white-space: pre;
  text-align: center;
}
.bar.side .sp-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.bar.side .now {
  flex: 1 1 auto;
  padding: 0;
  font-size: 13px;
}
.bar.side .now .artist {
  order: 2;
  font-size: 11px;
}
.bar.side .now .title {
  order: 1;
}
.bar.side .now .q {
  order: 3;
}
.bar.side .sp-seek {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.bar.side .prog {
  height: 26px;
  font-size: 13px;
}
.bar.side .sp-times {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  color: var(--dim);
}
.bar.side .cur,
.bar.side .dur {
  padding: 0;
  min-width: 0;
  min-height: 0;
  font-size: 10px;
}
/* Equal side columns keep play/pause on the centre line, however many
   buttons sit left (shuffle, repeat) or right (queue). */
.bar.side .sp-controls {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 4px;
}
/* The bottom bar names these grid areas; here they take the three columns. */
.bar.side .sp-controls > * {
  grid-area: auto;
}
.bar.side .transport {
  gap: 4px;
}
.bar.side .transport button {
  min-width: 34px;
}
.bar.side .toggle {
  min-width: 44px;
  font-size: 15px;
}
.bar.side .modes,
.bar.side .acts {
  gap: 0;
}
.bar.side .acts {
  justify-self: end;
}
.bar.side .modes button,
.bar.side .acts button {
  min-width: 32px;
  padding: 6px 4px;
  color: var(--dim);
}
.bar.side .modes button.on {
  color: var(--green);
}
.bar.side .sp-foot {
  display: flex;
  padding-top: 2px;
}
.bar.side .vol {
  flex: 1 1 auto;
  min-width: 0;
}
.bar.side .vol button {
  min-width: 30px;
  min-height: 30px;
  padding: 4px;
}
/* The blocks take the rest of the row; `fitVolume()` fills it with blocks. */
.bar.side .vol .blocks {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-align: left;
  padding: 4px 0;
}

/* The words of the playing track, under the controls (§7.17). A found lyric
   takes what it can get; a note stays one line and leaves the room to the
   queue. The words are data, so they are never uppercased. */
.bar.side > .sp-lyrics {
  display: flex;
  flex-direction: column;
  flex: 2 1 0;
  min-height: 0;
  border-top: 1px solid var(--line);
  padding-top: 8px;
}
/* No words (none, instrumental, not checked, still loading): no block. */
.bar.side > .sp-lyrics[hidden] {
  display: none;
}
.bar.side > .sp-lyrics[data-state="loading"],
.bar.side > .sp-lyrics[data-state="none"],
.bar.side > .sp-lyrics[data-state="instrumental"],
.bar.side > .sp-lyrics[data-state="unchecked"],
.bar.side > .sp-lyrics[data-state="unknown"] {
  flex: 0 0 auto;
}
.bar.side .sp-lyrics-body {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.bar.side .sp-lyrics-body::-webkit-scrollbar {
  width: 4px;
}
.bar.side .sp-lyrics-body::-webkit-scrollbar-track {
  background: transparent;
}
.bar.side .sp-lyrics-body::-webkit-scrollbar-thumb {
  background: var(--line);
}
.bar.side .sp-lyrics-body:hover::-webkit-scrollbar-thumb {
  background: var(--dim);
}
.bar.side .sp-lyrics-text {
  margin: 0;
  color: var(--fg);
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  text-transform: none;
}
.bar.side .sp-lyrics-note {
  margin: 0;
  color: var(--dim);
  font-size: 11px;
}

/* The queue under the player: it takes the rest of the column and scrolls
   on its own. On a short screen it keeps a minimum and the column scrolls. */
.bar.side > * {
  flex-shrink: 0;
}
.bar.side > .sp-queue {
  flex: 1 1 auto;
  min-height: 140px;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
  padding-top: 8px;
}
.bar.side .sp-qhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0 0 4px;
}
/* A folded section keeps only its head; the fold button stays in it. */
.bar.side .collapsed > :not(.sp-qhead) {
  display: none;
}
.bar.side > .sp-lyrics.collapsed,
.bar.side > .sp-queue.collapsed {
  flex: 0 0 auto;
  min-height: 0;
}
/* The title of a section folds it when clicked; it looks like the plain title. */
.bar.side .sp-fold {
  min-width: 0;
  min-height: 0;
  padding: 0;
}
.bar.side .sp-fold:hover {
  color: var(--fg);
}
.bar.side .sp-qcount {
  margin-right: auto;
  padding-left: 6px;
  color: var(--dim);
  font-size: 10px;
}
.bar.side .sp-qclear {
  min-width: 0;
  min-height: 0;
  padding: 0 2px;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
}
.bar.side .sp-qclear:hover {
  color: var(--red);
}
.bar.side .sp-qclear[disabled] {
  visibility: hidden;
}
.bar.side .sp-qempty {
  margin: 4px 0;
  color: var(--dim);
  font-size: 11px;
}
.bar.side .sp-qlist {
  flex: 1 1 0;
  min-height: 0;
  margin: 0 -4px;
  padding: 0;
  list-style: none;
  overflow-y: auto;
  scrollbar-width: none;
}
.bar.side .sp-qlist::-webkit-scrollbar {
  display: none;
}
.bar.side .sp-qrow {
  display: flex;
  align-items: center;
  gap: 4px;
  border-bottom: 1px solid var(--rule);
}
.bar.side .sp-qrow:hover {
  background: var(--hover);
}
.bar.side .sp-qrow[draggable="true"] {
  cursor: grab;
}
.bar.side .sp-qrow.dragging {
  opacity: 0.4;
}
.bar.side .sp-qrow.drop-before {
  box-shadow: inset 0 2px 0 var(--blue);
}
.bar.side .sp-qrow.drop-after {
  box-shadow: inset 0 -2px 0 var(--blue);
}
.bar.side .sp-qpick {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 5px 4px;
  text-align: left;
}
.bar.side .sp-qtitle,
.bar.side .sp-qartist {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar.side .sp-qtitle {
  font-size: 12px;
  color: var(--fg);
}
.bar.side .sp-qartist {
  font-size: 10px;
  color: var(--dim);
}
.bar.side .sp-qdur {
  flex: 0 0 auto;
  font-size: 10px;
  color: var(--dim);
}
.bar.side .sp-qdel {
  flex: 0 0 auto;
  min-width: 26px;
  min-height: 26px;
  padding: 2px;
  color: var(--dim);
  visibility: hidden;
}
.bar.side .sp-qrow:hover .sp-qdel,
.bar.side .sp-qdel:focus-visible {
  visibility: visible;
}
.bar.side .sp-qdel:hover {
  color: var(--red);
}
.bar.side .sp-qmore button {
  min-height: 0;
  padding: 6px 4px;
  font-size: 11px;
  color: var(--dim);
}

/* ---------- panels, menus, now playing ---------- */
.panel {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgb(0 0 0 / 55%);
}
.panel[hidden] {
  display: none;
}
.panel-box {
  width: 100%;
  max-width: 36em;
  max-height: 74dvh;
  overflow: auto;
  background: var(--bg);
  border: 1px solid var(--line);
  padding: 8px 14px calc(14px + env(safe-area-inset-bottom));
  box-sizing: border-box;
}
/* The row menu: a dropdown under its `…` button, no dimmed backdrop. */
.panel.pop {
  display: block;
  background: transparent;
}
.panel.pop[hidden] {
  display: none;
}
.panel.pop .panel-box {
  position: fixed;
  width: max-content;
  min-width: 15em;
  max-width: min(22em, calc(100vw - 16px));
  max-height: min(60dvh, 26em);
  padding: 4px 10px 8px;
  box-shadow: 0 6px 24px rgb(0 0 0 / 45%);
}
/* The upload and download modals: wide, centred, the head stays on top. */
/* A bottom panel keeps its head, with ×, on top while its list scrolls. */
.panel:not(.pop) .panel-box {
  padding-top: 0;
}
.panel:not(.pop) .panel-head {
  position: sticky;
  top: 0;
  z-index: 1;
  padding-top: 12px;
  background: var(--bg);
}
.panel-head .count {
  flex: 1;
}
.panel.sheet {
  align-items: center;
}
.panel.sheet .panel-box {
  max-width: 46em;
  max-height: 86dvh;
  padding-top: 0;
}
.panel.sheet .panel-head {
  position: sticky;
  top: 0;
  z-index: 1;
  padding-top: 12px;
  margin-bottom: 12px;
  background: var(--bg);
}
.panel.sheet .panel-head .count {
  flex: 1;
}
.panel.pop .panel-head .path {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.panel.pop .menu button {
  padding: 8px 6px;
}
.panel.pop .menu > button:last-child {
  border-bottom: 0;
}
.panel-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 4px 0 8px;
  border-bottom: 1px solid var(--line);
}
.menu {
  display: flex;
  flex-direction: column;
}
.menu button {
  display: flex;
  justify-content: flex-start;
  gap: 8px;
  align-items: baseline;
  width: 100%;
  text-align: left;
  padding: 10px 6px;
  border-bottom: 1px solid var(--rule);
}
.menu button .pname {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.menu button .tick {
  color: var(--green);
}
.menu button.has {
  color: var(--green);
}
/* The answer buttons of a confirm panel are two framed chips on the right,
   not menu rows: `no` reads plain, `yes` keeps the red of what it does. */
.panel .menu .menu-actions button {
  width: auto;
  justify-content: center;
  min-height: 34px;
  padding: 6px 16px;
  border: 1px solid var(--line);
}
.panel .menu .menu-actions button:hover,
.panel .menu .menu-actions button:focus-visible {
  border-color: var(--fg);
}
.panel .menu .menu-actions button.danger {
  border-color: var(--red);
}
.panel .menu .menu-actions button.danger:hover,
.panel .menu .menu-actions button.danger:focus-visible {
  background: var(--red);
  color: var(--bg);
}
.menu .kept {
  color: var(--fg);
  margin: 10px 0 6px;
}
.panel-foot {
  padding: 8px 0 0;
}
.qrow {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--rule);
}
.qrow .qpick {
  flex: 1;
  min-width: 0;
  display: grid;
  grid-template-columns: 1em minmax(0, 1.4fr) minmax(0, 1fr) 3.4em;
  gap: 8px;
  align-items: baseline;
  text-align: left;
  padding: 8px 4px;
}
.qrow .title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.qrow .artist,
.qrow .dur {
  color: var(--dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.qrow.playing .title,
.qrow.playing .num {
  color: var(--yellow);
  font-weight: 700;
}
.qacts {
  display: flex;
}
.qacts button {
  min-width: 30px;
  padding: 4px;
  color: var(--dim);
}
/* ---------- settings (§7.7) ---------- */
.srow {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
}
.srow .k {
  color: var(--dim);
  min-width: 6.5em;
}
.sec {
  font-size: 11px;
  font-weight: 700;
  color: var(--blue);
  margin: 20px 0 4px;
}
.tokens {
  margin-bottom: 6px;
}
.token-box {
  border: 1px dashed var(--line);
  padding: 8px;
  margin: 12px 0;
}
.token-value {
  display: block;
  color: var(--green);
  word-break: break-all;
  margin: 6px 0;
  font-size: 10px;
}
.hint {
  margin: 0;
}

/* Desktop: the sidebar and its grip stay in view while the list scrolls. */
/* `.wrap >` keeps this off the side player, which is `.bar.side` and fixed. */
@media (min-width: 701px) {
  .wrap > .side,
  .wrap > .grip {
    position: sticky;
    top: 0;
    align-self: flex-start;
    height: 100dvh;
    box-sizing: border-box;
  }
  .wrap > .side {
    overflow-y: auto;
  }
}

/* ---------- small screens (§2.6) ---------- */
/* One phone layout up to 700 px. Text is 13 px with 11 px for the second
   line; every control is at least `--hit` tall. */
@media (max-width: 700px) {
  :root {
    --gutter: 12px;
    --bar-h: 52px; /* the mini player: one row */
  }
  body.app {
    font-size: 13px;
  }
  .wrap {
    flex-direction: column;
    align-content: start;
  }
  .grip {
    display: none;
  }
  nav.side {
    position: static;
    max-height: none;
    overflow: visible;
    align-self: stretch;
    flex: 0 0 auto;
    width: 100%;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    border-bottom: 1px solid var(--line);
    padding: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left);
    box-sizing: border-box;
    gap: 0;
  }
  nav.side .nav-pl {
    display: flex;
  }
  .side-pls {
    display: none;
  }
  /* The four cells show the symbol only, with the count beside it. The
     word stays for screen readers and the tooltip. */
  nav.side a {
    justify-content: center;
    gap: 6px;
    padding: 0 2px;
    white-space: nowrap;
    min-height: var(--hit);
  }
  nav.side .nav-glyph {
    font-size: 16px;
    line-height: 1;
  }
  nav.side .nav-word {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  nav.side a .n {
    font-size: var(--fs-s);
  }
  /* The toolbar keeps the symbols only, on one row with the search box; a
     running state still shows. Box and buttons share one height. */
  .view {
    padding-top: 8px;
  }
  .list-head {
    gap: 8px;
    margin: -8px 0 6px;
    padding: 8px 0;
  }
  .lib-head {
    flex-wrap: nowrap;
  }
  .lib-head .search {
    min-width: 0;
    min-height: var(--hit);
  }
  .lib-head .tool {
    flex: 0 0 auto;
    min-width: var(--hit);
    min-height: var(--hit);
    padding: 7px 10px;
  }
  .lib-head .tool:not(.noted) .tool-label {
    display: none;
  }
  /* A phone row is two lines: title and time, then the artist. The row
     starts at the gutter like the toolbar above it; the playing row shows
     by its bold yellow title. The mark column comes back while a selection
     is open, because it holds the pick box (§7.13). */
  .row .pick {
    grid-template-columns: minmax(0, 1fr) 4.2em;
    grid-template-areas:
      "title dur"
      "artist artist";
    column-gap: 10px;
    row-gap: 2px;
    padding: 9px 0;
  }
  .row .num {
    display: none;
  }
  ul.rows:has(.pick[aria-pressed]) .pick {
    /* 1.2em: the pick box needs the width of a full block glyph. */
    grid-template-columns: 1.2em minmax(0, 1fr) 4.2em;
    grid-template-areas:
      "num title dur"
      ". artist artist";
  }
  ul.rows:has(.pick[aria-pressed]) .num {
    display: block;
    grid-area: num;
  }
  /* The file label would take its room from the artist; the side panel
     keeps it from 1080 px (§7.14). */
  .row .q {
    display: none;
  }
  .row .title {
    grid-area: title;
  }
  .row .artist {
    grid-area: artist;
    font-size: var(--fs-s);
  }
  .row .dur {
    grid-area: dur;
  }
  .row .dots {
    align-self: stretch;
    min-width: var(--hit);
    padding: 0;
  }
  .row-acts button {
    min-width: 36px;
    padding: 2px;
  }
  .bulk button {
    min-height: var(--hit);
  }
  /* The mini player (owner request, 2026-11-05): the track and play/pause
     on one row; the seek bar is a thin line on the bar's top edge that a
     finger can drag. A tap on the track opens the full-screen player,
     which holds every other control. */
  .bar:not(.side) {
    display: flex;
    align-items: center;
    gap: 4px;
    border-top: 0;
    padding-top: 0;
    padding-bottom: env(safe-area-inset-bottom);
    font-size: 12px;
  }
  .bar:not(.side) button {
    min-height: var(--hit);
  }
  .bar:not(.side) [data-act="prev"],
  .bar:not(.side) [data-act="next"],
  .bar:not(.side) .modes,
  .bar:not(.side) .cur,
  .bar:not(.side) .dur,
  .bar:not(.side) .vol,
  .bar:not(.side) .acts {
    display: none;
  }
  .bar:not(.side) .now {
    order: 1;
    flex: 1 1 0;
    flex-direction: row;
    align-items: center;
    align-self: stretch;
    gap: 8px;
    padding: 0 4px;
  }
  .bar:not(.side) .now .title {
    order: 1;
    flex: 0 1 auto;
  }
  .bar:not(.side) .now .artist {
    order: 2;
    flex: 0 10 auto;
    min-width: 0;
    font-size: var(--fs-s);
  }
  .bar:not(.side) .transport {
    order: 2;
  }
  .bar:not(.side) .toggle {
    min-width: var(--hit);
    padding: 0;
    font-size: 18px;
  }
  /* 24 px tall for the finger, the line itself on the bar's top edge. */
  .bar:not(.side) .prog {
    position: absolute;
    top: -12px;
    left: 0;
    right: 0;
    height: 24px;
    line-height: 24px;
    font-size: 13px;
    z-index: 1;
  }
  /* The full-screen player: the side player's column over the whole page. */
  body.player-full {
    overflow: hidden;
  }
  .bar.side.full {
    --art: min(100vw - 2 * var(--gutter) - 16px, 40vh);
    --sp-w: calc(var(--art) + 40px); /* the text cover's size reads this */
    inset: 0;
    width: auto;
    z-index: 40;
    gap: 16px;
    border-left: 0;
    padding: calc(4px + env(safe-area-inset-top)) max(calc(var(--gutter) + 8px), env(safe-area-inset-right))
      calc(20px + env(safe-area-inset-bottom)) max(calc(var(--gutter) + 8px), env(safe-area-inset-left));
    overscroll-behavior: contain;
  }
  .bar.side.full .sp-head {
    position: relative;
    min-height: var(--hit);
    align-items: center;
    padding: 0 var(--hit);
  }
  .bar.side.full .sp-close {
    position: absolute;
    left: 0;
    top: 0;
    min-width: var(--hit);
    min-height: var(--hit);
    padding: 0;
    font-size: 20px;
    color: var(--fg);
  }
  .bar.side.full .sp-art {
    width: var(--art);
    margin-top: 8px;
  }
  .bar.side.full .sp-art.has-art {
    max-height: var(--art);
  }
  .bar.side.full .now {
    font-size: 17px;
  }
  .bar.side.full .now .artist {
    font-size: 13px;
  }
  .bar.side.full .now .q {
    font-size: var(--fs-s);
  }
  .bar.side.full .prog {
    height: 32px;
  }
  .bar.side.full .cur,
  .bar.side.full .dur {
    font-size: var(--fs-s);
  }
  /* Two rows, as in a phone music app: ⇄ ◄◄ ► ►► ↻, then … and ≡ at the
     edges. The three groups give their buttons to one grid, so the row
     fits a 320 px screen. */
  .bar.side.full .sp-controls {
    grid-template-columns: repeat(5, auto);
    justify-content: space-between;
    row-gap: 4px;
  }
  .bar.side.full .sp-controls > .modes,
  .bar.side.full .sp-controls > .transport,
  .bar.side.full .sp-controls > .acts {
    display: contents;
  }
  .bar.side.full [data-act="shuffle"] { grid-area: 1 / 1; }
  .bar.side.full [data-act="prev"] { grid-area: 1 / 2; }
  .bar.side.full [data-act="toggle"] { grid-area: 1 / 3; }
  .bar.side.full [data-act="next"] { grid-area: 1 / 4; }
  .bar.side.full [data-act="repeat"] { grid-area: 1 / 5; }
  .bar.side.full [data-act="track-menu"] { grid-area: 2 / 1; }
  .bar.side.full [data-act="queue"] { grid-area: 2 / 5; }
  .bar.side.full .transport button {
    min-width: 52px;
    min-height: 52px;
    font-size: 20px;
  }
  .bar.side.full .transport .toggle {
    min-width: 64px;
    min-height: 64px;
    font-size: 28px;
  }
  .bar.side.full .modes button,
  .bar.side.full .acts button {
    min-width: var(--hit);
    min-height: var(--hit);
    font-size: 18px;
  }
  /* The words follow the controls and the whole player scrolls. */
  .bar.side.full > .sp-lyrics {
    flex: 0 0 auto;
  }
  .bar.side.full .sp-lyrics-body {
    flex: none;
    overflow: visible;
  }
  .bar.side.full .sp-lyrics-text {
    font-size: 14px;
  }
  /* The queue panel: two-line rows like the library, the actions a full
     touch target each. */
  .panel:not(.pop):not(.sheet) .panel-box {
    max-height: 85dvh;
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    padding-right: max(var(--gutter), env(safe-area-inset-right));
  }
  .qrow .qpick {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "title dur"
      "artist artist";
    column-gap: 10px;
    row-gap: 2px;
    padding: 9px 0;
  }
  .qrow .num {
    display: none;
  }
  .qrow .title {
    grid-area: title;
  }
  .qrow .artist {
    grid-area: artist;
    font-size: var(--fs-s);
  }
  .qrow .dur {
    grid-area: dur;
  }
  .qacts button {
    min-width: 40px;
    min-height: var(--hit);
    padding: 0;
    font-size: 15px;
  }
  .bar .cur,
  .bar .dur {
    min-width: 0;
    font-size: var(--fs-s);
    padding: 0 2px;
  }
  /* A modal fills the phone screen; its head clears the notch. */
  .panel.sheet {
    align-items: stretch;
  }
  .panel.sheet .panel-box {
    max-width: none;
    max-height: none;
    height: 100dvh;
  }
  .panel.sheet .panel-head {
    padding-top: calc(12px + env(safe-area-inset-top));
  }
  .newpl .field {
    flex-basis: 100%;
  }
  /* A settings row puts its name on its own line, the controls under it. */
  .srow .k {
    flex-basis: 100%;
  }
  /* Login and sign-up: the label sits over its field. */
  .field {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
  .field > span {
    padding-top: 6px;
  }
  .auth-actions {
    padding-left: 0;
  }
  .dl-line .bar-mini {
    display: none;
  }
}

/* ---------- web upload (§7.12) ---------- */
.drop {
  position: relative;
  display: grid;
  gap: 6px;
  place-items: center;
  text-align: center;
  padding: 44px 16px;
  border: 1px dashed var(--line);
  cursor: pointer;
  margin-bottom: 14px;
}
.drop input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.drop:hover,
.drop.over,
.drop:focus-within {
  border-color: var(--blue);
  background: var(--hover);
}
.drop-big {
  color: var(--blue);
  font-weight: 700;
}
.drop-hint {
  color: var(--dim);
}
.uploads {
  list-style: none;
  margin: 0;
  padding: 0;
}
.uprow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 9em;
  gap: 10px;
  align-items: center;
  padding: 6px 0;
  border-bottom: 1px solid var(--rule);
}
.uprow .name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.uprow .bar-mini .off {
  color: var(--line);
}
.uprow .state {
  color: var(--dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: right;
}
.uprow.ok .state {
  color: var(--green);
}
.uprow.err .state {
  color: var(--red);
}
@media (max-width: 700px) {
  .uprow {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .uprow .bar-mini {
    display: none;
  }
}

/* ---------- themes (settings → theme). Palettes from well-known terminal themes. ---------- */
html[data-theme="dracula"] {
  --bg: #282a36;
  --fg: #f8f8f2;
  --dim: #a2a9cc;
  --line: #44475a;
  --blue: #bd93f9;
  --yellow: #f1fa8c;
  --green: #50fa7b;
  --red: #ff6e6e;
  --sel: #44475a;
  --hover: #343746;
  --rule: #343746;
  color-scheme: dark;
}
html[data-theme="solarized"] {
  --bg: #002b36;
  --fg: #93a1a1;
  --dim: #839496;
  --line: #0f4553;
  --blue: #4ba3e3;
  --yellow: #d9a400;
  --green: #9cb300;
  --red: #ff5f57;
  --sel: #073642;
  --hover: #03303b;
  --rule: #073642;
  color-scheme: dark;
}
html[data-theme="gruvbox"] {
  --bg: #282828;
  --fg: #ebdbb2;
  --dim: #a89984;
  --line: #504945;
  --blue: #83a598;
  --yellow: #fabd2f;
  --green: #b8bb26;
  --red: #ff6450;
  --sel: #504945;
  --hover: #32302f;
  --rule: #3c3836;
  color-scheme: dark;
}
html[data-theme="nord"] {
  --bg: #2e3440;
  --fg: #d8dee9;
  --dim: #a0a8b8;
  --line: #4c566a;
  --blue: #88c0d0;
  --yellow: #ebcb8b;
  --green: #a3be8c;
  --red: #e88a92;
  --sel: #434c5e;
  --hover: #3b4252;
  --rule: #3b4252;
  color-scheme: dark;
}
html[data-theme="tokyo"] {
  --bg: #1a1b26;
  --fg: #c0caf5;
  --dim: #8890b8;
  --line: #3b4261;
  --blue: #7aa2f7;
  --yellow: #e0af68;
  --green: #9ece6a;
  --red: #f7768e;
  --sel: #283457;
  --hover: #232433;
  --rule: #24283b;
  color-scheme: dark;
}
html[data-theme="monokai"] {
  --bg: #272822;
  --fg: #f8f8f2;
  --dim: #a59f85;
  --line: #49483e;
  --blue: #66d9ef;
  --yellow: #e6db74;
  --green: #a6e22e;
  --red: #ff4f8b;
  --sel: #49483e;
  --hover: #3e3d32;
  --rule: #3e3d32;
  color-scheme: dark;
}
html[data-theme="catppuccin"] {
  --bg: #1e1e2e;
  --fg: #cdd6f4;
  --dim: #9399b2;
  --line: #45475a;
  --blue: #89b4fa;
  --yellow: #f9e2af;
  --green: #a6e3a1;
  --red: #f38ba8;
  --sel: #45475a;
  --hover: #313244;
  --rule: #313244;
  color-scheme: dark;
}
html[data-theme="phosphor"] {
  --bg: #050a05;
  --fg: #33ff66;
  --dim: #22a845;
  --line: #1a4d24;
  --blue: #66ffaa;
  --yellow: #ccff66;
  --green: #00ff41;
  --red: #ff6b6b;
  --sel: #123d1b;
  --hover: #0d1f10;
  --rule: #0f2614;
  color-scheme: dark;
}
html[data-theme="amber"] {
  --bg: #140d00;
  --fg: #ffb000;
  --dim: #b88000;
  --line: #4a3300;
  --blue: #ffcc55;
  --yellow: #ffe08a;
  --green: #ffd24d;
  --red: #ff6a3d;
  --sel: #3d2a00;
  --hover: #241800;
  --rule: #2a1d00;
  color-scheme: dark;
}
html[data-theme="light"] {
  --bg: #fdf6e3;
  --fg: #4c5f66;
  --dim: #5f747c;
  --line: #d6cfb8;
  --blue: #1a63a0;
  --yellow: #7a5c00;
  --green: #556400;
  --red: #b8231c;
  --sel: #e6dfc8;
  --hover: #f3ecd6;
  --rule: #eee8d5;
  color-scheme: light;
}
html[data-theme="onedark"] {
  --bg: #282c34;
  --fg: #abb2bf;
  --dim: #8b929e;
  --line: #3e4451;
  --blue: #61afef;
  --yellow: #e5c07b;
  --green: #98c379;
  --red: #e06c75;
  --sel: #3e4451;
  --hover: #2c313c;
  --rule: #2c313a;
  color-scheme: dark;
}
html[data-theme="rosepine"] {
  --bg: #191724;
  --fg: #e0def4;
  --dim: #908caa;
  --line: #403d52;
  --blue: #9ccfd8;
  --yellow: #f6c177;
  --green: #31b5a5;
  --red: #eb6f92;
  --sel: #2a283e;
  --hover: #1f1d2e;
  --rule: #26233a;
  color-scheme: dark;
}
html[data-theme="kanagawa"] {
  --bg: #1f1f28;
  --fg: #dcd7ba;
  --dim: #a6a69c;
  --line: #363646;
  --blue: #7e9cd8;
  --yellow: #e6c384;
  --green: #98bb6c;
  --red: #ff5d62;
  --sel: #2d4f67;
  --hover: #2a2a37;
  --rule: #2a2a37;
  color-scheme: dark;
}
html[data-theme="everforest"] {
  --bg: #2d353b;
  --fg: #d3c6aa;
  --dim: #9da9a0;
  --line: #475258;
  --blue: #7fbbb3;
  --yellow: #dbbc7f;
  --green: #a7c080;
  --red: #e67e80;
  --sel: #475258;
  --hover: #343f44;
  --rule: #3d484d;
  color-scheme: dark;
}
html[data-theme="ayu"] {
  --bg: #1f2430;
  --fg: #cccac2;
  --dim: #8a9199;
  --line: #3a4150;
  --blue: #73d0ff;
  --yellow: #ffd173;
  --green: #d5ff80;
  --red: #f28779;
  --sel: #33415e;
  --hover: #262c3a;
  --rule: #2a3140;
  color-scheme: dark;
}
html[data-theme="nightowl"] {
  --bg: #011627;
  --fg: #d6deeb;
  --dim: #8a9ab0;
  --line: #1d3b53;
  --blue: #82aaff;
  --yellow: #ecc48d;
  --green: #addb67;
  --red: #ef5350;
  --sel: #1d3b53;
  --hover: #0b2942;
  --rule: #0b2942;
  color-scheme: dark;
}
html[data-theme="github"] {
  --bg: #0d1117;
  --fg: #c9d1d9;
  --dim: #8b949e;
  --line: #30363d;
  --blue: #58a6ff;
  --yellow: #e3b341;
  --green: #3fb950;
  --red: #ff7b72;
  --sel: #1f3a5f;
  --hover: #161b22;
  --rule: #161b22;
  color-scheme: dark;
}
html[data-theme="synthwave"] {
  --bg: #262335;
  --fg: #f0eff1;
  --dim: #a59fc0;
  --line: #4a4366;
  --blue: #36f9f6;
  --yellow: #fede5d;
  --green: #72f1b8;
  --red: #fe4450;
  --sel: #463465;
  --hover: #2f2a44;
  --rule: #34294f;
  color-scheme: dark;
}
html[data-theme="latte"] {
  --bg: #eff1f5;
  --fg: #4c4f69;
  --dim: #5c5f77;
  --line: #ccd0da;
  --blue: #1e66f5;
  --yellow: #8a5a00;
  --green: #2d7a1f;
  --red: #c4122f;
  --sel: #dce0e8;
  --hover: #e6e9ef;
  --rule: #e6e9ef;
  color-scheme: light;
}
html[data-theme="githublight"] {
  --bg: #ffffff;
  --fg: #1f2328;
  --dim: #59636e;
  --line: #d1d9e0;
  --blue: #0969da;
  --yellow: #7d4e00;
  --green: #1a7f37;
  --red: #cf222e;
  --sel: #ddf4ff;
  --hover: #f6f8fa;
  --rule: #eaeef2;
  color-scheme: light;
}
.th-vscode { background: #1e1e1e; color: #cccccc; }
.th-vscode i:nth-child(1) { color: #3b8eea; }
.th-vscode i:nth-child(2) { color: #23d18b; }
.th-vscode i:nth-child(3) { color: #f5f543; }
.th-vscode i:nth-child(4) { color: #f14c4c; }
.th-dracula { background: #282a36; color: #f8f8f2; }
.th-dracula i:nth-child(1) { color: #bd93f9; }
.th-dracula i:nth-child(2) { color: #50fa7b; }
.th-dracula i:nth-child(3) { color: #f1fa8c; }
.th-dracula i:nth-child(4) { color: #ff6e6e; }
.th-solarized { background: #002b36; color: #93a1a1; }
.th-solarized i:nth-child(1) { color: #4ba3e3; }
.th-solarized i:nth-child(2) { color: #9cb300; }
.th-solarized i:nth-child(3) { color: #d9a400; }
.th-solarized i:nth-child(4) { color: #ff5f57; }
.th-gruvbox { background: #282828; color: #ebdbb2; }
.th-gruvbox i:nth-child(1) { color: #83a598; }
.th-gruvbox i:nth-child(2) { color: #b8bb26; }
.th-gruvbox i:nth-child(3) { color: #fabd2f; }
.th-gruvbox i:nth-child(4) { color: #ff6450; }
.th-nord { background: #2e3440; color: #d8dee9; }
.th-nord i:nth-child(1) { color: #88c0d0; }
.th-nord i:nth-child(2) { color: #a3be8c; }
.th-nord i:nth-child(3) { color: #ebcb8b; }
.th-nord i:nth-child(4) { color: #e88a92; }
.th-tokyo { background: #1a1b26; color: #c0caf5; }
.th-tokyo i:nth-child(1) { color: #7aa2f7; }
.th-tokyo i:nth-child(2) { color: #9ece6a; }
.th-tokyo i:nth-child(3) { color: #e0af68; }
.th-tokyo i:nth-child(4) { color: #f7768e; }
.th-monokai { background: #272822; color: #f8f8f2; }
.th-monokai i:nth-child(1) { color: #66d9ef; }
.th-monokai i:nth-child(2) { color: #a6e22e; }
.th-monokai i:nth-child(3) { color: #e6db74; }
.th-monokai i:nth-child(4) { color: #ff4f8b; }
.th-catppuccin { background: #1e1e2e; color: #cdd6f4; }
.th-catppuccin i:nth-child(1) { color: #89b4fa; }
.th-catppuccin i:nth-child(2) { color: #a6e3a1; }
.th-catppuccin i:nth-child(3) { color: #f9e2af; }
.th-catppuccin i:nth-child(4) { color: #f38ba8; }
.th-phosphor { background: #050a05; color: #33ff66; }
.th-phosphor i:nth-child(1) { color: #66ffaa; }
.th-phosphor i:nth-child(2) { color: #00ff41; }
.th-phosphor i:nth-child(3) { color: #ccff66; }
.th-phosphor i:nth-child(4) { color: #ff6b6b; }
.th-amber { background: #140d00; color: #ffb000; }
.th-amber i:nth-child(1) { color: #ffcc55; }
.th-amber i:nth-child(2) { color: #ffd24d; }
.th-amber i:nth-child(3) { color: #ffe08a; }
.th-amber i:nth-child(4) { color: #ff6a3d; }
.th-light { background: #fdf6e3; color: #4c5f66; }
.th-light i:nth-child(1) { color: #1a63a0; }
.th-light i:nth-child(2) { color: #556400; }
.th-light i:nth-child(3) { color: #7a5c00; }
.th-light i:nth-child(4) { color: #b8231c; }
.th-onedark { background: #282c34; color: #abb2bf; }
.th-onedark i:nth-child(1) { color: #61afef; }
.th-onedark i:nth-child(2) { color: #98c379; }
.th-onedark i:nth-child(3) { color: #e5c07b; }
.th-onedark i:nth-child(4) { color: #e06c75; }
.th-rosepine { background: #191724; color: #e0def4; }
.th-rosepine i:nth-child(1) { color: #9ccfd8; }
.th-rosepine i:nth-child(2) { color: #31b5a5; }
.th-rosepine i:nth-child(3) { color: #f6c177; }
.th-rosepine i:nth-child(4) { color: #eb6f92; }
.th-kanagawa { background: #1f1f28; color: #dcd7ba; }
.th-kanagawa i:nth-child(1) { color: #7e9cd8; }
.th-kanagawa i:nth-child(2) { color: #98bb6c; }
.th-kanagawa i:nth-child(3) { color: #e6c384; }
.th-kanagawa i:nth-child(4) { color: #ff5d62; }
.th-everforest { background: #2d353b; color: #d3c6aa; }
.th-everforest i:nth-child(1) { color: #7fbbb3; }
.th-everforest i:nth-child(2) { color: #a7c080; }
.th-everforest i:nth-child(3) { color: #dbbc7f; }
.th-everforest i:nth-child(4) { color: #e67e80; }
.th-ayu { background: #1f2430; color: #cccac2; }
.th-ayu i:nth-child(1) { color: #73d0ff; }
.th-ayu i:nth-child(2) { color: #d5ff80; }
.th-ayu i:nth-child(3) { color: #ffd173; }
.th-ayu i:nth-child(4) { color: #f28779; }
.th-nightowl { background: #011627; color: #d6deeb; }
.th-nightowl i:nth-child(1) { color: #82aaff; }
.th-nightowl i:nth-child(2) { color: #addb67; }
.th-nightowl i:nth-child(3) { color: #ecc48d; }
.th-nightowl i:nth-child(4) { color: #ef5350; }
.th-github { background: #0d1117; color: #c9d1d9; }
.th-github i:nth-child(1) { color: #58a6ff; }
.th-github i:nth-child(2) { color: #3fb950; }
.th-github i:nth-child(3) { color: #e3b341; }
.th-github i:nth-child(4) { color: #ff7b72; }
.th-synthwave { background: #262335; color: #f0eff1; }
.th-synthwave i:nth-child(1) { color: #36f9f6; }
.th-synthwave i:nth-child(2) { color: #72f1b8; }
.th-synthwave i:nth-child(3) { color: #fede5d; }
.th-synthwave i:nth-child(4) { color: #fe4450; }
.th-latte { background: #eff1f5; color: #4c4f69; }
.th-latte i:nth-child(1) { color: #1e66f5; }
.th-latte i:nth-child(2) { color: #2d7a1f; }
.th-latte i:nth-child(3) { color: #8a5a00; }
.th-latte i:nth-child(4) { color: #c4122f; }
.th-githublight { background: #ffffff; color: #1f2328; }
.th-githublight i:nth-child(1) { color: #0969da; }
.th-githublight i:nth-child(2) { color: #1a7f37; }
.th-githublight i:nth-child(3) { color: #7d4e00; }
.th-githublight i:nth-child(4) { color: #cf222e; }
.themes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13em, 1fr));
  gap: 6px;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
}
.th {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  text-align: left;
  border: 1px solid var(--line);
  padding: 8px 10px;
}
.th i {
  font-style: normal;
}
.th[aria-pressed="true"] {
  outline: 2px solid var(--green);
  outline-offset: 1px;
}

/* ---------- soulseek download (§11) ---------- */
.slsk-form {
  display: flex;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 10px;
}
.slsk-form .prompt { color: var(--green); }
.slsk-form input {
  flex: 1;
  min-width: 0;
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 0;
  padding: 7px 8px;
  min-height: 40px;
  font: inherit;
}
.slsk-form input:focus-visible { border-color: var(--blue); outline: none; }
.slsk-form button {
  border: 1px solid var(--line);
  color: var(--green);
  font-weight: 700;
  padding: 7px 12px;
  min-height: 40px;
  white-space: nowrap;
}
.slsk-form button:hover { border-color: var(--green); }
.slsk-status { color: var(--dim); margin: 10px 0 4px; min-height: 1.4em; }
.slsk-status.err { color: var(--red); }
ul.slsk-results, ul.slsk-jobs { list-style: none; margin: 0; padding: 0; }
.slsk-row, .slsk-job {
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--rule);
  padding: 8px 2px;
}
.slsk-row .num, .slsk-job .num {
  width: 2.2em;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.slsk-main { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.slsk-main .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slsk-main .artist { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slsk-row .dur { color: var(--dim); font-variant-numeric: tabular-nums; }
.slsk-get { color: var(--green); font-weight: 700; }
.slsk-folder {
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--line);
  padding: 14px 2px 6px;
}
.slsk-folder .title { color: var(--fg); font-weight: 700; }
.slsk-get-folder {
  border: 1px solid var(--line);
  color: var(--green);
  font-weight: 700;
  padding: 4px 10px;
  min-height: 32px;
  white-space: nowrap;
}
.slsk-get-folder:hover { border-color: var(--green); }
.slsk-job .state { color: var(--dim); white-space: nowrap; }
.slsk-job.done .state, .slsk-job.done .num { color: var(--green); }
.slsk-job.error .state, .slsk-job.error .num { color: var(--red); }
.slsk-job .bar-mini { color: var(--green); font-size: 11px; }
.slsk-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  color: var(--dim);
  margin: 18px 0 4px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 4px;
}
@media (max-width: 700px) {
  .slsk-row .dur { display: none; }
}

/* The sync modal (§12): the block bar, one row per song, and the error of the run in full. */
.sync-btn.busy { color: var(--yellow); }
.sync-line { margin: 4px 0 8px; }
.sync-line.err { color: var(--red); }
.sync-bar { margin: 0 0 12px; color: var(--dim); font-variant-numeric: tabular-nums; }
.sync-bar .on { color: var(--green); }
ol.sync-songs { list-style: none; margin: 0 0 12px; padding: 0; border-top: 1px solid var(--line); }
.sync-song {
  display: grid;
  grid-template-columns: 2ch minmax(0, 1fr) auto;
  column-gap: 8px;
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
}
.sync-song .name { overflow-wrap: anywhere; }
.sync-song .what { color: var(--dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.sync-song .mark { color: var(--dim); }
.sync-song .why { grid-column: 2 / -1; margin-top: 3px; color: var(--dim); font-size: 12px; overflow-wrap: anywhere; }
.sync-song.wait .name { color: var(--dim); }
.sync-song.find .mark, .sync-song.find .what,
.sync-song.get .mark, .sync-song.get .what,
.sync-song.save .mark, .sync-song.save .what { color: var(--yellow); }
.sync-song-bar .on { color: var(--green); }
.sync-song-bar .off { color: var(--dim); }
.sync-song.done .mark, .sync-song.done .what { color: var(--green); }
.sync-song.failed .mark, .sync-song.failed .what { color: var(--red); }
.sync-fold { margin: 0 0 12px; border-top: 1px solid var(--line); padding-top: 8px; }
.sync-fold summary {
  list-style: none;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sync-fold summary::-webkit-details-marker { display: none; }
.sync-fold summary:hover { color: var(--fg); text-decoration: underline; }
.sync-fold summary .err { color: var(--red); }
.sync-fold summary .sync-first { color: var(--dim); }
.sync-fold[open] summary .sync-first { display: none; }
.sync-pre {
  margin: 8px 0 0;
  padding: 8px 10px;
  max-height: 40dvh;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font: inherit;
  font-size: 12px;
  color: var(--fg);
  background: var(--hover);
  border-left: 2px solid var(--red);
  user-select: text;
}
.sync-again { margin: 6px 0 4px; }
.sync-again button { border: 1px solid var(--line); padding: 4px 10px; }
.sync-again button:hover { border-color: var(--green); }

/* ---------- screen saver (§7.19) ----------
   A 3 × 3 grid over the whole page. The disc, the player and the next-up list
   fill the middle column; a click on any other cell closes it (screensaver.ts).
   Everything fits one screen: the disc takes the height the player and the
   list leave, up to 440 px, so the page never scrolls. */
.saver {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  grid-template-columns: 1fr minmax(0, 460px) 1fr;
  grid-template-rows: 1fr minmax(140px, 440px) auto auto 1fr;
  row-gap: 20px;
  padding: 24px;
  background: var(--bg);
  color: var(--fg);
  overflow: hidden;
}
.saver[hidden] {
  display: none;
}
.saver > div {
  grid-column: 2;
  min-width: 0;
}
.ss-cdbox {
  grid-row: 2;
  container-type: size;
  display: flex;
  justify-content: center;
  align-items: center;
}
.ss-player {
  grid-row: 3;
}
.ss-next {
  grid-row: 4;
}

/* The disc, after the reference CD: a 120 mm disc with a 15 mm hole, clear
   plastic to 30 mm, a silver band to 40 mm with the stacking ring at 33 mm,
   the print from 40 mm to 116 mm, and the last millimetre clear plastic again.
   The radii below are those sizes as a share of the 60 mm radius. */
.ss-cd {
  position: relative;
  width: min(100cqw, 100cqh);
  aspect-ratio: 1;
  border-radius: 50%;
  container-type: inline-size;
  filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.4));
  background:
    radial-gradient(circle closest-side, transparent 12.5%, rgba(255, 255, 255, 0.55) 12.7%, rgba(255, 255, 255, 0.55) 13.1%, transparent 13.4%),
    radial-gradient(
      circle closest-side,
      transparent 12.5%,
      rgba(214, 219, 226, 0.34) 12.6%,
      rgba(214, 219, 226, 0.22) 24.9%,
      transparent 25%,
      transparent 96.7%,
      rgba(170, 176, 186, 0.5) 96.8%,
      rgba(214, 219, 226, 0.3) 99.3%,
      transparent 100%
    );
}
/* The silver: aluminium under lacquer, a satin mirror of the room. */
.ss-cd::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle closest-side, transparent 27.2%, rgba(0, 0, 0, 0.3) 27.7%, transparent 28.2%),
    conic-gradient(from 210deg, #9aa0a8, #eef1f4 12%, #b4b9c0 25%, #f7f9fb 37%, #a3a8b0 50%, #eef1f4 62%, #b4b9c0 75%, #f7f9fb 87%, #9aa0a8);
  -webkit-mask: radial-gradient(circle closest-side, transparent 24.9%, #000 25%, #000 33.6%, transparent 33.7%, transparent 96.4%, #000 96.5%, #000 96.8%, transparent 96.9%);
  mask: radial-gradient(circle closest-side, transparent 24.9%, #000 25%, #000 33.6%, transparent 33.7%, transparent 96.4%, #000 96.5%, #000 96.8%, transparent 96.9%);
}
/* The print. Only this layer turns: 18° a second, as in the reference. */
.ss-print {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(100cqw / 12);
  line-height: 1;
  color: var(--green);
  background: var(--hover);
  -webkit-mask: radial-gradient(circle closest-side, transparent 33.4%, #000 33.6%, #000 96.4%, transparent 96.6%);
  mask: radial-gradient(circle closest-side, transparent 33.4%, #000 33.6%, #000 96.4%, transparent 96.6%);
  animation: ss-turn 20s linear infinite;
  animation-play-state: paused;
}
.saver.spin .ss-print {
  animation-play-state: running;
}
.ss-print.hue-1 {
  color: var(--blue);
}
.ss-print.hue-2 {
  color: var(--yellow);
}
.ss-row {
  display: block;
  white-space: pre;
}
.ss-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}
.ss-print.has-art .ss-img {
  opacity: 1;
}
/* The lacquer's sheen and the faint spokes of the lights. It stays still
   while the print turns under it, as a reflection does. */
.ss-gloss {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  background:
    linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.2) 45%, rgba(255, 255, 255, 0.05) 54%, transparent 64%),
    conic-gradient(from 0deg, transparent 0 38deg, rgba(255, 255, 255, 0.12) 50deg, transparent 62deg, transparent 218deg, rgba(255, 255, 255, 0.12) 230deg, transparent 242deg);
  -webkit-mask: radial-gradient(circle closest-side, transparent 24.9%, #000 25%, #000 96.6%, transparent 96.7%);
  mask: radial-gradient(circle closest-side, transparent 24.9%, #000 25%, #000 96.6%, transparent 96.7%);
}
@keyframes ss-turn {
  to {
    transform: rotate(1turn);
  }
}

.ss-player {
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: center;
}
.ss-now {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ss-title,
.ss-artist,
.ss-qtitle,
.ss-qartist {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ss-title {
  font-weight: 700;
  font-size: 16px;
  color: var(--yellow);
}
.ss-artist {
  color: var(--dim);
}
.ss-prog {
  white-space: nowrap;
  overflow: hidden;
}
.ss-prog .on,
.ss-prog .dot {
  color: var(--green);
}
.ss-prog .off {
  color: var(--line);
}
.ss-times {
  display: flex;
  justify-content: space-between;
  color: var(--dim);
}
.ss-player .transport {
  display: flex;
  justify-content: center;
  gap: 18px;
}
.ss-player .transport button {
  padding: 4px 8px;
  font-size: 16px;
}
.ss-head {
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 400;
  color: var(--dim);
}
.ss-next ol {
  margin: 0;
  padding: 0;
  list-style: none;
}
.ss-next li button {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr) auto;
  gap: 12px;
  width: 100%;
  padding: 4px 0;
  text-align: left;
  border-bottom: 1px solid var(--rule);
}
.ss-next li button:hover {
  background: var(--hover);
}
.ss-qartist,
.ss-next .time {
  color: var(--dim);
}
/* A shared link (§7.20): the same disc and player, no next-up list, and the
   page works on a phone too, so the grid keeps a small side margin there. */
.saver.share {
  grid-template-rows: 1fr minmax(140px, 440px) auto 1fr;
}
.saver.share .ss-prog {
  cursor: pointer;
}
.ss-error {
  margin: 12px 0 0;
  color: var(--red);
  text-align: center;
}
@media (max-width: 700px) {
  .saver.share {
    grid-template-columns: 0 minmax(0, 1fr) 0;
    padding: 16px;
  }
}
.saver-min {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.saver-min input {
  width: 4.5em;
  padding: 2px 6px;
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line);
}
.saver-min input:focus {
  outline: none;
  border-color: var(--green);
}

/* ~/albums: a grid of covers, five to a row (owner request, 2026-11-01).
   albums.ts draws the text pattern under each image; a missing image is
   removed, so the pattern shows instead of a broken box. */
.albums {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 20px 14px;
}
.acard a,
.acard > button {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.acover {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  container-type: inline-size;
  display: grid;
  place-items: center;
  margin-bottom: 6px;
  border: 1px solid var(--line);
  background: var(--hover);
  color: var(--green);
}
.acover.hue-1 {
  color: var(--blue);
}
.acover.hue-2 {
  color: var(--yellow);
}
.acover-pat {
  font-size: 9cqw;
  line-height: 1.18;
  white-space: pre;
}
.acover-pat span {
  display: block;
}
.acover-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* ~/history uses the same cards; a card is a play button, not a link. */
.acard > button {
  width: 100%;
  padding: 0;
  text-align: left;
}
.acard .awhen {
  color: var(--dim);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}
.acard.playing .aname {
  color: var(--yellow);
}
.acard .aname,
.acard .aartist {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acard .aname {
  font-weight: 700;
}
.aartist {
  color: var(--dim);
}
.acard a:hover .acover,
.acard a:focus-visible .acover,
.acard > button:hover .acover,
.acard > button:focus-visible .acover {
  border-color: var(--green);
}
.acard a:hover .aname,
.acard > button:hover .aname {
  color: var(--green);
}
.ahead {
  display: flex;
  align-items: flex-end;
  gap: 18px;
  margin-bottom: 14px;
}
.acover.big {
  flex: 0 0 auto;
  width: min(220px, 38vw);
  margin: 0;
}
.ahead-text {
  min-width: 0;
}
.ahead .aname {
  margin: 0 0 2px;
  font-size: 1.4em;
  line-height: 1.2;
  overflow-wrap: anywhere;
}
.ahead .aartist,
.ahead .count {
  margin: 0 0 6px;
}
/* Under the side player the grid takes as many covers of 140 px as fit:
   two on a phone, three or four on a tablet. */
@media (max-width: 1079px) {
  .albums {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  }
}
@media (max-width: 700px) {
  .albums {
    gap: 16px 10px;
  }
  .ahead {
    flex-direction: column;
    align-items: flex-start;
  }
  .acover.big {
    width: min(240px, 70vw);
  }
}
