/* ── Card action-icon corner grammar ──
   top-left    = selection / kebab (⋯) menu
   top-right   = like (heart)
   bottom-right = play
   bottom-left  = download / add
   Library-detail cards (#libraryTracks) deviate from this on purpose: the
   bulk-select checkbox needs top-left, so the kebab moves to the mid-right
   edge (.kebab-lib, defined in index.html's inline <style> block) and the
   heart shifts to right:52px to clear both the kebab and the "remove"
   control. Those per-context offsets are load-bearing for bulk-select layout
   — left unchanged here; see index.html's kebab-btn/like-btn rules for the
   actual offsets. Hit targets stay >=40px in all contexts (kebab/like/
   card-fav-btn are all 40x40). */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 20px;
}

.card {
  background: var(--bg-card); border-radius: 16px;
  overflow: hidden; cursor: pointer; transition: all .25s cubic-bezier(.4,0,.2,1);
  border: 1px solid transparent; position: relative;
  /* Query container so the bottom action buttons below can anchor to the
     artwork's real bottom edge (100cqw = this element's own inline size =
     the height of the square .card-img, since aspect-ratio:1) instead of
     the bottom of the whole card (title/subtitle sit below the artwork).
     Harmless on variants with no cqw-based children (.lib-card, .fav-card,
     .skeleton-card doesn't even carry the .card class). .top-result-card
     and .song-list .song-row override the affected buttons with their own
     fixed/static rules — see cards.css §Card Action Buttons and search.css. */
  container-type: inline-size;
}
.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 12px 40px rgba(0,0,0,.5), 0 0 0 1px var(--border);
  border-color: var(--border);
}
.card:active { transform: translateY(-2px); }

.card-img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  background: var(--bg-elevated);
}

.card-body { padding: 14px; }
.card-title {
  font-size: 14px; font-weight: 600; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; line-height: 1.3;
}
.card-sub {
  font-size: 13px; color: var(--text-muted); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card-sub .clickable, .card-meta .clickable {
  cursor: pointer; transition: color .15s;
}
.card-sub .clickable:hover, .card-meta .clickable:hover {
  color: var(--accent); text-decoration: underline;
}
.card-meta {
  font-size: 11px; color: var(--text-dim); margin-top: 8px;
  display: flex; justify-content: space-between;
}

/* ── In Library Badge ── */
.in-library-badge {
  position: absolute; top: 10px; right: 10px;
  background: var(--accent); color: #000;
  font-size: 10px; font-weight: 700; text-transform: uppercase;
  padding: 4px 10px; border-radius: 20px;
  letter-spacing: 0.5px;
  box-shadow: 0 2px 12px rgba(0,0,0,.5);
  z-index: 2;
  animation: badgeFadeIn .3s ease;
}

.card.in-library { border-color: rgba(162, 230, 64, 0.2); }

/* ── Skeleton ── */
.skeleton {
  background: linear-gradient(90deg, var(--bg-elevated) 25%, var(--bg-card) 50%, var(--bg-elevated) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: var(--radius);
}

.skeleton-card { height: 280px; border-radius: 16px; }

/* ── Card Action Buttons ──
   bottom/top: these anchor to the BOTTOM of the square artwork, not the
   bottom of the whole card. The card-img is width:100%;aspect-ratio:1, so
   its rendered height equals the card's own inline size — which is exactly
   what `100cqw` resolves to against the `.card` query container above.
   `top: calc(100cqw - 44px)` = artwork's bottom edge minus an 8px inset
   minus the 36px button height, i.e. the button's bottom sits 8px above
   the artwork, same inset the old `bottom: 8px` used to give (before the
   card grew taller than the artwork once title/subtitle were added below).
   .top-result-card overrides .card-play-btn with a fixed px value instead
   (see search.css) because its artwork is a fixed 180/140px square, not
   proportional to the card's own width, so cqw does not apply there.
   .song-list .song-row resets .card-play-btn to position:static (higher
   specificity, see search.css) so these values are inert there. */
.card-play-btn { position: absolute; top: calc(100cqw - 44px); right: 8px; width: 36px; height: 36px; border-radius: 50%; background: var(--accent); color: #000; border: none; display: flex; align-items: center; justify-content: center; opacity: 0; transform: translateY(6px); transition: all .2s; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,.4); z-index: 3; }
.card:hover .card-play-btn { opacity: 1; transform: translateY(0); }
.card-play-btn:hover { transform: scale(1.1) !important; box-shadow: 0 4px 16px rgba(0,0,0,.5), var(--shadow-glow); }
/* Download is intentionally NOT shown on cards — it lives only in the ⋮ context
   menu so the card face stays play-first. Rule kept for any legacy render path. */
.card-dl-btn { position: absolute; top: calc(100cqw - 44px); left: 8px; width: 36px; height: 36px; border-radius: 50%; background: var(--accent); color: #000; border: none; display: flex; align-items: center; justify-content: center; opacity: 0; transform: translateY(6px); transition: all .2s; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,.4); z-index: 3; }
.card:hover .card-dl-btn { opacity: 1; transform: translateY(0); }
.card-dl-btn:hover { transform: scale(1.1) !important; box-shadow: 0 4px 16px rgba(0,0,0,.5), var(--shadow-glow); }
.card-radio-btn { position: absolute; top: calc(100cqw - 44px); left: 8px; width: 36px; height: 36px; border-radius: 50%; background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; opacity: 0; transform: translateY(6px); transition: all .2s; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,.4); z-index: 3; font-size: 16px; }
.card:hover .card-radio-btn { opacity: 1; transform: translateY(0); }
.card-radio-btn:hover { transform: scale(1.1) !important; background: var(--accent); color: #000; border-color: var(--accent); }
/* Quick "add to playlist" — sits bottom-left just right of the radio button
   (dl left:8, radio left:52, addpl left:96), clear of the play button (right). */
.card-addpl-btn { position: absolute; top: calc(100cqw - 44px); left: 52px; width: 36px; height: 36px; border-radius: 50%; background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; opacity: 0; transform: translateY(6px); transition: all .2s; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,.4); z-index: 3; }
.card:hover .card-addpl-btn { opacity: 1; transform: translateY(0); }
.card-addpl-btn:hover { transform: scale(1.1) !important; background: var(--accent); color: #000; border-color: var(--accent); }
/* Drag-to-reorder handle (library playlist detail only — added by JS there).
   Left edge, anchored just above the radio/add-to-playlist buttons' new
   artwork-relative band (top: calc(100cqw - 44px), 36px tall) with a fixed
   12px gap, instead of the old top:50%-of-whole-card. 50%-of-card-height
   would drift too close to (and, on the narrowest 150px grid columns,
   actually into) that band now that the buttons sit higher up against the
   artwork rather than at the very bottom of the card. Clear of checkbox
   (top-left), kebab/heart (right), remove (top-right), play (bottom-right).
   Pointer Events need touch-action:none so a touch-drag doesn't scroll the grid. */
.card-drag-handle { position: absolute; top: calc(100cqw - 84px); left: 8px; z-index: 5; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 6px; background: rgba(0,0,0,.5); color: var(--text-muted); cursor: grab; opacity: 0; transition: opacity .2s; touch-action: none; font-size: 13px; }
.card:hover .card-drag-handle { opacity: 1; }
.card-drag-handle:hover { color: var(--text); }
.card-drag-handle:active { cursor: grabbing; }
.card.lib-dragging { opacity: .5; }
/* Follow (heart-outline) button, artist cards only. Sized to match the
   kebab/like-btn 40x40 tap target (was 32x32 — the smallest tap target on
   the page, and permanently visible on touch devices). */
.card-fav-btn { position: absolute; top: 8px; right: 8px; width: 40px; height: 40px; border-radius: 50%; background: rgba(0,0,0,.6); color: var(--text); border: none; display: flex; align-items: center; justify-content: center; opacity: 0; transition: all .2s; cursor: pointer; z-index: 3; font-size: 18px; }
.card:hover .card-fav-btn { opacity: 1; }
.card-fav-btn:hover { color: #ef4444; transform: scale(1.1); }
.card-fav-btn.following { color: #ef4444; opacity: 1; }
/* When the "In Library" badge occupies the top-right corner, shift the
   follow button left to clear it — mirrors the identical .like-btn rule in
   index.html's inline <style> (`.card.in-library .like-btn { right: 84px; }`)
   which solves the same badge collision for the track "like" heart. Applies
   equally to grid cards and .top-result-card (the search hero), since both
   render with the shared .card + .in-library classes. */
.card.in-library .card-fav-btn { right: 84px; }
.new-badge { position: absolute; top: 8px; left: 8px; background: var(--accent); color: #000; font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 10px; z-index: 3; }
.fav-new-badge { position: absolute; top: 8px; left: 8px; background: var(--accent); color: #000; font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 10px; z-index: 3; }
.fav-auto-dl-badge { position: absolute; bottom: 52px; right: 8px; font-size: 10px; color: var(--accent); opacity: .7; }

/* ── Multi-select: shift/cmd-click selected state ── */
.card[data-selected="true"] {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-card));
  box-shadow: 0 0 0 2px var(--accent), 0 4px 20px rgba(0,0,0,.4);
}

/* ── BPM Badge ── */
.bpm-badge {
  background: var(--bg-elevated); color: var(--accent);
  font-size: 10px; font-weight: 700; padding: 2px 7px;
  border-radius: 8px; border: 1px solid var(--border);
  white-space: nowrap;
}

/* ── BPM Filter Bar ── */
.bpm-filter {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 14px; flex-wrap: wrap;
}
.bpm-preset {
  background: var(--bg-elevated); color: var(--text-muted);
  border: 1px solid var(--border); border-radius: 16px;
  padding: 5px 14px; font-size: 12px; cursor: pointer;
  transition: all .15s;
}
.bpm-preset:hover { border-color: var(--accent); color: var(--text); }
.bpm-preset.active { background: var(--accent); color: #000; border-color: var(--accent); font-weight: 600; }
.bpm-filter-sep { color: var(--text-dim); font-size: 12px; }
.bpm-range-label { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-muted); }
.bpm-range-input {
  width: 52px; padding: 4px 8px; font-size: 12px;
  background: var(--bg-elevated); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  text-align: center;
}
.bpm-range-input:focus { border-color: var(--accent); outline: none; }
.bpm-lowconf-label { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-muted); cursor: pointer; }
.bpm-lowconf-label input { cursor: pointer; }
.bpm-coverage { color: var(--text-dim); font-size: 12px; }
.bpm-filter-note { color: var(--text-dim); font-size: 12px; font-style: italic; }
.bpm-filter-disabled .bpm-preset,
.bpm-filter-disabled .bpm-range-input,
.bpm-filter-disabled .bpm-lowconf-label { opacity: .45; cursor: not-allowed; }
