/* Hive Pocket Media Player.
   Hive's own palette — orange on dark, Inter — because it is a Hive app, but laid out for one
   hand on a phone rather than an operator at a desk: nothing below 44px, nothing that needs a
   precise tap, and the visuals given the room they earn. */

:root {
  --ground: #131110;
  --panel:  #1B1817;
  --line:   #332D2A;
  --text:   #EFE9E3;
  --muted:  #9C9089;
  --hive:   #F6A518;
  --radius: 8px;
}

* { box-sizing: border-box; }

/* `hidden` is only a DEFAULT of display:none, and ANY author rule that sets display beats it.
   The transport swaps its play and pause glyphs with the attribute, and .stage canvas/.ctl svg
   rules were enough to make both draw at once — a play button wearing a pause symbol over it.
   Force it, and the attribute means what it says everywhere on the page. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--ground);
  color: var(--text);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: contain;   /* no pull-to-refresh yanking the page mid-scrub */
}

.mono { font-family: ui-monospace, "JetBrains Mono", Consolas, monospace; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden;
}

.app {
  display: flex;
  flex-direction: column;
  height: 100dvh;                    /* dvh, not vh: the phone's address bar changes the height */
  padding: max(0.5rem, env(safe-area-inset-top)) 0.75rem max(0.5rem, env(safe-area-inset-bottom));
  gap: 0.75rem;
}

/* ── header ── */
.bar { display: flex; align-items: center; gap: 0.7rem; flex: 0 0 auto; }
.mark { width: 30px; height: 30px; color: var(--hive); flex: 0 0 auto; }
.titles { min-width: 0; flex: 1; }
.bar h1 {
  margin: 0; font-size: 0.95rem; font-weight: 800;
  letter-spacing: 0.12em; line-height: 1.1;
}
/* Version chip. Always on screen: this is a beta handed to other people, and a bug report
   that does not say which build it came from costs more than the pixels. */
.sub { display: flex; align-items: center; gap: 6px; }
.ver {
  font-family: ui-monospace, "JetBrains Mono", Consolas, monospace;
  font-size: 0.58rem; font-weight: 600; letter-spacing: 0.08em;
  color: var(--hive); border: 1px solid var(--hive); border-radius: 3px;
  padding: 1px 4px; white-space: nowrap; flex: 0 0 auto;
}

.sub {
  margin: 1px 0 0; font-size: 0.7rem; color: var(--muted);
  font-family: ui-monospace, "JetBrains Mono", Consolas, monospace;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

#libNote { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.icon-btn {
  flex: 0 0 auto;
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--line); border-radius: var(--radius);
  color: var(--muted); cursor: pointer;
}
.icon-btn:active { color: var(--hive); border-color: var(--hive); }

/* ── adding a link ── */
.linkrow { display: flex; gap: 0.5rem; flex: 0 0 auto; }
#linkInput {
  flex: 1; min-width: 0; min-height: 44px; padding: 0 0.6rem;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; font-size: 16px;   /* 16px or Android zooms the page on focus */
}
#linkInput:focus { border-color: var(--hive); outline: none; }
.linkadd {
  flex: 0 0 auto; min-height: 44px; padding: 0 1rem;
  background: var(--hive); color: #131110; border: 0; border-radius: var(--radius);
  font: inherit; font-weight: 600; cursor: pointer;
}
.linknote { margin: 0; font-size: 0.72rem; color: var(--muted); flex: 0 0 auto; }

/* ── reporting a problem ── */
#reportText {
  width: 100%;
  box-sizing: border-box;
  background: var(--panel, #17150F);
  color: var(--fg, #EDE7DC);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.6rem;
  font: inherit;
  font-size: 0.85rem;
  line-height: 1.45;
  resize: vertical;
  min-height: 88px;
}
#reportText:focus { border-color: var(--hive); outline: none; }
/* Wrapped, scrollable and monospace: this is the block the person is being asked to VET before
   it leaves the phone, so it has to be readable rather than a smear that invites a blind tap. */
.diag {
  margin: 0.4rem 0 0;
  padding: 0.6rem;
  background: #0C0B0A;
  border: 1px solid var(--line);
  border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.68rem;
  line-height: 1.5;
  color: var(--muted);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 40vh;
  overflow: auto;
}
.linknote.bad { color: #E0655A; }

/* ── stage: the two canvases stacked, effects over the equalizer ── */
/* The wrapper is the flex item and the thing that goes full screen; the stage fills it. */
.stagewrap { position: relative; flex: 1 1 auto; min-height: 130px; }
.stage {
  position: absolute;
  inset: 0;
  height: 100%;
  background: #0C0B0A;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  /* It is a <button> now. Undo the parts of that a browser supplies and this design does not
     want, but keep the parts that make it a control: focus ring, keyboard, announcement. */
  display: block;
  width: 100%;
  padding: 0;
  color: inherit;
  font: inherit;
  text-align: center;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  /* No 300ms wait for a second tap that would only zoom the page. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#fxCanvas { pointer-events: none; transition: opacity 0.2s; }
/* Above the effects, and never in the way of the gesture that creates it. */
#boltCanvas { pointer-events: none; }
.stage-hint, .stage-tip {
  position: absolute; inset: auto 1rem 1rem; margin: 0; display: block;
  text-align: center; font-size: 0.78rem; color: var(--muted); line-height: 1.5;
}

/* Full screen, two ways. The native one is what a phone should get: it takes the browser's own
   chrome away too. It is also the one that can be missing or refused, so `.cover` does the job
   with layout alone and is what actually guarantees the tap did something. Both are applied;
   whichever lands, the stage covers the screen and one more tap comes back. */
.stagewrap.cover {
  position: fixed;
  inset: 0;
  z-index: 30;
  min-height: 0;
}
.stagewrap.cover .stage,
.stagewrap:fullscreen .stage { border: 0; border-radius: 0; }
.stagewrap:fullscreen { background: #0C0B0A; }
.stage-tip {
  background: rgba(0, 0, 0, 0.45);
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  inset: auto auto 1.2rem 50%;
  transform: translateX(-50%);
  transition: opacity 0.4s;
}

/* ── now playing ── */
.now { flex: 0 0 auto; }
.now-title {
  font-size: 0.95rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.now-sub {
  font-size: 0.72rem; color: var(--muted); margin-top: 2px;
  /* TWO LINES, not one with an ellipsis. This line carries the app's explanations — why a link
     has no visuals, why a link was refused, why a saved link no longer plays — and a cut-off
     at 'The link may be pri…' reads as a fault rather than as the reason it was written to be. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.scrub { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.5rem; }
.scrub .t { font-size: 0.68rem; color: var(--muted); flex: 0 0 auto; }
#seek {
  flex: 1; min-width: 0; height: 44px;          /* the target is 44px; the track is drawn thin */
  background: none; -webkit-appearance: none; appearance: none;
}
#seek::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: var(--line); }
#seek::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; margin-top: -7px;
  border-radius: 50%; background: var(--hive); border: 0;
}
#seek::-moz-range-track { height: 4px; border-radius: 2px; background: var(--line); }
#seek::-moz-range-thumb { width: 18px; height: 18px; border: 0; border-radius: 50%; background: var(--hive); }

/* ── transport ── */
.controls { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: 0.75rem; }
.ctl {
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: var(--radius);
  color: var(--muted); cursor: pointer;
}
.ctl[aria-pressed="true"] { color: var(--hive); }
.ctl.play {
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--hive); color: #131110;
}
.ctl:active { color: var(--hive); }
.ctl.play:active { filter: brightness(1.12); color: #131110; }

/* ── queue ── */
.queue-wrap { flex: 0 1 auto; min-height: 0; display: flex; flex-direction: column; }
.queue-head { font-size: 0.68rem; color: var(--muted); padding: 0 0 0.35rem; }
.queue {
  list-style: none; margin: 0; padding: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  border-top: 1px solid var(--line);
}
.row {
  display: flex; align-items: center; gap: 0.6rem;
  min-height: 44px; padding: 0.45rem 0.25rem;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.row:last-child { border-bottom: 0; }
.row .num { font-size: 0.66rem; color: var(--muted); width: 1.6rem; text-align: right; flex: 0 0 auto; }
/* flex: 1 is the whole fix. Without it the name is only as wide as its text, so the delete X
   FOLLOWS it: on a short track name the X sat in the middle of the row, exactly where a thumb
   lands when reaching for the track. Measured at 360px — a 13-character name put it at 210px
   of a 344px row. It has been there since the queue was written and only became dangerous when
   a folder could load 169 tracks at once. Now the name takes the room and the X is always at
   the right edge, where a destructive control belongs. */
.row .nm { font-size: 0.85rem; flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row.on { background: hsl(38 92% 55% / 0.14); box-shadow: inset 2px 0 0 0 var(--hive); }
.row.on .nm { color: var(--hive); font-weight: 600; }

/* Remove. Small, but still a 44px target — it sits next to the row that starts playback, and a
   near miss either deletes the wrong thing or plays something you were deleting. */
.rowdel {
  flex: 0 0 auto; min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; color: var(--muted);
  font-size: 1.1rem; line-height: 1; cursor: pointer;
}
.rowdel:active { color: #E0655A; }
.ctl { position: relative; }
.one {
  position: absolute; top: 6px; right: 6px;
  font-family: ui-monospace, Consolas, monospace; font-size: 0.55rem; font-weight: 700;
}

/* ── settings sheet ── */
.sheet-back { position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 20; }
.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 21;
  max-height: 82dvh; overflow-y: auto;
  background: var(--panel); border-top: 1px solid var(--line);
  border-radius: 14px 14px 0 0;
  padding: 0.6rem 1rem max(1rem, env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 1.1rem;
}
.sheet-grip { width: 36px; height: 4px; border-radius: 2px; background: var(--line); margin: 0 auto 0.2rem; }
.sheet h2 { margin: 0; font-size: 1rem; font-weight: 700; }
.set { display: flex; flex-direction: column; gap: 0.4rem; }
.set-lab { font-size: 0.8rem; font-weight: 600; }
.set-note { margin: 0; font-size: 0.72rem; color: var(--muted); line-height: 1.55; }
/* EVERY select in the sheet, not a list of ids. This rule used to name #ambientSel and
   #visualsSel, and each of the five pickers added after it — look, equalizer, colours, bursts,
   sensitivity — quietly rendered as an unstyled native control, light grey on black, in a dark
   app. Nobody noticed for five versions because the sheet was never looked at with fresh eyes.
   Keyed to the container so the next picker is styled by existing. */
.sheet select {
  width: 100%;
  min-height: 44px; padding: 0 0.6rem;
  background: var(--ground); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; font-size: 16px;   /* 16px or Android zooms on focus */
}
.set-rule { border: 0; border-top: 1px solid var(--line); margin: 0.2rem 0; }
.help {
  margin: 0; padding: 0 0 0 1rem;
  font-size: 0.72rem; color: var(--muted); line-height: 1.55;
}
.help li { margin: 0 0 0.4rem; }
.help b { color: var(--text); font-weight: 600; }
.sheet-btn {
  min-height: 48px; width: 100%;
  background: var(--hive); color: #131110; border: 0; border-radius: var(--radius);
  font: inherit; font-weight: 600; cursor: pointer;
}
.sheet-btn.danger { background: none; color: #E0655A; border: 1px solid #E0655A; }

:focus-visible { outline: 2px solid var(--hive); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/* The microphone's auto-start row. Same shape as the sheet's other explained controls: the
   checkbox is a real 20px target and the reason sits under the label rather than beside it. */
.mic-auto {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0.6rem 0 0.2rem; font-size: 0.85rem; cursor: pointer;
}
.mic-auto input { width: 20px; height: 20px; margin: 1px 0 0; flex: 0 0 auto; accent-color: var(--hive); }
.mic-auto small { display: block; margin-top: 3px; color: var(--muted); font-size: 0.72rem; line-height: 1.45; }
/* Listening. This used to set only border-color and color — written for a bordered button, and
   .sheet-btn has `border: 0` and a hive FILL, so the border went nowhere and the label became
   hive-on-hive: "Stop listening" was invisible from 1.6.0 until someone screenshotted the sheet
   with the microphone actually running. Inverted properly now, which also reads better: the
   solid fill means start, the outline means it is already going. */
#micBtn[aria-pressed="true"] {
  background: none;
  border: 1px solid var(--hive);
  color: var(--hive);
}

/* Landscape on a phone. Measured at 915x412 and 780x360: with the player showing, the stage was
   down to 36% of the height and hitting its 130px floor, because the header, now-playing,
   transport and queue all keep their portrait sizes. This buys the stage back about 70px from
   padding, gaps and the one line of helper text that is pure repetition once you are using the
   app. THE CONTROLS ARE NOT TOUCHED: 44px is the floor everywhere and this is not the place to
   negotiate it — the whole point of that rule is the moments when a phone is awkward to hold. */
@media (orientation: landscape) and (max-height: 480px) {
  .app { gap: 0.4rem; padding-left: 0.5rem; padding-right: 0.5rem; }
  .now-sub { display: none; }
  .scrub { margin-top: 0.25rem; }
  .queue-head { display: none; }
}

/* ── the settings sheet, after the 2026-09-10 review ── */

/* Section headings. Small and quiet: they are signposts for scanning, not content, and a sheet
   that shouts its own structure is worse than one that just has some. */
.sheet-sec {
  margin: 1.4rem 0 0.5rem; padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted);
}
.sheet-sec:first-of-type { border-top: 0; padding-top: 0; margin-top: 0.6rem; }

/* Groups. These are the submenus the deeper equalizer and effect settings will live in, so the
   summary is a real 44px row and not a text link — it has to survive being tapped by a thumb
   long before it has much inside it. */
.grp { margin-bottom: 0.5rem; }
.grp > summary {
  display: flex; align-items: center; gap: 0.5rem;
  min-height: 44px; padding: 0 0.2rem;
  font-size: 0.8rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--text); cursor: pointer; list-style: none;
}
.grp > summary::-webkit-details-marker { display: none; }
/* The chevron is drawn rather than borrowed from the default marker, which is inconsistent
   between engines and cannot be positioned. */
.grp > summary::before {
  content: ''; flex: 0 0 auto; width: 7px; height: 7px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg); margin-left: 3px;
  transition: transform 0.15s;
}
.grp[open] > summary::before { transform: rotate(45deg); }
.grp > summary:hover { color: var(--hive); }
.grp > .set:first-of-type, .grp > ul, .grp > p { margin-top: 0.2rem; }
.grp > .set, .grp > ul, .grp > p { padding-left: 1.1rem; }
/* The sheet spaces its own children with `gap`, which a <details> interrupts: gap applies to the
   sheet's flex children, and everything inside a group is a child of the group instead. Without
   this, a note ran straight into the next label with nothing between them. */
.grp > .set + .set, .grp > .set + ul, .grp > .set + p { margin-top: 1.1rem; }

/* A secondary action inside the sheet: it does something, but it is not the thing you came for,
   so it does not get the solid hive fill that Done and the microphone button use. */
.sheet-btn.ghost {
  background: none; color: var(--hive);
  border: 1px solid var(--line); min-height: 44px;
}
.sheet-btn.ghost:hover { border-color: var(--hive); }

/* Four header buttons is one more than this bar was laid out for, and at 320px the page began to
   scroll SIDEWAYS — the same class of fault as "the FX studio has no presets on my phone", which
   was also a row that could not shrink. Measured at 320, 360, 390 and 412 with the player both
   shown and hidden.

   What gives, in order: the gaps and the outer padding first, then the one line of subtitle,
   which repeats what the queue underneath already says. The version chip is NOT dropped and the
   buttons are NOT shrunk — the chip is how a bug report says which build it came from, and 44px
   is the floor everywhere. */
@media (max-width: 380px) {
  .bar { gap: 0.4rem; }
  .app { padding-left: 0.5rem; padding-right: 0.5rem; }
  .icon-btn { min-width: 40px; }
}
@media (max-width: 340px) {
  .sub { display: none; }
}

/* The transport at 320px, which overflowed by 2px LONG BEFORE the header gained a button — six
   controls that will not shrink below 44, a play button wider still, and five gaps between them
   come to more than the screen. Found by walking every element for a right edge past the viewport
   rather than by looking, because two pixels of sideways scroll is invisible until you try to
   swipe the queue and the whole page slides.
   It WRAPS rather than shrinking: a button pressed by someone whose hands shake does not get
   smaller to save a row. */
@media (max-width: 380px) {
  .controls { gap: 0.5rem; flex-wrap: wrap; row-gap: 0.5rem; }
}

/* The stage takes pointer gestures now, so the browser must not claim them first: without this a
   drag across the visuals scrolls the page instead of painting, and on a phone that is the only
   thing that would happen. */
.stage { touch-action: none; }


/* ── the tutorial ───────────────────────────────────────────────────────────────────────
   A dim sheet over the whole app with a ring cut around the control being talked about. The
   ring is a box-shadow spread rather than a real hole: an actual clip-path hole needs the
   dimming and the cut to agree pixel for pixel at every size, and a 40px shadow spread of the
   same colour does the same job with nothing to keep in sync.

   The card avoids the ring by moving to the other half of the screen, which is the whole
   reason the ring's position is computed rather than fixed. */
.tut {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: flex-end; justify-content: center;
  /* SAFE-AREA INSETS, and this is the whole bug rather than a nicety. Installed to a home
     screen there is no browser chrome, so a fixed overlay runs edge to edge and its bottom
     16px land under the system gesture bar — where the phone eats the tap before the page
     sees it. The card is the only way out of the tutorial, so an app that opened into it
     could not be dismissed, could not be used, and looked completely dead. It never showed
     in a browser tab because the browser's own bars absorb that strip. .app and .sheet had
     always accounted for this; the newest fixed element did not. */
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, calc(env(safe-area-inset-bottom) + 8px));
  /* AND A SAFETY NET. Pointer events pass through everywhere except the card, so a tutorial
     that somehow cannot be reached leaves the app working rather than bricked. A thing that
     explains the app must never be able to disable it. It also makes the tutorial better:
     "drag a finger across the stage and it paints" can be tried while the words are on screen
     instead of after. */
  pointer-events: none;
}
.tut-card, .tut-btn { pointer-events: auto; }
.tut.plain { background: rgba(0, 0, 0, 0.72); }
.tut.top { align-items: flex-start; }
.tut-ring {
  position: fixed;
  border: 2px solid var(--hive);
  border-radius: 14px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.72);
  pointer-events: none;
  transition: top 0.22s, left 0.22s, width 0.22s, height 0.22s;
}
@media (prefers-reduced-motion: reduce) { .tut-ring { transition: none; } }
.tut-card {
  position: relative;
  width: 100%; max-width: 380px;
  background: var(--panel, #17150F);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1rem 0.85rem;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}
.tut-step { margin: 0 0 0.3rem; font-size: 0.7rem; color: var(--muted); letter-spacing: 0.06em; }
.tut-title { margin: 0 0 0.4rem; font-size: 1.05rem; line-height: 1.3; }
.tut-body { margin: 0 0 0.9rem; font-size: 0.86rem; line-height: 1.55; color: var(--fg, #EDE7DC); }
.tut-btns { display: flex; gap: 0.5rem; }
/* 44px minimum, and Next takes the room left over so the thumb lands on it without aiming. */
.tut-btn {
  min-height: 44px; padding: 0 0.9rem;
  background: var(--hive); color: #131110; border: 0; border-radius: 10px;
  font: inherit; font-weight: 600; cursor: pointer; flex: 1 1 auto;
}
.tut-btn.ghost { background: none; color: var(--muted); border: 1px solid var(--line); flex: 0 0 auto; }
.tut-btn.ghost:hover { color: var(--hive); border-color: var(--hive); }
.tut-btn[hidden] { display: none; }


/* ── reconnecting a remembered folder ──
   A single full-width row under the transport. It is the only thing on screen at that moment
   worth tapping, so it is not competing with anything and does not need to be subtle. */
.reconnect { flex: 0 0 auto; margin-top: 0.5rem; }
.reconnect-btn {
  width: 100%; min-height: 44px;
  background: none; color: var(--hive);
  border: 1px dashed var(--hive); border-radius: var(--radius);
  font: inherit; font-weight: 600; cursor: pointer;
}
.reconnect-btn:hover { background: rgba(240, 160, 32, 0.08); }
#folderNote.bad { color: #E0655A; }
/* The instruction that makes the tap the LAST one. It sits under the button rather than inside
   it because it describes a choice the browser is about to offer, not what this button does. */
.reconnect-note {
  margin: 0.4rem 0 0; text-align: center;
  font-size: 0.72rem; line-height: 1.45; color: var(--muted);
}


/* ── the minimized player ──
   What it hides is what you READ; what it keeps is what you TOUCH. The transport row and the
   track name stay, the queue and the scrubber go, and the stage takes the room — which is the
   whole point, so .now must stop reserving height as well as stop showing content. */
.now { position: relative; }
.mini-btn {
  position: absolute; top: -2px; right: 0;
  width: 32px; height: 32px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; color: var(--muted);
  border: 0; border-radius: 8px; cursor: pointer;
  transition: transform 0.18s, color 0.18s;
}
.mini-btn:hover { color: var(--hive); }
.mini-btn[aria-pressed="true"] { transform: rotate(180deg); color: var(--hive); }
@media (prefers-reduced-motion: reduce) { .mini-btn { transition: none; } }
/* The title keeps clear of the button at every width, minimized or not. */
.now-title { padding-right: 38px; }

body.player-mini .queue-wrap,
body.player-mini .now-sub,
body.player-mini .scrub { display: none; }
/* Tighter, because the section is now one line and a button rather than four rows. */
body.player-mini .now { margin-bottom: 0.1rem; }
body.player-mini .controls { margin-top: 0.35rem; }


/* ── a queue whose folder is remembered but not yet unlocked ──
   Dimmed so it reads as waiting rather than as playing, and NOT disabled: pressing one of these
   is what reconnects the folder, so anything that discouraged the press would defeat it. */
/* Its own element rather than ::after inside .nm, which the name's own ellipsis would eat on
   any track long enough to need one — and long names are the common case. */
.row.locked .nm { opacity: 0.55; }
.row.locked .num { opacity: 0.55; }
/* order 2 with the delete pushed to 3, or the hint lands to the RIGHT of the delete button and
   the destructive control stops being the last thing in the row. Measured, not assumed: the
   first attempt put it last and moved the X inward by 77px. */
.rowdel { order: 3; }
.row.locked::before {
  content: 'tap to unlock';
  order: 2;
  font-size: 0.64rem; color: var(--hive); opacity: 0.9; white-space: nowrap; flex: 0 0 auto;
}
/* The stage says the same thing while the queue is scrolled out of sight. */
body.has-pending .reconnect-btn { border-style: solid; }


/* ── the busy row ──
   Sits under the transport with the reconnect bar, so it is in the same place whatever size the
   player is and never moves the stage. */
.busy {
  flex: 0 0 auto; margin-top: 0.5rem;
  display: flex; align-items: center; justify-content: center; gap: 0.55rem;
  min-height: 40px; padding: 0 0.6rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  font-size: 0.8rem; color: var(--muted);
}
.busy-ring {
  width: 16px; height: 16px; flex: 0 0 auto;
  border: 2px solid var(--line); border-top-color: var(--hive); border-radius: 50%;
  animation: busy-spin 0.8s linear infinite;
}
@keyframes busy-spin { to { transform: rotate(360deg); } }
.busy-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* REDUCED MOTION STILL NEEDS TO SHOW PROGRESS. A spinner that stops spinning is a spinner that
   says the app has hung, so the ring is replaced by a pulse in brightness rather than removed —
   the one thing it must never do is go still and silent. */
@media (prefers-reduced-motion: reduce) {
  .busy-ring { animation: busy-pulse 1.4s ease-in-out infinite; border-top-color: var(--hive); }
  @keyframes busy-pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
}
