/* =================================================================
   base.css — Global foundation styles
   Universal reset, :root custom properties, body, grid layout,
   scoped-search hook, marquee keyframes, user-select overrides,
   top-level scrollbar + .view-section visibility.
   ================================================================= */

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Segoe UI', system-ui, sans-serif;
}

/* Native <select> popup options — color-scheme:dark on :root handles this
   on most Chromium builds, but explicit colors on <option> are a cheap
   belt-and-suspenders fallback. Themes override the variables; the
   popup follows. Note: only `background-color` and `color` are reliably
   honored on <option> — fancier styling (radius, padding) is ignored
   by the OS-rendered listbox. */
option {
    background-color: var(--surface-elevated);
    color: var(--text-primary);
}

/* =====================================================================
   Theme tokens — semantic CSS custom properties driving the entire app.
   Each built-in theme (themes/<slug>.css) overrides this :root block
   under a [data-theme="<slug>"] selector. User-authored themes inject
   an inline <style id="user-theme-vars"> with the same shape.

   Naming is semantic, not literal — surfaces talk about role
   (`--surface-card`, `--surface-elevated`) not appearance
   (`--gray-2`). Theme authors override roles; layout never moves.

   Coverage: surfaces (chrome backgrounds), text (foreground roles),
   borders, accents (incl. RGB triplets so opacity-tinted overlays
   like `rgba(var(--accent-primary-rgb), 0.16)` are themeable), state
   (success/warning/danger/info), shadows (full box-shadow strings so
   themes can dial softness), overlay tints (hover/active/selected),
   and a couple of composed values (gradients).

   Old aliases (`--bg-dark`, `--bg-card`, etc.) sit alongside the new
   semantic names during the file-by-file color sweep; once every
   reference flips, the aliases get removed.
   ===================================================================== */
:root {
    /* Tells Chromium the page is dark so native form widgets (select
       dropdowns, scrollbars, date pickers) render with a dark UA stylesheet
       instead of the OS's white default. Without this, opening a <select>
       on Windows pops a white listbox over the dark UI and the user can't
       read options until hover. Light-theme authors can override this in
       their [data-theme="..."] block with `color-scheme: light`. */
    color-scheme: dark;

    /* ---- Surfaces (chrome backgrounds, layered front-to-back) ---- */
    --surface-app:          #0a0a0f;            /* root <body> background */
    /* RGB triplet for the app-surface — needed by the sticky header
       and certain dropdowns that use a translucent backdrop-filter. */
    --surface-app-rgb:      10, 10, 15;
    --surface-sidebar:      #12121a;            /* left nav + right detail panel */
    --surface-card:         #1a1a25;            /* default card background */
    --surface-card-hover:   #222230;            /* card hover/active state */
    --surface-elevated:     #1f1f2c;            /* modals, popovers, dropdowns */
    --surface-overlay:      rgba(0, 0, 0, 0.75); /* modal scrim */
    --surface-player:       #12121a;            /* bottom playback bar */

    /* ---- Text (foreground roles) ---- */
    --text-primary:         #ffffff;
    --text-secondary:       #8a8a9a;
    --text-muted:           #555566;
    --text-on-accent:       #ffffff;            /* foreground when sitting on accent fill */

    /* ---- Borders ---- */
    --border-subtle:        #2a2a3a;
    --border-strong:        #3a3a4a;
    --border-focus:         #ff6b35;            /* mirrors --accent-primary by default */

    /* ---- Accents (named so the existing accent picker still works) ---- */
    --accent-primary:       #ff6b35;
    /* The "currently playing / selected" bar that marks a row.
       ONE width for the whole app, on purpose.

       This bar used to be declared eleven separate times at five different
       widths: 2px, 3px, 5px, and — in the tiny player — 0.6vmin and
       0.8vmin. Viewport units were the worst of those, because they made
       the same row a different thickness depending on the window, so the
       sidebar, the queue and the mini player never agreed even with
       themselves.

       Pixels, not viewport units: a straight line has to be the same line. */
    --accent-bar-width:     3px;
    /* Secondary accent now resolves to the user's primary so the whole
       UI tracks one accent color instead of leaking the legacy yellow
       tint (#f7c548) into transport buttons, hi-res chips, headings,
       progress fills, etc. — see applyAccentColor() in views.js, which
       only updates accent-primary; everything secondary-derived now
       follows it automatically. */
    --accent-secondary:     var(--accent-primary);
    --accent-purple:        #9b5de5;
    --accent-cyan:          #00f5d4;
    /* RGB triplet for opacity-tinted overlays:
       rgba(var(--accent-primary-rgb), 0.16) — must stay in sync with
       --accent-primary. Updated by applyAccentColor() in views.js. */
    --accent-primary-rgb:   255, 107, 53;
    /* Same alias — every rgba(var(--accent-secondary-rgb), …) overlay
       picks up the primary's RGB triplet so opacity-tinted variants
       follow the picker too. */
    --accent-secondary-rgb: var(--accent-primary-rgb);
    --accent-purple-rgb:    155, 93, 229;
    --accent-cyan-rgb:      0, 245, 212;
    /* Built from accent-primary alone (lighter shade ↔ primary) so it
       follows the user's accent picker. The previous primary→secondary
       version (orange→yellow) clashed every time the user picked a non-
       orange accent and produced visible orange/yellow tints on transport
       buttons + decorative gradients. */
    --accent-gradient:      linear-gradient(135deg,
                                color-mix(in srgb, var(--accent-primary) 100%, white 18%),
                                var(--accent-primary));

    /* ---- Semantic state colors ---- */
    --state-success:        #3fa862;
    --state-warning:        #f7c548;
    --state-danger:         #d04a4a;
    --state-info:           #4a90d0;
    /* RGB triplets for opacity-tinted state surfaces (heart hover, etc.) */
    --state-danger-rgb:     208, 74, 74;
    --state-success-rgb:    63, 168, 98;
    --state-warning-rgb:    247, 197, 72;

    /* ---- Shadows (full box-shadow strings so themes can dial softness) ---- */
    --shadow-sm:            0 2px 8px rgba(0, 0, 0, 0.30);
    --shadow-md:            0 8px 24px rgba(0, 0, 0, 0.45);
    --shadow-lg:            0 20px 60px rgba(0, 0, 0, 0.60);

    /* ---- Overlay tints (hover/active/selected) ----
       On dark themes these are subtle white washes; on light themes
       they invert to subtle black. Used wherever a row, button, or
       card needs feedback without committing to an accent fill. */
    --overlay-hover:        rgba(255, 255, 255, 0.04);
    --overlay-active:       rgba(255, 255, 255, 0.08);
    --overlay-selected:     rgba(255, 255, 255, 0.06);

    /* ---- Audio-quality badges ---------------------------------------
       Each badge has a "format" (left half — vivid gradient or solid)
       and a "quality" (right half — softer tint of the same hue).
       Lossless = green, HiRes = gold, DSD = purple→pink, Lossy =
       neutral grey. Tokens here let theme authors restyle the badges
       for accessibility (light theme darkens hues for contrast) while
       preserving each badge's identity. */
    --badge-lossy-format-bg:        #3a3a48;
    --badge-lossy-format-text:      #c8c8d4;
    --badge-lossy-quality-bg:       #2a2a38;
    --badge-lossy-quality-text:     #9a9aa8;

    --badge-lossless-format-bg:     linear-gradient(135deg, #1f6d3a, #2fa45c);
    --badge-lossless-format-text:   #ffffff;
    --badge-lossless-quality-bg:    rgba(47, 164, 92, 0.2);
    --badge-lossless-quality-text:  #7ed99c;

    --badge-hires-format-bg:        linear-gradient(135deg, #c78a1a, #f7c548);
    --badge-hires-format-text:      #1c1208;
    --badge-hires-quality-bg:       rgba(247, 197, 72, 0.18);
    --badge-hires-quality-text:     #f7c548;
    /* Halo applied to .audio-badge-hires-glow — same hue as the
       gold gradient, double-stopped for the soft outer shadow. */
    --badge-hires-glow:             0 0 0 1px rgba(247, 197, 72, 0.25),
                                    0 2px 8px rgba(247, 197, 72, 0.25);

    --badge-dsd-format-bg:          linear-gradient(135deg, #6a1b9a, #e91e63);
    --badge-dsd-format-text:        #ffffff;
    --badge-dsd-quality-bg:         rgba(233, 30, 99, 0.2);
    --badge-dsd-quality-text:       #ff8aa6;
    --badge-dsd-glow:               0 0 0 1px rgba(233, 30, 99, 0.3),
                                    0 2px 10px rgba(233, 30, 99, 0.3);

    /* ---- Online-lookup provider badges ------------------------------
       Each provider has a brand colour used both on its toolbar button
       and on result cards. Themes can darken/lighten these for
       contrast, but the hue is provider identity (MB orange, Discogs
       green, Deezer blue, AudioDB purple) and changing it confuses
       muscle memory — so the defaults match each provider's brand. */
    --provider-musicbrainz-bg:      rgba(186, 71, 40, 0.85);
    --provider-musicbrainz-text:    #ffffff;
    --provider-discogs-bg:          rgba(46, 125, 60, 0.85);
    --provider-discogs-text:        #ffffff;
    --provider-deezer-bg:           rgba(0, 95, 213, 0.85);
    --provider-deezer-text:         #ffffff;
    --provider-audiodb-bg:          rgba(106, 39, 161, 0.85);
    --provider-audiodb-text:        #ffffff;

    /* ---- Layout (NOT theme-controlled — preserved for compatibility) ---- */
    --player-height:        90px;

    /* ---- Legacy aliases — removed once the color sweep retires them. ----
       Keeps existing CSS files working while we migrate references one
       file at a time. Each alias points at its semantic replacement. */
    --bg-dark:              var(--surface-app);
    --bg-sidebar:           var(--surface-sidebar);
    --bg-card:              var(--surface-card);
    --bg-card-hover:        var(--surface-card-hover);
}

body {
    background: var(--bg-dark);
    color: var(--text-primary);
    overflow: hidden;
    /* Disable accidental text selection when dragging across the UI. */
    -webkit-user-select: none;
    user-select: none;
}

/* Re-enable text selection in form controls and anything explicitly marked
   as editable. Without this, users couldn't select text in the search bar
   or tag editor. */
input,
textarea,
select,
[contenteditable="true"],
[contenteditable=""],
.allow-select,
.allow-select * {
    -webkit-user-select: text;
    user-select: text;
}

:root {
    --panel-width: 450px;
    --grid-zoom: 1;
    /* Independent zoom for entity-tile grids (Composers, Conductors,
       Genres, Artists, Performers, Labels, Orchestras, Lyricists,
       Arrangers, Producers). Album views keep --grid-zoom, so the
       user can scale the entity views down without shrinking album
       covers. Default 1.0 = current (largest) size. */
    --entity-zoom: 1;
}

/* View-scoped search — hides elements that don't match the current view's
   search query. Used by _applyViewSearch() in app.js. */
.search-hidden {
    display: none !important;
}

/* Hover marquee — see installHoverMarquee() / marqueeOn() in utils.js.
   `--marquee-target` is set inline to a negative pixel value equal to the
   overflow distance; `--marquee-duration` scales with the distance.
   `transform: translateX` is GPU-composited (no per-frame layout) so the
   scroll stays smooth even on weak hardware. The animation ping-pongs
   forever while hovered: scroll-out, pause at end, scroll-back, pause at
   start, repeat. No `forwards` fill-mode → no "stuck at end of word" feel. */
@keyframes marquee-scroll {
    0%   { transform: translateX(0); }
    45%  { transform: translateX(var(--marquee-target, 0)); }
    55%  { transform: translateX(var(--marquee-target, 0)); }
    100% { transform: translateX(0); }
}

.marquee-active {
    text-overflow: clip !important;
    overflow: hidden;
}
.marquee-active > .marquee-inner {
    display: inline-block;
    white-space: nowrap;
    will-change: transform;
    animation: marquee-scroll var(--marquee-duration, 6s) ease-in-out 0.4s infinite;
}

.app {
    display: grid;
    grid-template-columns: 240px 1fr;
    grid-template-rows: 1fr var(--player-height);
    height: 100vh;
}

.main {
    margin-right: 0;
    transition: none;
}

.main {
    background: var(--bg-dark);
    overflow-y: auto;
    padding-bottom: var(--player-height);
    overscroll-behavior: contain;
}

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.25);
}

/* Thumb tinted with the user's accent — translucent so it doesn't fight
   for attention against the rest of the chrome, fully opaque on hover so
   it's obvious which scrollable area you're over. */
::-webkit-scrollbar-thumb {
    background: rgba(var(--accent-primary-rgb), 0.55);
    border-radius: 5px;
    border: 2px solid transparent;
    background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
    background: rgba(var(--accent-primary-rgb), 0.85);
    background-clip: padding-box;
}

::-webkit-scrollbar-corner {
    background: transparent;
}

.view-section {
    display: none;
    padding: 20px 30px;
}

.view-section.active {
    display: block;
}

.main {
    margin-right: 0;
}

/* ---- Universal currently-playing marker ----
   Any track-row, list-item, card, etc. that carries the `.playing` class
   gets a small loudspeaker badge in its top-left corner. Renderers across
   the app (queue, album detail, preview, search, composer/works views,
   playlists, …) all add `.playing` to whichever row matches the active
   track via refreshNowPlayingMarkers() in player.js. The marker is a
   pseudo-element so it can't shift or break the row's existing layout.

   Implementation: CSS mask-image with an inline data-URI SVG of the
   feather "volume" speaker. The mask carves the silhouette; the
   background-color paints it in the user's accent. This lets the
   speaker render flat and accent-coloured instead of the OS's coloured
   `🔊` emoji which ignored the CSS `color`. */
.playing { position: relative; }
.playing::before {
    content: "";
    position: absolute;
    left: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    background-color: var(--accent-primary, #ff6b35);
    -webkit-mask-image:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polygon points='11 5 6 9 2 9 2 15 6 15 11 19 11 5'/><path d='M19.07 4.93a10 10 0 0 1 0 14.14'/><path d='M15.54 8.46a5 5 0 0 1 0 7.07'/></svg>");
            mask-image:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polygon points='11 5 6 9 2 9 2 15 6 15 11 19 11 5'/><path d='M19.07 4.93a10 10 0 0 1 0 14.14'/><path d='M15.54 8.46a5 5 0 0 1 0 7.07'/></svg>");
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
    pointer-events: none;
    z-index: 3;
    filter: drop-shadow(0 0 3px rgba(0,0,0,0.55));
    animation: playing-pulse 1.4s ease-in-out infinite;
}

/* Gentle pulse for the loudspeaker badge — preserves the existing
   translateY centering by including it in every keyframe. */
@keyframes playing-pulse {
    0%, 100% { transform: translateY(-50%) scale(1);    opacity: 0.85; }
    50%      { transform: translateY(-50%) scale(1.18); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .playing::before { animation: none; }
}
/* Linux / WebKitGTK: the "now playing" speaker and row pulses are infinite
   animations on masked, drop-shadowed pseudo-elements. Without GPU
   compositing (WEBKIT_DISABLE_DMABUF_RENDERER=1, VMs, many Intel/NVIDIA
   setups) every frame repaints them. Measured headless (software GL) with
   one track playing on Now Playing: WebKitWebProcess 25 % CPU with the
   pulses, 16 % without (OPEN_PROBLEMEN #1). Keep the static marker. */
html.platform-linux .playing::before,
html.platform-linux .queue-album-track.playing .qat-num::before,
html.platform-linux .queue-album-track[data-playing="1"] .qat-num::before,
html.platform-linux .detail-track-item.playing .detail-track-now-marker {
    animation: none;
}
/* Album/work cards mark themselves `has-playing` when one of their tracks
   is the active one. Same badge but anchored to the card's top-right so
   it doesn't sit on top of the cover art. */
.has-playing { position: relative; }
.has-playing::after {
    content: "";
    position: absolute;
    top: 6px;
    right: 6px;
    width: 16px;
    height: 16px;
    background-color: var(--accent-primary, #ff6b35);
    -webkit-mask-image:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polygon points='11 5 6 9 2 9 2 15 6 15 11 19 11 5'/><path d='M19.07 4.93a10 10 0 0 1 0 14.14'/><path d='M15.54 8.46a5 5 0 0 1 0 7.07'/></svg>");
            mask-image:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polygon points='11 5 6 9 2 9 2 15 6 15 11 19 11 5'/><path d='M19.07 4.93a10 10 0 0 1 0 14.14'/><path d='M15.54 8.46a5 5 0 0 1 0 7.07'/></svg>");
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
    pointer-events: none;
    z-index: 3;
    filter: drop-shadow(0 0 4px rgba(0,0,0,0.7));
}
