/* WaaxHead — an after-hours record-shop UI. Album cover art is the hero: results
   sit as square sleeves in a crate grid, and hovering one slides the vinyl out
   of its sleeve. Record metadata (year, format, catalog no., price, store) is
   set in monospace, like the typed index cards and price-gun stickers of a
   shop. Warm espresso dark by default; a kraft-paper light theme when the
   viewer prefers light. No web fonts, no CDNs — self-contained. */

:root {
  /* after-hours shop (dark) — the default identity */
  --bg: #17130f;
  --panel: #211b15;
  --raised: #2b2219;
  --ink: #efe7db;
  --muted: #a89a86;
  --line: #3a2f24;
  --amber: #e0a03c;
  --amber-ink: #1a1206;
  --rose: #d9836f;
  --rose-ink: #24120d;
  --oxblood: #c05f45;
  --green: #86ab63;
  --violet: #8f8ae0;
  --violet-ink: #17122e;
  --disc: #0c0a08;
  --shadow: 0 10px 30px rgba(0, 0, 0, 0.45);

  --ui: ui-sans-serif, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  --col: 880px;
}

@media (prefers-color-scheme: light) {
  :root {
    /* kraft sleeve (light) */
    --bg: #e6ddcb;
    --panel: #f1eadb;
    --raised: #fbf7ee;
    --ink: #241d15;
    --muted: #6f6350;
    --line: #d3c7b2;
    --amber: #a9701a;
    --amber-ink: #fdf6e9;
    --rose: #a5442f;
    --rose-ink: #fdf6e9;
    --oxblood: #9c3f2c;
    --green: #4f6d34;
    --violet: #524ba6;
    --violet-ink: #f3f1fe;
    --disc: #1a1510;
    --shadow: 0 8px 22px rgba(60, 45, 25, 0.16);
  }
}

* { box-sizing: border-box; }

html { height: 100%; }
body { min-height: 100%; } /* body must grow with content (not be pinned to one viewport) so the sticky .topbar stays pinned for the whole page scroll */

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 15px;
  line-height: 1.55;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* faint paper grain so flat areas aren't dead flat */
  background-image: radial-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px);
  background-size: 3px 3px;
}

/* ---- reusable tooltip (data-tip) ----
   A dependency-free hover/focus tooltip driven by a `data-tip` attribute
   instead of the native `title` attribute — WKWebView (the macOS desktop
   build) never renders `title` as a hover tooltip, so any phrase that needs
   one should carry `.has-tip` + `data-tip="…"` instead. The dotted underline
   + help cursor hints there's more on hover.
   The bubble itself is NOT a `::after` on `.has-tip` — several `.has-tip`
   triggers live inside `.prices`, which clips overflow for its rounded
   corners, so a tall bubble anchored there gets cut off. Instead the JS
   tooltip controller (see app.js) portals a single `.tip-bubble` node onto
   `document.body` and positions it with `position: fixed`, escaping any
   ancestor's overflow clip. */
.has-tip { position: relative; cursor: help; border-bottom: 1px dotted var(--muted); }
.tip-bubble {
  position: fixed;
  z-index: 1000;
  max-width: min(360px, 90vw);
  max-height: min(320px, 60vh);
  overflow-y: auto;
  padding: 10px 12px;
  line-height: 1.4;
  font-family: var(--ui);
  font-size: 11.5px;
  /* pre-line keeps the \n in a two-line data-tip (e.g. the account avatar's
     email + Discogs identity) on separate lines while still collapsing other
     whitespace, so existing single-line tips render unchanged. */
  white-space: pre-line;
  border-radius: 6px;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  pointer-events: none;
}

/* ---- top bar ---- */
.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 22px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(8px);
  z-index: 10;
}
.brand { display: flex; align-items: center; gap: 10px; }
/* wordmark + tagline stack vertically (wordmark on line 1, tagline on line
   2) with a tight gap so they read as one grouped label; this group is the
   thing .brand centers against the icon, and .topbar centers as a whole, so
   the pair moves together and stays tightly stacked. */
.brand-text { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.wordmark {
  font-weight: 800;
  font-size: 20px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1;
}
.tagline {
  margin: 0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--muted);
  line-height: 1.1;
}
/* app icon mark: a soft-rounded square that matches the header radius vocabulary */
.brand-icon {
  width: 56px;
  height: 56px;
  border-radius: 12px;
  display: block;
  align-self: center;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* region + format toggles: quiet native selects pushed to the end of the
   topbar. Themed entirely via the shop palette vars so they track both the
   espresso-dark and kraft-light schemes; an amber hover/focus accent matches
   the .starters chips and nav. The region control carries margin-left:auto so
   brand/tagline stay left and the pair sits at the right, spaced by the
   topbar's flex gap. */
.region-select,
.format-select {
  align-self: center;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 10px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.region-select { margin-left: auto; }
/* region-label: the static single-region readout shown instead of the select
   when the deployment serves only one region. Matches the select's mono/size/
   color and carries the same margin-left:auto so it sits at the right. */
.region-label {
  align-self: center;
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
}
.region-select:hover,
.format-select:hover { border-color: var(--amber); color: var(--amber); }
.region-select:focus-visible,
.format-select:focus-visible { border-color: var(--amber); }
.region-select[aria-busy="true"] { opacity: 0.6; cursor: progress; }
.region-select option,
.format-select option { color: var(--ink); background: var(--bg); }

/* settings gear button: a theme-aware masked icon (not a flat PNG, which would
   vanish in dark mode) — the PNG supplies only the gear shape via CSS mask,
   and background-color paints it in the current theme's ink color, sized to
   match the region/format controls */
.settings-btn {
  align-self: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  background-color: var(--ink);
  -webkit-mask: url("/settingsicon.png") center / contain no-repeat;
  mask: url("/settingsicon.png") center / contain no-repeat;
  cursor: pointer;
  transition: background-color 0.15s;
}
.settings-btn:hover,
.settings-btn:focus-visible { background-color: var(--amber); }

/* account strip: login state + Connect-Discogs entry point, shown only on the
   multi-user server (feature-detected via /auth/me). Hidden by default — the
   `.account-strip` display:flex would otherwise beat the UA `[hidden]` rule,
   so an explicit `[hidden] { display: none }` override keeps the personal app
   visually unchanged until JS reveals it after a 200 from /auth/me. */
.account-strip {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
}
.account-strip[hidden] { display: none; }
.account-email { color: var(--ink); }
.account-discogs { color: var(--muted); }
/* Gravatar avatar: a round 28px anchor next to Sign out. Its hover box (the
   portal .tip-bubble, via data-tip) carries the email + Discogs identity. */
.account-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  overflow: hidden;
  cursor: pointer;
  border: 1px solid var(--line);
}
.account-avatar-img {
  display: block;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
}
/* Initials fallback shown when the Gravatar image fails to load (offline or
   broken): a mono letter centered in the same round slot. */
.account-avatar-initial {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line);
}
.account-link { color: var(--amber); text-decoration: none; }
.account-link:hover,
.account-link:focus-visible { text-decoration: underline; }
.account-signout {
  align-self: center;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 10px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.account-signout:hover,
.account-signout:focus-visible { border-color: var(--amber); color: var(--amber); }
.account-signout[disabled] { opacity: 0.6; cursor: progress; }

/* Discogs connection status: standalone (personal) app ONLY, feature-detected
   via GET /settings (the multi-user server 404s it). Hidden by default — the
   `.discogs-conn` display:flex would otherwise beat the UA `[hidden]` rule, so
   an explicit `[hidden] { display: none }` override keeps the server UI
   unchanged until JS reveals it after a 200 from /settings. Mirrors the
   account-strip vocabulary so both read as the same quiet topbar affordance. */
.discogs-conn {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  line-height: 1;
}
.discogs-conn[hidden] { display: none; }

/* Top-right metadata block: version stacked over the #discogs-conn status
   line, right-aligned as a pair. Uses the existing theme tokens only (no new
   colors) so it reads as the same quiet topbar affordance as the rest of the
   header. */
.topbar-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0;
  text-align: right;
}
.app-version {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--muted);
  line-height: 1;
}
.discogs-conn-ok { color: var(--ink); }
.discogs-conn-off { color: var(--muted); }
.discogs-conn-connect {
  align-self: center;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 10px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.discogs-conn-connect:hover,
.discogs-conn-connect:focus-visible { border-color: var(--amber); color: var(--amber); }

/* first-run startup modal action row: reuses the .modal-overlay/.modal shells
   (see the release-overview modal below); its two buttons sit side by side,
   right-aligned, with the primary "Connect Discogs" carrying the amber accent
   and "Not now" the quiet raised-pill look. */
.discogs-firstrun-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
}
#discogs-firstrun-yes {
  padding: 10px 20px;
  border-radius: 10px;
  border: 0;
  background: var(--amber);
  color: var(--amber-ink);
  font-weight: 750;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
#discogs-firstrun-no {
  padding: 10px 18px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 13px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
#discogs-firstrun-no:hover,
#discogs-firstrun-no:focus-visible { border-color: var(--amber); color: var(--amber); }

/* ---- settings panel (gear button + modal) ----
   Reuses the .modal-overlay/.modal shells (see the release-overview modal
   below), but the settings panel is STATIC markup toggled via the `hidden`
   property rather than inserted/removed — an author rule setting `display`
   on the same selector would otherwise beat the UA `[hidden]` rule, so both
   toggled selectors get an explicit `[hidden] { display: none }` override. */
.settings-overlay[hidden] { display: none; }
/* Same gotcha for the base `.modal-overlay`: its author `display: flex` beats
   the UA `[hidden] { display: none }` rule, so a STATIC overlay carrying the
   `hidden` attribute (the first-run modal, which has only `.modal-overlay`)
   would otherwise stay painted as a full-screen scrim — eating clicks and
   ignoring the JS `overlay.hidden = true` used to dismiss it (the real cause of
   the "dead / undismissable first-run buttons" bug). This override makes
   `hidden` actually hide it. The release-overview modal is created/destroyed
   dynamically and never carries `hidden`, so it is unaffected. */
.modal-overlay[hidden] { display: none; }
.settings-overlay { z-index: 200; } /* above the release-overview modal (100) so an llm_unconfigured event always surfaces on top */
/* the first-run startup modal must sit ABOVE the settings overlay (200): on a
   keyless boot the settings panel auto-opens, and at z-index:100 (.modal-overlay)
   the first-run modal would be covered and its clicks eaten. 300 keeps it on top. */
#discogs-firstrun-overlay { z-index: 300; }
.settings-panel {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.settings-title { margin: 0; font-size: 18px; font-weight: 700; }
.settings-intro { color: var(--muted); font-size: 13px; margin: 10px 0 0; }
/* small muted note under the intro: the storage location (always shown). */
.settings-note {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.5;
  margin: 0;
}
.settings-banner {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--amber);
  background: color-mix(in srgb, var(--amber) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--amber) 45%, var(--line));
  border-radius: 8px;
  padding: 9px 13px;
}

/* ---- settings form (single Save — dev_stats parity) ----
   ONE <form id="settings-form"> holds every field plus one status line and
   one amber Save button; there is no per-field Save. */
#settings-fields { display: flex; flex-direction: column; gap: 18px; }
.settings-field { display: flex; flex-direction: column; gap: 8px; }
.settings-field[hidden] { display: none; }
.settings-label-row { display: flex; align-items: baseline; gap: 8px; }
.settings-label-row label { font-size: 13px; font-weight: 650; }
.settings-toggle-row { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; }
.settings-input {
  width: 100%;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  font-size: 13px;
  font-family: var(--ui);
}
.settings-input::placeholder { color: var(--muted); }

/* ---- settings current-value display (set/not-set + masked + reveal) ----
   dev_stats parity: each key row shows a set/not-set indicator (PLAIN
   colored/uppercase text — NOT a pill/badge: no background, border, or
   border-radius), the masked current value (or "—" when unset), and a
   Show/Hide reveal button that only appears when the server returned a full
   value (loopback-only build — see settings.go's settingsDeps
   security-posture comment). */
.settings-provider-model { margin: 0; font-family: var(--mono); font-size: 11px; color: var(--muted); }
.settings-current { display: flex; align-items: center; gap: 8px; min-height: 20px; }
.settings-indicator {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.settings-indicator.is-set { color: var(--green); }
.settings-indicator.not-set { color: var(--muted); }
.settings-masked {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--muted);
  word-break: break-all;
}
.reveal-btn {
  margin-left: auto;
  flex: none;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  cursor: pointer;
}
.reveal-btn:hover,
.reveal-btn:focus-visible { color: var(--amber); border-color: var(--amber); }

/* The value line and the enable toggle both carry an explicit display (flex),
   so the bare [hidden] attribute won't collapse them — restore that here so the
   three-state Discogs section (connect-prompt / OAuth / PAT) can hide each part
   by toggling `hidden`. */
.settings-current[hidden],
.settings-toggle-row[hidden] { display: none; }

/* Not-connected Discogs section: an instructional line + a Connect button that
   kicks off the loopback OAuth flow. The button reuses the amber accent of the
   first-run "Connect Discogs" action. */
#discogs-connect-prompt { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
#discogs-connect-prompt[hidden] { display: none; }
#discogs-oauth-connect {
  padding: 9px 18px;
  border-radius: 10px;
  border: 0;
  background: var(--amber);
  color: var(--amber-ink);
  font-weight: 750;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}

.settings-status {
  min-height: 18px;
  font-family: var(--mono);
  font-size: 12px;
  margin: 12px 0 0;
  color: var(--muted);
}

/* ---- server settings view (multi-user server: identity + Discogs + LLM) ----
   Built dynamically by renderServerSettings() into #settings-fields. Reuses the
   existing theme tokens + the .account-avatar / .account-signature vocabulary
   so it reads as the same quiet panel as the personal settings. */
.server-settings-section { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.server-settings-heading {
  margin: 0;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
}
.server-identity-row { display: flex; align-items: center; gap: 10px; }
/* The identity avatar is a non-interactive round slot in the modal (the click
   affordance is the topbar gravatar); reuse .account-avatar sizing but drop the
   pointer cursor. */
.server-identity-avatar { cursor: default; }
.server-identity-email { font-family: var(--mono); font-size: 13px; color: var(--ink); word-break: break-all; }
.server-discogs-linked { margin: 0; font-size: 13px; color: var(--ink); }
.server-discogs-method { margin: 0; }
.server-discogs-or { margin: 0; }
.server-discogs-oauth {
  padding: 9px 18px;
  border-radius: 10px;
  border: 0;
  background: var(--amber);
  color: var(--amber-ink);
  font-weight: 750;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.server-discogs-token { width: 100%; }
.server-discogs-pat-connect {
  align-self: flex-start;
  padding: 7px 14px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--ink);
  font-size: 12.5px;
  font-weight: 650;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.server-discogs-pat-connect:hover,
.server-discogs-pat-connect:focus-visible { border-color: var(--amber); color: var(--amber); }
.server-llm-status { margin: 0; }
.settings-status.error { color: var(--oxblood); }
.settings-status.ok { color: var(--green); }

.settings-actions { display: flex; justify-content: flex-end; margin-top: 12px; }
#settings-save {
  padding: 11px 22px;
  border-radius: 10px;
  border: 0;
  background: var(--amber);
  color: var(--amber-ink);
  font-weight: 750;
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
#settings-save:disabled { opacity: 0.5; cursor: default; }

/* ---- transcript ---- */
.transcript {
  flex: 1;
  width: 100%;
  max-width: var(--col);
  margin: 0 auto;
  padding: 26px 22px 8px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.welcome { margin: 6vh 0; }
/* App icon centred above the welcome heading, ~half the chat-window width
   (capped so it doesn't dominate on a wide window). The icon PNG is already a
   rounded-square with transparent corners, so no extra masking here. */
.welcome-icon { display: block; width: 34%; max-width: 270px; height: auto; margin: 0 auto 24px; border-radius: 22%; }
.welcome h1 {
  font-size: clamp(28px, 5vw, 44px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 12px;
}
.welcome p { color: var(--muted); max-width: 46ch; margin: 0 0 18px; }
.starters { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.starters li {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 12px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.starters li:hover { border-color: var(--amber); color: var(--amber); }

/* messages */
.msg { display: flex; flex-direction: column; gap: 8px; }
.msg .who {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.msg.user { align-items: flex-end; }
.msg.user .who { color: var(--amber); }
.bubble {
  white-space: pre-wrap;
  word-break: break-word;
  max-width: 92%;
}
.msg.user .bubble {
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 12px 12px 3px 12px;
  padding: 10px 14px;
  max-width: 80%;
}
.msg.agent .bubble {
  border-left: 2px solid var(--amber);
  padding-left: 14px;
}

/* ---- inline markdown in assistant text ---- */
/* Emphasis uses the amber accent (bold vs italic tell them apart); links use a
   distinct green so they read as links, with an obvious hover chip and never an
   underline. */
.md-strong { font-weight: 700; color: var(--amber); }
.md-em { font-style: italic; color: var(--amber); }
.md-link {
  color: var(--green);
  text-decoration: none;
  border-radius: 4px;
  padding: 0 3px;
  transition: color 0.15s, background-color 0.15s;
}
.md-link:hover,
.md-link:focus-visible {
  text-decoration: none;
  color: color-mix(in srgb, var(--green) 60%, #fff);
  background: color-mix(in srgb, var(--green) 20%, transparent);
}
/* block-level markdown, kept quiet and compact for the chat bubble */
.md-h {
  font-weight: 600;
  color: var(--ink);
  margin: 10px 0 4px;
  font-size: 1.05em;
}
.bubble .md-h:first-child { margin-top: 0; }
.md-list {
  margin: 4px 0;
  padding-left: 1.3em;
  list-style: disc;
}
.md-list li { margin: 2px 0; }
.md-p { margin: 6px 0; }
.bubble .md-p:first-child { margin-top: 0; }

/* ---- tool-call chip ---- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  width: fit-content;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--muted);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 13px;
}
.chip .spin {
  width: 12px; height: 12px; border-radius: 50%;
  background: repeating-radial-gradient(circle at center, var(--disc) 0 1px, #2a211800 1.2px 2.2px), var(--muted);
  animation: spin 1.1s linear infinite;
}
.chip.done { color: var(--green); border-color: color-mix(in srgb, var(--green) 40%, var(--line)); }
.chip.done .spin { animation: none; background: none; width: auto; height: auto; }
.chip.done .spin::before { content: "✓"; }

/* ---- thinking / background-activity indicator ----
   A quiet transcript-level cue shown inside the active agent message during
   the silent stretches of a turn: before the first byte arrives, and in the
   pause between a tool result and the next model output. Muted like .chip/
   .notes so it never competes with real content; the three dots pulse in a
   staggered wave. role="status" + aria-live="polite" (set in app.js)
   announces it to assistive tech without stealing focus. */
.thinking {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
}
.thinking-dots { display: inline-flex; gap: 3px; }
.thinking-dots .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--muted);
  animation: thinking-pulse 1.2s ease-in-out infinite;
}
.thinking-dots .dot:nth-child(2) { animation-delay: 0.15s; }
.thinking-dots .dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes thinking-pulse {
  0%, 80%, 100% { opacity: 0.25; transform: scale(0.85); }
  40% { opacity: 1; transform: scale(1); }
}
.thinking-label { letter-spacing: 0.01em; }

/* ---- results block: a quiet caption eyebrow above the grid ---- */
.result-block { display: flex; flex-direction: column; gap: 12px; }
.grid-caption {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- discogs cover grid (the hero) ---- */
.covers {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 18px 16px;
}
/* the "+N more" overflow indicator: a quiet, muted tile roughly matching a
   sleeve's square footprint, so it reads as "there's more here" without
   competing with the real cards. Theme-aware via the same custom properties
   the sleeve cards use (--line, --muted, --raised) so it tracks both the
   espresso-dark and kraft-light palettes. */
.cover-more {
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 12px;
  border: 1px dashed var(--line);
  border-radius: 3px;
  background: color-mix(in srgb, var(--raised) 55%, transparent);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.06em;
}
/* position:relative + a z-index bump on hover/focus lifts the whole card above
   its grid siblings, so the disc sliding out paints OVER the neighbour instead
   of being clipped behind it. Idle cards stay at the default z so they don't
   fight. Nothing here clips: neither .covers, .sleeve-card, nor .sleeve sets
   overflow, so the disc is free to extend past the card bounds. */
.sleeve-card { display: flex; flex-direction: column; gap: 8px; position: relative; padding: 8px; border-radius: 8px; border: 1px solid transparent; }
/* Only tiles wired up as clickable (role="button" + click handler in app.js)
   get the pointer cursor, so users see they open a detail view; plain tiles
   stay with the default cursor. */
.sleeve-card[role="button"] { cursor: pointer; }
/* Every sleeve card shares the base padded box model (padding + transparent
   border), so bottom-aligned tag footers line up across a grid row. Missing
   tiles get a warm rose wash + border to stand out; owned stays neutral. */
.sleeve-card.is-missing {
  background: color-mix(in srgb, var(--rose) 13%, transparent);
  border-color: color-mix(in srgb, var(--rose) 45%, var(--line));
}
/* ordered tile: mirrors the rose "missing" tile but in amber, so an
   incoming record reads as distinct from owned (green) and missing (rose). */
.sleeve-card.is-ordered {
  background: color-mix(in srgb, var(--amber) 12%, transparent);
  border-color: color-mix(in srgb, var(--amber) 45%, var(--line));
}
.covers .sleeve-card:hover,
.covers .sleeve-card:focus-visible,
.covers .sleeve-card:focus-within {
  z-index: 10;
}
.sleeve {
  position: relative;
  aspect-ratio: 1 / 1;
}
/* the vinyl disc, tucked behind the sleeve, peeking out on hover. The middle
   layer (--vinyl-tint, transparent by default) colours the disc by pressing;
   the black grooves show through its alpha. */
.sleeve .vinyl {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle at center, var(--amber) 0 8%, var(--disc) 8.5% 14%, transparent 14.5%),
    var(--vinyl-tint, linear-gradient(transparent, transparent)),
    repeating-radial-gradient(circle at center, var(--disc) 0 2px, #241c14 2.5px 4px);
  transform: translateX(0) rotate(0deg);
  transition: transform 0.42s cubic-bezier(0.22, 0.61, 0.36, 1);
  z-index: 0;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5);
}
.cover, .nocover {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  border-radius: 3px;
  box-shadow: var(--shadow), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
  background: var(--raised);
}
.cover { object-fit: cover; display: block; }
.nocover {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
  padding: 12px;
  border: 1px solid var(--line);
}
.nocover .stamp {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.2em;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 3px 7px;
}
.nocover .t { font-size: 12.5px; color: var(--ink); line-height: 1.2; }

/* keyboard parity: focusing the card slides the disc out too (not mouse-only) */
.sleeve-card:focus-visible .vinyl,
.sleeve-card:focus-within .vinyl { transform: translateX(34%) rotate(75deg); }

@media (hover: hover) {
  .sleeve:hover .vinyl { transform: translateX(34%) rotate(75deg); }
}

/* the meta fills the card below the square cover so the tag footer can bottom-
   align; the card is a flex column and grid siblings stretch to equal height. */
.sleeve-meta {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}
.sleeve-meta .title {
  font-size: 13.5px;
  font-weight: 650;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.sleeve-meta .data {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 3px;
}
.sleeve-meta .catno { color: color-mix(in srgb, var(--amber) 70%, var(--muted)); }

/* ---- artist entity card (a person/band, not an album) ---- */
/* A round framed photo makes it read unmistakably as an artist, so type=artist
   hits never masquerade as album sleeves. */
.artist-card { display: flex; flex-direction: column; gap: 8px; align-items: center; text-align: center; }
.artist-photo {
  position: relative;
  width: 78%;
  aspect-ratio: 1 / 1;
  margin-top: 4px;
}
.artist-img, .artist-placeholder {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  box-shadow: var(--shadow), inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.artist-img { object-fit: cover; display: block; background: var(--raised); }
/* neutral placeholder (initials), NOT a "no cover" stamp */
.artist-placeholder {
  display: none;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 38%, var(--raised), var(--panel));
  border: 1px solid var(--line);
}
.artist-placeholder .artist-initials {
  font-family: var(--mono);
  font-size: clamp(18px, 4vw, 28px);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--amber) 70%, var(--muted));
}
.artist-meta .name {
  font-size: 13.5px;
  font-weight: 650;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.artist-meta .kind, .label-meta .kind {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 3px;
}

/* ---- label entity chip ---- */
.label-card {
  align-self: start;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
}
.label-card .label-glyph { color: var(--amber); font-size: 16px; line-height: 1; }
.label-meta .name { font-size: 13px; font-weight: 600; line-height: 1.2; }

/* ---- collapsed type=artist disambiguation matches ---- */
details.artist-matches { font-family: var(--mono); font-size: 12px; color: var(--muted); }
details.artist-matches > summary {
  cursor: pointer;
  padding: 4px 0;
  color: var(--muted);
  list-style-position: inside;
}
details.artist-matches > summary:hover { color: var(--amber); }
details.artist-matches[open] > summary { margin-bottom: 12px; }

/* ---- rich artist profile card (get_artist_profile) ---- */
.artist-profile {
  display: flex;
  flex-direction: column;
  gap: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 16px;
  background: var(--panel);
}
.ap-head { display: flex; gap: 14px; align-items: center; }
/* reuse the circular .artist-photo/.artist-img/.artist-placeholder styles, but
   fixed-size as an avatar rather than a grid-cell hero */
.ap-head .artist-photo { width: 88px; flex: 0 0 auto; margin: 0; }
.ap-name { font-size: 18px; font-weight: 700; line-height: 1.2; }
.ap-real { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-top: 3px; }
.ap-bio { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink); white-space: pre-wrap; }
.ap-links { display: flex; flex-wrap: wrap; gap: 8px; }
/* each official link is a compact pill labelled by its host/service (see
   hostLabel) rather than a raw full URL. Text keeps the green .md-link colour at
   rest; hover shifts the border + text to the amber accent. Theme-aware via the
   shop palette vars. */
.ap-link {
  padding: 2px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12px;
}
.ap-link:hover,
.ap-link:focus-visible {
  color: var(--amber);
  background: transparent;
  border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
}
.ap-members-title {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.ap-chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.member-chip {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 12px;
}
button.member-chip { cursor: pointer; transition: color 0.15s, border-color 0.15s, background-color 0.15s; }
button.member-chip:hover,
button.member-chip:focus-visible {
  color: var(--amber);
  border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
  background: color-mix(in srgb, var(--amber) 12%, transparent);
}
button.member-chip:disabled { cursor: default; opacity: 0.65; }
.member-chip.disabled { color: var(--muted); opacity: 0.8; }
.artist-more { display: flex; flex-direction: column; gap: 12px; margin-top: 10px; }
.artist-more .artist-profile { background: var(--raised); }

/* ---- release overview modal ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 5vh 16px;
  background: color-mix(in srgb, #000 55%, transparent);
  backdrop-filter: blur(3px);
  overflow-y: auto;
}
.modal {
  position: relative;
  width: 100%;
  max-width: 640px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 20px;
}
.modal-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--raised);
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.modal-close:hover, .modal-close:focus-visible { color: var(--amber); border-color: var(--amber); }

.release-overview { display: flex; flex-direction: column; gap: 14px; }
.ro-head { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.ro-cover { flex: 0 0 auto; width: 160px; max-width: 40%; }
.ro-img, .ro-nocover {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 4px;
  box-shadow: var(--shadow), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
  object-fit: cover;
  display: block;
  background: var(--raised);
}
.ro-nocover {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.2em;
  color: var(--muted);
  border: 1px solid var(--line);
}
.ro-meta { flex: 1 1 220px; min-width: 200px; display: flex; flex-direction: column; gap: 4px; }
.ro-title { margin: 0; font-size: 19px; line-height: 1.2; }
.ro-artists { font-size: 14px; color: var(--ink); }
.ro-facts { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-top: 2px; }
.ro-line { display: flex; gap: 8px; font-family: var(--mono); font-size: 12px; margin-top: 2px; }
.ro-key { color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; flex: 0 0 58px; }
.ro-val { color: var(--ink); }
.ro-links { align-self: flex-start; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ro-discogs { font-family: var(--mono); font-size: 13px; }
.ro-prices {
  font-family: var(--mono);
  font-size: 13px;
  background: none;
  border: none;
  padding: 0 3px;
  margin: 0;
  cursor: pointer;
}

/* "Open Settings" control on a discogs_unconfigured turn: a link-styled button
   (an action, not navigation), so it resets the native button chrome the same
   way .ro-prices does while keeping the green .md-link colouring. */
.discogs-settings-link {
  font-family: var(--mono);
  font-size: 13px;
  background: none;
  border: none;
  padding: 0 3px;
  margin: 0;
  cursor: pointer;
}

.ro-section-title {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.ro-tracklist { border-top: 1px solid var(--line); padding-top: 12px; }
.track-row {
  display: grid;
  grid-template-columns: 42px 1fr auto;
  gap: 10px;
  padding: 4px 0;
  font-size: 13.5px;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}
.track-row:last-child { border-bottom: 0; }
.track-pos { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.track-dur { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.ro-notes { margin: 0; font-size: 13px; line-height: 1.5; color: var(--muted); white-space: pre-wrap; }

/* ---- pricewatch price view ---- */
.prices { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.prices .region {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
/* Discogs marketplace reference (used/seller copies) — a quiet muted eyebrow
   line above the shop price rows, distinct from them. Theme-aware via --muted. */
.discogs-ref {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.discogs-ref.dref-none { font-style: italic; }
.dref-label {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: color-mix(in srgb, var(--amber) 70%, var(--muted));
}
.discogs-ref .dref-link { font-family: var(--mono); font-size: 11px; }

.price-row {
  display: grid;
  grid-template-columns: 44px 1fr auto;
  gap: 4px 12px;
  align-items: center;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
}
.price-row:last-child { border-bottom: 0; }
/* thumbnail spans both text rows on the left */
.price-thumb {
  position: relative;
  grid-row: 1 / span 2;
  grid-column: 1;
  width: 44px;
  height: 44px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--raised);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.price-thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: color-mix(in srgb, var(--amber) 45%, transparent);
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
/* the cover thumbnail is wrapped in an external link (priceRowEl); the link must
   occupy the thumbnail's grid cell so the row layout is unchanged, and show a
   pointer cursor to signal it's clickable. */
.price-row > a { grid-row: 1 / span 2; grid-column: 1; cursor: pointer; }
.price-row > a .price-thumb { transition: filter 0.15s ease; }
.price-row > a:hover .price-thumb,
.price-row > a:focus-visible .price-thumb { filter: brightness(1.06); }
.price-row > a:hover .price-thumb::after,
.price-row > a:focus-visible .price-thumb::after { opacity: 1; }
.price-img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* faint vinyl-record placeholder for image-less listings: a dark disc
   (spindle hole + center label + concentric grooves) drawn low-contrast
   against the tile's raised background, filling most of the 44px tile */
.price-thumb.noimg {
  background:
    radial-gradient(circle closest-side at center,
      transparent 0%, transparent 92%, var(--raised) 94%),
    radial-gradient(circle closest-side at center,
      var(--bg) 0%, var(--bg) 5%, transparent 6%,
      color-mix(in srgb, var(--muted) 45%, transparent) 8%,
      color-mix(in srgb, var(--muted) 45%, transparent) 22%,
      transparent 24%),
    repeating-radial-gradient(circle at center,
      transparent 0, transparent 2px,
      color-mix(in srgb, var(--muted) 18%, transparent) 2px,
      color-mix(in srgb, var(--muted) 18%, transparent) 3px),
    color-mix(in srgb, var(--ink) 22%, var(--raised));
}
.price-row .title { grid-column: 2; grid-row: 1; font-size: 13.5px; }
.price-row .store { grid-column: 2; grid-row: 2; }
.price-row .title a { color: var(--ink); text-decoration: none; border-bottom: 1px solid transparent; }
.price-row .title a:hover { border-bottom-color: var(--amber); }
.price-row .store { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }
.price-row .amount {
  grid-row: 1 / span 2;
  grid-column: 3;
  font-family: var(--mono);
  font-size: 16px;
  font-weight: 700;
  color: var(--amber);
  white-space: nowrap;
  justify-self: end;
}
.badge {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  margin-left: 8px;
}
.badge.in { color: var(--green); border: 1px solid color-mix(in srgb, var(--green) 45%, transparent); }
.badge.out { color: var(--oxblood); border: 1px solid color-mix(in srgb, var(--oxblood) 45%, transparent); }
/* pre-order: distinct from in-stock (green) and out-of-stock (oxblood) */
.badge.preorder { color: var(--violet); border: 1px solid color-mix(in srgb, var(--violet) 50%, transparent); }
.badge.stale { color: var(--muted); border: 1px solid var(--line); }
.badge.missing { color: var(--muted); border: 1px solid var(--line); }
/* owned/missing badge sits under a sleeve card's metadata (not over the art) */
.owned-badge { margin-left: 0; margin-top: 6px; display: inline-block; }
/* the missing owned-badge is a solid rose chip (more specific than .badge.missing) */
.owned-badge.missing {
  color: var(--rose-ink);
  background: var(--rose);
  border-color: transparent;
}
/* the ordered owned-badge is a solid amber chip (distinct from green owned
   and rose missing) */
.owned-badge.ordered {
  color: var(--amber-ink);
  background: var(--amber);
  border-color: transparent;
}
/* the non-interactive "master release" pill marks a Discogs master (an album
   grouping, not a specific pressing). A quiet FILLED slate chip with no pointer,
   hover or active affordance, so it never reads as clickable like the want tag
   (grey outline) and stays clear of the accent ownership badges (green owned,
   amber ordered, rose missing, violet wanted). Theme-aware via the neutral
   palette vars (both light and dark). */
.master-badge {
  margin-left: 0;
  margin-top: 6px;
  display: inline-block;
  color: var(--muted);
  background: color-mix(in srgb, var(--muted) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--muted) 35%, var(--line));
  cursor: default;
}
/* a per-card note under a sleeve's metadata (e.g. an order's shop + date) */
.sleeve-note { color: var(--muted); font-size: 11px; margin-top: 2px; }

/* the sleeve-tile price chip: a small solid-amber pill anchored to the
   bottom-right corner of the cover art (.sleeve is already position:relative,
   above). It never collides with the ownership/master/want tags — those sit
   below the art, in .sleeve-tags, not overlaid on it. Hidden at rest (opacity
   0 + no pointer events) and revealed only on hover/focus, so an idle grid
   stays uncluttered with dollar signs; it's a real <button>, so it's still
   reachable by keyboard (focus-within/focus-visible) regardless of hover
   support. The solid amber fill (matching .owned-badge.ordered) plus a drop
   shadow keeps it legible over any cover art, in both themes. */
.sleeve-price-chip {
  position: absolute;
  bottom: 8px;
  right: 8px;
  z-index: 2;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  color: var(--amber-ink);
  background: var(--amber);
  border: 1px solid transparent;
  box-shadow: var(--shadow);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s;
}
.sleeve-card:hover .sleeve-price-chip,
.sleeve-card:focus-within .sleeve-price-chip,
.sleeve-price-chip:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

/* the tag footer groups the ownership badge + want tag and is pushed to the
   bottom of the (stretched) card, so tag rows line up across a grid row
   regardless of title length. The gap handles spacing, so the badges/tags drop
   their individual margins inside the footer. */
.sleeve-tags {
  margin-top: auto;
  padding-top: 6px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.sleeve-tags .owned-badge,
.sleeve-tags .master-badge,
.sleeve-tags .want-tag { margin: 0; }

/* the interactive wantlist tag: a small quiet grey outline pill (＋ want) that
   turns into a SOLID violet chip (✓ want) when the record is in the wantlist.
   Violet is the only accent not used by the ownership badges (green owned,
   amber ordered, rose missing), so a wanted tag never reads as one of those.
   Theme-aware via the shop palette vars (both light and dark). */
.want-tag {
  display: inline-block;
  margin-left: 0;
  margin-top: 6px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
.want-tag:hover,
.want-tag:focus-visible {
  color: var(--violet);
  border-color: color-mix(in srgb, var(--violet) 45%, var(--line));
}
/* the wanted state is a solid violet chip — distinct from the green owned and
   amber ordered badges */
.want-tag.wanted {
  color: var(--violet-ink);
  background: var(--violet);
  border-color: transparent;
}
.want-tag.wanted:hover,
.want-tag.wanted:focus-visible {
  color: var(--violet-ink);
  background: color-mix(in srgb, var(--violet) 88%, black);
  border-color: transparent;
}
.want-tag:disabled { cursor: default; opacity: 0.75; }
/* on a price row, sit the want tag inline next to the ＋ order pill */
.price-row .title .want-tag { margin-top: 0; margin-left: 8px; }
/* the stale pill becomes an activatable refresh control */
button.badge {
  background: transparent;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
button.badge.stale.refreshable::before { content: "⟳ "; }
button.badge.stale.refreshable:hover,
button.badge.stale.refreshable:focus-visible {
  color: var(--amber);
  border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
  background: color-mix(in srgb, var(--amber) 14%, transparent);
}
button.badge:disabled { cursor: default; opacity: 0.75; }
button.badge:disabled::before { content: none; }
/* the quiet "＋ order" pill on a price row: muted at rest, amber on hover,
   and a settled green "✓ ordered" once recorded (kept unobtrusive) */
button.badge.order-add { color: var(--muted); border: 1px solid var(--line); }
button.badge.order-add:hover,
button.badge.order-add:focus-visible {
  color: var(--amber);
  border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
  background: color-mix(in srgb, var(--amber) 14%, transparent);
}
button.badge.order-add.done {
  color: var(--green);
  border-color: color-mix(in srgb, var(--green) 45%, transparent);
  opacity: 1;
}
.notes { font-family: var(--mono); font-size: 11px; color: var(--muted); padding: 8px 14px; }
/* the warming/errored phrases carry a `.has-tip` tooltip (see the reusable
   tooltip rules near the top of this file) naming the affected shops. */

/* ---- wantlist prices (the "check shop prices" affordance + grouped view) ---- */
/* the caption row keeps the eyebrow caption on the left and the price-check
   button beside it. */
.wl-cap-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
/* the button is a quiet clickable pill (reuses the .badge look): muted at rest,
   amber on hover. Theme-aware via the shop palette vars. */
button.badge.wl-price-btn {
  color: var(--muted);
  border: 1px solid var(--line);
  cursor: pointer;
}
button.badge.wl-price-btn:hover,
button.badge.wl-price-btn:focus-visible {
  color: var(--amber);
  border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
  background: color-mix(in srgb, var(--amber) 14%, transparent);
}
button.badge.wl-price-btn:disabled { cursor: default; opacity: 0.75; }
/* the grouped results below the tiles: reused price rows grouped per release. */
.wl-prices { display: flex; flex-direction: column; }
/* each release's shop rows are kept contiguous in a .wl-group; a subtle top
   border separates one release's rows from the next (no per-album header row). */
.wl-group + .wl-group {
  margin-top: 10px;
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

/* ---- states ---- */
.notice {
  font-family: var(--mono);
  font-size: 12.5px;
  padding: 9px 13px;
  border-radius: 8px;
  border: 1px solid var(--line);
  width: fit-content;
}
.notice.error { color: var(--oxblood); border-color: color-mix(in srgb, var(--oxblood) 45%, var(--line)); }
.notice.error.has-error-detail { cursor: help; }
.notice.busy { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 45%, var(--line)); }

details.raw { font-family: var(--mono); font-size: 12px; color: var(--muted); }
details.raw summary { cursor: pointer; }
details.raw pre {
  overflow-x: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px;
}

/* ---- composer ---- */
.composer {
  position: sticky;
  bottom: 0;
  display: flex;
  gap: 10px;
  width: 100%;
  max-width: var(--col);
  margin: 0 auto;
  padding: 14px 22px 20px;
  background: linear-gradient(to top, var(--bg) 72%, transparent);
  z-index: 20;
}
#message {
  flex: 1;
  padding: 12px 15px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  font-size: 15px;
  font-family: var(--ui);
}
#message::placeholder { color: var(--muted); }
#send {
  padding: 12px 20px;
  border-radius: 10px;
  border: 0;
  background: var(--amber);
  color: var(--amber-ink);
  font-weight: 750;
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
#send:disabled { opacity: 0.5; cursor: default; }

/* ---- enrichment decoration (Wikipedia / MusicBrainz / TheAudioDB) ----
   Appended below a Discogs card by enrichmentSection: an About block, a lazy
   image row, metadata chips and a links row. Everything is width-bound to the
   card so wide banners / tall fanart can never bleed past the bubble. */
.enrichment {
  max-width: 100%;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.en-about { display: flex; flex-direction: column; gap: 4px; }
.en-about-short { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink); }
.en-about-long { margin: 0; font-size: 13px; line-height: 1.55; color: var(--muted); }
/* link-style toggle, aligned to the .md-link green; not a pill */
.en-more {
  align-self: flex-start;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--green);
  cursor: pointer;
}
.en-more:hover,
.en-more:focus-visible { color: color-mix(in srgb, var(--green) 60%, #fff); }
/* image row: wraps, and each thumb is capped so nothing forces the card wider */
.en-images { display: flex; flex-wrap: wrap; gap: 12px 8px; max-width: 100%; margin: 8px 0; }
.en-img {
  max-width: 100%;
  max-height: 96px;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 6px;
  background: var(--raised);
}
.en-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.en-chip {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 10px;
}
.en-links { display: flex; flex-wrap: wrap; gap: 8px; }
.en-link {
  padding: 2px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12px;
}
.en-link:hover,
.en-link:focus-visible {
  color: var(--amber);
  background: transparent;
  border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
}

:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; border-radius: 4px; }

@media (max-width: 560px) {
  .tagline { display: none; }
  .covers { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
  .transcript, .composer { padding-left: 14px; padding-right: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .chip .spin { animation: none; }
  .sleeve .vinyl,
  .sleeve:hover .vinyl,
  .sleeve-card:focus-visible .vinyl,
  .sleeve-card:focus-within .vinyl { transition: none; transform: none; }
  /* the price chip still hides/reveals, just instantly (no opacity fade) */
  .sleeve-price-chip { transition: none; }
  /* no pulsing — the static "waaxy is thinking…" label alone carries it */
  .thinking-dots .dot { animation: none; }
}
