/* ── Design system tokens ─────────────────────────────────────────────────── */
:root {
    --c-bg:          #0d0d12;
    --c-surface:     #16161c;
    --c-surface-2:   #1c1c26;
    --c-surface-3:   #24242e;
    --c-accent:      #ff5500;
    --c-accent-dim:  rgba(255, 85, 0, 0.13);
    --c-accent-glow: rgba(255, 85, 0, 0.22);
    --c-text:        #ffffff;
    --c-text-2:      rgba(255, 255, 255, 0.52);
    --c-text-3:      rgba(255, 255, 255, 0.28);
    --c-border:      rgba(255, 255, 255, 0.07);
    --c-border-2:    rgba(255, 255, 255, 0.13);
    --c-danger:      #e84848;
    --c-go:          #2cba6e;

    --r-xs:   4px;
    --r-sm:   8px;
    --r-md:   12px;
    --r-lg:   18px;
    --r-xl:   24px;
    --r-full: 9999px;

    --dur-micro:  90ms;
    --dur-fast:   150ms;
    --dur-base:   220ms;
    --dur-slow:   340ms;
    --ease-out:   cubic-bezier(0.16, 1, 0.3, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
    --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);

    /* ── Spacing scale ────────────────────────────────────── */
    --sp-1:  4px;
    --sp-2:  8px;
    --sp-3:  12px;
    --sp-4:  16px;
    --sp-5:  24px;
    --sp-6:  32px;
    --sp-7:  48px;

    /* ── Shadow scale ─────────────────────────────────────── */
    --shadow-xs: 0 1px 4px  rgba(0, 0, 0, 0.30);
    --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.40);
    --shadow-md: 0 4px 18px rgba(0, 0, 0, 0.50);
    --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.60);
    --shadow-xl: 0 16px 52px rgba(0, 0, 0, 0.72), 0 4px 12px rgba(0, 0, 0, 0.40);

    /* ── Typography scale ─────────────────────────────────── */
    --fs-xxs: 0.63rem;
    --fs-xs:  0.72rem;
    --fs-sm:  0.82rem;
    --fs-md:  0.92rem;
    --fs-lg:  1.1rem;
    --fs-xl:  1.5rem;
    --fs-2xl: 2.1rem;

    --fw-normal:    400;
    --fw-medium:    500;
    --fw-semibold:  600;
    --fw-bold:      700;
    --fw-extrabold: 800;

    --lh-tight:   1.2;
    --lh-normal:  1.5;
    --lh-relaxed: 1.75;

    /* ── Backdrop ─────────────────────────────────────────── */
    --backdrop:    blur(24px) saturate(160%);
    --backdrop-sm: blur(14px) saturate(140%);

    /* ── Z-index scale ────────────────────────────────────── */
    --z-base:    1;
    --z-sticky:  50;
    --z-nav:     200;
    --z-toast:   500;
    --z-overlay: 1100;
    --z-modal: 1200;

    /* ── GPU-acceleration hints ────────────────────────────────── */
    --gpu-will-change: transform, opacity;
}

/* ── Element reset ────────────────────────────────────────────────────────────
   Replaces the parts of Bootstrap Reboot the app actually relied on (Bootstrap
   itself is no longer shipped). Keep these values Reboot-identical — the whole
   layout was built on border-box sizing and 1.5 body line-height. */
*, *::before, *::after { box-sizing: border-box; }

body {
    font-size: 1rem;
    font-weight: 400;
    line-height: 1.5;
}

h1, h2, h3, h4, h5, h6 {
    margin-top: 0;
    margin-bottom: 0.5rem;
    font-weight: 500;
    line-height: 1.2;
}
h1 { font-size: calc(1.375rem + 1.5vw); }
h2 { font-size: calc(1.325rem + 0.9vw); }
h3 { font-size: calc(1.3rem + 0.6vw); }
h4 { font-size: calc(1.275rem + 0.3vw); }
h5 { font-size: 1.25rem; }
h6 { font-size: 1rem; }
@media (min-width: 1200px) {
    h1 { font-size: 2.5rem; }
    h2 { font-size: 2rem; }
    h3 { font-size: 1.75rem; }
    h4 { font-size: 1.5rem; }
}

p { margin-top: 0; margin-bottom: 1rem; }
ul, ol { margin-top: 0; margin-bottom: 1rem; padding-left: 2rem; }
small { font-size: 0.875em; }
b, strong { font-weight: bolder; }
img, svg { vertical-align: middle; }
label { display: inline-block; }
hr { margin: 1rem 0; border: 0; border-top: 1px solid currentColor; opacity: 0.25; }

input, button, select, optgroup, textarea {
    margin: 0;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
}
button, select { text-transform: none; }
button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; }
button:not(:disabled), [type="button"]:not(:disabled),
[type="reset"]:not(:disabled), [type="submit"]:not(:disabled),
[role="button"] { cursor: pointer; }
button { border-radius: 0; }
textarea { resize: vertical; }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    width: 100%;
    overflow: hidden;
    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', Arial, sans-serif;
    background: var(--c-bg);
    color: var(--c-text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* #app + .page carry the theme gradient so the background always fills the
   whole document height — without this, a short page or an overscroll bounce
   exposes a transparent strip (the body's flat --c-bg shows through instead of
   the theme gradient, and on some WebViews even transparency). */
#app {
    min-height: 100%;
    background: linear-gradient(180deg,
        var(--theme-bg-top, #0d0d12) 0%,
        var(--theme-bg-bottom, #0d0d12) 100%);
}

/* Mobile: body is the scroll container — more reliable than a nested
   CSS overflow-y element in Android MAUI WebView. */
@media (max-width: 640px) {
    html, body {
        height: auto;
        /* Always cover the viewport so an over-scroll / short page reveals the
           theme background instead of a transparent gap behind the fixed bars. */
        min-height: 100dvh;
        background: var(--c-bg);
        overflow-x: hidden;
        overflow-y: auto;
        /* Stop the rubber-band bounce from exposing a transparent strip when the
           user drags near the bottom nav. */
        overscroll-behavior-y: none;
    }

    /* On mobile the body scrolls, so #app must grow with its content (not just
       100% of the viewport) or the theme gradient stops at the first screenful
       and a transparent gap appears when scrolling further down. */
    #app {
        min-height: 100dvh;
    }

    /* .page is a flex column on mobile — make sure it stretches to fill #app
       and paints the theme gradient too, so there's no transparent seam between
       the top search bar and the content, or below the content above the
       bottom nav / player bar. */
    .page {
        min-height: 100dvh;
        background: linear-gradient(180deg,
            var(--theme-bg-top, #0d0d12) 0%,
            var(--theme-bg-bottom, #0d0d12) 100%);
    }
}

/* ── Global scrollbars (Spotify-style: themed, auto-hide on hover) ───────────
   The thumb is transparent by default and fades in only when the user hovers
   the scroll container (sidebar or main content). Uses the accent colour so it
   matches the active theme. Firefox gets the equivalent via scrollbar-color. */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: var(--r-full, 9999px);
    border: 2px solid transparent;
    background-clip: padding-box;
    transition: background var(--dur-base, 220ms) ease;
}
::-webkit-scrollbar-thumb:hover,
*:hover > ::-webkit-scrollbar-thumb {
    background: color-mix(in oklab, var(--c-accent, #ff5500) 55%, transparent);
    background-clip: padding-box;
}
::-webkit-scrollbar-thumb:active {
    background: color-mix(in oklab, var(--c-accent, #ff5500) 75%, transparent);
    background-clip: padding-box;
}
* {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}
/* Firefox: show the themed thumb on hover (scrollbar-color can't transition,
   so this is the closest equivalent to the WebKit fade). */
*:hover { scrollbar-color: color-mix(in oklab, var(--c-accent, #ff5500) 55%, transparent) transparent; }

/* ── Keyboard focus ring ──────────────────────────────────────────────────── */
/* Only shows for keyboard navigation (Tab), never for mouse/touch — so it aids
   accessibility without adding visual noise to pointer users. */
:focus-visible {
    outline: 2px solid var(--c-accent, #ff5500);
    outline-offset: 2px;
    border-radius: var(--r-xs, 4px);
}
:focus:not(:focus-visible) { outline: none; }

/* ── Reduced motion ───────────────────────────────────────────────────────── */
/* Users who ask the OS for less motion get instant transitions instead of
   animated ones. JS-driven canvas effects (visualizer) are unaffected. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── Pull-to-refresh indicator ───────────────────────────────────────────── */
#pull-refresh {
    position: fixed;
    top: calc(env(safe-area-inset-top) + 10px);
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    pointer-events: none;
    opacity: 0;
    transform: translateY(-20px);
    transition: opacity 180ms ease, transform 180ms ease;
    z-index: 9998;
}

#pull-refresh.active {
    opacity: 1;
    transform: translateY(0);
}

.pull-refresh__logo {
    font-size: 1.5rem;
    font-weight: 800;
    letter-spacing: -0.04em;
    color: var(--c-accent);
    user-select: none;
    line-height: 1;
}

.pull-refresh__spinner {
    width: 20px;
    height: 20px;
    border: 2px solid rgba(255, 85, 0, 0.15);
    border-top-color: var(--c-accent);
    border-radius: 50%;
    animation: pull-refresh-spin 0.7s linear infinite;
}

@keyframes pull-refresh-spin {
    to { transform: rotate(360deg); }
}

/* ── Splash / loading screen ─────────────────────────────────────────────── */
#splash {
    position: fixed;
    inset: 0;
    background: var(--c-bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 32px;
    z-index: 9999;
}

.splash-logo {
    font-size: 2.8rem;
    font-weight: 800;
    letter-spacing: -0.05em;
    color: var(--c-accent);
    user-select: none;
}

.splash-spinner {
    display: flex;
    gap: 7px;
}

.splash-spinner div {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--c-accent);
    animation: splash-bounce 1.2s ease-in-out infinite;
}

.splash-spinner div:nth-child(1) { animation-delay: 0s;    opacity: 0.25; }
.splash-spinner div:nth-child(2) { animation-delay: 0.15s; opacity: 0.25; }
.splash-spinner div:nth-child(3) { animation-delay: 0.3s;  opacity: 0.25; }
.splash-spinner div:nth-child(4) { animation-delay: 0.45s; opacity: 0.25; }

@keyframes splash-bounce {
    0%, 80%, 100% { transform: scale(0.55); opacity: 0.2; }
    40%           { transform: scale(1.0);  opacity: 1;   }
}

/* ── Blazor error UI ─────────────────────────────────────────────────────── */
a, .btn-link { color: var(--c-accent); }

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus,
.form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 2px rgba(255, 85, 0, 0.35);
}

.content { padding-top: 1.1rem; }
h1:focus  { outline: none; }

#blazor-error-ui {
    color-scheme: dark;
    background: rgba(28, 10, 12, 0.97);
    border-top: 1px solid rgba(232, 72, 72, 0.35);
    color: #e88;
    bottom: 0;
    box-shadow: var(--shadow-md, 0 -4px 18px rgba(0,0,0,0.50));
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem;
    position: fixed;
    width: 100%;
    z-index: var(--z-modal, 1200);
    font-size: var(--fs-sm, 0.82rem);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
    color: #e88;
}

.blazor-error-boundary {
    background: #2a1010;
    padding: 1rem 1rem 1rem 3.7rem;
    color: #e88;
    border-radius: var(--r-md);
    font-size: 0.82rem;
}

.blazor-error-boundary::after {
    content: "An error has occurred."
}

/* ── Global utilities ─────────────────────────────────────────────────────── */

.text-muted { color: var(--c-text-3); font-size: 0.85rem; }
.text-error { color: var(--c-danger); font-size: 0.85rem; }

.btn-subtle {
    background: var(--c-surface-2);
    border: 1px solid var(--c-border-2);
    border-radius: var(--r-full);
    color: var(--c-text-2);
    font-size: var(--fs-sm, 0.82rem);
    font-weight: var(--fw-medium, 500);
    padding: var(--sp-2, 7px) var(--sp-5, 18px);
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
    font-family: inherit;
}

/* Armed/toggled state for a .btn-subtle that turns a mode on rather than firing an
   action once — reads as state, not as hover. */
.btn-subtle--active {
    background: color-mix(in srgb, var(--c-accent, #ff5500) 16%, transparent);
    border-color: color-mix(in srgb, var(--c-accent, #ff5500) 55%, transparent);
    color: var(--c-accent, #ff5500);
}

.btn-subtle:hover {
    background: var(--c-surface-3);
    color: var(--c-text);
    border-color: var(--c-border-2);
}

/* ── Feed / Stream layout ─────────────────────────────────────────────────── */

/* The single definition of a feed page's frame. Liked and Home used to carry this
   as scoped overrides while History, Downloaded, Library and Queue wrote the same
   rules under selectors that never matched their markup (.history-page etc. against
   a <div class="feed-page">) — so four of the six rendered with no background and no
   edge bleed. Defining it once here is what keeps them identical.

   The negative margin pulls the frame out to the window edges so the background is
   full-bleed; the padding then re-insets the content. (It used to be described as
   escaping article.content's horizontal padding — that came from Bootstrap's px-4 and
   has not existed since the framework was dropped.) */
.feed-page {
    margin: -1.1rem -1.5rem 0 -2rem;
    padding: 0 0.25rem 80px 1rem;
    background: var(--c-bg, #0d0d12);
    min-height: 100vh;
    min-height: 100dvh;   /* dvh where supported: excludes mobile browser chrome */
    display: flex;
    flex-direction: column;
}

@media (max-width: 640px) {
    /* Pull out to the window edges, then re-inset symmetrically so cards sit a
       consistent distance off both edges rather than flush. */
    .feed-page {
        margin: -1.1rem -0.75rem 0 -0.75rem;
        padding: 0 0 80px;
    }
}

/* ── Bass-reactive glow variables ──────────────────────────────────────────────
   Registered so they do NOT inherit. A trace showed style recalculation at 10.6s over
   5,679 calls — one per animation frame — because these were written to <html> each
   frame and custom properties inherit, invalidating the whole document. main::before
   consumes them, and a pseudo-element inherits from its own element, so the value had
   to reach <main>, whose subtree is the entire track list.

   With inherits:false a write touches only the element itself. The consuming elements are
   therefore set individually (see _bassTargets in nuages.js) — including .avatar-lg, which
   a descendant selector reads and which would no longer receive the value from its parent.

   IMPORTANT, and the thing that is easy to get wrong: a pseudo-element does NOT inherit a
   non-inherited property from its originating element either. Every glow rule here is a
   ::before/::after, so setting --bass-intensity on <main> alone leaves main::before reading
   the initial value (0) and the glow never lights. Each of those rules therefore declares
   `--bass-hue: inherit; --bass-intensity: inherit;` explicitly. That opt-in is per rule, so
   it pulls the value across to the pseudo-element without putting the subtree back in the
   invalidation path.

   initial-value matches the var() fallbacks previously used throughout (240 / 0): a
   registered property uses its initial value instead of a var() fallback, so these must
   agree or the idle appearance changes. Where @property is unsupported the whole rule is
   ignored, the properties stay inherited, and setting them per element still works — so
   this degrades to the previous behaviour rather than breaking. */
@property --bass-hue {
    syntax: "<number>";
    inherits: false;
    initial-value: 240;
}

@property --bass-intensity {
    syntax: "<number>";
    inherits: false;
    initial-value: 0;
}

/* Primary play action. Shared: the track detail page and the mix page both lead with
   one, and a scoped copy would leave the other unstyled. */
.btn-play {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    background: var(--c-accent, #ff5500);
    border: none;
    border-radius: var(--r-xl, 20px);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    padding: 9px 22px;
    cursor: pointer;
    font-family: inherit;
    transition: background var(--dur-fast, 150ms), transform 80ms;
}

.btn-play:active { transform: scale(0.96); }
.btn-play:hover:not(:disabled) { background: color-mix(in oklab, var(--c-accent, #ff5500) 85%, black); }

/* The mix page disables this while a mix has no playable tracks; without a disabled
   state it stayed fully accented and looked pressable. */
.btn-play:disabled {
    opacity: 0.45;
    cursor: default;
    transform: none;
}

/* ── Mix artwork ───────────────────────────────────────────────────────────────
   Curated mixes often have no usable artwork. A quiet tinted tile with a small mark
   sits behind, and real artwork covers it when it loads. Layered rather than swapped
   so a broken image reveals the tile instead of the person placeholder the global
   image fallback would otherwise supply (see data-fallback="none"). */
.mix-art {
    position: relative;
}

.mix-art__tile {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    aspect-ratio: 1;
    background: linear-gradient(150deg,
        var(--c-surface-2, rgba(255, 255, 255, 0.05)) 0%,
        var(--c-surface-3, rgba(255, 255, 255, 0.09)) 100%);
    color: var(--c-text-3, rgba(255, 255, 255, 0.30));
}

.mix-art__tile svg {
    width: 28px;
    height: 28px;
}

.mix-art__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Centred content column, for pages whose content shouldn't stretch the full window.
   Deliberately a child of .feed-page rather than a modifier on it: the page background
   must stay full-bleed, so only the content narrows. Keep overlays (context menus,
   which render position:fixed siblings) OUTSIDE this wrapper. */
.page-column {
    display: flex;
    flex-direction: column;
    flex: 1;
    /* The critical companion to flex: 1 — same reason article carries it in MainLayout.
       A flex item defaults to min-height: auto, i.e. it refuses to shrink below its
       content. On a page that constrains its own height and puts a scroll container
       inside this wrapper (Lyrics), that default let this column grow past the page and
       the scroller inherited the overgrown height: scrollHeight == clientHeight, so it
       had nothing to scroll and scrollTo() silently did nothing. Harmless everywhere
       else — where the parent isn't height-constrained, content still sizes this. */
    min-height: 0;
    width: 100%;
    max-width: var(--page-column-max, 860px);
    margin-inline: auto;
    box-sizing: border-box;
}

/* Same constraint as .page-column, applied to a root's in-flow children instead of a
   wrapper. For pages whose root also hosts full-bleed decoration, where wrapping would
   mean re-indenting the whole file to move one element out. Decoration must be taken out
   of flow (position:absolute) AND exempted by the page — see .track-detail__backdrop. */
.page-column-host > * {
    width: 100%;
    max-width: var(--page-column-max, 860px);
    margin-inline: auto;
}

.feed-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 64px 24px;
    gap: 14px;
    flex: 1;
}

.feed-section {
    padding: 0 0 var(--sp-5, 24px);
    /* GPU: skip off-screen sections during layout — only paint what's visible.
       Compatible with all Blazor WebView targets (no ResizeObserver dependency). */
    content-visibility: auto;
    contain-intrinsic-size: 1px 500px;
}

.feed-section__title {
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    color: var(--c-text-2);
    text-transform: uppercase;
    letter-spacing: 0.10em;
    margin: 0 0 var(--sp-3);
    padding: 0 var(--sp-1);
}

/* ── Carousel (horizontal scroll row + arrows) ─────────────────────────────── */
.carousel { position: relative; }

.feed--row {
    display: grid;
    grid-auto-flow: column;
    /* These rows also carry the `.feed` class (for shared card styling), which sets an
       auto-fill grid-template-columns. Cancel it here so EVERY card flows into a uniform
       172px implicit column — otherwise the first ~8 fill the explicit auto-fill columns
       (smaller/stretched) and the rest get 172px, mismatching sizes and overflowing. */
    grid-template-columns: none;
    /* Fixed, larger cards that scroll as a carousel (arrows on desktop, swipe on
       mobile). Fixed width — not 1fr — so cards keep a consistent size and the row
       genuinely scrolls instead of stretching a handful of cards across the width. */
    grid-auto-columns: 172px;
    gap: var(--sp-3);
    overflow-x: auto;
    scroll-behavior: smooth;
    scroll-snap-type: x proximity;
    scroll-padding-left: var(--sp-1);
    padding-bottom: var(--sp-1);
    padding-right: var(--sp-3);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* Only handle horizontal pans — vertical swipes pass through to the
       parent article scroll container instead of being swallowed here. */
    touch-action: pan-x;
    overscroll-behavior-x: contain;
    /* GPU: promote scroll layer to the GPU so momentum scrolling doesn't
       trigger main-thread layout on every frame. */
    will-change: transform;
    contain: style paint;
}

.feed--row > .track-card { scroll-snap-align: start; }

.feed--row::-webkit-scrollbar { display: none; }

.carousel-arrow {
    position: absolute;
    top: 86px; /* roughly centered on the square artwork, above the title/footer */
    width: 38px;
    height: 38px;
    border-radius: var(--r-full, 9999px);
    background: color-mix(in oklab, var(--c-surface, #16161c) 82%, transparent);
    backdrop-filter: var(--backdrop-sm, blur(14px) saturate(140%));
    -webkit-backdrop-filter: var(--backdrop-sm, blur(14px) saturate(140%));
    border: 1px solid var(--c-border-2, rgba(255,255,255,0.13));
    color: var(--c-text, #fff);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
    opacity: 0;
    box-shadow: var(--shadow-md, 0 4px 18px rgba(0,0,0,0.50));
    transition: opacity var(--dur-fast, 150ms), background var(--dur-fast, 150ms), transform var(--dur-fast, 150ms);
}

.carousel-arrow--left  { left: -8px; }
.carousel-arrow--right { right: -8px; }

.carousel:hover .carousel-arrow { opacity: 1; }
.carousel-arrow:hover { background: var(--c-surface-2, #1c1c26); transform: scale(1.08); }
.carousel-arrow:active { transform: scale(0.94); }

@media (max-width: 640px) {
    /* Slightly smaller cards on the narrower mobile feed. */
    .feed--row { grid-auto-columns: 150px; }

    /* Arrows stay available on mobile but are clipped flush to both ends of the
       resized feed (left:0 / right:0) instead of overhanging at ∓8px — so they
       never spill past the feed edges into the page padding. No hover on touch,
       so they're always visible, and sized down + re-centered on the smaller
       150px artwork. */
    .carousel-arrow {
        display: flex;
        opacity: 1;
        width: 32px;
        height: 32px;
        top: 67px; /* ≈ vertical centre of the 150px mobile artwork */
    }
    .carousel-arrow--left  { left: 0; }
    .carousel-arrow--right { right: 0; }
}

/* ── Card grid ────────────────────── */
.feed {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    gap: var(--sp-2);
}

/* ── Track card (grid) ────────────── */
.track-card {
    background: var(--c-surface);
    border-radius: var(--r-md);
    overflow: hidden;
    cursor: pointer;
    transition: transform var(--dur-fast) var(--ease-out),
                background var(--dur-fast),
                box-shadow var(--dur-fast);
    -webkit-tap-highlight-color: transparent;
    user-select: none;
    contain: layout style;
    /* No permanent `will-change` -- see .liked-row. The transform it was hinting is
       scale(0.96) on :active, i.e. only while a card is actually being pressed, but the
       hint promoted EVERY card in every feed row to its own compositor layer for its
       whole lifetime. A trace showed 721 distinct layers and 1.8s in Layerize. The hint
       is scoped below instead, where it still lands well before the transform runs. */
}

/* Promote only the card being pointed at or pressed. :hover covers the mouse path into
   :active with time to spare; :active covers touch, which has no hover state; and
   :focus-visible covers the keyboard. */
.track-card:hover,
.track-card:active,
.track-card:focus-visible {
    will-change: transform;
}

.track-card:hover {
    background: var(--c-surface-2);
    box-shadow: var(--shadow-sm, 0 2px 10px rgba(0,0,0,0.40));
}

.track-card:active {
    transform: scale(0.96);
    transition-duration: var(--dur-micro);
}


/* ── Track artwork wrapper ─────────── */
.track-art-wrap {
    position: relative;
    overflow: hidden;
    line-height: 0;
    background: var(--c-surface-2);
}

.track-art-wrap .track-card__artwork {
    display: block;
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
}

/* ── Play/pause overlay ────────────── */
/* The disc and glyph reveal THEMSELVES; the container is never faded. An ancestor
   with opacity < 1 establishes a new backdrop root, which leaves a descendant's
   backdrop-filter with nothing to sample — so while this container faded from 0 to 1
   the blur did not render at all, then snapped to full strength the instant opacity
   reached exactly 1. That was the flash. Animating the blur radius could not fix it,
   because the blur was not being drawn during the fade in the first place. */
.track-play-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

/* Hidden state: transparent, unblurred, no ring. Every property here is animatable,
   so the disc eases in as one piece. */
.track-play-overlay::before {
    content: '';
    position: absolute;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0);
    backdrop-filter: blur(0px);
    -webkit-backdrop-filter: blur(0px);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0),
                0 4px 18px rgba(0, 0, 0, 0);
    transition: backdrop-filter var(--dur-fast) var(--ease-out),
                -webkit-backdrop-filter var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.track-play-overlay svg {
    width: 22px;
    height: 22px;
    color: #fff;
    position: relative;
    z-index: 1;
    opacity: 0;
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
    transition: opacity var(--dur-fast) var(--ease-out);
}

/* Shown: on hover, or permanently for the track currently playing. These selectors
   must stay in step with the glyph rule below. */
.track-card:hover .track-play-overlay::before,
.liked-row:hover .track-play-overlay::before,
.track-play-overlay--active::before {
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14),
                var(--shadow-md, 0 4px 18px rgba(0,0,0,0.50));
}

.track-card:hover .track-play-overlay svg,
.liked-row:hover .track-play-overlay svg,
.track-play-overlay--active svg {
    opacity: 1;
}

/* ── Card body ─────────────────────── */
.track-card__body {
    padding: var(--sp-2) var(--sp-2) var(--sp-3);
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.track-card__title-row {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
}

.track-card__title {
    flex: 1;
    min-width: 0;
    font-size: 0.80rem;
    font-weight: 500;
    color: var(--c-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.track-card__footer {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

.track-card__artist {
    flex: 1;
    min-width: 0;
    font-size: 0.72rem;
    color: var(--c-text-2);
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;
    transition: color var(--dur-fast);
    line-height: inherit;
    font-family: inherit;
}

.track-card__artist:hover { color: var(--c-text); }

/* ── Feed load more ─────────────────── */
.feed-loadmore {
    display: flex;
    justify-content: center;
    padding: 20px 0 4px;
}

/* ── Search bar (shared) ─────────────────────────────────────────────────── */
.liked-search-wrap {
    position: relative;
    margin-bottom: 12px;
}

.liked-search-icon {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--c-text-3);
    pointer-events: none;
}

.liked-search {
    width: 100%;
    box-sizing: border-box;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--r-full);
    color: var(--c-text);
    font-size: 0.84rem;
    padding: 9px 14px 9px 36px;
    outline: none;
    transition: border-color var(--dur-fast), background var(--dur-fast);
    font-family: inherit;
}

.liked-search::placeholder { color: var(--c-text-3); }

.liked-search:focus {
    border-color: var(--c-border-2);
    background: var(--c-surface-2);
}

.liked-search::-webkit-search-cancel-button { -webkit-appearance: none; }

/* ── Filter chips (shared) ───────────────────────────────────────────────── */
.filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 14px;
}

.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--r-full);
    color: var(--c-text-2);
    font-size: var(--fs-xs, 0.72rem);
    font-weight: var(--fw-medium, 500);
    padding: 5px var(--sp-3, 14px);
    cursor: pointer;
    transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast);
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
    font-family: inherit;
}

.filter-chip:hover {
    color: var(--c-text);
    background: var(--c-surface-2);
    border-color: var(--c-border-2);
}

.filter-chip--active {
    color: var(--c-accent);
    background: var(--c-accent-dim);
    border-color: rgba(255, 85, 0, 0.32);
}

.filter-chip--active:hover {
    background: rgba(255, 85, 0, 0.18);
    border-color: rgba(255, 85, 0, 0.5);
}

/* ── Go badge ─────────────────────────────────────────────────────────────── */
.go-badge {
    font-size: var(--fs-xxs, 0.63rem);
    font-weight: var(--fw-extrabold, 800);
    color: var(--c-go);
    background: rgba(44, 186, 110, 0.10);
    border: 1px solid rgba(44, 186, 110, 0.26);
    border-radius: var(--r-xs);
    padding: 1px 4px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    flex-shrink: 0;
    line-height: 1.5;
    user-select: none;
}

/* ── BPM badge ──────────────────────────────────────────────────────────────
   Shows the detected tempo next to the track title/artist. Uses the accent
   colour so it reads as a "metadata" chip consistent with the Go badge. */
.bpm-badge {
    font-size: var(--fs-xxs, 0.63rem);
    font-weight: var(--fw-bold, 700);
    color: var(--c-accent, #ff5500);
    background: var(--c-accent-dim, rgba(255, 85, 0, 0.13));
    border: 1px solid rgba(255, 85, 0, 0.26);
    border-radius: var(--r-xs, 4px);
    padding: 1px 4px;
    letter-spacing: 0.02em;
    flex-shrink: 0;
    line-height: 1.5;
    user-select: none;
    font-variant-numeric: tabular-nums;
}

/* ── Like button (inline) ─────────────────────────────────────────────────── */
.track-like-btn {
    background: none;
    border: none;
    color: var(--c-text-3);
    cursor: pointer;
    padding: 3px;
    display: flex;
    align-items: center;
    border-radius: 50%;
    transition: color var(--dur-fast), transform var(--dur-micro);
    flex-shrink: 0;
    line-height: 0;
    -webkit-tap-highlight-color: transparent;
}

.track-like-btn:hover { color: var(--c-danger); transform: scale(1.15); }
.track-like-btn:active { transform: scale(0.9); }
.track-like-btn--active { color: var(--c-danger); }
.track-like-btn--active:hover { color: #ff7070; }

/* ── Cache icon ──────────────────────────────────────────────────────────── */
.cache-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    line-height: 1;
    -webkit-tap-highlight-color: transparent;
}

.cache-icon--idle { color: var(--c-text-3); transition: color var(--dur-fast); }
.cache-icon--idle:hover { color: var(--c-accent); }
.cache-icon--cached { color: var(--c-accent); transition: color var(--dur-fast); }
.cache-icon--cached:hover { color: #e74c3c; }
.cache-icon--downloading { display: inline-flex; align-items: center; gap: 3px; cursor: default; }

.cache-spinner {
    width: 10px;
    height: 10px;
    border: 1.5px solid rgba(255, 255, 255, 0.12);
    border-top-color: var(--c-accent);
    border-radius: 50%;
    animation: cache-spin 0.7s linear infinite;
    flex-shrink: 0;
}

.cache-pct { font-size: 0.58rem; color: var(--c-accent); font-variant-numeric: tabular-nums; }

@keyframes cache-spin { to { transform: rotate(360deg); } }

/* ── User link ───────────────────────────────────────────────────────────── */
.user-link {
    background: none;
    border: none;
    padding: 0;
    font-size: inherit;
    font-weight: inherit;
    color: inherit;
    cursor: pointer;
    text-align: left;
    display: inline;
    line-height: inherit;
    transition: color var(--dur-fast);
    font-family: inherit;
}

.user-link:hover { color: var(--c-text); }

/* ── Context menu trigger ─────────────────────────────────────────────────── */
.ctx-trigger {
    background: none;
    border: none;
    color: var(--c-text-3);
    cursor: pointer;
    padding: 6px 4px;
    border-radius: var(--r-xs);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color var(--dur-fast), background var(--dur-fast);
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
}

.ctx-trigger:hover,
.ctx-trigger:active {
    color: var(--c-text-2);
    background: rgba(255, 255, 255, 0.07);
}

/* ── Shared row / list styles ────────────────────────────────────────────── */
.liked-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 14px 0 10px;
    padding: 0 2px;
}

.liked-view-toggle {
    display: flex;
    gap: 3px;
    background: var(--c-surface);
    border-radius: var(--r-sm);
    padding: 3px;
}

.liked-toggle-btn {
    background: none;
    border: none;
    border-radius: calc(var(--r-sm) - 2px);
    color: var(--c-text-3);
    padding: 5px 8px;
    cursor: pointer;
    display: flex;
    align-items: center;
    transition: color var(--dur-fast), background var(--dur-fast);
    -webkit-tap-highlight-color: transparent;
}

.liked-toggle-btn:hover { color: var(--c-text-2); }

.liked-toggle-btn--active {
    color: var(--c-text);
    background: var(--c-surface-2);
}

.liked-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    contain: layout style;
}

/* ── Track list row ─────────────── */
.liked-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 8px;
    border-radius: var(--r-sm);
    cursor: pointer;
    transition: background var(--dur-fast);
    -webkit-tap-highlight-color: transparent;
    /* No `will-change` here: a permanent hint promotes EVERY row to its own
       compositor layer for its whole lifetime. In a virtualized list that means
       constant layer create/destroy churn (Layerize/Commit) while scrolling. A
       plain background hover transition does not need a dedicated layer. */
}

.liked-row:hover { background: rgba(255, 255, 255, 0.05); }

.liked-row--playing .liked-row__title { color: var(--c-accent); }

.liked-row__art-wrap {
    position: relative;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    overflow: hidden;
    background: var(--c-surface-2);
}

.liked-row__art {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--r-sm);
    display: block;
}

.liked-row__art-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.52);
    opacity: 0;
    transition: opacity var(--dur-fast);
    color: #fff;
    border-radius: var(--r-sm);
}

.liked-row--playing .liked-row__art-overlay,
.liked-row__art-wrap:hover .liked-row__art-overlay { opacity: 1; }

.liked-row__meta {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.liked-row__title-row {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.liked-row__title {
    font-size: 0.84rem;
    font-weight: 600;
    color: var(--c-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 1 1 auto;
}

/* Second line: artist + small chips (Go badge, cached/offline icon). Keeping
   the chips here instead of on the title line means the title always gets the
   full meta width and never gets squeezed by badges. */
.liked-row__sub-row {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.liked-row__artist {
    font-size: 0.73rem;
    color: var(--c-text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 0 1 auto;
}

/* Chips never shrink/clip — the artist ellipsizes first. */
.liked-row__sub-row .go-badge,
.liked-row__sub-row .cache-icon,
.liked-row__sub-row > :not(.liked-row__artist) {
    flex-shrink: 0;
}

.liked-row__when {
    font-size: 0.68rem;
    color: var(--c-text-3);
    white-space: nowrap;
    flex-shrink: 0;
}

.liked-row__like-btn {
    background: none;
    border: none;
    color: var(--c-text-3);
    cursor: pointer;
    padding: 6px;
    display: flex;
    align-items: center;
    border-radius: 50%;
    transition: color var(--dur-fast), transform var(--dur-micro);
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
}

.liked-row__like-btn:hover { color: var(--c-danger); transform: scale(1.15); }
.liked-row__like-btn:active { transform: scale(0.9); }
.liked-row__like-btn--active { color: var(--c-danger); }

/* ── Mobile list rows ────────────────────────────────────────────────────────
   On narrow screens the single flex line ran out of room and the title was
   clipped to a few characters. With the Go/cached chips now on the second line
   (see .liked-row__sub-row) the title already gets the full meta width; here we
   just tighten gaps/padding and trim the right-hand column so it stays roomy. */
@media (max-width: 640px) {
    .liked-row {
        gap: 10px;
        padding: 8px 4px;
    }

    .liked-row__meta { flex: 1 1 auto; }

    .liked-row__title { font-size: 0.9rem; }
    .liked-row__artist { font-size: 0.76rem; }

    .liked-row__when { font-size: 0.66rem; }

    /* Trim the action buttons' tap padding so the time chip + heart + menu fit
       without crushing the meta column. */
    .liked-row__like-btn,
    .liked-row .ctx-trigger { padding: 4px; }
}
.liked-row__like-btn--active:hover { color: #ff7070; }

/* ── Card grid like button ──────── */
.liked-card-art-wrap {
    position: relative;
    overflow: hidden;
    line-height: 0;
    border-radius: var(--r-md) var(--r-md) 0 0;
}

.liked-card-art-wrap .track-card__artwork { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; }

.liked-card__like-btn {
    position: absolute;
    bottom: 6px;
    right: 6px;
    z-index: 2;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(6px);
    border: none;
    border-radius: 50%;
    color: rgba(255, 255, 255, 0.6);
    cursor: pointer;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity var(--dur-fast), color var(--dur-fast), transform var(--dur-micro);
    pointer-events: auto;
}

.liked-card-art-wrap:hover .liked-card__like-btn { opacity: 1; }
.liked-card__like-btn--active { color: var(--c-danger); opacity: 1; }
.liked-card__like-btn:hover { color: #ff7070; transform: scale(1.1); }

/* ── Track details badge (opens the cinematic track detail page) ─────────── */
.details-badge {
    position: absolute;
    top: 6px;
    right: 6px;
    z-index: 2;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: none;
    border-radius: 50%;
    color: rgba(255, 255, 255, 0.85);
    cursor: pointer;
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity var(--dur-fast), transform var(--dur-micro);
    pointer-events: auto;
}

.track-art-wrap:hover .details-badge { opacity: 1; }
.details-badge:hover { transform: scale(1.1); }

/* ── Album badge ─────────────────────────────────────────────────────────── */
.album-badge {
    position: absolute;
    bottom: 5px;
    left: 5px;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(4px);
    border-radius: var(--r-xs);
    padding: 2px 5px;
    display: flex;
    align-items: center;
    color: rgba(255, 255, 255, 0.8);
    pointer-events: none;
}

/* ── DRM badge (cover overlay) ─────────────────────────────────────────────── */
/* Marks Go / DRM-protected tracks on the artwork so streamers can spot copyright-
   sensitive titles at a glance. Sits in the top-left of any position:relative cover. */
.drm-badge {
    position: absolute;
    top: 6px;
    left: 6px;
    z-index: 2;
    font-size: 0.5rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    color: #fff;
    background: rgba(214, 40, 40, 0.88);
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: var(--r-xs, 4px);
    padding: 1px 4px;
    line-height: 1.4;
    pointer-events: none;
    user-select: none;
    backdrop-filter: var(--backdrop-sm, blur(14px));
    -webkit-backdrop-filter: var(--backdrop-sm, blur(14px));
}

/* Notice shown on the EQ page when the current track is full-DRM (no analyser). */
.eq-page__drm-note {
    margin: 0 0 14px;
    padding: 10px 12px;
    font-size: var(--fs-xs, 0.78rem);
    line-height: 1.4;
    color: var(--c-text-2, rgba(255,255,255,0.7));
    background: rgba(232, 150, 30, 0.10);
    border: 1px solid rgba(232, 150, 30, 0.30);
    border-radius: var(--r-sm, 8px);
}

/* "Soft" DRM (fetchable AES-128 — decrypts locally, EQ works): amber, not the red full-DRM. */
.drm-badge--soft {
    background: rgba(232, 150, 30, 0.90);
    text-transform: none;
    letter-spacing: 0.02em;
}

/* Smaller variant for the tiny now-playing cover in the player bar. */
.drm-badge--sm { top: 2px; left: 2px; font-size: 0.42rem; padding: 0 3px; }

.pb-cover-wrap { position: relative; flex-shrink: 0; line-height: 0; }

/* ── Profile tabs ────────────────────────────────────────────────────────── */
.profile-tabs {
    display: flex;
    gap: 0;
    border-bottom: 1px solid var(--c-border);
    margin: 0 0 16px;
}

.profile-tab {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--c-text-3);
    font-size: var(--fs-sm, 0.82rem);
    font-weight: var(--fw-medium, 500);
    padding: var(--sp-2, 10px) var(--sp-4, 16px);
    cursor: pointer;
    transition: color var(--dur-fast), border-color var(--dur-fast);
    margin-bottom: -1px;
    font-family: inherit;
    -webkit-tap-highlight-color: transparent;
}

.profile-tab:hover { color: var(--c-text-2); }

.profile-tab--active {
    color: var(--c-text);
    border-bottom-color: var(--c-accent);
}

.profile-liked-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 0 12px;
}

.profile-liked-header .feed-section__title { margin: 0; flex: 0 0 auto; }

.profile-liked-search {
    flex: 1;
    max-width: 280px;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--r-sm);
    color: var(--c-text);
    font-size: 0.78rem;
    padding: 6px 10px;
    outline: none;
    transition: border-color var(--dur-fast);
    font-family: inherit;
}

.profile-liked-search:focus { border-color: var(--c-accent); }
.profile-liked-search::placeholder { color: var(--c-text-3); }

/* ── Safe area ───────────────────────────────────────────────────────────── */
.status-bar-safe-area { display: none; }

/* Mobile (Android + iOS): fixed strip behind the OS status bar so the
   app background shows there instead of transparency. */
@media (max-width: 640px) {
    .status-bar-safe-area {
        display: flex;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        height: env(safe-area-inset-top, 24px);
        background: var(--c-bg, #0d0d12);
        z-index: calc(var(--z-nav, 200) + 10);
        pointer-events: none;
    }
}

/* ── Downloaded page specific styles ────────────────────────────────────────── */
.downloaded-page {
    min-height: 100dvh;
    padding: 0 0 24px;
    display: flex;
    flex-direction: column;
}

.downloaded-summary {
    font-size: 0.75rem;
    color: var(--c-text-3);
    padding: 0 2px 10px;
}

.downloaded-offline-badge {
    font-size: var(--fs-xxs, 0.63rem);
    font-weight: var(--fw-extrabold, 800);
    color: var(--c-accent);
    background: rgba(255, 85, 0, 0.10);
    border: 1px solid rgba(255, 85, 0, 0.26);
    border-radius: var(--r-xs);
    padding: 1px 4px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    flex-shrink: 0;
    line-height: 1.5;
    user-select: none;
}

.downloaded-remove-btn {
    background: none;
    border: none;
    color: var(--c-text-3);
    cursor: pointer;
    padding: 6px;
    display: flex;
    align-items: center;
    border-radius: 50%;
    transition: color var(--dur-fast), transform var(--dur-micro);
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
}

.downloaded-remove-btn:hover { color: var(--c-danger); transform: scale(1.15); }
.downloaded-remove-btn:active { transform: scale(0.9); }

/* ── Mobile touch targets ────────────────────────────────────────────────── */
@media (max-width: 640px) {
    .ctx-trigger         { padding: var(--sp-3, 12px) var(--sp-2, 10px); }
    .track-like-btn      { padding: var(--sp-2, 10px); }
    .liked-row__like-btn { padding: var(--sp-2, 10px); }
    .downloaded-remove-btn { padding: var(--sp-2, 10px); }
    .cache-icon          { padding: var(--sp-2, 8px); }
    .liked-row           { padding: var(--sp-2, 10px) 6px; }
}
