/* Listening party (listen.html). Theme tokens only — no new colors. */

/* An author-level `display` beats the UA's [hidden] rule, so anything this file
   gives a display value to must opt back out explicitly or it can never hide. */
.listen-shell [hidden],
.listen-create[hidden],
.listen-create [hidden],
.listen-mini-field[hidden] {
  display: none;
}

.listen-shell {
  display: grid;
  gap: 22px;
  /* The shared auth shell pads 120px and centres its grid, which suits a small sign-in panel
     floating under the fixed 64px nav. This page is a full app view: start at the top and
     clear the nav, nothing more.
     64px clears the nav; the rest is deliberate breathing room. */
  padding-top: 88px;
  align-items: start;
}

/* The global `section` rule pads every section by up to 120px/80px — right for the long
   scrolling home page, but here it lands *inside* the shell's own padding and doubles it.
   The shell already spaces this page. */
.listen-shell > section {
  padding: 0;
}

/* Both views open with a title straight after the nav, so the shell padding is the whole gap. */
.listen-shell .account-subhead,
.listen-shell .listen-room-head {
  margin-top: 0;
}

/* ---- hub ---- */
.listen-create {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: end;
  margin-top: 16px;
  padding: 18px 20px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

.listen-create > input[type="text"],
.listen-create > input:not([type]) {
  flex: 1;
  min-width: 220px;
}

.listen-mini-field {
  display: grid;
  gap: 4px;
  font-size: 12px;
  color: var(--text-muted);
}

.listen-mini-field input {
  width: 96px;
}

.listen-create-row {
  display: flex;
  gap: 16px;
  width: 100%;
  align-items: flex-start;
}

.listen-create-fields {
  display: grid;
  gap: 10px;
  flex: 1;
  min-width: 0;
}

.listen-cover-slot {
  width: 108px;
  height: 108px;
  flex: none;
  display: grid;
  place-items: center;
  padding: 0;
  overflow: hidden;
  color: var(--text-muted);
  background: var(--bg-panel);
  border: 1px dashed var(--border-medium);
  cursor: pointer;
}

.listen-cover-slot:hover { border-color: var(--accent); }

.listen-cover-slot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.listen-kind-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

.listen-kind {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  font-size: 14px;
}

.listen-kind-options {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: end;
}

.listen-party-cover {
  width: 64px;
  height: 64px;
  display: block;
  overflow: hidden;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

.listen-party-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.listen-party-body {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.listen-party-list {
  display: grid;
  gap: 10px;
  margin-top: 18px;
}

/* These cards are <button>s, and a button inherits neither colour nor font from the page —
   the UA gives it `buttontext`, which is black and unreadable on the dark theme. Set both
   here so every child inherits sane values. */
.listen-party-card,
.listen-cal-cell {
  color: var(--text-primary);
  font-family: inherit;
  font-size: inherit;
}

.listen-party-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 6px 14px;
  align-items: center;
  padding: 16px 18px;
  text-align: left;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
  cursor: pointer;
}

.listen-party-card:hover {
  border-color: var(--accent);
}

.listen-party-card.is-upcoming {
  opacity: 0.82;
}

.listen-hub .listen-party-card { grid-template-columns: auto minmax(0, 1fr); }

.listen-party-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
}

.listen-share-btn {
  min-width: 0;
  min-height: 32px;
  padding: 0 10px;
  font-size: 13px;
  line-height: 1;
}

.listen-party-card .account-muted {
  font-size: 13px;
}

/* ---- calendar hub ---- */
/* The public site treats panels as angled tickets. Keep that language in the hub, while the
   room remains deliberately square and utilitarian for long queues, chat, and playback. */
.listen-hub .listen-world-clock-cell,
.listen-hub .listen-mini,
.listen-hub .listen-cal-cell,
.listen-hub .listen-party-card,
.listen-hub .nf-empty {
  --listen-panel-bg: var(--bg-panel);
  --listen-panel-border: var(--border-medium);
  position: relative;
  isolation: isolate;
  background: transparent;
  border-color: transparent;
  clip-path: none;
}

.listen-hub .listen-world-clock-cell::before,
.listen-hub .listen-mini::before,
.listen-hub .listen-cal-cell::before,
.listen-hub .listen-party-card::before,
.listen-hub .nf-empty::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0 6px;
  background: var(--listen-panel-bg);
  border: 1px solid var(--listen-panel-border);
  transform: skewX(-6deg);
  pointer-events: none;
}

.listen-hub .listen-cal-cell,
.listen-hub .nf-empty { --listen-panel-bg: var(--bg-subtle); }

.listen-hub .listen-party-card:hover,
.listen-hub .listen-cal-cell:hover,
.listen-hub .listen-cal-cell.is-selected {
  --listen-panel-border: var(--accent);
  border-color: transparent;
}

.listen-hub .listen-party-card {
  padding-right: 28px;
  overflow: hidden;
}

.listen-hub .listen-party-card strong,
.listen-hub .listen-party-card .account-muted { overflow-wrap: anywhere; }

/* Calendar cells carry text right to their lower edge. Do not clip it merely to shape a card. */
.listen-hub .listen-cal-cell { overflow: visible; }

.listen-hub .listen-cal-chip {
  clip-path: none;
  padding-right: 9px;
}

.listen-world-clock {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin-top: 20px;
}

.listen-world-clock-cell {
  display: grid;
  gap: 3px;
  position: relative;
  min-width: 0;
  min-height: 78px;
  padding: 12px 98px 12px 14px;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
  overflow: hidden;
}

.listen-world-clock-place {
  min-width: 0;
}

.listen-world-clock-flag {
  position: absolute;
  top: 50%;
  right: 0;
  display: block;
  width: 84px;
  height: 42px;
  transform: translateY(-50%);
  object-fit: cover;
  border: 1px solid var(--border-medium);
}

.listen-world-clock-place .section-label {
  min-width: 0;
  font-size: 10px;
  letter-spacing: 1.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.listen-world-clock-zone {
  font-size: 12px;
  color: var(--text-muted);
}

.listen-world-clock-time {
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}

/* The host's date/time field is local; this preview makes its one selected instant readable
   before it is saved as UTC. It is deliberately separate from the live hub clock. */
.listen-candidate-clock {
  display: grid;
  gap: 4px;
  width: 100%;
  margin-top: 2px;
}

.listen-candidate-clock > .section-label,
.listen-candidate-clock > .account-muted { margin: 0; }

.listen-candidate-clock-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.listen-candidate-clock .listen-world-clock-cell { padding: 10px 76px 10px 12px; }
.listen-candidate-clock .listen-world-clock-flag { width: 62px; height: 31px; }
.listen-candidate-clock .listen-world-clock-time { font-size: 14px; }

/* Host creation is the only place this form appears now. Give it enough hierarchy that its
   party type, scheduling controls, and worldwide time check read as one small workflow. */
#panelHost .listen-create {
  display: grid;
  gap: 18px;
  margin-top: 20px;
  padding: 22px;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

#panelHost .listen-create-intro {
  display: grid;
  gap: 5px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-medium);
}

#panelHost .listen-create-intro .account-muted { margin: 0; }

#panelHost .listen-party-title-field {
  display: grid;
  gap: 5px;
  font-size: 12px;
  color: var(--text-muted);
}

#panelHost .listen-party-title-field input {
  width: 100%;
  min-height: 40px;
  padding: 0 12px;
  color: var(--text-primary);
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
  font: inherit;
}

#panelHost .listen-party-title-field input:focus { border-color: var(--accent); outline: none; }

#panelHost .listen-kind-row { gap: 8px; }

#panelHost .listen-kind {
  flex: 1 1 150px;
  min-height: 42px;
  padding: 9px 11px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
  cursor: pointer;
}

#panelHost .listen-kind:has(input:checked) {
  color: var(--text-primary);
  background: var(--bg-panel);
  border-color: var(--accent);
}

#panelHost .listen-kind-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  align-items: stretch;
  padding: 12px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

#panelHost .listen-kind-options > .listen-kind,
#panelHost .listen-kind-options > .listen-mini-field {
  display: grid;
  align-content: center;
  gap: 7px;
  min-width: 0;
  min-height: 76px;
  margin: 0;
  padding: 10px 11px;
  box-sizing: border-box;
  align-self: stretch;
  color: var(--text-muted);
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
}

/* This selector is more specific than the shared .listen-create [hidden] rule above. Keep
   the mode-specific fields genuinely absent until their corresponding party type is selected. */
#panelHost .listen-kind-options > .listen-mini-field[hidden] { display: none; }

#panelHost .listen-kind-options > .listen-kind {
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  color: var(--text-primary);
}

#panelHost .listen-kind-options > .listen-kind:has(input:checked) { border-color: var(--accent); }

#panelHost .listen-kind-options > .listen-mini-field > span {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.7px;
  text-transform: uppercase;
}

#panelHost .listen-kind-options > .listen-mini-field input,
#panelHost .listen-kind-options > .listen-mini-field select {
  width: 100%;
  min-height: 32px;
  padding: 0 8px;
  color: var(--text-primary);
  background: var(--bg-surface);
  border: 1px solid var(--border-medium);
  font: inherit;
}

#panelHost .listen-kind-options > .listen-mini-field input:focus,
#panelHost .listen-kind-options > .listen-mini-field select:focus {
  border-color: var(--accent);
  outline: none;
}

#panelHost .listen-kind-options > .listen-mini-field:has(input[type="datetime-local"]) {
  grid-column: span 2;
}

#panelHost .listen-kind-options > .listen-mini-field small {
  font-size: 11px;
  line-height: 1.25;
  color: var(--text-muted);
}

#panelHost .listen-candidate-clock {
  padding: 14px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

#panelHost .listen-create > .roadmap-add-btn {
  min-width: 150px;
  justify-self: start;
}

.party-private-picker {
  display: grid;
  gap: 14px;
  margin-top: 18px;
  padding: 18px;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

.party-private-picker[hidden] { display: none; }

.party-private-picker-head {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: end;
}

.party-private-picker-head .account-muted { margin: 4px 0 0; }
.party-private-picker-head input { width: min(320px, 100%); }

.party-private-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.party-private-columns > div {
  min-width: 0;
  padding: 12px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

.party-profile-list {
  display: grid;
  gap: 8px;
  max-height: 340px;
  overflow: auto;
}

.party-profile-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 9px;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

.party-profile-identity { display: flex; align-items: center; gap: 9px; min-width: 0; }
.party-profile-avatar { width: 34px; height: 34px; flex: none; border-radius: 50%; object-fit: cover; }
.party-profile-copy { display: grid; min-width: 0; }
.party-profile-copy strong, .party-profile-copy span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.listen-hub-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 24px;
  margin-top: 20px;
  align-items: start;
}

.listen-calendar-col { min-width: 0; }

.listen-side-col {
  display: grid;
  gap: 10px;
  align-content: start;
}

.listen-cal-head {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-bottom: 12px;
}

.listen-cal-title {
  flex: 1;
  margin: 0;
  font-family: 'Bebas Neue', sans-serif;
  font-size: 26px;
  letter-spacing: 1px;
}

.listen-cal-dows,
.listen-cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
}

.listen-cal-dows {
  margin-bottom: 4px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-muted);
  text-align: center;
}

.listen-cal-cell {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-height: 96px;
  padding: 6px 6px 6px 14px;
  text-align: left;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
  cursor: pointer;
  overflow: hidden;
}

.listen-cal-cell:hover { border-color: var(--accent); }
.listen-cal-cell.is-outside { opacity: 0.42; }
.listen-cal-cell.is-today .listen-cal-daynum { color: var(--accent); font-weight: 600; }
.listen-cal-cell.is-selected { border-color: var(--accent); }

.listen-cal-daynum {
  font-size: 12px;
  color: var(--text-muted);
}

.listen-cal-chip {
  padding: 2px 5px;
  font-size: 11px;
  line-height: 1.3;
  background: var(--bg-panel);
  border-left: 2px solid var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.listen-cal-more {
  font-size: 10px;
  color: var(--text-muted);
}

.listen-cal-time {
  align-self: center;
  font-size: 12px;
  color: var(--accent);
}

.listen-day-panel {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border-medium);
}

@media (max-width: 1024px) {
  /* Sidebar goes ABOVE the grid on narrow screens so "join now" stays the first thing seen. */
  .listen-hub-layout { grid-template-columns: 1fr; }
  .listen-side-col { order: -1; }
  .listen-cal-cell { min-height: 72px; }
  .listen-cal-chip { font-size: 10px; }
}

/* ---- room ---- */

/* Three regions: the room's columns, and a chat column that does not exist yet. Reserving the
   shape now is close to free; retrofitting a third column later would mean redoing this. The
   column is absent rather than empty, so nothing renders until there is something to render. */
.listen-room-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

.listen-room-layout.has-chat {
  /* Fluid rather than a fixed rail: at 320px the conversation was a column of two-word lines.
     Scales with the viewport but stops before it starts competing with the stage. */
  grid-template-columns: minmax(0, 1fr) clamp(340px, 30vw, 680px);
}

/* Scheduled parties collect songs before they start; only their chat composer waits for the
   start time. The conversation itself remains visible, so the room still reads as a party. */
.listen-room.is-chat-closed .chat-form,
.listen-room.is-chat-closed .chat-picker-pane,
.listen-room.is-chat-closed .chat-attach-bar {
  display: none;
}

@media (max-width: 1100px) {
  .listen-room-layout,
  .listen-room-layout.has-chat { grid-template-columns: minmax(0, 1fr); }
}

/* Submitting is the front door, so the form reads as the primary control here. */
.listen-submit {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.listen-submit input[type="url"] {
  flex: 1;
  min-width: 220px;
}

/* A native select inherits the platform's own light chrome, which reads as broken next to every
   other field on a dark page. Same tokens as the site's other inputs. */
.listen-submit select,
.listen-submit input[type="url"] {
  min-height: 34px;
  padding: 0 10px;
  color: var(--text-primary);
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
  font: inherit;
}

.listen-submit select {
  min-width: 150px;
}


/* Presence decides whose turn is next, so the room says who is here. */
.listen-roster-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  align-items: center;
  margin-bottom: 4px;
}

.listen-roster {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.listen-roster-chip {
  padding: 2px 9px;
  font-size: 12px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

/* Away, not gone: their lane is intact and rejoins the rotation when they come back. */
.listen-roster-chip.is-away,
.listen-skip-tag {
  color: var(--text-muted);
  border-style: dashed;
}

.listen-queue-row.is-skipped {
  opacity: 0.55;
}

.listen-room-head {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
}

.listen-room-title {
  flex: 1;
  min-width: 200px;
}

/* The room head reuses .account-title/.account-muted from the auth shell, sized for a lone
   sign-in headline. Here it sits above a live player and a full room layout, so it is trimmed
   down to read as a label, not a page title — tighter, not a different page. */
.listen-room-title .account-title {
  font-size: clamp(28px, 3.2vw, 40px);
  margin: 0;
}

.listen-room-head .account-muted {
  margin: 2px 0 0;
}

/* Phone-only People pane (the 4th swipe pane, ≤760px block below) — the roster bar above
   already covers this on desktop. */
.listen-people { display: none; }

/* Single column: the media gets the full width and the transport sits under it.
   Side-by-side left the controls in a cramped rail fighting the video for space. */
.listen-stage {
  display: grid;
  gap: 16px;
  padding: 18px 20px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
  position: relative;      /* the backdrop below is absolute against this */
  overflow: hidden;
}

/* Desktop/tablet already show the party name in .listen-room-head; phones hide that head
   (see the ≤760px block) and need the name somewhere on the stage instead. */
.listen-stage-title { display: none; }

/* The party's cover, pushed right back: blurred, dimmed and desaturated so nothing laid over
   it competes with the text. Scaled up because blur leaves a soft, transparent edge. */
.listen-stage-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(32px) saturate(0.55) brightness(0.85);
  opacity: 0.22;
  transform: scale(1.15);
  pointer-events: none;
  z-index: 0;
}

/* Everything real sits above the backdrop. */
.listen-stage > *:not(.listen-stage-bg) {
  position: relative;
  z-index: 1;
}

/* Nothing playing: the party's own art carries the stage instead of an empty player. */
.listen-stage-idle {
  display: grid;
  justify-items: center;
  gap: 14px;
  padding: 6px 0;
  text-align: center;
}

.listen-stage-poster {
  width: 100%;
  max-width: 480px;
  aspect-ratio: 1;
  object-fit: cover;
  display: block;
  border: 1px solid var(--border-medium);
}

/* Cover art is square, so full bleed would be a giant block — cap and centre it.
   Video is 16:9 and reads better edge to edge. */
.listen-cover {
  width: 100%;
  max-width: 380px;
  aspect-ratio: 1;
  justify-self: center;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
  overflow: hidden;
}

.listen-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.listen-yt {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 64vh;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

.listen-yt iframe {
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
}

.listen-now {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px 24px;
  align-items: center;
  min-width: 0;
}

.listen-now > .section-label,
.listen-now > h2,
.listen-now > .account-muted {
  grid-column: 1;
  margin: 0;
}

.listen-now > h2 {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.listen-now > .listen-volume {
  grid-column: 2;
  grid-row: 1 / span 3;
  justify-self: end;
}

.listen-now > .listen-progress,
.listen-now > .listen-times,
.listen-now > .listen-unmute {
  grid-column: 1 / -1;
}

.listen-now > .listen-progress {
  margin-top: 6px;
}

.listen-progress {
  height: 6px;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

/* Host gets a bigger hit area and a pointer; listeners keep a plain read-out. */
.listen-progress.is-seekable {
  height: 12px;
  cursor: pointer;
}

.listen-progress.is-seekable:hover,
.listen-progress.is-seekable:focus-visible {
  border-color: var(--accent);
}

.listen-progress-bar {
  /* Explicitly block: the room's bar is a <div> and works by default, but the mini player's is a
     <span> whose parent is neither grid nor flex, so it stays inline — and width and height do
     not apply to inline elements. The JS was setting a width the element could not honour. */
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.25s linear;
}

.listen-times {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--text-muted);
}

.listen-volume {
  display: flex;
  gap: 10px;
  align-items: center;
}

.listen-volume input[type="range"] {
  flex: 1;
  max-width: 220px;
  min-width: 110px;
  accent-color: var(--accent);
}

.listen-volume-value {
  min-width: 30px;
  font-size: 12px;
  color: var(--text-muted);
  text-align: right;
}

.listen-unmute {
  padding: 8px 12px;
  font-size: 13px;
  color: var(--accent);
  border: 1px dashed var(--accent);
}

.listen-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* Three button classes sit in this row and the primary one is a size smaller with a shallower
   slant (6px against 10px), so the parallelograms never lined up. Unify the geometry and let
   each class keep its own colours. The width clears the longest label, so
   every button is genuinely the same width rather than merely a shared minimum. */
.listen-controls > button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 180px;
  min-height: 44px;
  padding: 0 24px;
  font-size: 13px;
  letter-spacing: 2px;
  clip-path: polygon(10px 0%, 100% 0%, calc(100% - 10px) 100%, 0% 100%);
}

/* Sits in the host controls row; a setting, not an action, so it is a checkbox not a button. */
.listen-auto-toggle {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
}
.listen-auto-toggle input { accent-color: var(--accent); }

.listen-columns {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 22px;
}

.listen-col {
  min-width: 0;
}

.listen-queue,
.listen-library {
  display: grid;
  gap: 8px;
  margin-top: 12px;
}

.listen-queue-row,
.listen-lib-row {
  display: grid;
  grid-template-columns: auto 44px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

.listen-lib-row {
  grid-template-columns: 44px minmax(0, 1fr) auto;
}

/* Title and submitter were running together; give them distinct weight and size. */
.listen-row-thumb {
  width: 44px;
  height: 44px;
  overflow: hidden;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

.listen-row-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.listen-row-text {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.listen-row-text strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.listen-cohost-label { margin-left: 5px; color: var(--accent); font-size: 10px; text-transform: uppercase; }
.listen-cohost-action {
  margin-left: 7px;
  padding: 2px 5px;
  color: inherit;
  background: transparent;
  border: 1px solid currentColor;
  font: inherit;
  font-size: 10px;
  cursor: pointer;
}

.listen-track-link {
  color: inherit;
  text-decoration: none;
}

.listen-track-link:hover,
.listen-track-link:focus-visible {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.listen-row-sub {
  font-size: 12px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.listen-row-actions {
  display: flex;
  gap: 6px;
  align-items: center;
}

.listen-queue-row.is-playing {
  border-color: var(--accent);
}

.listen-queue-pos {
  min-width: 22px;
  color: var(--text-muted);
  font-size: 13px;
}

.listen-mini-btn {
  min-height: 34px;
  padding: 6px 12px;
  font-size: 13px;
}

/* ---- mini player (calendar view) ---- */
.listen-mini {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) minmax(80px, 160px) auto auto auto;
  gap: 12px;
  align-items: center;
  margin-top: 16px;
  padding: 10px 14px;
  background: var(--bg-subtle);
  border: 1px solid var(--accent);
}

.listen-mini-cover {
  width: 44px;
  height: 44px;
  overflow: hidden;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

.listen-mini-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.listen-mini-text {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.listen-mini-text strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.listen-mini-bar {
  height: 6px;
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

@media (max-width: 760px) {
  /* Drop the progress bar first; the controls matter more on a phone. */
  .listen-mini { grid-template-columns: 44px minmax(0, 1fr) auto; }
  .listen-mini-bar { display: none; }
}

/* ---- lyrics panel ---- */
/* With an audio track the cover sits left and the lyrics take the rest of the row;
   video tracks keep the full-width layout and never show this. */
.listen-stage.has-lyrics {
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 20px;
}

.listen-stage.has-lyrics .listen-cover {
  max-width: 300px;
  justify-self: start;
}

.listen-stage.has-lyrics .listen-now {
  grid-column: 1 / -1;
}

.listen-lyrics {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 8px;
  min-width: 0;
  max-height: 300px;
}

.listen-lyrics-head {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
}

/* offsetTop of a line must be measured against this box, so it has to be positioned.
   The scrollbar is hidden — the lyrics follow the song, so there is nothing to aim at —
   and the edges fade instead, which reads as "there is more" without furniture. */
.listen-lyrics-body {
  position: relative;
  overflow-y: auto;
  padding-right: 6px;
  line-height: 1.7;
  color: var(--text-muted);
  scrollbar-width: none;
  -ms-overflow-style: none;
  mask-image: linear-gradient(to bottom, transparent, #000 12%, #000 88%, transparent);
}

.listen-lyrics-body::-webkit-scrollbar {
  display: none;
}

.listen-lyric-line {
  padding: 1px 0;
  transition: color 0.15s linear;
}

/* The karaoke pointer: current line lifts out, sung lines recede. */
.listen-lyric-line.is-current {
  color: var(--accent);
  font-weight: 600;
}

.listen-lyric-line.is-past {
  opacity: 0.45;
}

.listen-lyric-section {
  margin-top: 10px;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.7;
}

@media (max-width: 900px) {
  .listen-stage.has-lyrics { grid-template-columns: 1fr; }
  .listen-stage.has-lyrics .listen-cover { justify-self: center; }
  .listen-lyrics { max-height: 220px; }
}

/* Word emphasis sits on top of the line highlight — the line stays the anchor, so a word
   whose timing drifts never leaves the reader looking at the wrong place. */
.listen-lyric-word {
  transition: color 0.12s linear, opacity 0.12s linear;
}

.listen-lyric-line.is-current .listen-lyric-word {
  opacity: 0.55;
}

.listen-lyric-line.is-current .listen-lyric-word.is-past-word {
  opacity: 0.8;
}

.listen-lyric-line.is-current .listen-lyric-word.is-current-word {
  opacity: 1;
  text-shadow: 0 0 12px color-mix(in srgb, var(--accent) 45%, transparent);
}

/* ---- chat ---- */
/* The chat is a full-height rail beside the whole room — stage, transport and queue — rather
   than something that starts below the player. It scrolls internally and stretches to whatever
   the rest of the room happens to be tall, so the conversation is always in view. */
.listen-main {
  display: grid;
  gap: 20px;
  align-content: start;
  min-width: 0;
}

/* Grouping div for the phone's swipe panes. On desktop it dissolves so the stage,
   mic strip and host controls stay direct grid children of .listen-main. */
.listen-pane-player { display: contents; }

/* Phone-only pane switcher; the mobile block enables it. */
.listen-pane-dots { display: none; }

/* ≥1101px: the player narrows and the add-a-song/library column rises beside it, full height;
   the queue stays under the player. Areas rather than order, because half of these children
   (.listen-mic, .listen-controls) can be [hidden] at any moment — a grid-area on a hidden
   element is simply inert, so nothing needs a fallback. */
@media (min-width: 1101px) {
  .listen-main {
    grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    grid-template-areas:
      "stage side"
      "mic side"
      "controls side"
      "queue side";
    /* minmax(0, 1fr), not a bare 1fr: the side column spans all four of these rows in one grid
       area, and a bare 1fr's automatic (content-based) minimum let that spanning item's own
       height feed back into the row's auto-sizing pass — asking the column for a height above
       its natural one (below) blew the grid's resolved height out by a few hundred px past what
       was asked for, confirmed by bisection (grid-template-rows: auto*3 + minmax(0,1fr) removed
       the drift entirely at every tested height; a bare 1fr did not, regardless of which element
       inside the column carried the min-height, so it was this row's minimum, not the flex/scroll
       setup below). The explicit 0 floor is what's needed; 1fr's usual "grow to fill leftover
       space" behavior is unaffected. */
    grid-template-rows: auto auto auto minmax(0, 1fr);
    gap: 20px 22px;
  }
  .listen-stage { grid-area: stage; }
  .listen-mic { grid-area: mic; }
  .listen-controls { grid-area: controls; }
  /* .listen-columns used to be the queue/add-a-song two-up grid; here it dissolves the same way
     .listen-pane-player does, so #queueBlock and the add-a-song column become direct children
     of .listen-main and can take their own grid-areas. */
  .listen-columns { display: contents; }
  #queueBlock { grid-area: queue; min-width: 0; }
  .listen-columns > .listen-col:last-child { grid-area: side; min-width: 0; }

  /* listen-ui.js's clampChatHeight() gives this column the same minHeight (not height — a
     queue taller than the viewport must still be able to make this column taller too) that it
     gives the chat rail, so the column's own box already reaches the bottom of the screen. A
     flex column lets that reach land on the one child worth stretching: the library list grows
     to fill the extra space and scrolls inside itself, while the heading/form/search above it
     keep their natural (block) height. min-height: 0 is the standard guard against a flex
     item's content protecting itself past an explicit min-height set from outside (belt and
     braces alongside the grid-row fix above). */
  .listen-columns > .listen-col:last-child {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  .listen-columns > .listen-col:last-child .listen-library {
    flex: 1 1 auto;
    max-height: none;
    min-height: 120px;
    overflow-y: auto;
  }

  /* The host-controls row now lives in the narrower stage column (~490px at 1440px-wide
     viewports) instead of the old full-width one; at the 180px floor the six buttons wrap into
     three uneven rows, taller than the row ever was before this change. 150px keeps them at the
     same two-row shape (measured: 98px either way at 1920px, but 98px vs 123px at 1440px). */
  .listen-controls > button {
    min-width: 150px;
  }

  /* has-lyrics' 300px cover leaves only ~128px for the lyrics column once the stage itself has
     narrowed to make room for the side column (measured at 1440px) — noticeably tighter than the
     ~245px it had edge-to-edge before. 240px gives the lyrics column the room back without
     shrinking the cover enough to look undersized next to it. */
  .listen-stage.has-lyrics {
    grid-template-columns: 240px minmax(0, 1fr);
  }
  .listen-stage.has-lyrics .listen-cover {
    max-width: 240px;
  }
}

.listen-chat {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto auto auto;
  gap: 10px;
  position: sticky;
  /* Sits high: the LIVE badge that used to occupy this row is gone. The 900px cap went with it
     — on a tall window it left the chat short while the page grew around it. */
  /* Level with the top of the player column beside it — they start on the same line rather
     than the chat sitting a notch lower. */
  top: 0;
  /* Short of a full viewport on purpose. The column starts partway down the page, so a full
     100vh put its bottom below the fold before any scrolling happened — the composer sat off
     the screen exactly when it was needed. */
  height: calc(100vh - 140px);
  max-height: none;
  padding: 14px 16px;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}

/* Dynamic viewport units track the browser UI showing/hiding, so the composer
   stops drifting below the fold on laptops with auto-hiding bars. */
@supports (height: 100dvh) {
  .listen-chat { height: calc(100dvh - 140px); }
}

.chat-list {
  display: grid;
  align-content: start;
  gap: 2px;
  overflow-y: auto;
  padding-right: 4px;
}

/* Two columns, not three: the tools used to sit in a trailing `auto` grid column, which
   permanently reserved a column-gap of empty space down the right edge of every message —
   on a phone that squeezed the actual text down to a sliver. The tools now float (see
   .chat-tools below) instead of living in the grid, so nothing needs that column any more. */
.chat-message {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  gap: 4px 10px;
  padding: 4px 6px;
  align-items: start;
  position: relative;
}

.chat-message:hover { background: var(--bg-panel); }

/* Consecutive messages from one author drop the avatar and header; the text lines up under the
   first one so the eye reads it as the same person still talking. */
.chat-message.is-grouped { grid-template-columns: 32px minmax(0, 1fr); }
.chat-message.is-grouped .chat-main { grid-column: 2; }

.chat-message.is-flashed { background: var(--accent-bg-subtle); }

.chat-avatar {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  overflow: hidden;
  font-size: 13px;
  color: var(--text-muted);
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
}

.chat-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.chat-main { min-width: 0; display: grid; gap: 3px; }

.chat-head { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.chat-author { font-size: 13px; }
.chat-time { font-size: 11px; color: var(--text-muted); }

/* A relayed message must never be mistaken for one sent here. */
.chat-badge {
  padding: 0 6px;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--border-accent);
}

.chat-body {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  overflow-wrap: anywhere;      /* a pasted URL must not widen the column */
  /* Somebody pasting lyrics means the line breaks ARE the message: without this the whole sheet
     collapses into one run-on paragraph, because a text node treats every newline as a space. */
  white-space: pre-wrap;
}

.chat-body.is-deleted { color: var(--text-muted); font-style: italic; }

/* Editing happens where the message sits, so it keeps its place in the conversation. */
.chat-edit {
  width: 100%;
  font: inherit;
  font-size: 14px;
  line-height: 1.45;
  color: var(--text-primary);
  background: var(--bg-panel);
  border: 1px solid var(--border-accent);
  padding: 4px 6px;
  resize: vertical;
}

/* A mention: ours resolved to a name, a Discord one standing in for the account number it
   arrived as. Being named yourself is louder than naming somebody else. */
.chat-mention {
  padding: 0 4px;
  color: var(--accent);
  background: var(--accent-bg-subtle);
  white-space: nowrap;
}

.chat-mention.is-me {
  color: var(--bg-panel);
  background: var(--accent);
}

/* The whole message, so being called by name survives a scroll back through a busy room. */
.chat-message.has-me { background: var(--accent-bg-subtle); }

/* Name completion, above the composer rather than over the conversation. */
.chat-form { position: relative; }

.chat-mention-list {
  position: absolute;
  bottom: calc(100% + 4px);
  left: 0;
  z-index: 5;
  min-width: 180px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  background: var(--bg-panel);
  border: 1px solid var(--border-accent);
}

.chat-mention-option {
  padding: 6px 10px;
  font-size: 13px;
  text-align: left;
  color: var(--text-primary);
  background: none;
  border: 0;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-mention-option.is-active,
.chat-mention-option:hover { background: var(--accent-bg-subtle); color: var(--accent); }

.chat-quote {
  display: flex;
  gap: 6px;
  align-items: baseline;
  padding: 2px 0 2px 8px;
  font-size: 12px;
  text-align: left;
  color: var(--text-muted);
  background: none;
  border: 0;
  border-left: 2px solid var(--border-accent);
  cursor: pointer;
}

.chat-quote { min-width: 0; }
.chat-quote-author { color: var(--accent); white-space: nowrap; }

/* flex:1 and min-width:0 are what make the ellipsis possible, not decoration. A flex item's
   automatic minimum size is its min-content width, and min-content for a nowrap line is the whole
   unwrapped line — so without this the quote refuses to shrink, `overflow: hidden` never gets a
   narrower box to clip against, and a long quoted message runs straight off the right edge instead
   of ending in an ellipsis. Same three properties as .chat-reply-body, which had it right. */
.chat-quote-body {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-attachments { display: grid; gap: 6px; justify-items: start; }

.chat-attachment {
  max-width: 100%;
  max-height: 220px;
  display: block;
  border: 1px solid var(--border-medium);
}

/* Stickers are transparent art, not photos — no frame. */
.chat-attachment.is-sticker { max-height: 140px; border: 0; }

/* Custom emoji sit in the line of text, so they are sized to it rather than to a fixed pixel
   height — they stay proportional if the chat font ever changes. */
.chat-emoji {
  height: 1.375em;
  width: auto;
  vertical-align: -0.3em;
  margin: 0 1px;
}

/* Emoji with no words around them get the larger treatment every chat client uses. */
.chat-body.is-jumbo .chat-emoji { height: 2.5em; vertical-align: -0.5em; }

.chat-reactions { display: flex; flex-wrap: wrap; gap: 4px; }

.chat-reaction {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 1px 7px;
  font-size: 12px;
  color: var(--text-primary);
  background: var(--bg-panel);
  border: 1px solid var(--border-medium);
  cursor: pointer;
}

.chat-reaction.is-mine { border-color: var(--accent); }
.chat-reaction-count { color: var(--text-muted); font-size: 11px; }
.chat-react-add { color: var(--text-muted); }

/* Tools appear on hover: always-visible controls on every message make a room feel cluttered.
   They float at the top edge of the message the way every chat client puts them, so they never
   push the text around as they appear. Positioned absolutely against .chat-message (already
   position: relative above) rather than living in the grid — a grid column reserved for them
   permanently ate width from the message body even while the tools themselves were invisible,
   which is what crushed phone messages down to a couple of words per line. Floating them over
   the top-right corner costs nothing until they actually show. */
.chat-tools {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 2px;
  opacity: 0;
  align-items: center;
  padding: 1px 3px;
  position: absolute;
  /* 0, not a small negative offset: on touch (tools permanently visible, see the hover:none
     rule below) the tray wraps to two ~32px-tall rows on a narrow phone message, and consecutive
     messages in this list sit only ~2px apart. A negative top pulled the tray 2px into the
     padding of whichever message sits above — never over its text, but into its box all the
     same. Starting flush with this message's own top edge costs nothing (the tray already
     overlays this message's own author/time line either way, same as any chat client's hover
     toolbar) and leaves a clean gap above instead of a razor-thin overlap. */
  top: 0;
  right: 6px;
  /* Up to seven buttons (four reactions, more-reactions, reply, delete) rendered as color emoji
     glyphs run wider than they look — six of them alone come to ~220px. width: max-content is
     what keeps this box exactly as wide as its own buttons instead of stretching to fill
     .chat-message (its containing block, since it has no explicit grid placement of its own),
     which would otherwise paint its opaque background over the author/time line or the message
     text beside it. On a narrow phone column ~220px is close to the whole message width, so
     max-width + wrap let it break onto a second row rather than spilling past the message's own
     left edge — over the avatar of its own row, or into the horizontal scroller the overflow
     guard below is there to prevent. */
  width: max-content;
  max-width: calc(100% - 12px);
  z-index: 2;
  background: var(--bg-panel);
  border: 1px solid var(--border-faint);
  transition: opacity 0.12s;
}

.chat-message:hover .chat-tools,
.chat-message:focus-within .chat-tools { opacity: 1; }

/* Keyboard users never hover, and a touch screen has no hover at all — on those the tools stay
   visible rather than being unreachable. Permanently visible also means permanently sized —
   the desktop hover tray (padding, 13-14px glyphs) was measured overlapping this message's own
   author/time line on a phone track, so touch gets a tighter version of the same buttons rather
   than fewer of them: reacting is "the friction worth removing" (see listen-chat.js), so cutting
   a reaction here to buy width would undo that. Shrinking the seven buttons (four reactions,
   more-reactions, reply, delete) from ~32px to ~26px each takes the tray from ~222px to ~186px —
   real width back, but still short: the actual author+time text (not just its stretched grid
   cell) runs to within ~95px of the message's right edge, and even six comfortably-tappable
   buttons need more room than that. Delete is the one of the seven that is conditional already
   (host or own message only) and rarer than reacting, so it is the one dropped here — a host
   still has it from the desktop hover tray; this only removes the touch-permanent copy. */
@media (hover: none) {
  .chat-tools {
    opacity: 1;
    padding: 0;
  }

  .chat-tools .chat-tool,
  .chat-tools .chat-tool-emoji {
    padding: 0 4px;
    font-size: 12px;
    min-width: 22px;
    min-height: 22px;
  }

  .chat-tools .chat-tool[title="Delete"] {
    display: none;
  }
}

.chat-tool-emoji {
  font-size: 14px;
  line-height: 1.4;
  padding: 0 3px;
  filter: grayscale(0.35);
  transition: filter 0.12s, transform 0.12s;
}

.chat-tool-emoji:hover {
  filter: none;
  transform: scale(1.2);
  border-color: transparent;
}

.chat-tool {
  padding: 0 6px;
  font-size: 13px;
  color: var(--text-muted);
  background: none;
  border: 1px solid transparent;
  cursor: pointer;
}

.chat-tool:hover { color: var(--accent); border-color: var(--border-accent); }

.chat-reply-bar {
  display: flex;
  gap: 6px;
  align-items: baseline;
  padding: 6px 8px;
  font-size: 12px;
  background: var(--bg-panel);
  border-left: 2px solid var(--accent);
}

.chat-reply-body {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-muted);
}

.chat-form { display: grid; gap: 8px; }

.chat-form textarea {
  width: 100%;
  min-height: 38px;
  /* Grown by the composer as lines are added, up to this. Kept in step with CHAT_INPUT_MAX. */
  max-height: 160px;
  padding: 9px 10px;
  /* The box sizes itself now, so a drag handle would only fight it. */
  resize: none;
  overflow-y: auto;
  color: var(--text-primary);
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
  font: inherit;
  font-size: 14px;
}

.chat-form-tools { display: flex; gap: 6px; align-items: center; }
.chat-form-tools .roadmap-add-btn { margin-left: auto; }

.chat-picker {
  position: absolute;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  max-width: 220px;
  padding: 6px;
  background: var(--bg-panel);
  border: 1px solid var(--border-accent);
}

.chat-picker-emoji {
  padding: 2px 5px;
  font-size: 17px;
  background: none;
  border: 0;
  cursor: pointer;
}

.chat-picker-emoji:hover { background: var(--bg-subtle); }

@media (max-width: 1100px) {
  /* Stacked, so chat gets a sane height of its own rather than inheriting the tall desktop one,
     and stops sticking — a sticky panel in a single column just covers the page. */
  .listen-chat { position: static; height: 420px; max-height: none; }
}

/* Attaching, inline in the composer rather than a browser prompt. */
.chat-attach-bar {
  display: flex;
  gap: 6px;
  align-items: center;
  padding: 8px;
  background: var(--bg-panel);
  border: 1px solid var(--border-accent);
}

.chat-attach-bar input[type="url"] {
  flex: 1;
  min-width: 0;
  padding: 7px 9px;
  color: var(--text-primary);
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
  font: inherit;
  font-size: 13px;
}

/* Search-and-pick, like Discord. Sits above the composer inside the chat rail. */
.chat-picker-panel {
  display: grid;
  gap: 8px;
  padding: 8px;
  background: var(--bg-panel);
  border: 1px solid var(--border-accent);
}

.chat-picker-tabs { display: flex; gap: 4px; align-items: center; }

.chat-picker-tab {
  padding: 3px 10px;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
  background: none;
  border: 1px solid transparent;
  cursor: pointer;
}

.chat-picker-tab.is-active { color: var(--accent); border-color: var(--border-accent); }
.chat-picker-close { margin-left: auto; }

.chat-picker-pane[hidden] { display: none; }
.chat-picker-pane { display: grid; gap: 8px; }

.chat-picker-pane input[type="search"] {
  padding: 7px 9px;
  color: var(--text-primary);
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
  font: inherit;
  font-size: 13px;
}

/* Fixed height with its own scroll: the grid must not shove the composer off screen. */
.chat-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 6px;
  max-height: 260px;
  overflow-y: auto;
}

.chat-picker-cell {
  padding: 0;
  aspect-ratio: 1;
  overflow: hidden;
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
  cursor: pointer;
}

.chat-picker-cell:hover { border-color: var(--accent); }
.chat-picker-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.chat-picker-grid .nf-empty { grid-column: 1 / -1; }

/* "Still there?" — loud enough to notice, gone the moment it is answered or lapses. */
.listen-ping {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 10px 14px;
  background: var(--accent-bg-subtle);
  border: 1px solid var(--accent);
}

.listen-ping .roadmap-add-btn { margin-left: auto; }

/* Searching your own tracks. A library only grows, and scrolling is not a strategy. */
.listen-lib-search {
  width: 100%;
  margin-bottom: 8px;
  padding: 7px 9px;
  color: var(--text-primary);
  background: var(--bg-input);
  border: 1px solid var(--border-medium);
  font: inherit;
  font-size: 13px;
}

.listen-library { max-height: 380px; overflow-y: auto; }

/* Shown when the room cannot reach the server. Quiet, because it is usually momentary. */
.listen-trouble {
  padding: 8px 14px;
  font-size: 13px;
  color: var(--text-muted);
  background: var(--bg-subtle);
  border: 1px solid var(--border-medium);
}


/* ---- phone: the room is four swipe panes (Tracks · Player · Chat · People) ---- */
@media (max-width: 760px) {
  /* .listen-shell's grid column is an implicit `auto` track: sized to its item's max-content.
     The panes below use a percentage flex-basis with flex-shrink: 0, and with the column's
     size still indefinite that basis falls back to each pane's own content width — the
     panes' widths sum instead of sharing 100%, and the whole shell balloons past the viewport.
     Capping the track at the available inline size (not its content) breaks that feedback loop;
     the panes then correctly read the shell's real width and the overflow moves where it
     belongs, inside `.listen-room-layout`'s own scroller. */
  .listen-shell { grid-template-columns: minmax(0, 1fr); }

  /* Viewport-bounded column: header and bars keep their height, the pane
     row takes the rest. 104px = the shell's 88px top padding + breathing room. */
  .listen-room {
    display: flex;
    flex-direction: column;
    height: calc(100vh - 104px);
  }
  @supports (height: 100dvh) {
    .listen-room { height: calc(100dvh - 104px); }
  }

  /* Chrome is just the nav and the tabs now: the whole head row (title, meta, #backToHub) is
     dropped rather than compacted. The party name lives on the stage title row instead
     (#stagePartyTitle); the way back is the leading arrow on the pane switcher below.
     Dropping the row loses nothing live: renderRoom always clears #roomStatus to '' (the
     LIVE badge it once held was removed), so nothing user-facing is hidden along with it. */
  .listen-room-head { display: none; }

  /* The back affordance is a leading button in #paneDots, not a link in the site's shared
     <nav>. It started there (a `.listen-nav-back` anchor after .nav-brand), but the top bar
     has no free width for it: .nav-auth is position:absolute, right-anchored, and its own
     width grows with the signed-in user's display name — at 390px a longer name (e.g.
     "DIGITALSCRIBE") already pushes .nav-auth's left edge under .nav-brand with nothing else
     in the nav at all, and at 320px that's true even signed out. A back link added to that
     row can only make an already width-starved strip worse, at any width, for any user.
     #paneDots isn't crowded by anything outside itself, but its own four dots (Tracks/
     Player/Chat/People) already run wider than a 320px viewport on their own — adding a
     fifth flex item to that centered, unwrapped row would just get squeezed off both edges
     along with them. Taking the button out of that flow entirely — pinned absolute to the
     row's own left edge — is what actually guarantees it stays on screen and clear of the
     dots, independent of how many dots there are or how tight the label text runs. */
  .listen-pane-back { display: none; }
  .listen-pane-back.is-active {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    left: 4px;
    top: 50%;
    transform: translateY(-50%);
    min-width: 40px;
    min-height: 40px;
    font-size: 15px;
    color: var(--text-muted);
    background: none;
    border: 1px solid transparent;
    cursor: pointer;
  }

  /* Twenty listeners must not push the player off screen: drop the whole bar rather than
     let it wrap or scroll sideways. */
  .listen-roster-bar { display: none; }

  /* The roster bar's chips do not fit a 390px chat pane, so phones get their own People pane
     instead (below) — the chat pane keeps the desktop's plain 5-row grid, untouched. */

  /* Dissolve the desktop column so the panes become siblings. */
  .listen-main { display: contents; }

  .listen-room-layout,
  .listen-room-layout.has-chat {
    display: flex;
    flex: 1;
    min-height: 0;
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* The desktop grid rule sets align-items: start (a cross-axis property, so it survives the
       switch to flex here — no phone rule touched it until now). Left alone, each pane sizes to
       its own content height instead of the row's full height, which is exactly the space the
       panes below need to hand to their flex:1 children. Stretch is what makes the pane lock
       (Step 2) possible at all. */
    align-items: stretch;
  }
  .listen-room-layout::-webkit-scrollbar { display: none; }

  /* 92% + centre-snap = the neighbour pane peeks in at the edge. The pane itself never
     scrolls — each one lays out as a flex column so a single inner box (the queue/library
     list, the lyrics body, the chat list) does the scrolling instead. */
  .listen-columns,
  .listen-pane-player,
  .listen-chat {
    flex: 0 0 92%;
    scroll-snap-align: center;
    min-height: 0;
    overflow-y: hidden;
    overflow-x: hidden;
  }
  /* display:flex also replaces .listen-columns' two-column grid. Once the queue moves into
     the player pane (see #queueBlock below), only the add-a-song column is left here, so it
     just needs to fill the pane — no stacking to arrange. */
  .listen-columns { order: 1; display: flex; flex-direction: column; gap: 22px; overflow-y: hidden; }
  /* The pane used to be a locked box (its own overflow-y: hidden, from the shared rule above)
     with a single inner scroller doing all the work. Task 2 makes the WHOLE Player tab scroll
     instead — stage, the pinned transport, host controls, then the full queue reads on down —
     so this pane needs to be the scroll container. overflow-x stays hidden (inherited from the
     shared rule; nothing here overrides it) so the horizontal swipe-snap between tabs is
     untouched. */
  .listen-pane-player { order: 2; display: flex; flex-direction: column; gap: 16px; overflow-y: auto; }

  /* Player pane internals: the stage now sizes to its own content (the cover/lyrics) rather
     than filling the pane — filling made sense when the pane was a locked box with only one
     thing scrolling inside it (the lyrics body); now the pane itself scrolls, so a fixed-fill
     stage would just leave dead space below a short cover instead of letting the transport
     and queue sit closer to it. */
  .listen-stage {
    flex: none;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
  }
  .listen-stage-idle,
  .listen-cover {
    flex: 0 1 auto;
    min-height: 0;
    align-self: center;
    max-width: min(100%, 42vh);
  }
  .listen-yt { flex: none; }
  .listen-lyrics { flex: 1 1 0; min-height: 60px; max-height: none; }

  /* The party name: .listen-room-head is display:none at this width (see above), so this is
     the only place left for it. A normal flex row, ~20px tall — nothing below needs to know
     it is there. */
  .listen-stage-title {
    display: block;
    flex: none;
    font-size: 12px;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--text-muted);
    text-align: center;
  }

  /* The desktop transport is a 2-column grid (title | volume, volume spanning 3 rows) — at
     390px the auto-sized volume column starves the title column, so the kicker/title/creator
     truncate to almost nothing and the mute+slider visually overlap the title text. One column:
     every child (including volume) drops into the next row in DOM order — kicker, title,
     creator, progress, times, volume, unmute — since none of them has an explicit grid-row and
     the desktop grid-column rules (1, or 1/-1) all resolve to the single track anyway.

     It is also promoted (listen-ui.js layoutForPhone) to be a direct child of .listen-pane-player
     right after .listen-stage, so position:sticky pins it against the PANE's own scrollport
     (the pane is the scroll container now — see overflow-y:auto above) instead of against the
     stage, whose own overflow:hidden would otherwise make IT the sticky containing block and
     leave the transport stuck to the stage's edge, disappearing once the stage scrolled past.
     That reparenting is also why the old margin-top:auto is gone: it used to shove the
     transport to the bottom of a stage that was filling the whole pane (flex:1 1 0 above); the
     stage no longer fills anything, and the transport is no longer even its child.
     No horizontal inset to correct for: .listen-pane-player carries no padding of its own at
     this width (checked — the shared/phone-specific rules above set none), so the sticky bar's
     box already runs edge to edge across the pane without any negative-margin offset.

     --bg-subtle is a 3.5% alpha tint (styles.css :root), not a solid fill — everywhere else it
     is used, whatever sits behind it never moves, so the near-transparency is invisible. Here
     the bar has to occlude queue rows sliding past underneath it; measured with a screenshot
     pixel diff (a bright #00ff00 marker row scrolled to sit exactly behind the pinned bar vs.
     nothing behind it — verify-mv4-t2-opacity.js), --bg-subtle let it bleed straight through
     (avg per-channel delta ~78, several dozen pixels reading as visibly green). --bg-surface is
     the theme's solid panel fill (both themes define it as a fully opaque color, not an alpha
     mix) and is what the rest of the room already uses wherever something needs to sit flush
     opaque above other content (styles.css/grand-final.css cards and headers) — the same
     pixel-diff script comes back byte-identical with it here. */
  .listen-now {
    flex: none;
    grid-template-columns: minmax(0, 1fr);
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--bg-surface);
    border-bottom: 1px solid var(--border-medium);
    padding: 8px 12px;
    margin: 0;
  }
  .listen-now > .listen-volume {
    grid-column: 1;
    grid-row: auto;
    justify-self: stretch;
  }

  #voiceBar,
  #hostControls { flex: none; }

  /* The queue rejoins the player pane here (moved by listen-ui.js layoutForPhone) instead of
     living in its own Tracks-tab pane on a phone — the Tracks tab keeps only add-a-song. Its
     heading + rows keep their natural height; nothing here needs to scroll on its own because
     the whole pane does now. */
  #queueBlock { flex: none; display: block; }

  /* The audio-track layout (.has-lyrics) sets its own gap for the desktop side-by-side grid —
     an unconditional rule with more classes in the selector, so it outranks the plain
     .listen-stage rule above by specificity alone, regardless of source order. Restate it here
     so the phone stage is consistent whether or not a lyrics track is on. */
  .listen-stage.has-lyrics { gap: 10px; }

  /* has-lyrics without has-lyric-text (a track with no lyrics yet, or a failed alignment) is
     meant to fall through to the plain phone cover rule above (max-width: min(100%, 42vh)) —
     but the desktop-wide ".listen-stage.has-lyrics .listen-cover { max-width: 300px }" rule
     (further up this file) has the same specificity as that plain rule and sits later in
     source order, so on a phone it would otherwise win and leave the cover pinned to a flat
     300px regardless of viewport height. Restate the responsive cap here, after it, so a
     has-lyrics-only stage keeps the same sharp, viewport-relative cover as any other phone
     track. When has-lyric-text is also present the rule below this one wins instead (same
     specificity, later in the file) and takes the cover full-bleed. */
  .listen-stage.has-lyrics .listen-cover { max-width: min(100%, 42vh); }

  /* Lyrics own the pane on a phone, but only once there is text to give it to — has-lyrics
     alone just means "this is an audio track" (renderLyrics in listen.js sets it whenever the
     source isn't YouTube, placeholder text and all). has-lyric-text is the narrower class,
     set only when renderLyrics has real lines (parsed or estimated) to show; a track with no
     lyrics yet, or a failed alignment, keeps the plain sharp cover below instead.
     The cover stops being a shrinking box competing with the text for height and becomes a
     full-bleed blurred backdrop instead (same recipe as .listen-stage-bg above). The lyrics
     panel then gets a fixed viewport-relative height (below) instead of growing to fill the
     stage — the stage no longer fills the pane (flex: none, above), so "the rest of the
     stage" is no longer a meaningful height to grow into.
     .listen-now used to get position:relative/z-index:1 for free from the unconditional
     ".listen-stage > *:not(.listen-stage-bg)" rule near the top of this file just by being a
     stage child; now that listen-ui.js's layoutForPhone promotes it to a .listen-pane-player
     child instead, that rule no longer reaches it — the sticky rule on .listen-now (above)
     already gives it its own stacking context (position + z-index), so it still paints above
     this backdrop without needing anything extra here. */
  .listen-stage.has-lyric-text .listen-cover {
    position: absolute;
    inset: 0;
    max-width: none;
    width: 100%;
    height: 100%;
    align-self: auto;
    border: 0;
    /* 6px, down from 14px: the art was too washed out to recognize behind the lyrics. Checked
       against a bright, busy test cover at 390x844 in both themes — the current line's halo
       (text-shadow, below) already carries all of the readability here regardless of blur
       radius, and the one line that IS harder to read against a bright patch (the lead-in
       words of the current line, faded via .is-current .listen-lyric-word's own opacity) reads
       identically at 6px and at the old 14px, so that's a pre-existing word-fade characteristic,
       not something this change touches. No need to step to 8px or touch the shadow. */
    filter: blur(6px) saturate(0.8) brightness(0.85);
    opacity: 0.45;
    transform: scale(1.1);
    pointer-events: none;
    z-index: 0;
  }

  /* The stage no longer fills the pane (flex: none, above) and the pane is now the thing that
     scrolls, so an intrinsic flex:1 1 0 here has nothing bounded to grow into — it would just
     collapse to its min-content. A fixed viewport-relative height gives the lyrics their own
     bounded box again; the body inside it (.listen-lyrics-body) keeps doing the actual
     scrolling, same as before. */
  .listen-stage.has-lyric-text .listen-lyrics {
    position: relative;
    z-index: 1;
    flex: none;
    height: 46vh;
    max-height: none;
  }

  /* Measured against a bright test cover: the current line's accent-colored text sat at
     ~1.5-2:1 contrast on dark and ~2.8-3.6:1 on light, both well under WCAG's 4.5:1 floor for
     body text, in exactly the region a listener needs to read most (the karaoke pointer).
     A soft halo in the panel's own background tone (not a hardcoded shadow color, so it still
     tracks a theme swap) restores an edge around every glyph regardless of what is directly
     behind it. */
  .listen-stage.has-lyric-text .listen-lyric-line {
    text-shadow: 0 0 8px color-mix(in srgb, var(--bg-subtle) 80%, transparent);
  }

  /* The track cover is now itself a backdrop — showing the party's own cover behind it too
     (#stageBackdrop, unconditionally shown whenever something is playing and the party has
     cover art) would double it up: two blurred images stacked, one darkening the other. Only
     the track's cover backdrop should show once lyrics take the pane. */
  .listen-stage.has-lyric-text .listen-stage-bg { display: none; }

  /* Tracks pane: queue and library are the two things worth scrolling; everything else
     (headings, the add-a-song form, the search box) keeps its natural size. */
  .listen-col { flex: 1 1 0; display: flex; flex-direction: column; }
  .listen-queue,
  .listen-library { flex: 1 1 0; overflow-y: auto; max-height: none; }
  .listen-queue { min-height: 0; }
  /* The library sits behind a heading, the whole add-a-song form and a second heading + search
     box, so an even 50/50 split between the two .listen-col starves it down to a sliver. Give it
     a floor so it never collapses that far.
     This only works because the two columns are NOT both min-height: 0. An explicit
     min-height: 0 tells the flex algorithm "ignore this item's content when negotiating space",
     which is exactly what we want for the queue column (whose content-based minimum should be
     near-zero, since .listen-queue is free to shrink into its own scrollbar) — but giving BOTH
     columns that same escape hatch means neither one's real minimum (now including this floor)
     ever surfaces during layout, and the excess simply overflows out of both boxes at once
     instead of the flex algorithm reassigning space. Leaving the library's column at its default
     auto min-height lets its true content minimum — headings + form + floor — win the fight for
     space, and the queue column (min-height: 0, first in source) is the one asked to give up
     the difference, exactly as intended. */
  .listen-library { min-height: 140px; }
  .listen-submit { flex: none; }

  .listen-chat { order: 3; }

  /* The chat pane keeps its internal grid (list scrolls, composer pinned). `height: auto` would
     size this to its content instead — with 92% flex-basis siblings that content-fit height can
     run past the room's fixed viewport-bounded height (see .listen-room above), pushing the
     composer off-screen with no way to reach it. 100% takes the row's cross-axis stretch height
     instead, so the grid's `1fr` list row actually has something to shrink within — the list
     scrolls on `.chat-list`'s own overflow and the composer row stays put at the bottom. */
  .listen-chat {
    display: grid;
    position: static;
    height: 100%;
    overflow-y: hidden;
    overflow-x: hidden;
    padding-bottom: calc(14px + env(safe-area-inset-bottom));
  }

  /* People pane: the 4th swipe pane, holding the full roster the desktop roster bar shows
     inline. Own scroller on .listen-people-list, same pane-lock shape as the other three. */
  .listen-people {
    display: flex;
    flex-direction: column;
    order: 4;
    flex: 0 0 92%;
    scroll-snap-align: center;
    min-height: 0;
    overflow-y: hidden;
    overflow-x: hidden;
    padding: 14px 16px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-medium);
  }
  .listen-people-list {
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    display: grid;
    gap: 8px;
    align-content: start;
    margin-top: 10px;
  }
  .listen-person-row {
    display: flex;
    gap: 10px;
    align-items: center;
    padding: 8px 10px;
    background: var(--bg-panel);
    border: 1px solid var(--border-medium);
  }
  .listen-person-row.is-away { color: var(--text-muted); border-style: dashed; }
  .listen-person-queued { margin-left: auto; font-size: 12px; color: var(--text-muted); }

  /* Host controls: two per row instead of six 180px bars. */
  .listen-controls > button {
    min-width: 0;
    flex: 1 1 40%;
  }

  /* The pane switcher: labels double as dots, accent marks the active pane.
     It is a <nav>, and the site's bare `nav {}` rule (styles.css) fixes any nav to the top of
     the viewport as the header bar — position, z-index, height, background, blur, border. That
     rule matched here too and this element, being later in the DOM, painted over the real nav,
     hiding the brand and hamburger. Reset every property that rule sets so this nav lays out
     as a plain flex row in normal flow, below the real header. */
  .listen-pane-dots {
    display: flex;
    gap: 4px;
    justify-content: center;
    /* relative, not static: still escapes the site's `nav { position: fixed }` rule (the
       point of this reset), but also gives the back button below a containing block to pin
       itself to, clear of the centered dot group regardless of how many dots there are. */
    position: relative;
    top: auto;
    left: auto;
    right: auto;
    z-index: auto;
    height: auto;
    /* The 44px of left padding reserves the back button's own footprint (4px inset + 40px
       width) out of the space the dots center themselves in. It closes the gap almost
       exactly at 390px and above — verified clear at 360/390/480/760 — and leaves only a
       ~7px box-model overlap with the Tracks dot's own padding at 320px, where the four
       dots' combined width (even trimmed below) still slightly exceeds what's left. 320px
       is narrower than any current phone; the button paints above the dots (default stacking
       order for an absolutely-positioned element over static siblings) so it stays fully
       visible and tappable there regardless. */
    padding: 8px 0 8px 44px;
    background: none;
    backdrop-filter: none;
    border-bottom: none;
  }
  .listen-pane-dot {
    min-height: 40px;
    /* Trimmed from 6px 12px: reclaims width so the row clears the back button's reserved
       space at 390px (see above) without shrinking the 40px tap-target height. */
    padding: 6px 6px;
    font-size: 11px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-muted);
    background: none;
    border: 1px solid transparent;
    cursor: pointer;
  }
  .listen-pane-dot.is-active {
    color: var(--accent);
    border-color: var(--border-accent);
  }

  /* Phone tap targets. */
  .listen-mini-btn { min-height: 40px; }
  .chat-tool { padding: 4px 8px; }

  /* Android auto-zooms the whole page when a focused text field computes below 16px, and the
     zoom sticks around after the keyboard closes — the user sees the page panned/cut (nav
     clipped, panes shifted) even though nothing actually overflowed. Every field a phone user
     can focus inside this room was 13-14px; bump each to 16px here so focusing never triggers
     the zoom. Scoped to this media query so desktop keeps its existing sizes untouched. */
  #chatInput,
  .listen-submit input[type="url"],
  .listen-submit select,
  .listen-lib-search,
  .chat-picker-pane input[type="search"],
  .chat-attach-bar input[type="url"] {
    font-size: 16px;
  }
}

/* ---- phone: the hub trades the month grid for an agenda list ---- */
.listen-agenda { display: none; }

@media (max-width: 760px) {
  .party-private-picker-head { align-items: stretch; flex-direction: column; }
  .party-private-picker-head input { width: 100%; }
  .party-private-columns { grid-template-columns: 1fr; }
  .listen-world-clock { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .listen-hub .listen-world-clock-cell { padding-right: 76px; }
  .listen-hub .listen-world-clock-flag { width: 62px; height: 31px; }
  .listen-candidate-clock-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  #panelHost .listen-create { padding: 16px; }
  #panelHost .listen-create-row { flex-direction: column; }
  #panelHost .listen-cover-slot { width: 100%; height: 132px; }
  #panelHost .listen-create > .roadmap-add-btn { width: 100%; }
  #panelHost .listen-kind-options > .listen-mini-field:has(input[type="datetime-local"]) { grid-column: span 1; }

  .listen-cal-head,
  .listen-cal-dows,
  .listen-cal-grid,
  .listen-day-panel { display: none; }

  .listen-agenda { display: grid; gap: 10px; }
  .listen-agenda .listen-agenda-day { margin-top: 8px; }
  .listen-agenda .listen-party-card { min-height: 44px; }
}

/* The room's scroll bars, matching the admin pattern: the native bar is hidden and a thumb in
   the theme's red is drawn over the edge of the panel. Full accent only on hover, so a bar
   sitting there permanently does not compete with the accents that carry meaning. */
.listen-scroll-host { position: relative; }

.chat-list,
.listen-library {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.chat-list::-webkit-scrollbar,
.listen-library::-webkit-scrollbar { display: none; }

.listen-scrollbar {
  position: absolute;
  top: 0;
  right: 0;
  width: 10px;
  background: var(--border-faint);
  cursor: pointer;
  /* Out of the way until there is something to scroll; the driver shows it. */
  display: none;
}

.listen-scrollbar-thumb {
  position: absolute;
  left: 2px;
  right: 2px;
  min-height: 40px;
  background: var(--accent-dim);
  transition: background 0.15s;
  cursor: grab;
}

.listen-scrollbar-thumb:hover,
.listen-scrollbar-thumb.is-dragging { background: var(--accent); }

.listen-scrollbar-thumb.is-dragging { cursor: grabbing; }

/* The host mic strip. Quiet by default — it is a second audio source layered over the music, so
   it announces itself without competing with the room's own transport. */
.listen-mic {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  margin: 0 0 10px;
  background: var(--bg-panel);
  border: 1px solid var(--border-faint);
}

.listen-mic-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  /* On air. Kept subtle rather than blinking: a pulsing red dot beside the music is a
     distraction for the whole time somebody is talking. */
  box-shadow: 0 0 0 3px var(--accent-bg-subtle);
}

.listen-mic-label {
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
}

.listen-mic-volume { margin-left: auto; display: flex; align-items: center; }
.listen-mic-volume input { width: 110px; accent-color: var(--accent); }

/* Label text for screen readers only — the slider has no visible label because the strip
   already says what it controls. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
