/* listen-along single-page app (index.html): layout and the few pieces stock
 * Bootstrap does not cover. Colours come only from Bootstrap tokens
 * (--bs-*) and theme.css (--app-*); no literal colours here.
 * Everything is in @layer custom so Bootstrap utilities still win.
 * See DESIGN.md before adding to this file.
 */
@layer custom {

  /* ---------------------------------------------------------------- Views */
  .view {
    display: none;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
  }
  .view.active { display: flex; }

  /* The page column is a size container: rules about what fits in it use
     @container page, because the sidebar and chat panel take a viewport-sized
     guess out of it. Not the dashboard: it has fixed children of its own. */
  #landing-view, #lobby-view, #radio-view, #profile-view, #app-view, .library-page { container: page / inline-size; }

  /* With a mouse the page column scrolls instead of the document, so the fixed
     chat panel and player bar never move when a page starts or stops overflowing
     or an overlay opens, and no scrollbar sits beyond them (theme.css has the
     gutter). Touch keeps document scrolling (the browser's address bar collapses
     with it) and has overlay scrollbars. */
  @media (pointer: fine) {
    html, body { overflow: hidden; }
    .view.active:not(#lobby-view, #radio-view) {
      height: 100dvh;
      min-height: 0;
      overflow: hidden auto;
    }
    body.has-player-bar .view.active:not(#lobby-view, #radio-view) {
      height: calc(100dvh - var(--app-player-height, 0px));
      padding-bottom: 0;
    }
    .view .dashboard-layout { min-height: auto; } /* grows with its content, the view scrolls */
  }

  .app-main {
    flex: 1;
    padding-block: 1.25rem 2rem;
  }
  .app-center {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
  }
  .app-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
    font-size: var(--bs-font-size-sm);
    color: var(--bs-fg-3);
  }
  .language-selector { width: auto; }

  /* ---------------------------------------------------------------- Login */
  .login-card { max-width: 26rem; }
  .app-logo {
    width: 4rem;
    height: 4rem;
    border-radius: var(--bs-radius-7);
  }
  .pending-icon { color: var(--bs-fg-3); }
  .pending-user-info {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-size: var(--bs-font-size-sm);
    color: var(--bs-fg-2);
  }
  .pending-user-info img {
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
  }

  /* -------------------------------------------------------------- Landing */
  .lobby-card { cursor: pointer; user-select: none; } /* a double-click plays */
  /* Quick play: shown on hover / keyboard focus; always there on touch. */
  .lobby-quick-play { opacity: 0; }
  .lobby-card:hover .lobby-quick-play,
  .lobby-card:focus-within .lobby-quick-play,
  .lobby-quick-play:focus-visible { opacity: 1; }
  @media (hover: none) { .lobby-quick-play { opacity: 1; } }
  .lobbies-empty { padding-block: 2rem; text-align: center; }
  /* Header actions: "Music library & requests" (secondary) next to the primary
     "New playlist"; the actions wrap under the title rather than squeeze it, and
     the library label folds to its icon when the column is narrow. */
  .landing-header { flex-wrap: wrap; }
  .landing-title { flex: 1 1 7rem; }
  @container page (width < 34rem) {
    .landing-library-label { display: none; }
    .landing-library-btn { padding-inline: 0.625rem; }
  }
  .playlist-private-badge { display: inline-flex; gap: 0.25rem; align-items: center; }

  /* New playlist dialog (new-playlist.js) */
  .new-playlist-field-error { color: var(--app-danger); }
  .new-playlist-field-error:empty { display: none; }
  .replace-note-danger { color: var(--app-danger); }
  /* The long song dialog scrolls in its body: the title and the button stay on screen. */
  .replace-form { display: flex; flex-direction: column; overflow: hidden; }
  .replace-form > .card-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .new-playlist-choice { cursor: pointer; }
  .lobby-card-stat {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
  }
  .pin-indicator {
    margin-right: 0.25rem;
    color: var(--bs-primary-fg);
    vertical-align: -2px;
  }

  .rejoin-prompt {
    position: fixed;
    bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    left: 50%;
    z-index: var(--bs-z-toast);
    width: min(22rem, calc(100% - 2rem));
    transform: translateX(-50%);
  }

  /* ---------------------------------------------------------------- Lobby */
  #lobby-view.active, #radio-view.active {
    height: 100vh;
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
  }
  .lobby-info { line-height: 1.2; }
  .lobby-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    scroll-padding-top: 2rem; /* the sticky .queue-head covers that much */
  }
  /* The song table takes the whole column; only the two inputs keep a cap. */
  .libpage-header > .container { max-width: 56rem; }
  .lobby-header > .container,
  .lobby-container { max-width: none; }
  .lobby-container { padding-block: 1rem; }
  .add-song, .song-filter-input { max-width: 56rem; }
  .tab-panel { display: none; }
  .tab-panel.active { display: block; }

  /* Stats strip: one line that wraps between facts, never inside one. */
  .playlist-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    padding: 0;
    margin: 0 0 0.75rem;
    font-size: var(--bs-font-size-sm);
    color: var(--app-text-muted);
    list-style: none;
  }
  .playlist-stats li { display: flex; max-width: 100%; min-width: 0; white-space: pre; }
  .playlist-stats b { font-weight: var(--bs-font-weight-medium); color: var(--app-text); }
  .playlist-stat-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

  /* Windowed list (virtual-list.js): rows are placed by position in a list as tall
     as all of them, so the scrollbar and the sticky header behave as if they were all there. */
  .vlist { position: relative; height: calc(var(--vl-count) * var(--vl-row-height)); }
  .vlist > .vlist-row {
    position: absolute;
    top: calc(var(--vl-pos) * var(--vl-row-height));
    right: 0;
    left: 0;
    height: var(--vl-row-height);
    overflow: hidden;
  }

  /* Queue list */
  .queue-list { --vl-row-height: 3.5rem; }
  .queue-list.vlist > .queue-item { min-height: 0; border-radius: 0; }
  .queue-list.vlist > .queue-item-last { border-bottom-right-radius: var(--bs-border-radius-lg); border-bottom-left-radius: var(--bs-border-radius-lg); }
  /* Compact form first (phones): thumb, two lines of text, ⋮. From a 480px page
     column the row and the sticky .queue-head share one grid, see "Song table". */
  .queue-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 3.5rem;
    padding: 0.25rem 0.5rem;
  }
  .queue-item-main { display: flex; flex: 1; gap: 0.625rem; align-items: center; min-width: 0; }
  .queue-head, .queue-item-num:not(.is-editing), .queue-cell { display: none; }
  .queue-item-num { flex-shrink: 0; }
  .queue-item-thumb { position: relative; flex-shrink: 0; width: 2.5rem; height: 2.5rem; }
  .queue-item-status {
    position: absolute;
    right: 2px;
    bottom: 2px;
    padding: 1px 3px;
    font-size: 0.625rem;
    line-height: 1;
    background-color: var(--bs-bg-body);
    border-radius: var(--bs-radius-pill);
  }
  .queue-item-status.ready { color: var(--bs-success-fg); }
  .queue-item-info { flex: 1; min-width: 0; }
  .queue-item-title {
    overflow: hidden;
    font-weight: var(--bs-font-weight-medium);
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .queue-item-meta {
    display: flex;
    gap: 0.125rem 0.5rem;
    min-width: 0;
    line-height: 1.25;
    align-items: center;
    font-size: var(--bs-font-size-xs);
    color: var(--bs-fg-3);
  }
  .queue-item-meta .badge { flex-shrink: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
  .queue-item-artist, .queue-item-added-by { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .queue-item-duration { flex-shrink: 0; font-variant-numeric: tabular-nums; }
  .queue-item-added-by { flex-shrink: 2; }

  /* Song table. Which columns exist depends on the page column, not the viewport. */
  .queue-item-pos { font-size: var(--bs-font-size-sm); font-variant-numeric: tabular-nums; color: var(--app-text-muted); }
  .queue-item-glyph { display: none; }
  .queue-item-eq { color: var(--app-accent-text); }
  .queue-item.playing .queue-item-pos { color: var(--app-accent-text-strong); }
  @media (hover: hover) {
    .queue-item:hover .queue-item-pos, .queue-item:hover .queue-item-eq { display: none; }
    .queue-item:hover .queue-item-glyph { display: block; }
  }
  .queue-item:focus-visible .queue-item-pos, .queue-item:focus-visible .queue-item-eq,
  .queue-item:has(:focus-visible) .queue-item-pos, .queue-item:has(:focus-visible) .queue-item-eq { display: none; }
  .queue-item:focus-visible .queue-item-glyph,
  .queue-item:has(:focus-visible) .queue-item-glyph { display: block; }
  /* ⋮: on hover and focus, always on touch; it keeps its place so nothing jumps. */
  @media (hover: hover) {
    .queue-item .song-menu-btn { opacity: 0; }
    .queue-item:hover .song-menu-btn, .queue-item:focus-visible .song-menu-btn,
    .queue-item:focus-within .song-menu-btn, .queue-item .song-menu-btn[aria-expanded="true"] { opacity: 1; }
  }
  .queue-head {
    position: sticky;
    top: 0;
    z-index: 3;
    align-items: center;
    min-height: 2rem;
    padding-inline: 0.5rem;
    white-space: nowrap;
    background-color: var(--app-surface);
    border: var(--bs-border-width) solid var(--app-border);
    border-bottom: 0;
    border-radius: var(--bs-border-radius-lg) var(--bs-border-radius-lg) 0 0;
    box-shadow: 0 var(--bs-border-width) 0 var(--app-border);
  }
  .queue-head > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .queue-head-num { text-align: center; }
  .queue-head:not([hidden]) + .queue-list { border-top-left-radius: 0; border-top-right-radius: 0; }
  .queue-head:not([hidden]) + .queue-list > :first-child { border-top-left-radius: 0; border-top-right-radius: 0; }
  .queue-cell { overflow: hidden; font-size: var(--bs-font-size-sm); color: var(--app-text-muted); text-overflow: ellipsis; white-space: nowrap; }
  .queue-col-dur { font-variant-numeric: tabular-nums; text-align: end; }
  .queue-head .queue-col-by, .queue-head .queue-col-date { display: none; }
  @container page (width >= 480px) {
    .queue-head, .queue-item {
      --queue-columns: 2.5rem minmax(0, 1fr) 4.25rem 2.5rem;
      display: grid;
      grid-template-columns: var(--queue-columns);
      gap: 0.75rem;
    }
    .queue-item-num, .queue-item-num:not(.is-editing) { display: flex; justify-content: center; }
    .queue-cell.queue-col-dur { display: block; }
    .queue-item-compact { display: none; }
    .queue-item-actions { justify-content: flex-end; }
  }
  @container page (width >= 650px) {
    .queue-head, .queue-item { --queue-columns: 2.5rem minmax(0, 2fr) minmax(0, 1fr) 4.25rem 2.5rem; }
    .queue-cell.queue-col-by { display: block; }
    .queue-head .queue-col-by { display: inline; }
  }
  @container page (width >= 800px) {
    .queue-head, .queue-item { --queue-columns: 2.5rem minmax(0, 2fr) minmax(0, 1fr) 6.5rem 4.25rem 2.5rem; }
    .queue-cell.queue-col-date { display: block; }
    .queue-head .queue-col-date { display: inline; }
  }
  /* Radio's table has no Added by / Added, whatever the width. */
  @container page (width >= 480px) {
    .radio-table .queue-head, .radio-table .queue-item { --queue-columns: 2.5rem minmax(0, 1fr) 4.25rem 2.5rem; }
  }
  .queue-item-progress { --bs-progress-height: 0.25rem; margin-top: 0.25rem; }
  .queue-item-actions {
    display: flex;
    flex-shrink: 0;
    gap: 0.125rem;
    align-items: center;
  }
  .queue-item-listeners { display: flex; flex-shrink: 0; padding-left: 0.375rem; }
  .queue-listener-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    margin-left: -0.375rem;
    font-size: 0.625rem;
    font-weight: var(--bs-font-weight-semibold);
    background-color: var(--bs-bg-2);
    border: 2px solid var(--bs-bg-body);
    border-radius: 50%;
  }
  .queue-listener-avatar.emoji { font-size: 0.75rem; }
  .libpage-title { font-weight: var(--bs-font-weight-medium); }
  .queue-item.playing { background-color: var(--app-accent-subtle); }
  .queue-item.playing .queue-item-title,
  .libpage-item.is-playing .libpage-title { font-weight: var(--bs-font-weight-semibold); color: var(--app-accent-text-strong); }
  /* Normal mode: hover and press read as grey, distinct from the blue playing row. */
  .queue-list:not(.is-editing) .queue-item:not(.playing):hover { background-color: var(--bs-bg-1); }
  .queue-list:not(.is-editing) .queue-item:not(.playing):active { background-color: var(--bs-bg-2); }
  /* Normal mode: the whole row plays (queue.js). */
  .queue-list:not(.is-editing) .queue-item { cursor: pointer; }
  /* Edit mode (queue-edit.js): drag handle + remove; rows slide while dragging. */
  .queue-item-handle { flex-shrink: 0; color: var(--bs-fg-4); cursor: grab; touch-action: none; }
  .queue-item-handle:active { cursor: grabbing; }
  .queue-list.is-reordering .queue-item { transition: transform 0.15s ease; }
  .queue-list.is-reordering .queue-item.dragging {
    z-index: 2;
    background-color: var(--bs-bg-2);
    box-shadow: var(--bs-box-shadow);
    transition: none;
  }
  body.app-dragging, body.app-dragging * { cursor: grabbing !important; user-select: none; }
  @container page (width < 576px) {
    .lobby-header .btn-icon:not(.btn-xs) { --bs-btn-min-height: 2rem; }
    .lobby-header .container { gap: 0.25rem; }
  }
  /* Touch: 2.5rem targets. The broken icon keeps its size in the title line and
     gets a larger invisible hit area instead. */
  @media (pointer: coarse) {
    .queue-item-handle, .queue-item-remove, .song-menu-btn,
    .libpage-item .btn-sm { --bs-btn-min-height: 2.5rem; }
    .app-navbar .btn-icon:not(.btn-xs) { --bs-btn-min-height: 2.5rem; }
    .song-broken-btn { position: relative; }
    .song-broken-btn::after { position: absolute; inset: -0.5rem; content: ""; }
  }
  .queue-item-meta > :empty { display: none; }
  .user-count { white-space: nowrap; }

  /* Your activity and library tabs: one line, sideways scroll when the column is too narrow. */
  .profile-activity-tabs, .libpage-tabs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .profile-activity-tabs .nav-link, .libpage-tabs .nav-link { flex-shrink: 0; }
  .profile-activity-tabs .nav-link:focus-visible, .libpage-tabs .nav-link:focus-visible { outline-offset: -2px; }

  /* Avatar emoji picker (profile page) */
  .profile-page-emoji-btn {
    --bs-btn-padding-x: 0;
    width: 5rem;
    height: 5rem;
    font-size: 2.5rem;
  }
  .emoji-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
    gap: 0.25rem;
  }
  .emoji-option { font-size: 1.25rem; }

  /* "How to use" snippet under a new CLI token (profile page): scrolls
     sideways inside the card instead of widening the page. */
  /* /app: numbered install and connect steps. */
  .app-steps {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding-left: 1.5rem;
  }
  .app-steps li { overflow-wrap: anywhere; }

  .profile-token-usage {
    padding: 0.5rem 0.75rem;
    overflow-x: auto;
    white-space: pre;
    color: var(--bs-fg-body);
    background-color: var(--bs-bg-body);
    border-radius: var(--bs-radius-3, 0.375rem);
  }

  /* Player bar */
  .sticky-player {
    flex-shrink: 0;
    padding-block: 0.5rem calc(0.375rem + env(safe-area-inset-bottom, 0px));
    background-color: var(--app-raised);
    border-top: var(--bs-border-width) solid var(--app-border);
    box-shadow: 0 -0.25rem 1rem color-mix(in srgb, var(--bs-shadow-color) 45%, transparent);
  }
  .sticky-player > .container { max-width: none; }
  /* Below 768px one row with the seek line wrapped under it: the left and centre
     zones dissolve, chat and mute stay in reach of the transport buttons. */
  .sticky-player-row { display: flex; flex-wrap: wrap; gap: 0 0.75rem; align-items: center; }
  .player-zone-left,
  .player-zone-center { display: contents; }
  .player-zone-right {
    display: flex;
    flex-shrink: 0;
    gap: 0.125rem;
    align-items: center;
    margin-inline-start: -0.625rem;
  }
  .album-art { width: 3rem; height: 3rem; }
  .placeholder-art {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
  }
  .placeholder-art svg { width: 45%; height: 45%; }
  .track-info { flex: 1; min-width: 0; }
  .track-title {
    margin: 0;
    font-size: var(--bs-font-size-md);
    font-weight: var(--bs-font-weight-semibold);
    line-height: 1.3;
  }
  .track-artist { margin: 0; font-size: var(--bs-font-size-sm); color: var(--bs-fg-3); }
  .sticky-player-controls {
    display: flex;
    flex-shrink: 0;
    gap: 0.125rem;
    align-items: center;
  }
  /* The one big button: accent pill, a size up from the other controls. */
  .btn-play { --bs-btn-min-height: 2.75rem; }
  .btn-play > svg { width: 1.5rem; height: 1.5rem; }
  /* Other controls: clearly clickable grey icons that brighten on hover. */
  .btn-control:not(.active):not(:disabled) { color: var(--app-text-secondary); }
  .btn-control:not(.active):not(:disabled):hover { color: var(--app-text); background-color: var(--bs-bg-3); }
  .btn-control:not(.active):not(:disabled):active { background-color: var(--bs-bg-4); }
  .volume-range { width: 5.5rem; margin-left: 0.25rem; }
  .sticky-player-progress { flex: 0 0 100%; order: 1; min-width: 0; margin-top: 0.125rem; }
  .time-display { display: flex; align-items: center; justify-content: space-between; margin-top: -0.125rem; }
  @media (pointer: coarse) {
    .btn-control { --bs-btn-min-height: 2.5rem; }
    /* A 40px hit area around the same 20px of layout: the track stays centred
       and looks the same. The buttons above it stay on top. */
    #progress-bar { position: relative; height: 2.5rem; margin-block: -0.625rem; }
    .sticky-player-controls,
    .player-zone-right { position: relative; z-index: 1; }
  }
  @media (width < 768px) {
    .volume-range { display: none; }
  }
  /* Full viewport width, three zones; equal side columns keep the centre on the
     viewport's centre whatever the song. Times sit beside the seek track, with one
     width so a longer duration does not push the track off centre. */
  @media (width >= 768px) {
    .sticky-player { padding-block: 0.5rem calc(0.625rem + env(safe-area-inset-bottom, 0px)); }
    .sticky-player > .container { padding-inline: 1rem; }
    .sticky-player-row {
      display: grid;
      grid-template-columns: minmax(0, 3fr) minmax(0, 4fr) minmax(0, 3fr);
      gap: 1rem;
    }
    .player-zone-left { display: flex; gap: 0.5rem; align-items: center; min-width: 0; }
    .player-zone-left .player-open { flex: 0 1 auto; }
    .album-art { width: 3.5rem; height: 3.5rem; }
    .player-zone-center { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
    .sticky-player-controls { justify-content: center; }
    .sticky-player-progress {
      display: grid;
      grid-template-columns: 2.5rem minmax(0, 1fr) 2.5rem;
      column-gap: 0.5rem;
      align-items: center;
      margin-top: 0;
    }
    .sticky-player-progress > .form-range { grid-area: 1 / 2; }
    .time-display { display: contents; }
    #current-time { grid-area: 1 / 1; text-align: end; }
    #duration { grid-area: 1 / 3; }
    .player-zone-right { justify-content: flex-end; min-width: 0; margin-inline-start: 0; }
    /* 79px at 800 wide up to 125px at 1280. */
    .volume-range { flex-shrink: 0; width: clamp(4.75rem, 9.6vw + 0.125rem, 7.8125rem); margin-left: 0.25rem; }
    .player-zone-right .social-toggle { margin-left: 0.25rem; }
  }
  /* Phones: slim bar — cover, title, play/pause, next, thin seek line. */
  @media (width < 576px) {
    .sticky-player { padding-block: 0.375rem calc(0.25rem + env(safe-area-inset-bottom, 0px)); }
    .player-extra,
    .time-display { display: none !important; }
    .sticky-player-controls,
    .player-zone-right { gap: 0; }
    .player-zone-right { margin-inline-start: -0.75rem; }
    .album-art { width: 2.5rem; height: 2.5rem; }
    .track-title { font-size: var(--bs-font-size-sm); }
    .track-artist { font-size: var(--bs-font-size-xs); }
    .btn-play { --bs-btn-min-height: 2.5rem; }
  }

  /* Player bar extras (player-bar.js): the bar is fixed at the bottom of every
     page; its measured height is --app-player-height on body, which pads the
     views and the library so nothing hides behind it. */
  .player-bar {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: calc(var(--bs-z-fixed) + 1); /* above the library overlay */
  }
  body.has-player-bar .view,
  body.has-player-bar .libpage-body {
    padding-bottom: var(--app-player-height, 6rem);
  }
  body.has-player-bar .rejoin-prompt { bottom: calc(var(--app-player-height, 6rem) + 1rem); }
  .player-open {
    display: flex;
    flex: 1;
    gap: 0.75rem;
    align-items: center;
    min-width: 0;
    padding: 0;
    color: inherit;
    text-align: start;
    background: none;
    border: 0;
    border-radius: var(--bs-radius-5);
  }
  .player-open:not(:disabled) { cursor: pointer; }
  .player-open:not(:disabled):hover .track-title { text-decoration: underline; }
  /* Listen-along strip (player-bar.js) above the bar's row. */
  .listen-strip {
    display: flex;
    gap: 0.375rem;
    align-items: center;
    min-width: 0;
    padding-top: 0.375rem;
    font-size: var(--bs-font-size-xs);
    color: var(--bs-primary-fg);
  }
  .listen-strip[hidden] { display: none; }
  .listen-stop { flex-shrink: 0; }
  .player-bar.is-empty .track-title { color: var(--bs-fg-3); font-weight: var(--bs-font-weight-normal); }

  .audio-unlock-prompt {
    position: fixed;
    bottom: calc(var(--app-player-height, 6rem) + 2rem);
    left: 50%;
    z-index: var(--bs-z-popover);
    transform: translateX(-50%);
  }

  /* ------------------------------------------------------ Playlists sidebar */
  /* sidebar.js. Fixed left column above the player bar from 768px, pushing the
     page right; a drawer below that. Body classes: has-plnav (approved
     account), plnav-open (drawer open). */
  body { --app-sidebar-width: 16.5rem; }
  @media (width >= 1440px) { body { --app-sidebar-width: 19rem; } }
  .app-sidebar {
    display: none;
    flex-direction: column;
    background-color: var(--app-surface);
    border-right: var(--bs-border-width) solid var(--bs-border-color);
  }
  .plnav-open-btn, .plnav-backdrop { display: none; }
  .plnav-head { flex-shrink: 0; min-height: 3.5rem; padding: env(safe-area-inset-top, 0px) 0.5rem 0 0.75rem; }
  .plnav-home { min-width: 0; margin-inline-end: auto; color: inherit; text-decoration: none; }
  .plnav-home img { flex-shrink: 0; }
  .plnav-brand-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .plnav-avatar, .plnav-toggle, .plnav-toggle-expand { display: none; }
  /* Radio: above the list, not part of it (no search, filter or sort reaches it).
     At rest the info tint, so it never reads as the selected row; when current or
     playing, the usual selection tint with an accent icon. */
  .plnav-radio {
    display: flex;
    user-select: none;
    flex-shrink: 0;
    gap: 0.625rem;
    align-items: center;
    min-height: 3.5rem;
    padding: 0.375rem 0.625rem;
    margin: 0.125rem 0.5rem 0.375rem;
    color: var(--app-text);
    text-decoration: none;
    background-color: var(--app-info-subtle);
    border: var(--bs-border-width) solid var(--app-info-border);
    border-radius: var(--bs-border-radius-lg);
  }
  .plnav-radio:hover { color: var(--app-text); background-color: color-mix(in srgb, var(--app-info-subtle) 80%, var(--app-info-solid)); }
  .plnav-radio:active { background-color: color-mix(in srgb, var(--app-info-subtle) 60%, var(--app-info-solid)); }
  .plnav-radio:focus-visible { outline: 2px solid var(--app-focus); outline-offset: 2px; }
  .plnav-radio-icon {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    color: var(--app-info);
    border-radius: var(--bs-border-radius);
  }
  .plnav-radio-icon svg { width: 1.5rem; height: 1.5rem; }
  .plnav-radio-text { display: flex; flex: 1; flex-direction: column; min-width: 0; line-height: 1.25; }
  .plnav-radio-name { font-size: var(--bs-font-size-sm); font-weight: var(--bs-font-weight-semibold); }
  .plnav-radio-meta { overflow: hidden; font-size: var(--bs-font-size-xs); color: var(--app-text-secondary); text-overflow: ellipsis; white-space: nowrap; }
  .plnav-radio .plnav-radio-eq { display: none; flex-shrink: 0; color: var(--app-accent-text); }
  .plnav-radio.is-playing .plnav-radio-eq { display: inline-flex; }
  .plnav-radio.is-current { background-color: var(--app-accent-subtle); border-color: var(--app-accent-border); }
  .plnav-radio.is-current:hover { background-color: color-mix(in srgb, var(--app-accent-subtle) 80%, var(--app-accent-border)); }
  .plnav-radio.is-current .plnav-radio-icon { color: var(--app-accent-text); }
  .plnav-radio.is-current .plnav-radio-name { color: var(--app-accent-text-strong); }
  .plnav-title-row { flex-shrink: 0; justify-content: space-between; padding: 0.25rem 0.5rem 0.25rem 0.75rem; }
  /* Sort: the current choice + an icon, opening a 208px menu of 40px rows. */
  .plnav-sort { flex-shrink: 0; gap: 0.25rem; margin-inline-start: auto; padding-inline: 0.5rem 0.375rem; white-space: nowrap; font-size: var(--bs-font-size-xs); }
  .plnav-sort svg { width: 1rem; height: 1rem; }
  .plnav-sort-menu { width: 13rem; }
  .plnav-sort-menu .menu-item { min-height: 2.5rem; align-items: center; }
  /* Filter chips: one 32px line that scrolls sideways when the words do not fit. */
  .plnav-chips { display: flex; flex-shrink: 0; gap: 0.25rem; padding: 0.25rem 0.75rem; overflow-x: auto; scrollbar-width: none; }
  .plnav-chips::-webkit-scrollbar { display: none; }
  .plnav-chip { position: relative; flex-shrink: 0; padding-inline: 0.5rem; border-radius: 1rem; white-space: nowrap; }
  .plnav-chip[aria-pressed="true"] { color: var(--app-accent-text); background-color: var(--app-accent-subtle); border-color: var(--app-accent-border); }
  .plnav-search-wrap { position: relative; flex-shrink: 0; padding: 0.25rem 0.75rem 0.5rem; }
  .plnav-search { padding-right: 2rem; }
  .plnav-search::-webkit-search-cancel-button { display: none; }
  .plnav-search-clear { position: absolute; top: 50%; right: 1rem; transform: translateY(calc(-50% - 0.125rem)); }
  .plnav-scroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-bottom: 0.5rem; }
  .plnav-list { --bs-list-group-bg: transparent; }
  .plnav-row { display: flex; gap: 0.125rem; align-items: center; min-width: 0; padding: 0.25rem 0.25rem 0.25rem 0.75rem; border-inline: 0; cursor: pointer; user-select: none; }
  .plnav-row:hover { background-color: var(--bs-bg-1); }
  .plnav-row:active { background-color: var(--bs-bg-2); }
  .plnav-link { flex: 1; min-width: 0; padding-block: 0.125rem; }
  .plnav-link:focus-visible { outline: 2px solid var(--app-focus); outline-offset: 0; border-radius: var(--bs-radius-3, 0.25rem); }
  .plnav-name { display: block; font-size: var(--bs-font-size-sm); font-weight: var(--bs-font-weight-medium); }
  .plnav-meta { display: flex; gap: 0.25rem; align-items: center; }
  .plnav-row.is-current { background-color: var(--app-accent-subtle); }
  .plnav-row.is-current .plnav-name { font-weight: var(--bs-font-weight-semibold); color: var(--app-accent-text-strong); }
  /* Pin and ⋮: on hover / keyboard focus, always on touch; a pinned row always
     shows its pin. They keep their space so names do not jump. */
  .plnav-pin, .plnav-menu-btn { flex-shrink: 0; opacity: 0; }
  .plnav-row:hover .plnav-pin, .plnav-row:hover .plnav-menu-btn,
  .plnav-row:focus-within .plnav-pin, .plnav-row:focus-within .plnav-menu-btn,
  .plnav-menu-btn[aria-expanded="true"], .plnav-pin.active { opacity: 1; }
  @media (hover: none) { .plnav-pin, .plnav-menu-btn { opacity: 1; } }
  /* A column of boxed toggles would outweigh the names: accent icon only. */
  .plnav-pin.active:not(:hover) { color: var(--app-accent-text); background-color: transparent; border-color: transparent; }
  .plnav-empty { padding: 1.5rem 1rem; text-align: center; }
  .landing-home-hint { display: none; }

  @media (width >= 768px) {
    body.has-plnav { padding-left: var(--app-sidebar-width); }
    body.has-plnav .app-sidebar {
      position: fixed;
      top: 0;
      bottom: var(--app-player-height, 0px);
      left: 0;
      z-index: calc(var(--bs-z-fixed) + 1); /* its drag handle reaches over the library overlay */
      display: flex;
      width: var(--app-sidebar-width);
    }
    body.has-plnav .library-page { left: var(--app-sidebar-width); }
    .plnav-close-btn { display: none; }
    .plnav-toggle { display: inline-flex; flex-shrink: 0; }
    /* The rail (sidebar.js): avatars instead of rows, the same list and menus. */
    body.plnav-rail { --app-sidebar-width: 4.5rem; }
    body.plnav-rail .plnav-toggle-collapse { display: none; }
    body.plnav-rail .plnav-toggle-expand { display: block; }
    body.plnav-rail .plnav-head { flex-direction: column; padding: calc(env(safe-area-inset-top, 0px) + 0.5rem) 0 0.25rem; }
    body.plnav-rail .plnav-home { margin-inline-end: 0; }
    body.plnav-rail .plnav-brand-name,
    body.plnav-rail #plnav-title,
    body.plnav-rail .plnav-search-wrap,
    body.plnav-rail .plnav-sort,
    body.plnav-rail .plnav-chips,
    body.plnav-rail .plnav-meta,
    body.plnav-rail .plnav-name,
    body.plnav-rail .plnav-pin,
    body.plnav-rail .plnav-menu-btn,
    body.plnav-rail .plnav-empty,
    body.plnav-rail #plnav-list .people-heading { display: none; }
    body.plnav-rail .plnav-radio-text, body.plnav-rail .plnav-radio .plnav-radio-eq { display: none; }
    body.plnav-rail .plnav-radio { justify-content: center; align-self: center; width: 2.5rem; min-height: 2.5rem; padding: 0; margin: 0.25rem 0; }
    body.plnav-rail .plnav-title-row { justify-content: center; padding-inline: 0; border-bottom: var(--bs-border-width) solid var(--bs-border-color); }
    body.plnav-rail .plnav-scroll { padding-top: 0.25rem; scrollbar-width: none; }
    body.plnav-rail .plnav-list ~ .plnav-list { padding-top: 0.25rem; margin-top: 0.25rem; border-top: var(--bs-border-width) solid var(--bs-border-color); }
    body.plnav-rail .plnav-row { justify-content: center; padding: 0.1875rem 0; border: 0; }
    body.plnav-rail .plnav-link { flex: none; padding: 0; border-radius: var(--bs-radius-5); }
    body.plnav-rail .plnav-avatar {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 2.5rem;
      height: 2.5rem;
      font-size: var(--bs-font-size-sm);
      font-weight: var(--bs-font-weight-semibold);
      color: var(--app-text-secondary);
      background-color: var(--bs-bg-2);
      border-radius: var(--bs-radius-5);
    }
    body.plnav-rail .plnav-row.is-pinned .plnav-avatar { color: var(--app-accent-text-strong); background-color: var(--app-accent-subtle); }
    body.plnav-rail .plnav-row.is-current .plnav-avatar { color: var(--app-on-accent); background-color: var(--app-accent); }
    /* The sidebar carries the brand and your playlists: the landing page is
       for browsing other people's. */
    body.has-plnav .landing-brand,
    body.has-plnav .my-playlists-section { display: none; }
    body.has-plnav .landing-home-hint { display: block; }
    body.has-plnav .landing-header .tagline { display: none; }
  }
  @media (width < 768px) {
    body.has-plnav .plnav-open-btn { display: inline-flex; flex-shrink: 0; }
    /* The drawer holds Home, so the playlist page's ☰ takes the back button's place. */
    body.has-plnav #back-btn, body.has-plnav .radio-back { display: none; }
    body.has-plnav .app-sidebar {
      position: fixed;
      top: 0;
      bottom: var(--app-player-height, 0px);
      left: 0;
      z-index: var(--bs-z-drawer);
      display: flex;
      width: min(20rem, 85vw);
      visibility: hidden;
      box-shadow: var(--bs-box-shadow-lg);
      transform: translateX(-100%);
      transition: transform 0.2s ease, visibility 0.2s;
    }
    body.plnav-open { overflow: hidden; }
    /* Visible at once on opening, so focus can move in straight away. */
    body.plnav-open .app-sidebar { visibility: visible; transform: none; transition: transform 0.2s ease, visibility 0s; }
    body.plnav-open .rejoin-prompt { display: none; }
    body.plnav-open .plnav-backdrop {
      position: fixed;
      inset: 0 0 var(--app-player-height, 0px);
      z-index: calc(var(--bs-z-drawer) - 1);
      display: block;
      background-color: var(--app-backdrop);
    }
  }
  @media (width < 400px) {
    body.has-plnav .landing-brand-name { display: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    body.has-plnav .app-sidebar { transition: none; }
  }
  @media (pointer: coarse) {
    .plnav-pin, .plnav-menu-btn, .plnav-sort, #plnav-new { --bs-btn-min-height: 2.5rem; }
    #plnav-new { --bs-btn-min-width: 2.5rem; }
    /* These keep their look; an invisible 40px hit area, inside the row's padding. */
    .plnav-chip { padding-inline: 0.75rem; }
    .plnav-chip::after { content: ""; position: absolute; inset: -0.3125rem 0; }
    .plnav-search-clear::after { content: ""; position: absolute; inset: -0.5rem; }
  }

  /* ------------------------------------------------------- Modal and toasts */
  /* Overlay toggled with the hidden attribute; the box inside is a .card. */
  .app-modal {
    position: fixed;
    inset: 0;
    z-index: var(--bs-z-dialog);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background-color: var(--app-backdrop);
  }
  .app-modal-card {
    --bs-card-bg: var(--app-overlay);
    --bs-card-border-color: var(--app-border-strong);
    --bs-card-cap-bg: var(--bs-bg-3);
    width: 100%;
    max-width: 28rem;
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
    box-shadow: var(--bs-box-shadow-xl);
  }
  /* ------------------------------------------------------- Context menu */
  /* context-menu.js: one fixed .menu at the pointer (or under a ⋮ button),
     above the player bar, the library overlay and dialogs' backdrops. */
  .app-ctx-menu {
    position: fixed;
    z-index: var(--bs-z-popover);
    max-width: min(20rem, calc(100vw - 1rem));
  }
  .app-ctx-menu.show { display: flex; opacity: 1; transform: none; }
  .app-ctx-menu[hidden] { display: none; transition: none; }
  .app-ctx-menu:focus { outline: none; }
  .app-ctx-menu .menu-item { white-space: normal; }
  .app-ctx-menu .menu-item:focus-visible { outline: 2px solid var(--bs-primary-fg); outline-offset: -2px; }
  .app-ctx-header { max-width: 100%; font-weight: var(--bs-font-weight-medium); }
  @media (pointer: coarse) {
    .app-ctx-menu .menu-item { min-height: 2.75rem; }
  }
  /* Submenu (context-menu.js): beside the menu, a bottom sheet on phones. */
  .app-ctx-menu .menu-item .menu-item-content { min-width: 0; overflow-wrap: anywhere; }
  .app-ctx-chevron { display: inline-flex; flex-shrink: 0; margin-inline-start: auto; color: var(--app-text-muted); }
  .app-ctx-chevron svg { width: 1.25rem; height: 1.25rem; }
  .app-ctx-sub {
    width: 18rem;
    max-height: min(26rem, calc(100dvh - 1rem));
    flex-direction: column;
  }
  .app-ctx-sub.app-ctx-sheet { width: auto; max-height: 75dvh; }
  .app-ctx-sub-head { display: flex; gap: 0.5rem; align-items: center; padding: 0 0.25rem 0.25rem; }
  .app-ctx-sub-search { padding: 0.25rem 0.25rem 0.5rem; }
  .app-ctx-sub-list {
    flex: 1 1 auto;
    min-height: 3rem;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .app-ctx-sub-footer {
    flex-shrink: 0;
    padding-top: 0.25rem;
    margin-top: 0.25rem;
    border-top: var(--bs-border-width) solid var(--app-border);
  }
  /* Reported song (song-reports.js): a small muted-warning icon button after
     the title; it opens the report details popover. */
  .song-broken-btn { flex-shrink: 0; --bs-btn-min-height: 1.5rem; color: var(--bs-warning-fg); opacity: 0.85; }
  .song-broken-btn:hover, .song-broken-btn[aria-expanded="true"] { opacity: 1; }
  .song-broken-btn svg { width: 1rem; height: 1rem; }
  .app-ctx-popover { min-width: 12rem; }
  .app-ctx-popover .app-ctx-text { padding-block: 0; white-space: normal; overflow-wrap: anywhere; }
  .app-ctx-popover .app-ctx-text.fg-3 { padding-bottom: 0.375rem; }
  .libpage-reports li { overflow-wrap: anywhere; }
  .report-preset.active {
    color: var(--bs-primary-fg);
    background-color: var(--bs-primary-bg-subtle);
    border-color: var(--bs-primary-border);
  }

  .share-qr {
    max-width: 100%;
    height: auto;
    padding: 0.5rem;
    background-color: #fff; /* QR codes need a white quiet zone in both modes */
    border-radius: var(--bs-radius-5);
  }

  .app-toasts {
    position: fixed;
    bottom: calc(var(--app-player-height, 0px) + 1rem + env(safe-area-inset-bottom, 0px));
    left: 50%;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: stretch;
    width: min(22rem, calc(100% - 2rem));
    transform: translateX(-50%);
  }
  .app-toasts .toast { width: 100%; }
  .app-toast-success { --bs-toast-bg: var(--app-success-subtle); }
  .app-toast-error { --bs-toast-bg: var(--app-danger-subtle); }

  /* -------------------------------------------------------- Music library */
  .library-page {
    position: fixed;
    inset: 0;
    z-index: var(--bs-z-fixed);
    display: flex;
    flex-direction: column;
    background-color: var(--app-page-bg);
  }
  body.libpage-open { overflow: hidden; }
  .libpage-body { flex: 1; min-height: 0; overflow-y: auto; }
  .libpage-container { max-width: 56rem; padding-block: 0.75rem 2rem; }
  .libpage-context:not(.muted) { color: var(--bs-primary-fg); }
  .libpage-tab .badge { margin-left: 0.25rem; }
  .libpage-search { flex: 1 1 14rem; width: auto; }
  .libpage-item { display: flex; gap: 0.75rem; align-items: center; }
  .libpage-info { flex: 1; }
  .libpage-meta { overflow-wrap: anywhere; }
  .libpage-add { min-width: 4.5rem; }
  .libpage-add.added:disabled {
    --bs-btn-disabled-bg: var(--bs-success-bg-subtle);
    --bs-btn-disabled-color: var(--bs-success-fg);
    --bs-btn-disabled-border-color: transparent;
    opacity: 1;
  }
  .libpage-play.playing {
    color: var(--bs-primary-fg);
    background-color: var(--bs-primary-bg-subtle);
    border-color: var(--bs-primary-border);
  }
  .libpage-pager {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    justify-content: space-between;
    margin-top: 1rem;
  }
  .libpage-pages .page-link { min-width: 2rem; }
  .libpage-pager-top {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    justify-content: flex-end;
    margin-bottom: 0.5rem;
  }
  .libpage-pager-top .libpage-page { font-size: var(--bs-font-size-lg); line-height: 1; }
  .libpage-pager-label { min-width: 7.5rem; text-align: center; font-variant-numeric: tabular-nums; }
  /* Narrow column: the row's actions go on their own line under the text, so
     they never squeeze the title. */
  @container page (width < 576px) {
    .libpage-item { gap: 0.5rem; padding-inline: 0.625rem; }
    .libpage-thumb { width: 2.5rem; height: 2.5rem; }
    .libpage-add { min-width: 3.75rem; }
    .libpage-item[data-id] { flex-wrap: wrap; }
    .libpage-item[data-id] .libpage-info { flex-basis: calc(100% - 3rem); }
    .libpage-item[data-id] .libpage-play { margin-inline-start: auto; }
  }
  /* Download tab (jobs.js): downloaders-online dot; job rows wrap their
     action button under the text on phones. */
  .dl-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--bs-bg-4); flex: none; }
  .dl-dot.online { background-color: var(--app-success); }
  @container page (width < 400px) {
    .dl-item { flex-wrap: wrap; }
    .dl-item .dl-cancel, .dl-item .dl-retry { margin-left: auto; }
  }

  /* ------------------------------------------------- Chat and people panel */
  /* social.js. One #social-panel, shown by body classes (never [hidden]):
     desktop (>= 1010px): fixed right column above the player bar, pushing
     the page left; .social-collapsed hides it. 17.5rem up to 1200px.
     narrower: only on /social (.social-route), as a full-screen view.
     #social-rail: the labelled opener at the right edge, from 1010px, whenever
     the column is not there. --app-social-inset is what either takes. */
  body { --app-social-width: 20rem; --app-rail-width: 3.5rem; }
  @media (width < 1200px) { body { --app-social-width: 17.5rem; } }
  .social-rail { display: none; }
  .social-panel {
    display: none;
    flex-direction: column;
    background-color: var(--app-surface);
  }
  .social-header {
    display: flex;
    flex-shrink: 0;
    gap: 0.25rem;
    align-items: center;
    padding: 0.375rem 0.5rem 0;
    border-bottom: var(--bs-border-width) solid var(--bs-border-color);
  }
  .social-tabs { --bs-nav-gap: 1rem; flex: 1; padding-inline: 0.5rem; }
  .social-tabs .nav-link { gap: 0.375rem; align-items: center; }
  .social-chat,
  .social-people { display: flex; flex: 1; flex-direction: column; min-height: 0; }
  .social-people { display: block; overflow-y: auto; padding-bottom: 0.5rem; }
  .social-chat[hidden],
  .social-people[hidden] { display: none; }

  @media (width >= 1010px) {
    body.has-social { --app-social-inset: var(--app-rail-width); padding-right: var(--app-social-inset); }
    body.has-social .library-page { right: var(--app-social-inset); }
    body.has-social .social-rail {
      position: fixed;
      top: 0;
      right: 0;
      bottom: var(--app-player-height, 0px);
      z-index: var(--bs-z-fixed);
      display: flex;
      flex-direction: column;
      gap: 0.25rem;
      align-items: center;
      width: var(--app-rail-width);
      padding: 0.5rem 0.25rem;
      background-color: var(--app-surface);
      border-left: var(--bs-border-width) solid var(--bs-border-color);
    }
  }
  .social-rail-btn {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    align-items: center;
    width: 100%;
    padding: 0.375rem 0;
  }
  .social-rail-btn svg { width: 1.25rem; height: 1.25rem; }
  .social-rail-btn [data-online-count] { color: var(--app-text); background-color: var(--bs-bg-4); }
  .social-rail-label { font-size: var(--bs-font-size-xs, 0.75rem); line-height: 1.2; }
  @media (width >= 1010px) {
    body.has-social:not(.social-collapsed) { --app-social-inset: var(--app-social-width); }
    body.has-social:not(.social-collapsed) .social-rail { display: none; }
    body.has-social:not(.social-collapsed) .social-panel {
      position: fixed;
      top: 0;
      right: 0;
      bottom: var(--app-player-height, 0px);
      z-index: var(--bs-z-fixed);
      display: flex;
      width: var(--app-social-width);
      border-left: var(--bs-border-width) solid var(--bs-border-color);
    }
    .social-back { display: none; }
  }
  @media (width < 1010px) {
    body.has-social.social-route .social-panel {
      position: fixed;
      inset: 0 0 var(--app-player-height, 0px);
      z-index: calc(var(--bs-z-fixed) + 1); /* over the library overlay */
      display: flex;
      padding-top: env(safe-area-inset-top, 0px);
    }
    body.social-route { overflow: hidden; }
    .social-collapse { display: none; }
  }

  /* Drag handles on the column dividers (panel-widths.js): only where a column
     sits beside the page. 8px of hit area on the side that has no scrollbar: the
     page for the sidebar (its own list scrolls at its edge), the panel for the chat
     (the page column scrolls at its edge). At rest nothing shows. On hover or focus
     the divider itself lightens, with a short neutral grip at its middle. */
  .panel-resizer {
    position: absolute;
    top: 0;
    bottom: 0;
    z-index: 1;
    display: none;
    width: 0.5rem;
    cursor: col-resize;
    touch-action: none;
    user-select: none;
    -webkit-touch-callout: none;
  }
  .plnav-resizer { left: 100%; }
  .social-resizer { left: calc(-1 * var(--bs-border-width)); }
  .panel-resizer::before {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--bs-border-width);
    content: "";
    background-color: var(--app-border-hover);
    opacity: 0;
  }
  .panel-resizer::after {
    position: absolute;
    top: 50%;
    left: -0.0625rem;
    width: 0.25rem;
    height: 1.5rem;
    content: "";
    background-color: var(--app-border-hover);
    border-radius: 0.125rem;
    opacity: 0;
    transform: translateY(-50%);
  }
  @media (hover: hover) {
    .panel-resizer:hover::before, .panel-resizer:hover::after { opacity: 1; }
  }
  .panel-resizer:focus-visible::before, .panel-resizer:focus-visible::after,
  .panel-resizer.is-dragging::before, .panel-resizer.is-dragging::after { opacity: 1; }
  .panel-resizer:focus-visible { outline: 2px solid var(--app-focus); outline-offset: -2px; }
  body.panel-resizing, body.panel-resizing * { cursor: col-resize !important; user-select: none !important; }
  @media (pointer: coarse) { .panel-resizer { width: 0.75rem; } }
  @media (width >= 768px) {
    body.has-plnav:not(.social-route) .plnav-resizer { display: block; }
  }
  @media (width >= 1010px) {
    body.has-plnav .plnav-resizer { display: block; }
    body.has-social:not(.social-collapsed) .social-resizer { display: block; }
  }

  /* Chat */
  .chat-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0.5rem 0.75rem;
  }
  .chat-older { padding: 0.25rem 0 0.5rem; }
  .chat-empty { padding: 2rem 0.5rem; text-align: center; }
  .chat-message {
    display: flex;
    gap: 0.625rem;
    align-items: flex-start;
    padding: 0.375rem 0.25rem 0.125rem;
    border-radius: var(--bs-radius-4);
  }
  .chat-message.is-grouped { padding-top: 0; }
  .chat-message:hover { background-color: var(--bs-bg-1); }
  .chat-body { flex: 1; }
  .chat-author { font-size: var(--bs-font-size-sm); }
  .chat-time { flex-shrink: 0; }
  .chat-text {
    font-size: var(--bs-font-size-sm);
    color: var(--bs-fg-2);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }
  .chat-text a { color: var(--bs-primary-fg); }
  .chat-avatar-spacer { flex-shrink: 0; width: 2rem; }
  .chat-delete { flex-shrink: 0; opacity: 0; }
  .chat-message:hover .chat-delete,
  .chat-delete:focus-visible { opacity: 1; }
  @media (hover: none) { .chat-delete { opacity: 1; } }
  .chat-form {
    flex-shrink: 0;
    padding: 0.5rem 0.75rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
    border-top: var(--bs-border-width) solid var(--bs-border-color);
  }
  body.has-player-bar .chat-form { padding-bottom: 0.5rem; }

  /* Avatars (chat and people): photo, emoji or initials in a grey circle. */
  .social-avatar {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    overflow: hidden;
    font-size: var(--bs-font-size-xs);
    font-weight: var(--bs-font-weight-semibold);
    color: var(--bs-fg-2);
    background-color: var(--bs-bg-2);
    border-radius: 50%;
  }
  .social-avatar.is-emoji { font-size: 1.125rem; }
  .social-avatar img { width: 100%; height: 100%; object-fit: cover; }

  /* People */
  .people-heading { margin: 0; padding: 0.75rem 1rem 0.25rem; letter-spacing: 0.04em; }
  .people-list { --bs-list-group-bg: transparent; }
  .person { display: flex; gap: 0.625rem; align-items: center; padding-block: 0.375rem; }
  .person:not(.is-online) .social-avatar { opacity: 0.6; }
  .person:not(.is-online) .person-name { color: var(--bs-fg-3); }
  .person-avatar-wrap { position: relative; flex-shrink: 0; }
  .person-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 0.75rem;
    height: 0.75rem;
    background-color: var(--bs-bg-4);
    border: 2px solid var(--bs-bg-body);
    border-radius: 50%;
  }
  .person.is-online .person-dot { background-color: var(--app-success); }
  .person { cursor: pointer; }
  .person:focus-visible { outline: 2px solid var(--app-focus); outline-offset: -2px; }
  .person[aria-expanded="true"] { background-color: var(--bs-bg-1); }
  .person-activity { color: var(--bs-primary-fg); }
  .person-activity.is-paused { color: var(--bs-fg-3); }
  .person-activity .icon-sm { flex-shrink: 0; }

  /* Equaliser: three bars bouncing while someone plays (still when reduced motion). */
  .person-eq { display: inline-flex; flex-shrink: 0; gap: 2px; align-items: flex-end; width: 0.75rem; height: 0.75rem; }
  .person-eq i { flex: 1; height: 40%; background-color: currentColor; border-radius: 1px; animation: person-eq 0.9s ease-in-out infinite; }
  .person-eq i:nth-child(2) { animation-delay: -0.3s; }
  .person-eq i:nth-child(3) { animation-delay: -0.6s; }
  @keyframes person-eq { 0%, 100% { height: 30%; } 50% { height: 100%; } }
  @media (prefers-reduced-motion: reduce) {
    .person-eq i { animation: none; }
    .person-eq i:nth-child(1) { height: 60%; }
    .person-eq i:nth-child(2) { height: 100%; }
    .person-eq i:nth-child(3) { height: 45%; }
  }

  /* Person card (social.js, a context-menu.js popover; a bottom sheet on phones). */
  .person-card-pop { width: 18rem; padding: 0; }
  .person-card { display: flex; flex-direction: column; gap: 0.75rem; width: 100%; padding: 0.875rem; }
  .person-card .person-dot { border-color: var(--app-overlay); }
  .person-card .is-online .person-dot { background-color: var(--app-success); }
  .person-card-avatar { width: 3rem; height: 3rem; font-size: var(--bs-font-size-sm); }
  .person-card-avatar.is-emoji { font-size: 1.5rem; }
  .person-card-online { color: var(--app-success); }
  .person-card-song { padding: 0.5rem; background-color: var(--bs-bg-1); border: 1px solid var(--app-border); border-radius: var(--bs-border-radius); }
  .person-card-label { color: var(--bs-primary-fg); }
  .person-card-cover {
    display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center;
    width: 3rem; height: 3rem; overflow: hidden; color: var(--bs-fg-3);
    background-color: var(--bs-bg-2); border-radius: calc(var(--bs-border-radius) - 2px);
  }
  .person-card-cover img { width: 100%; height: 100%; object-fit: cover; }
  .person-card-cover svg { width: 1.5rem; height: 1.5rem; }
  .person-card-action { gap: 0.5rem; }
  .person-card-action svg { width: 1.125rem; height: 1.125rem; }
  /* Phones: the popover becomes a bottom sheet over a dimmed page. */
  .app-ctx-sheet {
    inset: auto 0 0 0;
    width: auto;
    max-width: none;
    border-bottom: 0;
    border-radius: var(--bs-border-radius-lg) var(--bs-border-radius-lg) 0 0;
    box-shadow: 0 0 0 100vmax rgb(0 0 0 / 0.5), var(--bs-box-shadow-lg);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .app-ctx-sheet .person-card { padding: 1rem 1rem 1.25rem; }

  /* Chat button in the player bar, with the unread badge on its corner. */
  .social-toggle { position: relative; }
  .social-toggle-badge {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 1.125rem;
    padding: 0.125rem 0.25rem;
    font-size: 0.625rem;
    line-height: 1;
    color: var(--bs-primary-contrast);
    background-color: var(--bs-primary-bg);
    border-radius: var(--bs-radius-pill);
  }

  /* ------------------------------------------------------------ Dashboard */
  .dashboard-topbar .container-fluid { gap: 0.5rem; justify-content: flex-start; }
  .dashboard-layout { display: flex; flex: 1; min-height: 0; }
  .dashboard-sidebar {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    width: 15rem;
    background-color: var(--bs-bg-body);
    border-right: var(--bs-border-width) solid var(--bs-border-color);
  }
  .sidebar-nav .list-group-item { display: flex; gap: 0.75rem; align-items: center; }
  .sidebar-nav { flex: none; }
  .sidebar-overlay { display: none; }
  @media (width >= 1024px) {
    .sidebar-hamburger,
    .sidebar-close { display: none; }
    /* Long sections scroll; the section list stays in reach. */
    .dashboard-sidebar {
      position: sticky;
      top: 0;
      align-self: flex-start;
      height: calc(100dvh - var(--app-player-height, 0px) - 3.5rem);
      overflow-y: auto;
    }
  }
  @media (width < 1024px) {
    .dashboard-sidebar {
      position: fixed;
      top: 0;
      bottom: 0;
      left: 0;
      z-index: var(--bs-z-drawer);
      box-shadow: var(--bs-box-shadow-lg);
      transform: translateX(-100%);
      overflow-y: auto;
      transition: transform 0.2s ease;
    }
    .dashboard-sidebar.open { transform: none; }
    .sidebar-overlay.open {
      position: fixed;
      inset: 0;
      z-index: calc(var(--bs-z-drawer) - 1);
      display: block;
      background-color: var(--app-backdrop);
    }
  }
  .dashboard-main {
    flex: 1;
    min-width: 0;
    max-width: 64rem;
    padding: 1.25rem;
  }
  .dashboard-section { display: none; }
  .dashboard-section.active { display: block; }
  .dashboard-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
    gap: 1rem;
  }
  /* One fact per line, never wrapped: the wide content is in .stat-meta lines. */
  .stat-card { gap: 0.25rem; }
  .stat-label,
  .stat-value,
  .stat-meta { display: block; white-space: nowrap; }
  .stat-value { font-variant-numeric: tabular-nums; line-height: 1.2; }
  .stat-warning { color: var(--app-warning); }
  .adm-item { flex-wrap: wrap; }
  .adm-item-main { flex-basis: 12rem; }
  .dashboard-lobby-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.25rem 1rem;
    align-items: center;
  }
  .dashboard-lobby-age { grid-row: 1; grid-column: 2; text-align: right; }
  .dashboard-lobby-actions { grid-column: 2; }
  .dashboard-user-item { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
  .dashboard-user-avatar { font-size: 1.25rem; }
  .dashboard-user-avatar,
  .dashboard-user-avatar-img { width: 2.5rem; height: 2.5rem; }
}

