/* 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) */
  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);
}
@media (max-width: 420px) {
  .field {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
  .field > span {
    padding-top: 6px;
  }
  .auth-actions {
    padding-left: 0;
  }
}

/* ---------- 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(78px + env(safe-area-inset-bottom));
}
.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 18px 24px;
}
.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 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); }
@media (max-width: 700px) {
  .dl-line .bar-mini { display: none; }
}

.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 8px calc(4px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--bg);
  font-size: 11px;
  min-height: 52px;
}
.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;
}
.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.
 * `.bar.side` is set by `mountBar()`; the media query below is the guard,
 * so a stale class can never change the phone layout.
 */
@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. */
.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 .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) ---------- */
@media (max-width: 700px) {
  .wrap {
    flex-direction: column;
    align-content: start;
    padding-bottom: calc(96px + env(safe-area-inset-bottom));
  }
  .grip {
    display: none;
  }
  .side {
    position: static;
    max-height: none;
    overflow: visible;
    align-self: stretch;
    flex: 0 0 auto;
    width: 100%;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border-bottom: 1px solid var(--line);
    padding: 0;
    gap: 0;
  }
  .side .nav-pl {
    display: flex;
  }
  .side-pls {
    display: none;
  }
  .side a {
    flex-direction: column;
    justify-content: center;
    gap: 0;
    position: relative;
  }
  .side .nav-glyph,
  .side .nav-word {
    flex: 0 0 auto;
    max-width: 100%;
    font-size: 9px;
    line-height: 1.2;
    text-align: center;
  }
  .side a .n {
    position: absolute;
    right: 4px;
    top: 3px;
    font-size: 9px;
  }
  .side a {
    padding: 4px 2px;
    white-space: nowrap;
    min-height: 46px;
    font-size: 10px;
    overflow: hidden;
  }
  .view {
    padding: 10px 10px 20px;
  }
  .row .pick {
    /* 1.2em: the pick box needs the width of a full block glyph (§7.13). */
    grid-template-columns: 1.2em minmax(0, 1fr) 4.2em;
    grid-template-areas:
      "num title dur"
      ". artist artist";
    row-gap: 0;
    padding: 7px 4px;
  }
  .row .num {
    grid-area: num;
  }
  /* A phone row is two lines and 320 px wide; 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: 10px;
  }
  .row .dur {
    grid-area: dur;
  }
  .row-acts button {
    min-width: 26px;
    padding: 2px;
  }
  .bar {
    grid-template-columns: auto auto minmax(0, 1fr) auto;
    grid-template-areas:
      "transport cur prog dur"
      "now now modes acts";
    row-gap: 0;
    padding: 2px 6px calc(2px + env(safe-area-inset-bottom));
  }
  .bar .vol {
    display: none;
  }
  .bar .now {
    flex-direction: row;
    align-items: baseline;
    gap: 6px;
    padding: 2px 0;
  }
  .bar .modes {
    justify-self: start;
  }
  .bar .now .artist {
    flex: 0 1 auto;
  }
  .bar .now .title {
    flex: 1 1 auto;
  }
  .bar .prog {
    height: 40px;
  }
  .bar .transport button {
    min-width: 30px;
    padding: 2px;
  }
  .bar .toggle {
    min-width: 34px;
  }
  .bar .acts button {
    min-width: 32px;
    padding: 4px;
  }
  .bar .modes button {
    min-width: 28px;
    padding: 4px 2px;
  }
  .bar .cur,
  .bar .dur {
    font-size: 10px;
    padding: 4px 2px;
  }
  .list-head {
    gap: 8px;
  }
  /* The toolbar keeps the symbols only, on one row with the search box; a
     running state still shows. */
  .lib-head {
    flex-wrap: nowrap;
  }
  .lib-head .search {
    min-width: 0;
  }
  .lib-head .tool {
    flex: 0 0 auto;
    padding: 7px 10px;
  }
  .lib-head .tool:not(.noted) .tool-label {
    display: none;
  }
  /* A modal fills the phone screen. */
  .panel.sheet {
    align-items: stretch;
  }
  .panel.sheet .panel-box {
    max-width: none;
    max-height: none;
    height: 100dvh;
  }
  .newpl .field {
    flex-basis: 100%;
  }
}

/* ---------- 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: 380px) {
  /* The three symbol-plus-word cells stay on one row even at 320 px; the
     word sits under the symbol, so the cell stays narrow. */
  .side {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 520px) {
  .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: 640px) {
  .slsk-row .dur { display: none; }
}

/* The sync modal (§12): stage, block bar, counts, and every error 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); }
.sync-counts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11em, 1fr));
  gap: 4px 18px;
  margin: 0 0 14px;
}
.sync-counts div { display: flex; justify-content: space-between; gap: 8px; border-bottom: 1px solid var(--rule); padding: 4px 0; }
.sync-counts dt { color: var(--dim); }
.sync-counts dd { margin: 0; font-variant-numeric: tabular-nums; }
.sync-counts .ok dd { color: var(--green); }
.sync-counts .err dd { 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;
}
ul.sync-misses { list-style: none; margin: 8px 0 0; padding: 0; }
.sync-miss { padding: 6px 0; border-bottom: 1px solid var(--rule); }
.sync-miss .sync-pre { max-height: 20dvh; }
.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: 719px) {
  .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 {
  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;
}
.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 {
  border-color: var(--green);
}
.acard a: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;
}
@media (max-width: 1000px) {
  .albums {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
@media (max-width: 760px) {
  .albums {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (max-width: 520px) {
  .albums {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px 10px;
  }
  .ahead {
    flex-direction: column;
    align-items: flex-start;
  }
  .acover.big {
    width: min(240px, 70vw);
  }
}
