/* =========================================================
   SIDEBAR — researched against Apple Music, Spotify, and YouTube
   Music's own desktop sidebars, taking one clear idea from each
   rather than copying any single one wholesale:

   - Spotify: a resizable width (drag the right edge) and a
     collapsible icon-only "rail" mode — the two features every one
     of its own users reaches for constantly, and the biggest gap
     the old fixed-272px sidebar had.
   - Apple Music: restraint — one accent color (this app's pink, not
     a rainbow of playlist-cover colors), generous row height, real
     icons per library row instead of plain text, so "Workout" and
     "Chill Vibes" are told apart at a glance even before reading.
   - YouTube Music: primary navigation (Home/Search/Library) visually
     separated from the dynamic, scrollable library/playlists list
     below it — already true here, kept and reinforced.

   Collapse + resize are desktop/tablet features specifically — the
   mobile off-canvas drawer (css/responsive.css, ≤767px) is a
   different interaction model entirely (a temporary overlay, not a
   persistent column), so both controls are hidden there and the
   drawer's own width rules take over untouched.
   ========================================================= */

.sidebar {
  grid-column: 1;
  grid-row: 1;

  position: relative; /* anchors .sidebar-resize-handle */

  min-height: 0;
  min-width: var(--sidebar-collapsed-width); /* the CSS-side floor — js/sidebar.js enforces the real min/max during drag, this is just a backstop */
  max-width: var(--sidebar-max-width);

  display: flex;
  flex-direction: column;

  padding: 20px 14px;

  border-radius: 14px;

  background: var(--surface);

  overflow: hidden;
}

/* =========================================================
   BRAND + COLLAPSE TOGGLE
   ========================================================= */

.sidebar-top {
  display: flex;
  align-items: center;

  gap: 8px;

  margin-bottom: 26px;
}

.brand {
  display: flex;
  align-items: center;

  gap: 11px;
  min-width: 0;

  color: var(--text);

  font-size: var(--fs-title);
  font-weight: var(--fw-display); /* brand wordmark earns display-weight boldness at title size */

  letter-spacing: var(--ls-display);
}

.brand-word {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The real mark (assets/brand/earloop-mark.svg) already carries its own glow
   and color — no boxed tile behind it (that was only ever needed for
   the plain placeholder glyph this replaced); it sits directly on
   the sidebar's own dark surface, the way a brand mark normally
   would rather than being re-boxed in another colored badge. */
.logo {
  width: 38px;
  height: 38px;

  flex-shrink: 0;

  display: flex;
  align-items: center;
  justify-content: center;
}

.logo img {
  width: 100%;
  height: 100%;

  object-fit: contain;
}

.sidebar-collapse-btn {

  flex-shrink: 0;

  width: var(--tap-md);
  height: var(--tap-md);

  border: none;
  border-radius: 50%;

  background: transparent;
  color: var(--text-muted);

  cursor: pointer;

  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.sidebar-collapse-btn:hover {
  background: var(--surface-alt);
  color: var(--text);
}

.sidebar-collapse-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.sidebar-collapse-btn svg {
  width: var(--icon-md);
  height: var(--icon-md);
}

/* =========================================================
   NAVIGATION
   ========================================================= */

.playlist-add-btn {
  flex-shrink: 0;

  width: var(--tap-sm);
  height: var(--tap-sm);

  display: inline-flex;
  align-items: center;
  justify-content: center;

  border: none;
  border-radius: 50%;

  background: transparent;
  color: var(--text-faint);

  cursor: pointer;

  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.playlist-add-btn svg {
  width: var(--icon-xs);
  height: var(--icon-xs);
}

.playlist-add-btn:hover {
  background: var(--surface-alt);
  color: var(--text);
}

.nav {
  display: flex;
  flex-direction: column;

  gap: 2px;
  /* Used to come from the "Menu" label div above it (removed — the
     nav items need no section heading of their own). */
  margin-top: 6px;
  margin-bottom: 22px;
}

.nav a {
  display: flex;
  align-items: center;

  gap: 12px;
  min-height: 40px;
  padding: 9px 12px;

  border-radius: 8px;
  border-left: 3px solid transparent;

  color: var(--text-muted);

  text-decoration: none;

  font-size: var(--fs-body-lg);
  font-weight: var(--fw-body-lg);

  transition:
    background 0.15s ease,
    color 0.15s ease,
    border-color 0.15s ease;
}

.nav a svg {
  width: var(--icon-md);
  height: var(--icon-md);

  flex-shrink: 0;
  color: currentColor;
  opacity: 0.85;
}

.nav-label-text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.nav a:hover {
  color: var(--text);
  background: var(--surface-alt);
}

/* Active nav is color-only — white text, pink icon — deliberately no
   background fill or left-border accent here (unlike playlist rows
   below, which keep both): a top-level destination like Home/Radio
   only ever has one "current" state to begin with, so a stronger
   decoration would be more emphasis than the distinction needs. */
.nav a.active {
  color: var(--text);

  border-left-color: transparent;

  font-weight: 700;
}

.nav a.active svg {
  color: var(--pink);
  opacity: 1;
}

/* =========================================================
   PLAYLISTS
   ========================================================= */

.playlists {
  flex: 1;

  overflow-y: auto;
  overflow-x: hidden;

  padding: 4px 2px 10px;
}

.playlist-item {
  display: flex;
  align-items: center;

  gap: 12px;
  min-height: 52px;
  padding: 7px 10px;

  border-radius: 8px;
  border-left: 3px solid transparent;

  color: var(--text-muted);

  font-size: var(--fs-body);
  font-weight: var(--fw-body);

  cursor: pointer;

  transition:
    color 0.15s ease,
    background 0.15s ease,
    border-color 0.15s ease;
}

/* A real cover-art-style tile per playlist, not a flat gray icon
   glyph sitting on bare background. Custom playlists get a
   per-instance color (js/playlist-colors.js, inline style — see
   .playlist-cover-initials below) or real album art once they have a
   first song; this rule is just the sized, clipped frame both of
   those fill. Liked Songs is the one fixed exception — its own
   permanent pink gradient + heart, set via .playlist-pinned below,
   never a random color or a photo. */
.playlist-icon {
  position: relative; /* anchors .playlist-cover-play */

  flex-shrink: 0;

  width: 36px;
  height: 36px;

  border-radius: 8px;

  overflow: hidden;
}

.playlist-icon svg {
  width: 18px;
  height: 18px;
}

/* Same "eclipse" gradient recipe as every generated playlist cover
   (js/playlist-colors.js's coverGradientCss() — starfield dots, a
   faint ring pattern, then a radial glow), hand-written here with
   Liked Songs' own fixed pink triple since this cover isn't
   per-instance/random the way a playlist's is, so there's nothing to
   generate at runtime. Kept in sync by eye with that function's own
   pink palette entry, not derived from it. */
.playlist-pinned .playlist-icon {
  display: flex;
  align-items: center;
  justify-content: center;

  background:
    radial-gradient(circle at 1.5px 1.5px, rgba(255, 255, 255, 0.55) 1px, transparent 1.4px) 0 0/16px 16px,
    radial-gradient(circle at 9px 6px, rgba(255, 255, 255, 0.35) 0.8px, transparent 1.2px) 0 0/22px 22px,
    repeating-radial-gradient(circle at 32% 28%, transparent 0px, transparent 7px, rgba(255, 255, 255, 0.1) 7px, rgba(255, 255, 255, 0.1) 8px),
    radial-gradient(circle at 32% 26%, #ffb3d1 0%, var(--pink) 45%, #8a0f3f 100%);
  color: #fff;
}

/* Cover art fallback for a custom playlist with no songs yet —
   initials on its randomly-assigned color, the same idea as
   .track-cover-fallback for songs with no artwork. */
.playlist-cover-initials {
  width: 100%;
  height: 100%;

  display: flex;
  align-items: center;
  justify-content: center;

  color: #fff;
  /* The eclipse background's own rings/stars are busier than a flat
     fill — a shadow keeps the initials reliably legible against
     whichever part of the pattern lands behind them. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);

  font-size: 14px;
  font-weight: 700;
}

.playlist-icon img,
.playlist-tile-cover img {
  width: 100%;
  height: 100%;

  object-fit: cover;
}

/* Hover/focus play affordance on any playlist cover (Liked Songs
   included) — a dark scrim + centered play triangle, same idea as
   .queue-play-overlay for queue rows. */
.playlist-cover-play {
  position: absolute;
  inset: 0;

  display: flex;
  align-items: center;
  justify-content: center;

  background: rgba(0, 0, 0, 0.45);
  color: #fff;

  opacity: 0;

  transition: opacity 0.15s ease;
}

.playlist-cover-play svg {
  width: 16px;
  height: 16px;
}

.playlist-item:hover .playlist-cover-play,
.playlist-item:focus-visible .playlist-cover-play {
  opacity: 1;
}

/* A flex row, not just a truncating span directly — the "now playing"
   eq-bars glyph (js/user-playlists.js) needs to sit outside the part
   that actually truncates, or a long name's own ellipsis would clip
   the glyph away as part of its overflowing content. */
.playlist-name {
  display: flex;
  align-items: center;

  gap: 6px;
  min-width: 0;
}

.playlist-name-text {
  flex: 1;
  min-width: 0;

  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.playlist-name .eq-bars,
.playlist-tile-name .eq-bars {
  flex-shrink: 0;
}

.playlist-item:hover {
  color: var(--text);

  background: var(--surface-alt);
}

.playlist-item.active {
  color: var(--text);

  background: var(--surface-alt);

  border-left-color: var(--pink);

  font-weight: 700;
}

/* The pinned "hero" row — Liked Songs always reads as liked, not
   just when active, so it stays visually anchored at the top of an
   otherwise plain-text list instead of blending in until clicked. */
.playlist-pinned {
  /* Genuinely pinned — stays stuck to the top of .playlists' own
     scroll while the rest of the list scrolls underneath it, not
     just sorted first (so it can still scroll out of view once
     there are enough playlists to actually need scrolling). */
  position: sticky;
  top: 0;
  z-index: 3;

  /* Taller than a normal single-line playlist row — .playlist-text
     stacks the name and the "Pinned" badge below, unlike a regular
     row's .playlist-name, which sits alone. */
  min-height: 60px;

  font-weight: 600;

  /* var(--surface) as an opaque base layer, the tint gradient on top
     of it — the tint alone fades to fully transparent, which would
     let scrolling rows show through while this is stuck in place. A
     permanent tint (not just on hover) since the row itself should
     read as "the hero item" at rest, the same way its gradient cover
     tile already does. .playlist-item:hover's own background (higher
     specificity: two selectors vs. one here) still wins on hover. */
  background:
    linear-gradient(120deg, var(--pink-light) 0%, transparent 65%),
    var(--surface);
}

.playlist-text {
  display: flex;
  flex-direction: column;

  gap: 2px;
  min-width: 0;
}

/* "Currently playing" — distinct from .active (which just means
   "this is the row for whatever playlist you're browsing"; a
   playlist can be browsed without playing, or playing while you've
   browsed elsewhere — see js/player.js's getActivePlaylistId()). */
.active-playing .playlist-name,
.active-playing .playlist-tile-name {
  color: var(--now-playing);
}

.active-playing .eq-bars {
  /* Reused as-is from the main track list's own .playing row — see
     css/components.css — just recolored and set inline here since
     it sits next to running text instead of in its own idx column. */
  display: inline-flex;
  vertical-align: middle;

  margin-left: 8px;
}

.active-playing .eq-bars span {
  background: var(--now-playing);
}

/* Separates nav / Liked Songs / Your Playlists into three visually
   distinct sections — deliberately visible even in collapsed rail
   mode (unlike most of this file's other collapsed-only content),
   since that separation is exactly what tells collapsed icons apart
   from collapsed playlist thumbnails at a glance. */
.sidebar-divider {
  flex-shrink: 0;

  height: 1px;
  margin: 10px 10px;

  background: var(--border);
}

.liked-songs-section {
  padding: 4px 2px;
}

/* =========================================================
   LIBRARY TOOLBAR (section label + search-filter + sort/view-as)
   ========================================================= */

.library-toolbar {
  position: relative; /* anchors .library-sort-menu */

  /* Lets the sort chip react to the toolbar's own width (which
     follows the resizable sidebar), not the viewport's. */
  container-type: inline-size;

  display: flex;
  align-items: center;

  gap: 6px;
  padding: 0 2px 8px;
}

/* "Playlists" — an explicit section label rather than the
   surrounding controls being the only signal of what they act on.
   Shrinks/truncates before it ever forces the row to wrap: search,
   sort, and create are the higher-priority interactive controls at
   the sidebar's narrowest resizable width. */
.library-toolbar-label {
  flex-shrink: 0;
  min-width: 0;

  padding-left: 8px;

  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;

  color: var(--text-faint);

  font-size: var(--fs-label);
  font-weight: var(--fw-label);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
}

.library-search-toggle {
  flex-shrink: 0;

  width: var(--tap-sm);
  height: var(--tap-sm);

  display: inline-flex;
  align-items: center;
  justify-content: center;

  border: none;
  border-radius: 50%;

  background: transparent;
  color: var(--text-faint);

  cursor: pointer;

  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.library-search-toggle svg {
  width: var(--icon-xs);
  height: var(--icon-xs);
}

.library-search-toggle:hover,
.library-search-toggle.active {
  background: var(--surface-alt);
  color: var(--text);
}

.library-search-input {
  width: 0;
  min-width: 0;

  padding: 0;

  border: none;
  outline: none;

  background: transparent;
  color: var(--text);

  font-size: var(--fs-body-sm);

  opacity: 0;

  transition:
    width 0.2s ease,
    opacity 0.15s ease,
    padding 0.2s ease;
}

.library-search-input::placeholder {
  color: var(--text-faint);
}

.library-search-input.expanded {
  width: auto;
  flex: 1 1 auto;

  padding: 6px 4px;

  opacity: 1;
}

/* Sort button hides while search is expanded (mirrors Spotify's own
   toolbar, which is one row that can't fit both at full width) —
   width:0 rather than display:none so the layout doesn't jump. */
.library-sort-btn.hidden-by-search {
  width: 0;
  min-width: 0;
  padding: 0;
  opacity: 0;
  pointer-events: none;
}

.library-sort-btn {
  flex-shrink: 0;

  /* Pushes sort (+ the always-visible add button after it) to the
     toolbar's right edge, with the search toggle anchored left and
     the collapsed (width:0) search input contributing no width in
     between — without this the whole row packed tight on the left
     with a large dead gap on the right. */
  margin-left: auto;

  display: flex;
  align-items: center;

  gap: 6px;
  padding: 6px 10px;

  border: none;
  border-radius: var(--radius-pill);

  /* A resting background, not just on :hover — bare text+icon with
     zero visual weight reads as inert label copy, not an actual
     control. This is what actually communicates "clickable" at a
     glance, the same job a filled pill does everywhere else in this
     app (.hero-badge, .search).

     NOTE: this used to be var(--surface) — identical to .sidebar's
     own background, which made the "pill" completely invisible
     against its own container (a real bug: the control looked like
     bare text with zero affordance). --surface-alt is the sidebar's
     one actual lighter tone, so it's visibly a raised chip against
     the panel behind it. */
  background: var(--surface-alt);
  color: var(--text-muted);

  font-size: var(--fs-body-sm);
  font-weight: var(--fw-body-sm);
  white-space: nowrap;

  cursor: pointer;

  transition:
    background 0.15s ease,
    color 0.15s ease,
    width 0.15s ease,
    opacity 0.15s ease,
    padding 0.15s ease;
}

.library-sort-btn svg {
  flex-shrink: 0;

  width: var(--icon-xs);
  height: var(--icon-xs);
}

.library-sort-btn:hover {
  background: var(--pink-light);
  color: var(--text);
}

/* =========================================================
   SORT MENU (dropdown)
   ========================================================= */

.library-sort-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  left: 0;

  z-index: 20;

  padding: 8px;

  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);

  background: var(--surface-alt);

  box-shadow: var(--shadow-md);
}

.sort-menu-label {
  padding: 6px 10px 4px;

  color: var(--text-faint);

  font-size: var(--fs-label);
  font-weight: var(--fw-label);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
}

.sort-menu-item {
  width: 100%;

  display: flex;
  align-items: center;

  gap: 10px;
  padding: 8px 10px;

  border: none;
  border-radius: var(--radius-sm);

  background: transparent;
  color: var(--text-muted);

  font-size: var(--fs-body-sm);
  font-weight: var(--fw-body-sm);
  text-align: left;

  cursor: pointer;

  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.sort-menu-item:hover {
  background: var(--surface);
  color: var(--text);
}

/* Active = white text + a bit bolder, not a pink recolor or a
   checkmark — the arrow (below) alone signals "this is the active
   sort," matching the reference exactly. */
.sort-menu-item.active {
  color: var(--text);
  font-weight: 600;
}

.sort-menu-text {
  flex: 1;

  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sort-menu-item-arrow {
  flex-shrink: 0;

  width: 14px;
  height: 14px;

  transition: transform 0.15s ease;
}

/* base.css's global `svg { display: block; }` is an author-origin
   rule, which beats the `[hidden]` attribute's UA-stylesheet default
   regardless of selector specificity — every hidden SVG in this app
   needs its own explicit override like this one to actually hide. */
.sort-menu-item-arrow[hidden] {
  display: none;
}

.sort-menu-item-arrow.asc {
  transform: rotate(180deg);
}

.sort-menu-divider {
  height: 1px;
  margin: 6px 8px;

  background: var(--border);
}

.view-as-row {
  display: flex;

  gap: 4px;
  padding: 4px 6px 2px;
}

.view-as-btn {
  flex: 1;

  display: flex;
  align-items: center;
  justify-content: center;

  height: 34px;

  border: none;
  border-radius: var(--radius-sm);

  background: transparent;
  color: var(--text-faint);

  cursor: pointer;

  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.view-as-btn svg {
  width: var(--icon-sm);
  height: var(--icon-sm);
}

.view-as-btn:hover {
  background: var(--surface);
  color: var(--text);
}

.view-as-btn.active {
  background: var(--pink-light);
  color: var(--pink);
}

/* =========================================================
   PLAYLIST-LIST VIEW MODES
   ========================================================= */

.playlist-empty {
  padding: 12px 10px;

  color: var(--text-faint);

  font-size: var(--fs-body-sm);
}

/* A genuinely empty library — see js/user-playlists.js's
   renderCustomPlaylists(). Fills the space a bare, near-empty list
   would otherwise leave blank with an actual invitation to act. */
.playlist-empty-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;

  gap: 12px;
  padding: 16px 12px;

  border-radius: var(--radius-md);

  background: var(--surface-alt);
}

.playlist-empty-state p {
  color: var(--text-muted);

  font-size: var(--fs-body-sm);
  line-height: 1.5;
}

.playlist-empty-cta {
  padding: 8px 16px;

  border: none;
  border-radius: var(--radius-pill);

  background: var(--pink-fill);
  color: #fff;

  font-size: var(--fs-body-sm);
  font-weight: 700;

  cursor: pointer;

  transition:
    background 0.15s ease,
    transform 0.1s ease;
}

.playlist-empty-cta:hover {
  background: var(--pink-fill-hover);
  transform: scale(1.03);
}

/* Compact — name only, tightest row height. The icon is still in the
   DOM (see js/user-playlists.js) so collapsed rail mode — which
   shows every playlist row as a centered icon regardless of view
   mode — has something to fall back to; it's just hidden here. */
.playlist-list.view-compact .playlist-item-compact {
  min-height: 32px;
  padding: 6px 10px;
}

.playlist-list.view-compact .playlist-item-compact .playlist-icon {
  display: none;
}

/* Default — same icon-tile + name row V1 already established. */

/* Compact grid / Grid — square tiles, name hidden until hover/focus.
   Same tile markup for both; only the column density differs.
   auto-fit, not auto-fill: with too few tiles to fill a row, auto-fill
   still reserves the empty columns (tiles stay pinned small in one
   corner with dead space alongside); auto-fit collapses those empty
   tracks so the 1fr unit actually stretches the real tiles to fill
   the row instead. Identical to auto-fill whenever there are enough
   tiles to fill a row anyway — this only changes the too-few case,
   which never came up in the sidebar's own narrow column but is
   exactly the case js/library-view.js's full-width reuse of this
   same class hits constantly.
   UPDATE: auto-fill after all. Stretching was the bigger problem: two
   playlists in the full-width Library became two 560px tiles. Tiles
   now keep their size and leave the rest of the row empty, which is
   what Spotify's own grid does. */
.playlist-list.view-compactGrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));

  gap: 8px;

  padding: 4px 6px;
}

.playlist-list.view-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));

  gap: 10px;

  padding: 4px 6px;
}

.playlist-tile {
  position: relative;

  flex-direction: column;
  align-items: stretch;

  min-height: 0;
  padding: 0;

  border-left: none;
  border-radius: 8px;

  overflow: hidden;
}

.playlist-tile-cover {
  position: relative; /* anchors .playlist-cover-play */

  width: 100%;
  aspect-ratio: 1;

  display: flex;
  align-items: center;
  justify-content: center;

  color: #fff;
}

.playlist-tile-cover .playlist-cover-initials {
  font-size: 22px;
}

.playlist-tile-cover svg {
  width: 28px;
  height: 28px;
}

.playlist-tile-name {
  position: absolute;
  inset: 0;

  display: flex;
  align-items: flex-end;

  gap: 6px;
  padding: 8px;
  min-width: 0;

  background: linear-gradient(to top, rgba(0, 0, 0, 0.75), transparent 60%);
  color: #fff;

  font-size: var(--fs-body-sm);
  font-weight: 600;

  /* Truncation itself lives on .playlist-name-text (below), not here —
     same clipping-the-eq-bars-glyph bug as .playlist-name, fixed the
     same way: the ellipsis needs to bound just the text, not this
     whole flex row the "now playing" glyph also sits in. */
  opacity: 0;

  transition: opacity 0.15s ease;
}

.playlist-tile:hover .playlist-tile-name,
.playlist-tile:focus-visible .playlist-tile-name {
  opacity: 1;
}

.playlist-tile.active {
  /* Higher specificity than the base .playlist-item.active rule
     (border-left-color/background), which a plain grid tile has no
     use for — an outline reads better on a square cover. */
  border-left: none;
  background: transparent;

  outline: 2px solid var(--pink);
  outline-offset: -2px;
}

/* =========================================================
   RESIZE HANDLE (desktop/tablet only — see the ≤767px override below)
   ========================================================= */

.sidebar-resize-handle {
  position: absolute;
  top: 0;
  /* Flush with the true right edge, not straddling it (was right:-4px) —
     .sidebar has overflow:hidden (for its own rounded corners), which
     silently clips both the paint AND the hit-test of anything that
     pokes outside its box. A handle half inside/half outside was
     therefore only half-clickable — real drags failed the same way an
     automated pointerdown at its geometric center did. Sitting fully
     inside keeps the same invisible-until-hover affordance with a
     reliable full-width hit area. */
  right: 0;
  bottom: 0;

  width: 8px;

  cursor: col-resize;

  /* Invisible at rest — a persistent visible seam down the whole
     sidebar edge would look like a design element, not an control.
     The hover/active feedback below is what actually communicates
     "this is draggable," the same restrained-until-relevant pattern
     Spotify's own resize handle uses. */
  background: transparent;

  z-index: 5;
}

.sidebar-resize-handle:hover,
.sidebar-resize-handle.active {
  background: var(--pink-fill);
  opacity: 0.5;
}

/* =========================================================
   COLLAPSED (rail mode)
   ========================================================= */

/* The logo sits 4px in from the padding in both states: that centres
   it on the rail, so it never moves when the sidebar collapses. */
.sidebar-top {
  padding-left: 4px;
}

/* Collapsed keeps the expanded padding and simply fills its narrower
   grid column (css/layout.css). One width source for both, so the
   sidebar and the page beside it move together, and every icon keeps
   its exact x position: with 14px padding the 76px rail centres a
   20px nav icon (to within 1px) with no re-layout. */

/* Deliberately no justify-content override here — the toggle stays
   flush left, exactly where it sits expanded, so collapsing doesn't
   also relocate the one button collapsed mode still shows (see
   js/sidebar.js's own header comment on why that matters). */

.sidebar.collapsed .library-sort-menu,
.sidebar.collapsed .playlist-empty-state,
.sidebar.collapsed .sidebar-resize-handle {
  display: none;
}

/* Compact and tile rows change shape in the rail (they show a cover
   they don't show expanded), so they still re-centre. Default rows
   don't need to: see .playlists .playlist-item below. */
.sidebar.collapsed .playlist-item-compact,
.sidebar.collapsed .playlist-tile {
  justify-content: center;

  gap: 0;
  padding-left: 0;
  padding-right: 0;

  border-left: none;
}

/* Labels stay in place and fade, instead of being removed (which made
   them vanish on the first frame of collapse and arrive at full
   strength, squashed, on expand). The rail clips them. */
.brand-word,
.library-title-text,
.library-search-toggle,
.library-sort-btn,
.playlist-add-btn,
.nav-label-text,
.playlist-name,
.playlist-text {
  transition: opacity 120ms ease;
}

.sidebar.collapsed .brand-word,
.sidebar.collapsed .library-title-text,
.sidebar.collapsed .library-search-toggle,
.sidebar.collapsed .library-sort-btn,
.sidebar.collapsed .playlist-add-btn,
.sidebar.collapsed .nav-label-text,
.sidebar.collapsed .playlist-name,
.sidebar.collapsed .playlist-text {
  opacity: 0;
  visibility: hidden;
}

.nav a,
.playlist-item {
  overflow: hidden;
}

/* Default playlist rows: the 36px cover sits 20px from the sidebar's
   edge in both states (14px sidebar padding + 3px accent border +
   3px), which is exactly where the 76px rail centres it. So collapsing
   only fades the names; the covers never move. */
.playlists .playlist-item:not(.playlist-item-compact):not(.playlist-tile) {
  padding-left: 3px;
}

.sidebar.collapsed .playlists {
  padding-inline: 0;

  scrollbar-width: none;
}

/* =========================================================
   COLLAPSE MOTION
   =========================================================
   Only while :root has .sidebar-motion (js/sidebar.js adds it for
   the toggle). Collapse: labels fade out in 120ms, as the column
   starts closing. Expand: the column opens first, labels fade in over
   its last stretch, so text never shows half-squashed. */
.sidebar-motion .app {
  /* Ease-in-out, not ease-out: the width moves evenly instead of
     covering half the distance in the first 45ms and then crawling. */
  transition: grid-template-columns 300ms cubic-bezier(0.4, 0, 0.2, 1);
}

.sidebar-motion .sidebar:not(.collapsed) .brand-word,
.sidebar-motion .sidebar:not(.collapsed) .library-title-text,
.sidebar-motion .sidebar:not(.collapsed) .library-search-toggle,
.sidebar-motion .sidebar:not(.collapsed) .library-sort-btn,
.sidebar-motion .sidebar:not(.collapsed) .playlist-add-btn,
.sidebar-motion .sidebar:not(.collapsed) .nav-label-text,
.sidebar-motion .sidebar:not(.collapsed) .playlist-name,
.sidebar-motion .sidebar:not(.collapsed) .playlist-text {
  transition: opacity 160ms ease 160ms;
}

.sidebar-motion .sidebar.collapsed .brand-word,
.sidebar-motion .sidebar.collapsed .library-title-text,
.sidebar-motion .sidebar.collapsed .library-search-toggle,
.sidebar-motion .sidebar.collapsed .library-sort-btn,
.sidebar-motion .sidebar.collapsed .playlist-add-btn,
.sidebar-motion .sidebar.collapsed .nav-label-text,
.sidebar-motion .sidebar.collapsed .playlist-name,
.sidebar-motion .sidebar.collapsed .playlist-text {
  transition:
    opacity 120ms ease,
    visibility 0s linear 120ms;
}

/* Opening: content at its final width, revealed by the column. */
.sidebar-opening .sidebar > :not(.sidebar-resize-handle) {
  flex-shrink: 0;

  width: var(--sidebar-reveal-width);
}

@media (prefers-reduced-motion: reduce) {
  .sidebar-motion .app,
  .sidebar-motion .sidebar .brand-word,
  .sidebar-motion .sidebar .library-title-text,
  .sidebar-motion .sidebar .library-search-toggle,
  .sidebar-motion .sidebar .library-sort-btn,
  .sidebar-motion .sidebar .playlist-add-btn,
  .sidebar-motion .sidebar .nav-label-text,
  .sidebar-motion .sidebar .playlist-name,
  .sidebar-motion .sidebar .playlist-text {
    transition: none;
  }
}

/* .playlist-pinned's extra height (for the "Pinned" badge beneath its
   name) has nothing to accommodate once collapsed — that badge is
   hidden along with the rest of .playlist-text, so it should match
   every other row's height here instead of standing out as taller. */
.sidebar.collapsed .playlist-pinned {
  min-height: 52px;
}

/* Every playlist row collapses to a centered icon regardless of the
   chosen view mode — grid tiles specifically need reshaping back
   into a normal row (they're square tiles with an absolute-
   positioned name overlay otherwise, neither of which makes sense
   at rail width). */
.sidebar.collapsed .playlist-list.view-grid,
.sidebar.collapsed .playlist-list.view-compactGrid {
  display: block;
}

.sidebar.collapsed .playlist-tile {
  flex-direction: row;

  aspect-ratio: auto;
  min-height: 42px;
  padding: 7px 0;
}

.sidebar.collapsed .playlist-tile-cover {
  width: 36px;
  height: 36px;

  aspect-ratio: auto;
  border-radius: 8px;
}

.sidebar.collapsed .playlist-tile-cover svg {
  width: 18px;
  height: 18px;
}

.sidebar.collapsed .playlist-tile-name {
  display: none;
}

/* Rail mode's own active indicator for playlist rows — a left border
   reads oddly flush against a centered icon with no room either
   side of it, so this switches to a small underline instead, same
   pink, same idea. Nav items (Home/Library/Radio) get no equivalent
   here — same as expanded mode, the icon's own pink color is the
   only active indicator, matching the "color only, no decoration"
   nav spec. */
.sidebar.collapsed .playlist-item.active {
  position: relative;
}

.sidebar.collapsed .playlist-item.active::after {
  content: "";

  position: absolute;
  bottom: 2px;
  left: 50%;

  width: 16px;
  height: 2px;

  transform: translateX(-50%);

  border-radius: var(--radius-pill);

  background: var(--pink-fill);
}

/* "Now playing" has no equivalent indicator in rail mode otherwise —
   its whole existing signal (green text + eq-bars, above) lives
   inside .playlist-name, which is hidden entirely when collapsed
   (icon-only rows). A ring around the cover is the one thing that
   still fits: real information a glance at the collapsed rail
   couldn't otherwise show at all. Coexists fine with the plain
   .active underline above — "currently browsed" and "currently
   playing" aren't mutually exclusive. */
.sidebar.collapsed .active-playing .playlist-icon,
.sidebar.collapsed .active-playing .playlist-tile-cover {
  box-shadow: 0 0 0 2px var(--now-playing);
}

/* Too narrow for "Playlists" + search + "Recents" + create in one
   row: the sort chip goes icon-only rather than cutting the section
   label down to "PLAYLIS...". The menu itself still shows which sort
   is active. */
@container (max-width: 170px) {
  #librarySortLabel {
    display: none;
  }

  .library-sort-btn {
    gap: 0;
  }
}

/* Pinned Liked Songs stays a full-width row above the tiles in the
   grid views (js/library-view.js puts it in the same list), instead of
   being squeezed into one tile-sized cell. */
.playlist-list.view-grid .playlist-pinned,
.playlist-list.view-compactGrid .playlist-pinned {
  grid-column: 1 / -1;
}

/* Grid (not compact grid): the name sits under the cover, always
   visible. Compact grid keeps the hover overlay, since its tiles are
   too small for a caption. */
.playlist-list.view-grid .playlist-tile {
  gap: 0; /* .playlist-item's row gap would push the caption down */

  overflow: visible;
}

.playlist-list.view-grid .playlist-tile-cover {
  border-radius: 8px;

  overflow: hidden;
}

.playlist-list.view-grid .playlist-tile-name {
  position: static;

  padding: 8px 2px 0;

  background: none;
  color: var(--text);

  opacity: 1;
}

/* =========================================================
   SIDEBAR REFINEMENTS (docs/prompts/SIDEBAR-AUDIT-PROMPT.md run)
   ========================================================= */

/* S1. Where you are vs. where the pointer is.
   Hover: a neutral grey. Current page: a pink-tinted background plus a
   short pink bar at the left edge, the same marker for nav items and
   playlists. The old 3px border on a rounded row read as a faint
   curve and looked like hover. */
.nav a,
.playlist-item {
  position: relative;

  border-left-color: transparent;

  transition:
    background var(--dur-fast) ease,
    color var(--dur-fast) ease;
}

.nav a.active,
.playlist-item.active {
  border-left-color: transparent;
}

.playlist-item.active {
  background: var(--pink-soft);
}

.playlist-item.active:hover {
  background: var(--pink-light);
}

.nav a::before,
.playlist-item::before {
  content: "";

  position: absolute;
  left: 0;
  top: 50%;

  width: 3px;
  height: 0;

  border-radius: 0 3px 3px 0;

  background: var(--pink);

  transform: translateY(-50%);

  transition: height var(--dur-base) var(--ease-out);
}

.nav a.active::before,
.playlist-item.active::before {
  height: 20px;
}

/* Tiles show the selection as an outline instead (css above). */
.playlist-tile::before {
  display: none;
}

/* Liked Songs is pinned, not selected: no permanent pink wash (it
   made it look like the current page). Its heart cover already makes
   it stand out. Opaque, because it sticks while the list scrolls. */
.playlist-pinned {
  background: var(--surface);
}

.playlist-pinned:hover {
  background: var(--surface-alt);
}

.playlist-pinned.active {
  background: linear-gradient(var(--pink-soft), var(--pink-soft)), var(--surface);
}

/* Pin marker after a pinned playlist's name */
.playlist-pin {
  width: 11px;
  height: 11px;

  flex-shrink: 0;

  color: var(--pink);
}

/* S2. "⋯" on hover/focus (always shown on touch screens) */
.playlist-more-btn {
  width: 28px;
  height: 28px;

  flex-shrink: 0;
  margin-left: auto;

  display: grid;
  place-items: center;

  border: none;
  border-radius: 50%;

  background: transparent;
  color: var(--text-muted);

  cursor: pointer;

  opacity: 0;

  transition:
    opacity var(--dur-fast) ease,
    background var(--dur-fast) ease,
    color var(--dur-fast) ease;
}

.playlist-more-btn svg {
  width: 16px;
  height: 16px;
}

.playlist-item:hover .playlist-more-btn,
.playlist-item:focus-within .playlist-more-btn,
.playlist-item:focus-visible .playlist-more-btn {
  opacity: 1;
}

.playlist-more-btn:hover {
  background: var(--surface);
  color: var(--text);
}

@media (hover: none) {
  .playlist-more-btn {
    opacity: 1;
  }
}

/* On tiles the button floats over the cover's corner. */
.playlist-tile .playlist-more-btn {
  position: absolute;
  top: 6px;
  right: 6px;

  z-index: 2;

  background: rgba(0, 0, 0, 0.55);
  color: #fff;
}

/* No "⋯" in the collapsed rail: right-click still works there. */
.sidebar.collapsed .playlist-more-btn {
  display: none;
}

/* S8. Playlist filter: the same pill as the main search box, instead
   of a sharp pink rectangle. */
.library-search-input.expanded {
  padding: 6px 12px;

  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  outline: none;

  background: var(--surface-alt);

  transition: border-color var(--dur-fast) ease;
}

.library-search-input.expanded:focus,
.library-search-input.expanded:focus-visible {
  border-color: var(--pink);
  outline: none;
}

.playlist-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;

  gap: 8px;
  padding: 14px 12px;

  color: var(--text-muted);

  font-size: var(--fs-body-sm);
}

.playlist-empty-clear {
  padding: 5px 14px;

  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);

  background: transparent;
  color: var(--text);

  font: inherit;
  font-weight: 700;

  cursor: pointer;
}

.playlist-empty-clear:hover {
  background: var(--surface-alt);
}

/* S7. The resize edge shows itself on hover. */
.sidebar-resize-handle::after {
  transition: opacity var(--dur-fast) ease, background var(--dur-fast) ease;
}

@media (prefers-reduced-motion: reduce) {
  .nav a::before,
  .playlist-item::before {
    transition: none;
  }
}


/* =========================================================
   LAYOUT (Spotify-style; supersedes the rules above where they meet)
   =========================================================
   Brand, then the two destinations (Home, Radio), then "Your Library"
   as one section that IS the library: its icon collapses the sidebar,
   its title opens the full Library page, + creates a playlist. Before,
   a "Your Library" link sat above a separate "Playlists" list of the
   same things, under a four-control header in 11px caps.

   Alignment: every icon and cover is centred 38px from the sidebar's
   edge, the centre of the 76px rail, so nothing moves on collapse. */

/* ---- Nav: no edge bar; current page = bold text, pink icon ---- */
.nav a {
  padding: 9px 12px 9px 14px;

  border-left: none;
}

.nav a::before,
.playlist-item::before {
  display: none;
}

.nav a.active svg {
  color: var(--pink);
  opacity: 1;
}

/* ---- Collapse toggle (in .sidebar-footer, see below) ----
   The icon shows which way it will go. */
.sidebar-top {
  gap: 6px;
}

.sidebar-footer .sidebar-collapse-btn:hover {
  background: var(--fill-hover);
  color: var(--text);
}

.toggle-icon-open,
.sidebar.collapsed .toggle-icon-close {
  display: none;
}

.sidebar.collapsed .toggle-icon-open {
  display: block;
}

/* ---- "Your Library": one row ----
   [icon Your Library] ......... [search] [sort] [+]
   Sort is icon-only (the current order is in its tooltip and menu);
   "Alphabetical" plus everything else didn't fit one row at the
   default width. Searching swaps the title for the search field. */
.sidebar .library-toolbar {
  gap: 2px;
  padding: 0 0 8px 4px;
}

.library-title {
  flex: 1;

  min-width: 0;

  display: flex;
  align-items: center;

  gap: 10px;
  padding: 6px 4px 6px 10px;

  border-radius: var(--radius-md);

  color: var(--text-muted);

  font-size: var(--fs-body-lg);
  font-weight: 700;
  text-decoration: none;

  transition: color var(--dur-fast) ease, background var(--dur-fast) ease;
}

.library-title svg {
  width: var(--icon-md);
  height: var(--icon-md);

  flex-shrink: 0;
}

.library-title-text {
  min-width: 0;

  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.library-title:hover,
.library-title.active {
  color: var(--text);
}

.sidebar .library-toolbar:has(.library-search-input.expanded) .library-title {
  display: none;
}

.sidebar .library-toolbar .library-search-toggle,
.sidebar .library-toolbar .library-sort-btn,
.sidebar .library-toolbar .playlist-add-btn {
  flex-shrink: 0;

  width: var(--tap-sm);
  height: var(--tap-sm);

  margin-left: 0;
  padding: 0;

  display: inline-flex;
  align-items: center;
  justify-content: center;

  border-radius: 50%;

  background: transparent;
  color: var(--text-muted);
}

.sidebar .library-toolbar .library-search-toggle:hover,
.sidebar .library-toolbar .library-sort-btn:hover,
.sidebar .library-toolbar .playlist-add-btn:hover {
  background: var(--fill-hover);
  color: var(--text);
}

.sidebar .library-toolbar #librarySortLabel {
  display: none;
}

/* ---- Rows: one height, name + "Playlist · 5 songs" ---- */
.playlists .playlist-item:not(.playlist-item-compact):not(.playlist-tile) {
  min-height: 52px;

  padding: 6px 8px 6px 6px;

  border-left: none;
}

.playlist-pinned {
  min-height: 52px;
}

.playlist-name {
  color: var(--text);

  font-size: var(--fs-body-lg);
  font-weight: 500;
}

.playlist-item.active,
.playlist-pinned {
  font-weight: inherit;
}

.playlist-meta {
  display: flex;
  align-items: center;

  gap: 4px;
  min-width: 0;

  color: var(--text-muted);

  font-size: var(--fs-body-sm);
  font-weight: var(--fw-body-sm);

  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.playlist-meta .playlist-pin {
  width: 12px;
  height: 12px;

  color: var(--pink);
}

/* ---- Row states ----
   rest: no fill | hover: --row-hover | open: --row-active, with the
   name in full-strength text | press: --row-press. Pink stays for
   "now playing" (title + equaliser). Fills change in 120ms; nothing
   moves or scales, so a long list doesn't shimmer under the pointer.
   Measured against the sidebar surface: hover 1.1:1, open 1.3:1,
   press 1.5:1 in Day, and similar steps in Night (before, open was
   1.1:1, the same as hover, so it didn't read as selected). */
.playlist-item {
  transition: background 120ms ease, color 120ms ease;
}

.playlist-item:hover {
  background: var(--row-hover);
}

.playlist-item.active,
.playlist-item.active:hover {
  background: var(--row-active);
}

.playlist-item:active {
  background: var(--row-press);
}

/* Liked Songs sticks to the top while the list scrolls, so each fill
   sits on an opaque surface. */
.playlist-pinned {
  background: var(--surface);
}

.playlist-pinned:hover {
  background: linear-gradient(var(--row-hover), var(--row-hover)), var(--surface);
}

.playlist-pinned.active,
.playlist-pinned.active:hover {
  background: linear-gradient(var(--row-active), var(--row-active)), var(--surface);
}

.playlist-pinned:active {
  background: linear-gradient(var(--row-press), var(--row-press)), var(--surface);
}

/* Keyboard focus: drawn inside the row, so the scrolling list can't
   clip it (the default ring sits 2px outside). */
.playlists .playlist-item:focus-visible {
  outline: 2px solid var(--pink);
  outline-offset: -2px;
}

.library-title:focus-visible {
  outline: 2px solid var(--pink);
  outline-offset: -2px;
}

/* Header buttons: a fill on hover, a firmer one and a slight press-in
   on click, the same as the player's icon buttons. */
.sidebar .library-toolbar .library-search-toggle,
.sidebar .library-toolbar .library-sort-btn,
.sidebar .library-toolbar .playlist-add-btn,
.sidebar-footer .sidebar-collapse-btn {
  transition: background 120ms ease, color 120ms ease, transform 120ms ease;
}

.sidebar .library-toolbar .library-search-toggle:active,
.sidebar .library-toolbar .library-sort-btn:active,
.sidebar .library-toolbar .playlist-add-btn:active,
.sidebar-footer .sidebar-collapse-btn:active {
  background: var(--row-press);
  transform: scale(0.92);
}

.sidebar .library-toolbar .library-search-toggle.active,
.sidebar .library-toolbar .library-sort-btn[aria-expanded="true"] {
  background: var(--row-active);
  color: var(--text);
}

.library-title:active {
  color: var(--text);
  opacity: 0.8;
}

/* On the rail the library row stays (so the list below never jumps)
   and shows only the library icon, still a link to Library, at the
   same spot as when expanded: 14 + 4 + 10px puts it on the rail's
   centre. The search field can't stay open in a 76px rail. */
.sidebar.collapsed .library-search-input {
  display: none;
}

/* ---- Searching the library: one field, full width ----
   [ 🔍 Search in Your Library        ] [+]
   The search icon moves inside the field (the toggle sits over the
   field's left padding, so a click on it still closes search), the
   field takes all the room the title and sort gave up, and + stays,
   since it's the one action that still applies. */
.sidebar .library-toolbar:has(.library-search-input.expanded) .library-sort-btn {
  display: none;
}

.sidebar .library-toolbar:has(.library-search-input.expanded) .library-search-toggle {
  position: relative;
  z-index: 1;

  margin-right: -34px;

  color: var(--text-muted);
}

.sidebar .library-toolbar:has(.library-search-input.expanded) .library-search-toggle:hover {
  background: transparent;
  color: var(--text);
}

.sidebar .library-toolbar .library-search-input.expanded {
  flex: 1 1 auto;

  height: var(--tap-sm);

  margin-right: 4px;
  padding: 0 12px 0 34px;

  border: 1px solid var(--input-border);
}

.sidebar .library-toolbar .library-search-input.expanded:focus {
  border-color: var(--pink);
}

/* The logo and name are a link to Home. */
a.brand {
  color: inherit;
  text-decoration: none;

  border-radius: var(--radius-md);
}

a.brand:hover .brand-word {
  color: var(--text);
}

a.brand:active {
  opacity: 0.8;
}

a.brand:focus-visible {
  outline: 2px solid var(--pink);
  outline-offset: 2px;
}

/* ---- Footer: the collapse toggle ----
   Same 4px inset as the rows above it, so its 40px box is centred on
   the rail (x = 38px) and doesn't move when you click it. */
.sidebar-footer {
  flex-shrink: 0;

  display: flex;

  padding: 8px 0 0 4px;
  margin-top: 4px;

  border-top: 1px solid var(--border);
}

.sidebar-footer .sidebar-collapse-btn {
  color: var(--text-muted);
}

/* =========================================================
   NEW PLAYLIST (js/user-playlists.js, markNewPlaylists)
   =========================================================
   About 1.2s, in this order, all on Material's "emphasized
   decelerate" curve, the one for things entering:
   0 to 280ms     the row opens into the list (no sudden shove)
   0 to 420ms     the cover pops from 85% with a slight overshoot
   120 to 820ms   the earloop spiral draws itself around the cover
   820 to 1200ms  the spiral fades
   0 to 1200ms    a soft pink wash on the row settles away
   The rail shows the same cover pop and spiral. --new-elapsed lets a
   rebuilt row pick up mid-animation. Reduced motion: the wash only. */
.playlist-item.is-new {
  animation:
    new-row-open 280ms cubic-bezier(0.05, 0.7, 0.1, 1) both,
    new-row-glow 1200ms ease-out both;
  animation-delay: var(--new-elapsed, 0ms);
}

.playlist-item.is-new .playlist-icon,
.playlist-item.is-new .playlist-tile-cover {
  animation: new-cover-pop 420ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  animation-delay: var(--new-elapsed, 0ms);
}

.new-spiral {
  position: absolute;
  z-index: 2;
  top: 50%;

  overflow: visible;

  pointer-events: none;

  animation: new-spiral-fade 1200ms linear both;
  animation-delay: var(--new-elapsed, 0ms);
}

.new-spiral path {
  fill: none;
  stroke: var(--pink);
  stroke-width: 22;
  stroke-linecap: round;
  stroke-linejoin: round;

  stroke-dasharray: 1;
  stroke-dashoffset: 1;

  animation: new-spiral-draw 700ms cubic-bezier(0.05, 0.7, 0.1, 1) both;
  animation-delay: calc(var(--new-elapsed, 0ms) + 120ms);
}

@keyframes new-row-open {
  from {
    max-height: 0;
    min-height: 0;
    padding-top: 0;
    padding-bottom: 0;
    opacity: 0;
  }

  to {
    max-height: 64px;
  }
}

@keyframes new-row-glow {
  from {
    box-shadow: inset 0 0 0 100px var(--pink-light);
  }

  to {
    box-shadow: inset 0 0 0 100px transparent;
  }
}

@keyframes new-cover-pop {
  from {
    transform: scale(0.85);
  }
}

@keyframes new-spiral-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes new-spiral-fade {
  0%,
  68% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .playlist-item.is-new {
    animation: new-row-glow 1200ms ease-out both;
    animation-delay: var(--new-elapsed, 0ms);
  }

  .playlist-item.is-new .playlist-icon,
  .playlist-item.is-new .playlist-tile-cover,
  .new-spiral {
    animation: none;
  }

  .new-spiral {
    display: none;
  }
}

/* ---- Open vs. hover, without spending the pink ----
   The fills alone (hover 1.1:1, open 1.3:1) were too close to tell
   apart at a glance. Pink stays reserved for "now playing", so the
   open playlist also gets a heavier name and a hairline outline:
   shape and weight, not another colour. */
.playlists .playlist-item.active {
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

.playlists .playlist-item.active .playlist-name-text {
  color: var(--text);
  font-weight: 700;
}

/* "⋯" on the same 32px button size as the toolbar's other icon
   buttons (--tap-sm), not 28. */
.playlist-more-btn {
  width: var(--tap-sm);
  height: var(--tap-sm);
}

/* =========================================================
   HOVER GLIDE (js/hover-glide.js)
   =========================================================
   One hover fill per list that slides between rows instead of each
   row's own fill blinking on and off. Rows sit above it; open rows
   keep their own fill. Same colour as the old per-row hover. */
.has-hover-glide {
  position: relative;
}

.hover-glide {
  position: absolute;
  top: 0;
  left: 0;

  z-index: 0;

  border-radius: 8px;
  background: var(--row-hover);

  opacity: 0;
  pointer-events: none;

  transition:
    transform 220ms cubic-bezier(0.2, 0, 0, 1),
    height 220ms cubic-bezier(0.2, 0, 0, 1),
    width 220ms cubic-bezier(0.2, 0, 0, 1),
    opacity 160ms ease;
}

.hover-glide.is-visible {
  opacity: 1;
}

/* Arriving from outside the list: appear in place, don't slide in. */
.hover-glide.is-placing {
  transition: none;
}

.has-hover-glide .nav a,
.has-hover-glide.nav a,
.has-hover-glide .playlist-item:not(.playlist-pinned) {
  position: relative;
  z-index: 1;
}

.has-hover-glide.nav a:hover,
.has-hover-glide .playlist-item:not(.playlist-pinned):not(.active):hover {
  background: transparent;
}

/* The glide is drawn in the light-on-dark rail as well; while the
   sidebar changes width it steps aside (the JS hides it). */
@media (prefers-reduced-motion: reduce) {
  .hover-glide {
    transition: opacity 120ms ease;
  }
}

/* =========================================================
   YOUR LIBRARY HEADER: one family of controls
   =========================================================
   Title, search, sort and + share one shape (8px corners, like the
   rows below), one gliding hover fill (js/hover-glide.js), one press
   (a slight squeeze) and one open state. The title is as wide as its
   words, so the hover fill hugs it instead of spanning empty space.
   On the Library page it turns pink-iconed like Home and Radio do. */
.sidebar .library-toolbar .library-title {
  flex: 0 1 auto;

  margin-right: auto;
  padding: 6px 10px;

  border-radius: 8px;
}

.sidebar .library-toolbar .library-search-toggle,
.sidebar .library-toolbar .library-sort-btn,
.sidebar .library-toolbar .playlist-add-btn {
  border-radius: 8px;
}

.sidebar .library-toolbar .library-title,
.sidebar .library-toolbar .library-search-toggle,
.sidebar .library-toolbar .library-sort-btn,
.sidebar .library-toolbar .playlist-add-btn {
  position: relative;
  z-index: 1;

  transition:
    color 150ms ease,
    background 150ms ease,
    transform 150ms cubic-bezier(0.2, 0, 0, 1),
    opacity 120ms ease;
}

.sidebar .library-toolbar.has-hover-glide .library-title:hover,
.sidebar .library-toolbar.has-hover-glide .library-search-toggle:hover,
.sidebar .library-toolbar.has-hover-glide .library-sort-btn:hover,
.sidebar .library-toolbar.has-hover-glide .playlist-add-btn:hover {
  background: transparent;
  color: var(--text);
}

.sidebar .library-toolbar .library-title:active,
.sidebar .library-toolbar .library-search-toggle:active,
.sidebar .library-toolbar .library-sort-btn:active,
.sidebar .library-toolbar .playlist-add-btn:active {
  opacity: 1;
  transform: scale(0.94);
}

/* Selected: you're on the Library page. */
.sidebar .library-toolbar .library-title.active {
  color: var(--text);
}

.sidebar .library-toolbar .library-title.active svg {
  color: var(--pink);
}

/* Open: its menu is showing. */
.sidebar .library-toolbar .library-sort-btn[aria-expanded="true"],
.sidebar .library-toolbar .playlist-add-btn[aria-expanded="true"] {
  background: var(--row-active);
  color: var(--text);
}

.sidebar .library-toolbar .library-sort-btn svg {
  transition: opacity 150ms ease;
}

/* ---- Search in Your Library ----
   A quiet field in the row's own shape: soft fill, no outline at
   rest, the search icon plain inside it (no extra circle). Focus adds
   a pink edge and a soft halo, the clear sign of where typing goes. */
.sidebar .library-toolbar .library-search-input.expanded {
  height: 32px;

  margin-right: 4px;
  padding: 0 12px 0 34px;

  border: 1px solid transparent;
  border-radius: 8px;

  background: var(--fill-hover);
  color: var(--text);

  font-size: var(--fs-body-sm);

  transition:
    background 150ms ease,
    border-color 150ms ease,
    box-shadow 150ms ease;

  animation: library-search-in 180ms cubic-bezier(0.2, 0, 0, 1);
}

.sidebar .library-toolbar .library-search-input.expanded:focus,
.sidebar .library-toolbar .library-search-input.expanded:focus-visible {
  border-color: var(--pink);
  outline: none;

  background: var(--surface);

  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pink) 18%, transparent);
}

.sidebar .library-toolbar:has(.library-search-input.expanded) .library-search-toggle,
.sidebar .library-toolbar:has(.library-search-input.expanded) .library-search-toggle:hover,
.sidebar .library-toolbar:has(.library-search-input.expanded) .library-search-toggle.active {
  width: 32px;

  margin-right: -32px;

  background: transparent;
  color: var(--text-muted);
}

@keyframes library-search-in {
  from {
    opacity: 0;
    transform: translateX(-6px);
  }
}

/* =========================================================
   LIBRARY VIEWS: Liked Songs follows "View as" too
   =========================================================
   js/user-playlists.js puts the view on .playlists as well as the
   list. Compact: Liked Songs is a compact row. Grids: the whole area
   is one grid (the list's own box steps aside), so Liked Songs is the
   first card, the same size as the rest. Not in the collapsed rail,
   which has its own one-column layout. */
/* Same box as a compact row (3px accent edge, 44px tall), so names line up. */
.sidebar .playlists.view-compact .playlist-pinned {
  min-height: 44px;

  padding: 6px 10px;

  border-left: 3px solid transparent;
}

.sidebar .playlists.view-compact .playlist-pinned .playlist-icon,
.sidebar .playlists.view-compact .playlist-pinned .playlist-meta {
  display: none;
}

.sidebar:not(.collapsed) .playlists.view-grid,
.sidebar:not(.collapsed) .playlists.view-compactGrid {
  display: grid;
  align-content: start;

  padding: 4px 6px 10px;
}

.sidebar:not(.collapsed) .playlists.view-grid {
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));

  gap: 10px;
}

.sidebar:not(.collapsed) .playlists.view-compactGrid {
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));

  gap: 8px;
}

.sidebar:not(.collapsed) .playlists.view-grid .playlist-list,
.sidebar:not(.collapsed) .playlists.view-compactGrid .playlist-list {
  display: contents;
}

.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) :is(.playlist-empty, .playlist-empty-state) {
  grid-column: 1 / -1;
}

.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned,
.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned:hover {
  position: relative;
  top: auto;

  min-height: 0;

  flex-direction: column;
  align-items: stretch;

  gap: 0;
  padding: 0;

  background: transparent;
}

.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned .playlist-icon {
  width: 100%;
  height: auto;

  aspect-ratio: 1;

  border-radius: 8px;
}

.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned .playlist-icon > svg {
  width: 32%;
  height: 32%;
}

.sidebar:not(.collapsed) .playlists.view-grid .playlist-pinned .playlist-text {
  padding: 8px 2px 0;
}

.sidebar:not(.collapsed) .playlists.view-grid .playlist-pinned .playlist-name {
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.sidebar:not(.collapsed) .playlists.view-grid .playlist-pinned .playlist-meta,
.sidebar:not(.collapsed) .playlists.view-compactGrid .playlist-pinned .playlist-text {
  display: none;
}

/* =========================================================
   CARDS: open on the card, play on the button
   =========================================================
   On a card the whole cover used to be a play target, so a click
   meant to look inside a playlist started it instead. Now play is a
   round button in the cover's corner (shown on hover or focus) and
   the rest of the card opens the playlist, as on Spotify's and Apple
   Music's cards. The collapsed rail keeps its small covers as they
   are. */
.sidebar:not(.collapsed) .playlist-tile .playlist-cover-play,
.library-view .playlist-tile .playlist-cover-play,
.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned .playlist-cover-play {
  inset: auto 6px 6px auto;

  width: 32px;
  height: 32px;

  border-radius: 50%;

  background: var(--pink-fill);
  color: #fff;

  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);

  transform: translateY(4px);

  transition:
    opacity 150ms ease,
    transform 180ms cubic-bezier(0.2, 0, 0, 1),
    background 150ms ease;
}

.sidebar:not(.collapsed) .playlists.view-compactGrid .playlist-cover-play {
  width: 24px;
  height: 24px;

  inset: auto 4px 4px auto;
}

.sidebar:not(.collapsed) .playlist-tile:is(:hover, :focus-visible) .playlist-cover-play,
.library-view .playlist-tile:is(:hover, :focus-visible) .playlist-cover-play,
.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned:is(:hover, :focus-visible) .playlist-cover-play {
  opacity: 1;
  transform: none;
}

.sidebar:not(.collapsed) .playlist-tile .playlist-cover-play:hover,
.library-view .playlist-tile .playlist-cover-play:hover,
.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned .playlist-cover-play:hover {
  background: var(--pink-fill-hover);
  transform: scale(1.06);
}

.sidebar:not(.collapsed) .playlist-tile .playlist-cover-play svg,
.library-view .playlist-tile .playlist-cover-play svg {
  width: 14px;
  height: 14px;
}

@media (prefers-reduced-motion: reduce) {
  .sidebar .library-toolbar .library-search-input.expanded {
    animation: none;
  }
}

/* =========================================================
   SEARCH IN YOUR LIBRARY: opening and closing
   =========================================================
   The field lies over the row and is revealed from the left (a clip
   that opens to full width), while the title and sort button fade out
   beneath it without changing size, so nothing in the row reflows. The
   search icon slides from its place to the start of the field and
   back (js/user-playlists.js, slideSearchIcon). Closing runs the same
   motion back. 260ms throughout. */
.sidebar .library-toolbar:has(.library-search-input.expanded) .library-title,
.sidebar .library-toolbar:has(.library-search-input.expanded) .library-sort-btn {
  display: flex;

  opacity: 0;
  pointer-events: none;
}

/* Keeps its size while hidden (an older rule shrank it to nothing). */
.sidebar .library-toolbar:has(.library-search-input.expanded) .library-sort-btn {
  width: var(--tap-sm);
}

.sidebar .library-toolbar .library-title,
.sidebar .library-toolbar .library-sort-btn {
  transition:
    opacity 200ms ease,
    color 150ms ease,
    background 150ms ease,
    transform 150ms cubic-bezier(0.2, 0, 0, 1);
}

/* Open: the icon sits at the start of the field (it slides there,
   js/user-playlists.js, slideSearchIcon). */
.sidebar .library-toolbar:has(.library-search-input.expanded) .library-search-toggle {
  position: absolute;
  top: 0;
  bottom: 8px;
  left: 4px;

  z-index: 3;

  height: auto;

  margin: 0;
}

.sidebar .library-toolbar .library-search-input,
.sidebar .library-toolbar .library-search-input.expanded {
  position: absolute;
  top: 0;
  bottom: 8px; /* the toolbar's bottom padding */
  left: 4px;
  right: 40px; /* leaves + in view */

  z-index: 2;

  width: auto;
  height: auto;

  margin: 0;
  padding: 0 12px 0 34px;

  opacity: 1;

  animation: none;
}

.sidebar .library-toolbar .library-search-input {
  visibility: hidden;

  clip-path: inset(0 100% 0 0 round 8px);

  transition:
    clip-path 260ms cubic-bezier(0.2, 0, 0, 1),
    visibility 0s linear 260ms,
    background 150ms ease,
    border-color 150ms ease,
    box-shadow 150ms ease;
}

.sidebar .library-toolbar .library-search-input.expanded {
  visibility: visible;

  clip-path: inset(0 0 0 0 round 8px);

  transition:
    clip-path 260ms cubic-bezier(0.2, 0, 0, 1),
    visibility 0s linear 0s,
    background 150ms ease,
    border-color 150ms ease,
    box-shadow 150ms ease;
}

/* The focus halo sits outside the field; clip it in, not off. */
.sidebar .library-toolbar .library-search-input.expanded:focus,
.sidebar .library-toolbar .library-search-input.expanded:focus-visible {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--pink) 18%, transparent);
}

/* ---- Results ---- */

/* Liked Songs steps aside when it doesn't match (its row is a flex
   box, which would otherwise outrank the hidden attribute). */
.sidebar .playlist-pinned[hidden] {
  display: none;
}

/* "Has “Song”": why a playlist matched, in pink like other matches. */
.playlist-meta.is-match {
  color: var(--pink);
}

.playlist-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;

  gap: 8px;
  padding: 16px 12px;
}

.playlist-empty-title {
  margin: 0;

  color: var(--text);

  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.4;
}

.playlist-empty-body {
  margin: 0 0 4px;

  color: var(--text-muted);

  font-size: var(--fs-body-sm);
  line-height: 1.5;
}

.playlist-empty-search {
  padding: 8px 16px;

  border: none;
  border-radius: var(--radius-pill);

  background: var(--pink-fill);
  color: #fff;

  font: inherit;
  font-size: var(--fs-body-sm);
  font-weight: 700;

  cursor: pointer;

  transition: background 150ms ease, transform 150ms cubic-bezier(0.2, 0, 0, 1);
}

.playlist-empty-search:hover {
  background: var(--pink-fill-hover);
}

.playlist-empty-search:active,
.playlist-empty-clear:active {
  transform: scale(0.96);
}

.playlist-empty .playlist-empty-clear {
  padding: 7px 16px;

  font-size: var(--fs-body-sm);
}

@media (prefers-reduced-motion: reduce) {
  .sidebar .library-toolbar .library-title,
  .sidebar .library-toolbar .library-sort-btn,
  .sidebar .library-toolbar .library-search-toggle,
  .sidebar .library-toolbar .library-search-input {
    transition: none;
  }
}

/* A playlist photo over its colour cover (js/playlist-rows.js): the
   photo fills the square; if it fails to load, it steps aside and the
   colour cover underneath shows. */
.playlist-icon:has(.playlist-cover-photo),
.playlist-tile-cover:has(.playlist-cover-photo) {
  position: relative;
}

.playlist-icon .playlist-cover-photo,
.playlist-tile-cover .playlist-cover-photo {
  position: absolute;
  inset: 0;

  width: 100%;
  height: 100%;

  object-fit: cover;
}

.playlist-cover-photo.is-failed {
  display: none;
}

/* In the collapsed rail a playlist is just its cover, and the hover play
   overlay covered all of it, so a click could only ever play (and an
   empty playlist did nothing). The rail opens what you click, like the
   cards ("browse first"); Play is in the right-click menu. */
.sidebar.collapsed .playlist-cover-play {
  display: none;
}

/* =========================================================
   CARDS: hover and open states
   =========================================================
   A card is its cover, so the states live on the cover: hover lifts
   and brightens it, open draws a pink ring around it. No row fill or
   outline around the whole card (that showed as a grey band under the
   name and a box that didn't fit). Same in the collapsed rail, where
   the small pink line under the open one stays. */
.sidebar .playlists .playlist-tile,
.sidebar .playlists .playlist-tile:hover,
.sidebar .playlists .playlist-tile.active,
.sidebar .playlists .playlist-tile.active:hover,
.sidebar .playlists .playlist-tile:active,
.library-view .playlist-tile.active {
  outline: none;

  background: transparent;
  box-shadow: none;
}

.playlist-tile .playlist-tile-cover {
  transition:
    transform 180ms cubic-bezier(0.2, 0, 0, 1),
    filter 180ms ease,
    box-shadow 180ms ease;
}

.playlist-tile:hover .playlist-tile-cover {
  filter: brightness(1.06);
  transform: translateY(-2px);
}

.playlist-tile:active .playlist-tile-cover {
  transform: scale(0.97);
}

.playlist-tile.active .playlist-tile-cover {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--pink);
}

/* Liked Songs as a card (grid views) behaves the same. */
.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned .playlist-icon {
  transition:
    transform 180ms cubic-bezier(0.2, 0, 0, 1),
    filter 180ms ease,
    box-shadow 180ms ease;
}

.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned:hover .playlist-icon {
  filter: brightness(1.06);
  transform: translateY(-2px);
}

.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned.active .playlist-icon {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--pink);
}

@media (prefers-reduced-motion: reduce) {
  .playlist-tile:hover .playlist-tile-cover,
  .sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned:hover .playlist-icon {
    transform: none;
  }
}

/* =========================================================
   COVER STATES: one design for every view, open or rail
   =========================================================
   Checked as a matrix (list, compact, compact grid, grid; expanded
   and rail; colour, photo and song-art covers; rest, hover, open).
   Rings drawn outside a cover were clipped by the card or row they
   sit in (compact grid lost its open state entirely; the rail cut it
   to two arcs), so states are drawn inside the cover's edge, above
   the artwork, where nothing can clip them:
   - open: a pink ring (with a thin gap so it reads on pink covers too)
   - playing, in the rail: a ring in the now-playing colour
   Every cover keeps its rounded corners, photos included. */
.playlist-icon,
.playlist-tile-cover {
  position: relative;

  overflow: hidden;
}

.playlist-tile .playlist-tile-cover,
.playlist-tile.active .playlist-tile-cover,
.sidebar.collapsed .active-playing .playlist-icon,
.sidebar.collapsed .active-playing .playlist-tile-cover,
.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned.active .playlist-icon {
  box-shadow: none;
}

.playlist-tile.active .playlist-tile-cover::after,
.sidebar.collapsed .playlist-item.active .playlist-icon::after,
.sidebar:not(.collapsed) .playlists:is(.view-grid, .view-compactGrid) .playlist-pinned.active .playlist-icon::after,
.sidebar.collapsed .active-playing:not(.active) :is(.playlist-icon, .playlist-tile-cover)::after {
  content: "";

  position: absolute;
  inset: 0;

  z-index: 2;

  border-radius: inherit;

  box-shadow: inset 0 0 0 2px var(--pink), inset 0 0 0 3.5px var(--surface);

  pointer-events: none;
}

.sidebar.collapsed .active-playing:not(.active) :is(.playlist-icon, .playlist-tile-cover)::after {
  box-shadow: inset 0 0 0 2px var(--now-playing), inset 0 0 0 3.5px var(--surface);
}

/* ---- The rail: the same in every view ----
   Just covers, 36px, rounded. Hover lifts the cover; open is the ring.
   No row box, no underline, no gliding fill (the cover is the target). */
.sidebar.collapsed .playlists .playlist-item,
.sidebar.collapsed .playlists .playlist-item:hover,
.sidebar.collapsed .playlists .playlist-item.active,
.sidebar.collapsed .playlists .playlist-item.active:hover {
  background: transparent;
  box-shadow: none;
  outline: none;
}

.sidebar.collapsed .playlist-item.active::after {
  display: none;
}

.sidebar.collapsed .playlist-icon,
.sidebar.collapsed .playlist-tile-cover {
  width: 36px;
  height: 36px;

  border-radius: 8px;

  transition:
    transform 180ms cubic-bezier(0.2, 0, 0, 1),
    filter 180ms ease;
}

.sidebar.collapsed .playlist-item:hover :is(.playlist-icon, .playlist-tile-cover) {
  filter: brightness(1.08);
  transform: translateY(-2px);
}

.sidebar.collapsed .playlist-item:active :is(.playlist-icon, .playlist-tile-cover) {
  transform: scale(0.95);
}

/* Compact view hides covers in the open sidebar, but the rail is
   nothing but covers. */
.sidebar.collapsed .playlist-list.view-compact .playlist-item-compact .playlist-icon,
.sidebar.collapsed .playlists.view-compact .playlist-pinned .playlist-icon {
  display: flex;
}

.sidebar.collapsed .playlists .playlist-item-compact {
  min-height: 50px;
}

@media (prefers-reduced-motion: reduce) {
  .sidebar.collapsed .playlist-item:hover :is(.playlist-icon, .playlist-tile-cover) {
    transform: none;
  }
}

/* Compact rows in the rail: no text padding or accent edge, so the
   cover sits on the rail's centre like every other view's. */
.sidebar.collapsed .playlists .playlist-item-compact,
.sidebar.collapsed .playlists.view-compact .playlist-pinned {
  justify-content: flex-start;

  /* The same box as a default row in the rail (the hidden name still
     takes room, so centring pushed the cover 6px left). */
  padding: 6px 8px 6px 6px;

  border-left: none;
}

/* Compact grid: the card is its cover, with the name laid over it on
   hover. The ring goes on the card, above that overlay, so it stays
   while you hover. */
.sidebar:not(.collapsed) .playlist-list.view-compactGrid .playlist-tile.active .playlist-tile-cover::after {
  display: none;
}

.sidebar:not(.collapsed) .playlist-list.view-compactGrid .playlist-tile.active::after {
  content: "";

  position: absolute;
  inset: 0;

  z-index: 3;

  border-radius: 8px;

  box-shadow: inset 0 0 0 2px var(--pink), inset 0 0 0 3.5px var(--surface);

  pointer-events: none;
}
