/* ============================================================
   PI-Website · redesign v1
   White-dominant, professional, minimal.
   Font: Inter everywhere (display + body)
   ============================================================ */

:root {
    --font-display: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

    --color-paper: #ffffff;
    --color-surface: #ffffff;
    --color-surface-2: #f7f8f9;
    --color-ink: #101319;
    --color-muted: #667085;
    --color-faint: #98a2b3;
    --color-line: #e6e8ec;
    --color-line-strong: #d4d8de;

    /* single accent: refined signal blue (nod to the old #06c) */
    --color-accent: #1f5eff;
    --color-accent-soft: #eef3ff;
    --pos: #12805c;
    --neg: #c93b3b;

    --radius-sm: 6px;
    --radius-md: 10px;
    --radius-lg: 14px;

    --shadow-sm: 0 1px 2px rgba(16, 19, 25, 0.05);
    --shadow-md: 0 4px 16px rgba(16, 19, 25, 0.07);

    --space-xs: 4px;
    --space-sm: 8px;
    --space-md: 16px;
    --space-lg: 24px;
    --space-xl: 40px;

    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    overflow-x: clip;
}

body {
    font-family: var(--font-body);
    background: var(--color-paper);
    color: var(--color-ink);
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

h1, h2, h3 {
    font-family: var(--font-display);
    font-style: normal;
    letter-spacing: -0.01em;
}

/* ------------------------------------------------------------
   Top navigation
   ------------------------------------------------------------ */
.top-links {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-lg);
    background: rgba(255, 255, 255, 0.92);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--color-line);
    font-family: var(--font-body);
    flex-wrap: wrap;
}

.top-links .brand {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 15.5px;
    color: var(--color-ink);
    text-decoration: none;
    margin-right: var(--space-md);
    letter-spacing: -0.02em;
}

.top-links .nav-group {
    display: flex;
    align-items: center;
    gap: 2px;
    flex-wrap: wrap;
    margin: 0 auto;
}

.top-links a {
    color: var(--color-muted);
    text-decoration: none;
    font-size: 14.5px;
    font-weight: 500;
    padding: 6px 12px;
    border-radius: 999px;
    transition: color 0.15s var(--ease-out), background-color 0.15s var(--ease-out);
    white-space: nowrap;
}

.top-links a:hover {
    color: var(--color-ink);
    background: var(--color-surface-2);
}

.top-links a.active {
    color: var(--color-ink);
    background: var(--color-accent-soft);
}

.top-links .logout-link {
    position: static;
    order: 99;
    color: var(--color-faint);
}

/* guest nav (beats/loops + login only): balance brand on the left with the
   login link on the right so the links sit truly centered */
.top-links.guest-nav {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--space-xs);
    flex-wrap: nowrap;
}
.top-links.guest-nav .brand { grid-column: 1; justify-self: start; margin-right: 0; }
.top-links.guest-nav .nav-group { grid-column: 2; margin: 0; justify-content: center; }
.top-links.guest-nav .nav-group a { text-align: center; }
.top-links.guest-nav .logout-link { grid-column: 3; order: 0; justify-self: end; }

.top-links .logout-link:hover {
    color: var(--neg);
    background: transparent;
}

/* ------------------------------------------------------------
   Period switcher (monthly / biweekly / weekly / daily)
   ------------------------------------------------------------ */
.period-links {
    display: flex;
    justify-content: center;
    gap: var(--space-xs);
    margin: var(--space-lg) auto var(--space-md);
    padding: 3px;
    width: fit-content;
    max-width: calc(100vw - 32px);
    background: var(--color-surface-2);
    border: 1px solid var(--color-line);
    border-radius: 999px;
}

.period-links a {
    font-family: var(--font-body);
    font-size: 13.5px;
    font-weight: 500;
    color: var(--color-muted);
    text-decoration: none;
    padding: 6px 18px;
    border-radius: 999px;
    transition: color 0.15s var(--ease-out), background-color 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out);
    white-space: nowrap;
}

.period-links a:hover {
    color: var(--color-ink);
}

.period-links a.current {
    background: #e4e7eb;
    color: var(--color-ink);
    box-shadow: var(--shadow-sm);
}

/* ------------------------------------------------------------
   Chart image frames (stats pages)
   ------------------------------------------------------------ */
.image-container {
    width: 100%;
    overflow-x: auto;
    text-align: center;
    padding: 0 var(--space-md) var(--space-xl);
}

.image-container img {
    width: min(1240px, 96vw);
    height: auto;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    background: var(--color-surface);
}

/* ------------------------------------------------------------
   Data columns / cards / tables
   ------------------------------------------------------------ */
.columns {
    display: block;
    padding: 0 var(--space-md) var(--space-xl);
    max-width: 1240px;
    margin: 0 auto;
    width: 100%;
}

.column {
    width: 100%;
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-lg);
    padding: var(--space-lg);
    margin: 0 auto var(--space-lg);
    box-shadow: var(--shadow-sm);
    overflow-x: auto;
}

.column h2 {
    font-size: 17px;
    font-weight: 600;
    margin: 0 0 var(--space-md);
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--color-line);
    text-align: left;
    color: var(--color-ink);
}

.list {
    font-size: 14px;
    line-height: 1.6;
    white-space: pre-line;
    color: var(--color-ink);
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13.5px;
    font-variant-numeric: tabular-nums;
}

.table th {
    font-size: 11.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-muted);
    background: transparent;
    padding: var(--space-sm) var(--space-sm);
    text-align: center;
    border-bottom: 1px solid var(--color-line-strong);
    white-space: nowrap;
}

.table td {
    padding: 8px var(--space-sm);
    text-align: center;
    border-bottom: 1px solid var(--color-line);
    color: var(--color-ink);
    white-space: nowrap;
}



.table tbody tr {
    transition: background-color 0.12s var(--ease-out);
}

.table tbody tr:hover { background: var(--color-surface-2); }

#all-artists-table th { cursor: pointer; user-select: none; }
#all-artists-table th:hover { color: var(--color-ink); }
#all-artists-table th.sort-asc::after { content: ' \2191'; color: var(--color-accent); }
#all-artists-table th.sort-desc::after { content: ' \2193'; color: var(--color-accent); }

@media (max-width: 720px) {
    .top-links { padding: var(--space-sm) var(--space-md); }
    .top-links .nav-group { margin: 0; }
    .column { padding: var(--space-md); }
    .table { font-size: 12.5px; }
}

/* ------------------------------------------------------------
   Forms, buttons, inputs
   ------------------------------------------------------------ */
input[type="text"], input[type="password"], input[type="email"], textarea {
    font-family: var(--font-body);
    font-size: 15px;
    color: var(--color-ink);
    background: var(--color-surface);
    border: 1px solid var(--color-line-strong);
    border-radius: var(--radius-md);
    padding: 10px 14px;
    outline: none;
    transition: border-color 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out);
}

input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, textarea:focus {
    border-color: var(--color-accent);
    box-shadow: 0 0 0 3px var(--color-accent-soft);
}

input[type="text"]::placeholder, input[type="password"]::placeholder, input[type="email"]::placeholder, textarea::placeholder {
    color: var(--color-faint);
}

button, .btn {
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: 500;
    color: var(--color-surface);
    background: var(--color-ink);
    border: none;
    border-radius: var(--radius-md);
    padding: 10px 20px;
    cursor: pointer;
    transition: opacity 0.15s var(--ease-out), transform 0.1s var(--ease-out);
}

button:hover, .btn:hover { opacity: 0.85; }
button:active, .btn:active { transform: translateY(1px); }
button:focus-visible, .btn:focus-visible,
a:focus-visible, input:focus-visible, textarea:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

button:disabled { opacity: 0.45; cursor: default; }

/* ------------------------------------------------------------
   Page shells (beats, loops, graphs, comp, request, login)
   ------------------------------------------------------------ */
.page-title {
    font-size: 26px;
    font-weight: 600;
    margin: var(--space-xl) 0 var(--space-xs);
    text-align: center;
}

.page-subtitle {
    text-align: center;
    color: var(--color-muted);
    font-size: 14px;
    margin: 0 0 var(--space-lg);
}

.container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 0 var(--space-md) var(--space-xl);
    width: 100%;
}

.note {
    text-align: center;
    color: var(--color-faint);
    font-size: 12.5px;
    margin-top: var(--space-sm);
}

.error {
    text-align: center;
    color: var(--neg);
    margin-top: var(--space-sm);
    font-size: 14px;
}

.search {
    display: flex;
    gap: var(--space-sm);
    justify-content: center;
    align-items: center;
    margin-bottom: var(--space-lg);
    flex-wrap: wrap;
}

.search input { width: min(360px, 80vw); }

#chart {
    width: 100%;
    height: 70vh;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.releases-container { display: flex; gap: var(--space-lg); margin-top: var(--space-lg); flex-wrap: wrap; }
.releases-box { flex: 1 1 380px; }

.section-title {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 15px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-muted);
    margin: var(--space-xl) 0 var(--space-sm);
    text-align: center;
}

.track {
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    padding: var(--space-sm) var(--space-md);
    margin: var(--space-sm) 0;
    transition: border-color 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out);
}

.track:hover {
    border-color: var(--color-line-strong);
    box-shadow: var(--shadow-sm);
}

.track-name-row { display: flex; align-items: center; gap: var(--space-sm); margin: 0 0 6px; }
.track-name { font-size: 13.5px; font-weight: 500; color: var(--color-ink); }

.dl-btn {
    font-size: 14px;
    color: var(--color-faint);
    text-decoration: none;
    line-height: 1;
    cursor: pointer;
    flex-shrink: 0;
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    transition: color 0.15s var(--ease-out), background-color 0.15s var(--ease-out);
}

.dl-btn:hover { color: var(--color-accent); background: var(--color-accent-soft); }

audio {
    width: 100%;
    height: 36px;
    margin: 4px 0;
    border-radius: var(--radius-sm);
}

/* clickable artist names in stats tables */
.artist-link {
    color: var(--color-ink);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color 0.12s var(--ease-out), border-color 0.12s var(--ease-out);
}

.artist-link:hover {
    color: var(--color-accent);
    border-bottom-color: var(--color-accent);
}

.artist-link:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}


/* beats + loops: cards 10% wider (500 -> 550px), centered, spaced */
.track {
    max-width: 550px;
    margin: 10px auto;
}
/* ------------------------------------------------------------
.track {
    max-width: 550px;
    margin: 10px auto;
}

/* ------------------------------------------------------------
   Melodies page: pink dither backdrop, viewer only tracklist,
   fixed bottom media bar. Viewer only by design: no editing UI,
   no shuffle, no search, no save to library.
   ------------------------------------------------------------ */

/* auto-hiding scrollbars ---------------------------------------------
   the native bar of the PAGE (html.ml-page, only this page) and of the
   tracklist are hidden outright, so nothing is ever reserved and nothing
   shifts sideways when a bar would appear. The overlay thumb below is
   painted by JS: it fades in while a region is actually scrolling and
   fades back out 3s after the last scroll event. */
html.ml-page { scrollbar-width: none; }

html.ml-page::-webkit-scrollbar { width: 0; height: 0; display: none; }

.ml-sb {
    position: absolute;
    top: 0;
    right: 2px;
    width: 6px;
    height: 40px; /* JS size */
    border-radius: 999px;
    background: var(--color-line-strong);
    opacity: 0;
    pointer-events: none;
    will-change: transform, opacity;
    transition: opacity 0.45s var(--ease-out);
    z-index: 2;
}

.ml-sb.is-on { opacity: 1; }

/* the page bar floats against the viewport, not against a container */
.ml-sb-page { position: fixed; }

#dither {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    pointer-events: none;
    opacity: 0.24; /* mirrors the locked dither cfg opacity */
}

/* the media bar floats over the content, so the room it needs is reserved from
   the start: body padding and the shell min-height below both assume the bar is
   up, whether or not it actually is. Revealing the bar therefore never moves
   the cover, the title or the tracklist -- only the bar itself animates. */
body.has-player { padding-bottom: 116px; }

/* the shell owns the vertical centering: it is at least a viewport tall minus
   the room the floating bar needs, and the two-column grid is centered inside
   it, so the cover and the tracklist card travel as one group.
   100vh first as a fallback for browsers without dvh. */
.ml-shell {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 987px; /* was 940px: content box 5% wider */
    margin: 0 auto;
    padding: var(--space-lg) var(--space-md);
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: calc(100vh - 136px);
    min-height: calc(100dvh - 136px);
}

/* two columns on desktop: the cover art stands alone on the left, the title,
   the track count and the tracklist share the right column.
   phones under 640px stack, with a smaller cover. */
.ml-layout {
    display: grid;
    grid-template-columns: clamp(300px, 32vw, 400px) minmax(0, 1fr);
    gap: clamp(20px, 2.6vw, 36px);
    align-items: start;
    width: 100%;
    margin: 0;
}

.ml-cover-col { min-width: 0; }

.ml-cover {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-lg);
    background: var(--color-surface-2);
    box-shadow: var(--shadow-md);
}

/* the right column is ONE squircle card: title, track count and tracklist all
   live inside it. Tracks are no longer boxes of their own. */
.ml-main {
    --ml-card-pad: clamp(18px, 2.2vw, 24px);
    min-width: 0;
    padding: var(--ml-card-pad);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: 20px;
    box-shadow: 0 1px 2px rgba(16, 19, 25, 0.04), 0 18px 40px -24px rgba(16, 19, 25, 0.16);
    overflow: hidden;
}

.ml-title {
    font-size: 34px;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.1;
    margin: 0;
}

.ml-count {
    color: var(--color-muted);
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
    margin: 6px 0 0;
}

/* tracklist ----------------------------------------------------------
   the list bleeds out to the card edges so a row wash and the 1px dividers
   span the full inner width, while the row padding keeps the content lined
   up with the title above it. Rows touch: no gaps, no boxes.
   The wrapper carries the bleed and is the anchor for the overlay scroll
   thumb, so the list itself owns nothing but the scrolling. */
.ml-list-wrap {
    position: relative;
    margin: var(--space-md) calc(-1 * var(--ml-card-pad)) 0;
}

.ml-list {
    list-style: none;
    margin: 0;
    padding: 0;
    /* many tracks scroll inside the card instead of stretching the page: about
       nine rows tall, then the list scrolls on its own. */
    max-height: 500px;
    max-height: min(58dvh, 500px);
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    /* the bar is ours (the .ml-sb overlay thumb): hiding the native one
       reserves nothing, so a bar appearing never shifts the rows */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.ml-list::-webkit-scrollbar { width: 0; height: 0; display: none; }

.ml-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 8px var(--ml-card-pad);
    margin: 0;
    background: transparent;
    border: 0;
    border-radius: 0;
    transition: background-color 0.15s var(--ease-out);
}

.ml-row + .ml-row { border-top: 1px solid var(--color-line); }

.ml-row:hover { background: var(--color-surface-2); }

.ml-row.is-current { background: var(--color-accent-soft); }

.ml-row-btn {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 2px 0;
    margin: 0;
    font: inherit;
    text-align: left;
    color: inherit;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.ml-row-btn:hover {
    opacity: 1;
    background: none;
}

.ml-num {
    flex: 0 0 auto;
    width: 20px;
    text-align: right;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--color-muted);
}

.ml-ico {
    flex: 0 0 auto;
    display: block;
    color: var(--color-faint);
    transition: color 0.15s var(--ease-out);
}

.ml-row:hover .ml-ico,
.ml-row.is-current .ml-ico { color: var(--color-accent); }

.ml-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.ml-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--color-ink);
    overflow-wrap: anywhere;
}

.ml-note {
    font-size: 12.5px;
    color: var(--color-muted);
    overflow-wrap: anywhere;
}

/* icon-only download: 30px squircle, hairline border, ink glyph. Hidden until
   the row is hovered (or holds focus, which covers keyboard and touch taps),
   but it always keeps its 30px slot, so revealing it never shifts the row.
   The ::after carries the touch target, so the button can stay small. */
.ml-dl {
    position: relative;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    color: var(--color-ink);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: 9px;
    text-decoration: none;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s var(--ease-out), color 0.15s var(--ease-out), border-color 0.15s var(--ease-out), background-color 0.15s var(--ease-out);
}

/* invisible hit slop: 30px of ink, ~45px of finger. The left edge stops at
   the row button's right edge (the 8px flex gap), so it never steals a tap
   meant for play. */
.ml-dl::after {
    content: "";
    position: absolute;
    inset: -8px -5px -8px -8px;
}

.ml-row:hover .ml-dl,
.ml-row:focus-within .ml-dl {
    opacity: 1;
    pointer-events: auto;
}

.ml-dl svg { display: block; }

.ml-dl:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
    background: var(--color-accent-soft);
}

.ml-dl:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

.ml-empty {
    margin: var(--space-lg) 0 0;
    padding: var(--space-lg);
    border: 1px dashed var(--color-line-strong);
    border-radius: var(--radius-md);
    color: var(--color-muted);
    font-size: 14px;
    text-align: center;
}

/* floating liquid-glass media bar ------------------------------------
   hidden until the first track starts: opacity + translateY, revealed
   once and left visible afterwards. */
.ml-player {
    position: fixed;
    left: 50%;
    bottom: var(--space-md);
    z-index: 3;
    width: min(987px, calc(100% - 32px)); /* tracks the 5% wider content box */
    padding: 8px 14px;
    background: rgba(255, 255, 255, 0.55);
    backdrop-filter: blur(20px) saturate(1.6);
    -webkit-backdrop-filter: blur(20px) saturate(1.6);
    border: 1px solid rgba(255, 255, 255, 0.6);
    border-radius: var(--radius-lg);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 16px 48px -12px rgba(26, 28, 34, 0.08);
    opacity: 0;
    transform: translateX(-50%) translateY(14px);
    pointer-events: none;
    transition: opacity 0.28s var(--ease-out), transform 0.28s var(--ease-out);
}

.ml-player.is-up {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: auto;
}

.ml-audio { display: none; }

.ml-player-inner {
    display: grid;
    /* volume | transport (prev, play/pause, next) | title row + waveform */
    grid-template-columns: auto auto minmax(0, 1fr);
    align-items: center;
    gap: var(--space-md);
}

.ml-controls {
    display: flex;
    align-items: center;
    gap: 6px;
}

.ml-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    color: var(--color-ink);
    background: var(--color-surface);
    border: 1px solid var(--color-line);
    border-radius: 50%;
    cursor: pointer;
    transition: color 0.15s var(--ease-out), border-color 0.15s var(--ease-out), background-color 0.15s var(--ease-out);
}

/* the circles stay 30/34px, the touch targets underneath grow to ~40-46px.
   Horizontal slop is 3px so neighbouring slops meet in the middle of the 6px
   control gap: every pixel in between is live and no button steals a tap
   meant for its neighbour. */
.ml-btn::after {
    content: "";
    position: absolute;
    inset: -6px -3px;
    border-radius: 50%;
}

.ml-btn:hover {
    opacity: 1;
    color: var(--color-accent);
    border-color: var(--color-accent);
    background: var(--color-accent-soft);
}

.ml-btn svg { display: block; }

.ml-btn-play {
    width: 34px;
    height: 34px;
    color: var(--color-surface);
    background: var(--color-ink);
    border-color: var(--color-ink);
}

.ml-btn-play:hover {
    color: var(--color-surface);
    background: var(--color-accent);
    border-color: var(--color-accent);
}

.ml-now {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* the elapsed/total readout first, then the track title filling the rest of
   the line */
.ml-now-row {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
    min-width: 0;
}

.ml-now-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* single elapsed/total readout */
.ml-time {
    flex: 0 0 auto;
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
    color: var(--color-muted);
    white-space: nowrap;
}

/* waveform: click or drag to seek. The wrapper is the flex item so the
   skeleton can sit exactly over the canvas. It lives in a column flex
   container, so its height is fixed rather than grown.
   --ml-wave-prog is the play ratio: the JS writes it here (inline, so it
   beats this default) and the skeleton below masks itself with it, which is
   what keeps the loading bars behind the playhead. */
.ml-wave-wrap {
    position: relative;
    flex: 0 0 auto;
    min-width: 0;
    height: 24px;
    --ml-wave-prog: 0%;
    border-radius: var(--radius-sm);
}

.ml-wave {
    display: block;
    width: 100%;
    height: 100%;
    cursor: pointer;
    touch-action: none;
    border-radius: var(--radius-sm);
}

.ml-wave:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

/* loading skeleton: placeholder bars while the peaks decode, swapped for the
   real waveform as soon as they land. Heights come from the clip-path steps,
   gaps from the stripe background, and the two stay aligned because both are
   expressed as percentages of the element width.
   The mask keeps the skeleton BEHIND the playhead: JS writes the play ratio
   into --ml-wave-prog every frame, so only the part the playhead has already
   passed is ever painted -- the front stays empty (or the upcoming color from
   the canvas underneath) until the real peaks arrive. */
.ml-wave-sk {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    -webkit-mask-image: linear-gradient(90deg, #000 0 max(0px, calc(var(--ml-wave-prog, 0%) - 3px)), transparent max(0px, calc(var(--ml-wave-prog, 0%) - 3px)) 100%);
    mask-image: linear-gradient(90deg, #000 0 max(0px, calc(var(--ml-wave-prog, 0%) - 3px)), transparent max(0px, calc(var(--ml-wave-prog, 0%) - 3px)) 100%);
    transition: opacity 0.18s var(--ease-out);
    background-image: repeating-linear-gradient(90deg,
        transparent 0 0.9792%,
        var(--color-line) 0.9792% 3.1875%,
        transparent 3.1875% 4.1667%);
    -webkit-clip-path: polygon((0.9792% 29%), (3.1875% 29%), (5.1459% 17%), (7.3542% 17%), (9.3125% 33%), (11.5208% 33%), (13.4792% 10%), (15.6875% 10%), (17.6459% 24%), (19.8542% 24%), (21.8125% 4%), (24.0208% 4%), (25.9792% 27%), (28.1875% 27%), (30.1459% 19%), (32.3542% 19%), (34.3125% 35%), (36.5208% 35%), (38.4792% 14%), (40.6875% 14%), (42.6459% 25%), (44.8542% 25%), (46.8125% 7%), (49.0208% 7%), (50.9792% 31%), (53.1875% 31%), (55.1459% 21%), (57.3542% 21%), (59.3125% 28%), (61.5208% 28%), (63.4792% 15%), (65.6875% 15%), (67.6459% 32%), (69.8542% 32%), (71.8125% 18%), (74.0208% 18%), (75.9792% 26%), (78.1875% 26%), (80.1459% 11%), (82.3542% 11%), (84.3125% 34%), (86.5208% 34%), (88.4792% 20%), (90.6875% 20%), (92.6459% 30%), (94.8542% 30%), (96.8125% 22%), (99.0208% 22%), (96.8125% 78%), (99.0208% 78%), (92.6459% 70%), (94.8542% 70%), (88.4792% 80%), (90.6875% 80%), (84.3125% 66%), (86.5208% 66%), (80.1459% 89%), (82.3542% 89%), (75.9792% 74%), (78.1875% 74%), (71.8125% 82%), (74.0208% 82%), (67.6459% 68%), (69.8542% 68%), (63.4792% 85%), (65.6875% 85%), (59.3125% 72%), (61.5208% 72%), (55.1459% 79%), (57.3542% 79%), (50.9792% 69%), (53.1875% 69%), (46.8125% 93%), (49.0208% 93%), (42.6459% 75%), (44.8542% 75%), (38.4792% 86%), (40.6875% 86%), (34.3125% 65%), (36.5208% 65%), (30.1459% 81%), (32.3542% 81%), (25.9792% 73%), (28.1875% 73%), (21.8125% 96%), (24.0208% 96%), (17.6459% 76%), (19.8542% 76%), (13.4792% 90%), (15.6875% 90%), (9.3125% 67%), (11.5208% 67%), (5.1459% 83%), (7.3542% 83%), (0.9792% 71%), (3.1875% 71%));
    clip-path: polygon((0.9792% 29%), (3.1875% 29%), (5.1459% 17%), (7.3542% 17%), (9.3125% 33%), (11.5208% 33%), (13.4792% 10%), (15.6875% 10%), (17.6459% 24%), (19.8542% 24%), (21.8125% 4%), (24.0208% 4%), (25.9792% 27%), (28.1875% 27%), (30.1459% 19%), (32.3542% 19%), (34.3125% 35%), (36.5208% 35%), (38.4792% 14%), (40.6875% 14%), (42.6459% 25%), (44.8542% 25%), (46.8125% 7%), (49.0208% 7%), (50.9792% 31%), (53.1875% 31%), (55.1459% 21%), (57.3542% 21%), (59.3125% 28%), (61.5208% 28%), (63.4792% 15%), (65.6875% 15%), (67.6459% 32%), (69.8542% 32%), (71.8125% 18%), (74.0208% 18%), (75.9792% 26%), (78.1875% 26%), (80.1459% 11%), (82.3542% 11%), (84.3125% 34%), (86.5208% 34%), (88.4792% 20%), (90.6875% 20%), (92.6459% 30%), (94.8542% 30%), (96.8125% 22%), (99.0208% 22%), (96.8125% 78%), (99.0208% 78%), (92.6459% 70%), (94.8542% 70%), (88.4792% 80%), (90.6875% 80%), (84.3125% 66%), (86.5208% 66%), (80.1459% 89%), (82.3542% 89%), (75.9792% 74%), (78.1875% 74%), (71.8125% 82%), (74.0208% 82%), (67.6459% 68%), (69.8542% 68%), (63.4792% 85%), (65.6875% 85%), (59.3125% 72%), (61.5208% 72%), (55.1459% 79%), (57.3542% 79%), (50.9792% 69%), (53.1875% 69%), (46.8125% 93%), (49.0208% 93%), (42.6459% 75%), (44.8542% 75%), (38.4792% 86%), (40.6875% 86%), (34.3125% 65%), (36.5208% 65%), (30.1459% 81%), (32.3542% 81%), (25.9792% 73%), (28.1875% 73%), (21.8125% 96%), (24.0208% 96%), (17.6459% 76%), (19.8542% 76%), (13.4792% 90%), (15.6875% 90%), (9.3125% 67%), (11.5208% 67%), (5.1459% 83%), (7.3542% 83%), (0.9792% 71%), (3.1875% 71%));
}

.ml-wave-wrap.is-loading .ml-wave-sk {
    opacity: 1;
    animation: ml-sk-pulse 1.8s ease-in-out infinite;
}

/* the sweep is masked to the same stripes, so it never paints the gaps */
.ml-wave-sk::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 50%, rgba(255, 255, 255, 0) 100%);
    background-size: 45% 100%;
    background-repeat: no-repeat;
    -webkit-mask-image: repeating-linear-gradient(90deg, transparent 0 0.9792%, #000 0.9792% 3.1875%, transparent 3.1875% 4.1667%);
    mask-image: repeating-linear-gradient(90deg, transparent 0 0.9792%, #000 0.9792% 3.1875%, transparent 3.1875% 4.1667%);
    animation: ml-sk-sweep 1.5s linear infinite;
}

@keyframes ml-sk-pulse {
    0%, 100% { opacity: 0.55; }
    50% { opacity: 1; }
}

@keyframes ml-sk-sweep {
    from { background-position: -60% 0; }
    to { background-position: 160% 0; }
}

.ml-vol-wrap {
    position: relative;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

/* the speaker is the toggle: it opens the popup that holds the slider */
.ml-vol-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--color-faint);
    cursor: pointer;
    transition: color 0.15s var(--ease-out);
}

.ml-vol-btn:hover,
.ml-vol-btn[aria-expanded="true"] {
    color: var(--color-ink);
}

.ml-vol-btn:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

/* same trick as the transport buttons: 30px glyph, ~42px finger target */
.ml-vol-btn::after {
    content: "";
    position: absolute;
    inset: -6px;
    border-radius: 50%;
}

/* the icon follows the button colour, so hover and open read on the glyph */
.ml-vol-icon {
    display: block;
    flex: 0 0 auto;
    color: inherit;
}

/* tiny level read-out under the glyph, filled by the same --ml-vol-pct var
   the slider track uses (accent up to the level, line past it) */
.ml-vol-lvl {
    position: absolute;
    left: 50%;
    bottom: 3px;
    width: 12px;
    height: 2px;
    border-radius: 999px;
    transform: translateX(-50%);
    background: linear-gradient(to right,
        var(--color-accent) 0 var(--ml-vol-pct, 50%),
        var(--color-line) var(--ml-vol-pct, 50%) 100%);
}

/* the popup is glass like the bar and sits absolutely above the icon, which
   is the bar's left edge now, so opening it reserves no space and never
   shifts the bar. --ml-vol-len is the slider length and the panel is sized
   off it. */
.ml-vol-pop {
    --ml-vol-len: 90px;
    position: absolute;
    left: 0;
    bottom: calc(100% + 10px);
    z-index: 4;
    width: 34px;
    height: calc(var(--ml-vol-len) + 16px);
    box-sizing: border-box;
    background: rgba(255, 255, 255, 0.55);
    backdrop-filter: blur(20px) saturate(1.6);
    -webkit-backdrop-filter: blur(20px) saturate(1.6);
    border: 1px solid rgba(255, 255, 255, 0.6);
    border-radius: var(--radius-md);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 16px 48px -12px rgba(26, 28, 34, 0.18);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(6px);
    transition: opacity 0.18s var(--ease-out), transform 0.18s var(--ease-out), visibility 0s linear 0.18s;
}

.ml-vol-pop.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0);
    transition: opacity 0.18s var(--ease-out), transform 0.18s var(--ease-out), visibility 0s;
}

/* volume: thin track, round thumb, accent fill (webkit + moz). The range
   keeps its horizontal geometry and is rotated -90deg inside the popup, so
   the fill runs bottom to top like a fader without depending on how each
   engine lays out a vertical range. */
.ml-vol {
    position: absolute;
    top: 50%;
    left: 50%;
    -webkit-appearance: none;
    appearance: none;
    display: block;
    width: var(--ml-vol-len, 90px);
    height: 18px;
    margin: 0;
    padding: 0;
    background: transparent;
    cursor: pointer;
    transform: translate(-50%, -50%) rotate(-90deg);
}

.ml-vol::-webkit-slider-runnable-track {
    height: 4px;
    border-radius: 999px;
    background: linear-gradient(to right,
        var(--color-accent) 0 var(--ml-vol-pct, 50%),
        var(--color-line) var(--ml-vol-pct, 50%) 100%);
}

.ml-vol::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 12px;
    height: 12px;
    margin-top: -4px;
    border: 2px solid var(--color-surface);
    border-radius: 50%;
    background: var(--color-accent);
    box-shadow: 0 1px 2px rgba(16, 19, 25, 0.25);
}

.ml-vol::-moz-range-track {
    height: 4px;
    border-radius: 999px;
    background: var(--color-line);
}

.ml-vol::-moz-range-progress {
    height: 4px;
    border-radius: 999px;
    background: var(--color-accent);
}

.ml-vol::-moz-range-thumb {
    width: 12px;
    height: 12px;
    border: 2px solid var(--color-surface);
    border-radius: 50%;
    background: var(--color-accent);
    box-shadow: 0 1px 2px rgba(16, 19, 25, 0.25);
}

.ml-vol:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 4px;
    border-radius: 999px;
}

@media (prefers-reduced-motion: reduce) {
    .ml-player { transition: none; }

    /* the overlay scroll thumb blinks in place instead of fading */
    .ml-sb { transition: none; }

    /* the volume popup appears in place instead of sliding up */
    .ml-vol-pop,
    .ml-vol-pop.is-open { transition: none; transform: none; }

    /* the download squircle appears instantly instead of fading in */
    .ml-dl { transition: none; }

    /* static gray bars instead of a shimmer */
    .ml-wave-sk { transition: none; }
    .ml-wave-wrap.is-loading .ml-wave-sk { animation: none; opacity: 1; }
    .ml-wave-sk::after { display: none; animation: none; }
}

/* the bar stacks at phone widths: transport + volume on top, title,
   waveform and time across the full width underneath */
@media (max-width: 640px) {
    .ml-layout {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-lg);
    }

    .ml-cover {
        width: 180px;
        max-width: 60vw;
        height: auto;
    }

    .ml-title { font-size: 27px; }

    /* touch: there is no hover to reveal a row's download, so it is always
       there on phones, and the transport / speaker glyphs get a size that
       reads as a real target on glass */
    .ml-dl {
        opacity: 1;
        pointer-events: auto;
    }

    /* the floating bar overlaps the BOTTOM OF THE TRACKLIST, so the list
       reserves the slice of itself that can hide behind it: the bar measures
       ~111px at these widths, it sits 16px off the bottom and the last row
       wants 12px of air above it -> a 139px baseline. env() adds the home
       indicator on phones that have one.
       Padding inside the scroller is what lifts the last rows: swiping the
       list to its end now raises them clear of the bar instead of parking
       them under it (the list used to be reachable only by scrolling the
       whole page, which the list's overscroll containment swallows).
       The player JS rewrites both values from the live geometry (page
       scrolled, shorter viewport); this rule is what applies before it runs
       and if it never does. */
    .ml-list {
        --ml-safe: env(safe-area-inset-bottom, 0px);
        padding-bottom: calc(139px + var(--ml-safe));
        scroll-padding-bottom: calc(139px + var(--ml-safe));
    }

    /* the glass volume popup all but vanishes over the bright dither art on a
       phone, so at phone widths it goes solid: white fill, hairline border and
       a soft drop shadow, drawn without the backdrop blur. Above 640px the
       glass version above still applies, untouched. */
    .ml-vol-pop {
        background: var(--color-surface);
        border-color: var(--color-line);
        box-shadow: 0 1px 2px rgba(16, 19, 25, 0.06), 0 18px 36px -14px rgba(16, 19, 25, 0.3);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }

    .ml-btn {
        width: 32px;
        height: 32px;
    }

    .ml-btn-play {
        width: 36px;
        height: 36px;
    }

    .ml-vol-btn {
        width: 34px;
        height: 34px;
    }

    /* the stacked bar is taller, so the reserved clearance is taller too --
       and it is reserved unconditionally, like the desktop rule */
    body.has-player { padding-bottom: 150px; }

    .ml-shell {
        min-height: calc(100vh - 190px);
        min-height: calc(100dvh - 190px);
    }

    .ml-player-inner {
        grid-template-columns: auto minmax(0, 1fr);
        gap: var(--space-sm) var(--space-md);
    }

    .ml-controls {
        grid-column: 1;
        grid-row: 1;
    }

    .ml-vol-wrap {
        grid-column: 2;
        grid-row: 1;
        justify-self: end;
    }

    .ml-now {
        grid-column: 1 / -1;
        grid-row: 2;
    }
}

@media (max-width: 360px) {
    .ml-shell {
        padding-left: var(--space-sm);
        padding-right: var(--space-sm);
    }

    .ml-cover {
        width: 150px;
        max-width: 56vw;
    }

    .ml-player {
        width: calc(100% - 16px);
        bottom: var(--space-sm);
        padding: 8px 10px;
    }

    .ml-btn {
        width: 30px;
        height: 30px;
    }

    .ml-btn-play {
        width: 34px;
        height: 34px;
    }

    /* a shorter fader in the popup on the narrowest phones */
    .ml-vol-pop { --ml-vol-len: 72px; }

    .ml-wave-wrap { height: 20px; }

    .ml-dl {
        width: 28px;
        height: 28px;
        border-radius: 8px;
    }
}
