/* ============================================================
   Laivy Hart shared theme: Pure Bordeaux palette, typography,
   light/dark tokens, and shared UI primitives.
   Loaded by index.html, about.html and admin.html so the whole
   site feels like one place in two lightings.
   ============================================================ */

/* ------------------------------------------------------------
   Theming is two independent dimensions:
     data-color = bordeaux | emerald | royal | gold  (the accent family)
     data-theme = dark | light                       (the lighting)
   The final look is color x lighting. The accent family (accent 1/2/3,
   wash tones) is identical in both lightings; the backgrounds and text
   change with the lighting. Both attributes live on <html> and are set
   before first paint by an inline script on each page.
   ------------------------------------------------------------ */
:root{
  /* Bordeaux is the default color theme. accent-*-rgb are bare triplets so
     they can be dropped into rgba() for accent-tinted shadows and washes. */
  --accent-1:#9E2B4E;
  --accent-2:#C13584;
  --accent-3:#E0699A;
  --accent-1-rgb:158,43,78;
  --accent-2-rgb:193,53,132;
  --wash-1:#4A1230;
  --wash-2:#7A1F6B;
  --stroke-alpha:0.16;
  /* RADIO-2: the filled (playing) station button. Darker than accent-1/2 so
     white text clears 4.5:1 in every color (bordeaux 8.6/6.5, emerald 7.5/5.8,
     royal 7.9/5.9, gold 6.4/5.0); the plain accent gradient does not (gold's
     accent-2 is 2.2:1 under white). */
  --station-on-1:#8A2546;
  --station-on-2:#A82C6E;

  /* Derived accent tokens, shared by every color theme. Because these hold
     var() references, they resolve per-element against whichever accent the
     active data-color sets, so switching color recolors the whole UI. */
  --accent-grad:linear-gradient(135deg, var(--accent-1), var(--accent-2));
  --accent-solid:var(--accent-1);
  --logo-grad:linear-gradient(135deg, var(--accent-1), var(--accent-3));

  --font-heb:'Frank Ruhl Libre', serif;
  --font-display:'Fraunces', 'Frank Ruhl Libre', Georgia, serif;
  --font-ui:'Inter', system-ui, -apple-system, sans-serif;

  --radius:20px;
  --radius-sm:12px;
  --radius-xs:9px;

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

/* ---- Color themes: accent family + wash (identical in both lightings) ---- */
[data-color="emerald"]{
  --accent-1:#0F7B5A;
  --accent-2:#14A67C;
  --accent-3:#4FD1A5;
  --accent-1-rgb:15,123,90;
  --accent-2-rgb:20,166,124;
  --wash-1:#0E4A38;
  --wash-2:#166B52;
  --station-on-1:#0B6147;
  --station-on-2:#0E7356;
}
[data-color="royal"]{
  --accent-1:#3A4FC4;
  --accent-2:#5B6BF0;
  --accent-3:#8E9BFF;
  --accent-1-rgb:58,79,196;
  --accent-2-rgb:91,107,240;
  --wash-1:#1E2456;
  --wash-2:#3730A3;
  --station-on-1:#3446B0;
  --station-on-2:#4656D6;
}
[data-color="gold"]{
  --accent-1:#B08423;
  --accent-2:#D6A93E;
  --accent-3:#EBCB7E;
  --accent-1-rgb:176,132,35;
  --accent-2-rgb:214,169,62;
  --wash-1:#4A3714;
  --wash-2:#6B5220;
  --stroke-alpha:0.18;
  --station-on-1:#7A5A14;
  --station-on-2:#8C6A1C;
}

/* ---- Lighting: neutral tokens (same across all color themes) ---- */
/* Dark is the default lighting. */
:root,
[data-theme="dark"]{
  --glass-bg:rgba(255,255,255,0.045);
  --glass-border:rgba(255,255,255,0.10);
  --glass-hi:rgba(255,255,255,0.07);
  --line:rgba(255,255,255,0.08);

  --chip-bg:rgba(255,255,255,0.06);
  --chip-border:rgba(255,255,255,0.12);

  --play-bg:var(--accent-grad);
  --play-fg:#ffffff;

  /* Accent-tinted hairline: accent-2 in dark, accent-1 in light. */
  --accent-stroke:rgba(var(--accent-2-rgb), var(--stroke-alpha));

  /* BADGE GOLD. Every badge on every surface uses this ONE token -- rows,
     now-playing, the song page, artist cards and the trophy case. Deliberately
     NOT a colour per badge family: the owner asked for one warm award tone,
     not a potpourri.

     Theme-aware the way --star-accent is, and for the same measured reason: a
     bright gold on an ivory ground washes out (that is the trap the rating
     stars hit at 1.92:1). Bright metallic gold on the dark grounds, deep brass
     on the light ones. Measured against all four backgrounds in each lighting:
     dark 11.83-14.28:1, light 5.32-5.51:1. One value covers all four light
     backgrounds, so unlike --star-accent no per-theme override is needed. */
  --badge-gold:#EFC868;
  /* The artist-card overlay sits on a fixed dark plate over artwork, so it
     does NOT follow the lighting -- it always wants the bright gold. */
  --badge-gold-bright:#EFC868;
  /* ARTIST-2: the milestone ring/ribbon's silver and bronze. Same fixed-plate
     reasoning as --badge-gold-bright (they sit on the card's own
     rgba(0,0,0,0.82) scrim band, identical in every theme, same convention as
     .hero-scrim) -- so, like --badge-gold-bright, neither gets a light-mode
     override. Measured against the worst case (a pure-white cover under that
     scrim): gold-bright 8.50:1, silver 9.87:1, bronze 5.83:1 -- all clear
     4.5:1 with room to spare. */
  --badge-silver:#D7DCE4;
  --badge-bronze:#D79E6E;

  /* The rating stars, same rule as the hairline above and for the same reason:
     accent-2 is the bright shade and reads on a dark ground, accent-1 is the
     deep one and is what stays legible on ivory. A star is a control, not
     decoration, so it has to hold up in both lightings. */
  --star-accent:var(--accent-2);
  /* The contribute invitation's text/outline (nav link, Upload button). It is
     TEXT, so it must clear 4.5:1 on the page ground, which accent-2 does not
     in the light themes (gold-light 1.92:1). The pale accent-3 on the dark
     grounds, the deep accent-1 on the light ones, and emerald/gold light one
     step deeper still -- see the overrides below the palette blocks. */
  --invite-accent:var(--accent-3);

  --ambient-opacity:0.55;
  --ambient-blur:72px;

  color-scheme:dark;
}

[data-theme="light"]{
  --glass-bg:rgba(255,255,255,0.58);
  --glass-border:rgba(42,16,24,0.09);
  --glass-hi:rgba(255,255,255,0.75);
  --line:rgba(42,16,24,0.10);

  --chip-bg:rgba(42,16,24,0.05);
  --chip-border:rgba(42,16,24,0.12);

  --play-bg:var(--accent-solid);
  --play-fg:#ffffff;

  --accent-stroke:rgba(var(--accent-1-rgb), var(--stroke-alpha));
  --star-accent:var(--accent-1);
  --invite-accent:var(--accent-1);
  /* Deep brass: the light counterpart of --badge-gold. See the note above. */
  --badge-gold:#7F5B10;

  --ambient-opacity:0.16;
  --ambient-blur:84px;

  color-scheme:light;
}

/* LISTEN-1: every native <select>'s OPENED dropdown, in all 8 theme x
   lighting combinations (option/optgroup are not covered by the :root
   color-scheme above on their own in every browser, which is how a select
   ends up white text on a white/light native popup over a dark theme --
   "white on white"). color-scheme set directly on the control, PLUS the
   theme's own solid, opaque surface + text tokens on option/optgroup
   (including the optgroup label itself) -- the one thing every browser's
   native dropdown reliably honors regardless of color-scheme support. */
select{color-scheme:dark;}
[data-theme="light"] select{color-scheme:light;}
/* Only option/optgroup -- not select itself, which keeps whatever closed-
   state background each page already gives it (.lv-input, .sort-select,
   admin's own field styles, ...). This is purely about the opened list. */
option, optgroup{background-color:var(--bg-2); color:var(--text);}

/* ---- Backgrounds + text: color theme x lighting ---- */
/* :root doubles as the Bordeaux-dark fallback if JS is disabled. */
:root,
[data-color="bordeaux"][data-theme="dark"]{
  --bg:#140810; --bg-2:#1d0d17;
  --text:#F4E9EE; --text-dim:#B598A4; --text-faint:#7C6470;
}
[data-color="bordeaux"][data-theme="light"]{
  --bg:#F5ECEE; --bg-2:#efe1e5;
  --text:#2A1018; --text-dim:#8A6875; --text-faint:#A88E99;
}
[data-color="emerald"][data-theme="dark"]{
  --bg:#08130F; --bg-2:#0E1F17;
  --text:#E9F5EF; --text-dim:#94B8AB; --text-faint:#5E7C71;
}
[data-color="emerald"][data-theme="light"]{
  --bg:#ECF4F0; --bg-2:#E2EDE7;
  --text:#0C231A; --text-dim:#5E7C71; --text-faint:#8AA89C;
}
[data-color="royal"][data-theme="dark"]{
  --bg:#0A0C1A; --bg-2:#12152B;
  --text:#ECEEF9; --text-dim:#9599B8; --text-faint:#63678A;
}
[data-color="royal"][data-theme="light"]{
  --bg:#ECEEF7; --bg-2:#E2E5F1;
  --text:#12152C; --text-dim:#63678A; --text-faint:#9599B8;
}
[data-color="gold"][data-theme="dark"]{
  --bg:#14100A; --bg-2:#201A0F;
  --text:#F5EEDE; --text-dim:#B8A582; --text-faint:#7C6E52;
}
[data-color="gold"][data-theme="light"]{
  --bg:#F5F0E4; --bg-2:#EDE6D3;
  --text:#2A2110; --text-dim:#7C6E52; --text-faint:#A89A76;
}

/* Gold is the one accent that will not clear 3:1 against its own ivory ground
   even at accent-1: #B08423 on #F5F0E4 measures 2.99:1. An outline star is a
   control, not decoration, so gold-light takes one step deeper (3.74:1). Every
   other colour x lighting uses its accent untouched. */
[data-color="gold"][data-theme="light"]{ --star-accent:#9C7420; }
/* --invite-accent: accent-1 is 4.12:1 (emerald) and 2.72:1 (gold) on the
   Upload button's wash in light, so both take a deeper shade of their own hue. */
[data-color="emerald"][data-theme="light"]{ --invite-accent:#0B6147; }
[data-color="gold"][data-theme="light"]{ --invite-accent:#7A5A14; }

*{box-sizing:border-box;}
html,body{margin:0; padding:0;}
body{
  background:var(--bg);
  color:var(--text);
  font-family:var(--font-ui);
  -webkit-font-smoothing:antialiased;
  transition:background 0.5s var(--ease), color 0.4s var(--ease);
}

@media (prefers-reduced-motion: reduce){
  *{animation-duration:0.001ms !important; transition-duration:0.001ms !important;}
}

/* ---------- Glass surface ---------- */
.glass{
  background:var(--glass-bg);
  border:1px solid var(--glass-border);
  border-radius:var(--radius);
  backdrop-filter:blur(22px) saturate(1.25);
  -webkit-backdrop-filter:blur(22px) saturate(1.25);
}

/* ---------- Brand wordmark ---------- */
.wordmark{
  font-family:var(--font-display);
  font-weight:600;
  font-size:1.15rem;
  letter-spacing:0.01em;
  display:inline-flex;
  align-items:center;
  gap:0.5rem;
  color:var(--text);
}
.wordmark .brand-dot{
  width:0.7rem; height:0.7rem; border-radius:50%;
  background:var(--accent-grad);
  box-shadow:0 0 14px rgba(var(--accent-2-rgb),0.6);
  flex-shrink:0;
}
.wordmark .hl-ai{
  background:var(--accent-grad);
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent;
  font-style:italic;
}

/* ---------- LAIVYHART logo (one line, color shift is the separator) ---------- */
.logo{
  display:inline-flex; align-items:baseline; white-space:nowrap;
  font-family:'Archivo Black', var(--font-ui);
  font-weight:900; text-transform:uppercase;
  letter-spacing:-0.03em; line-height:1;
  font-size:clamp(1.5rem, 5.2vw, 2.4rem); text-decoration:none;
}
.logo .l1{ color:var(--text); }
.logo .l2{
  background:var(--logo-grad);
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent;
}

/* ---------- Header wordmark (the brand artwork) ----------
   Replaces the CSS text logo on the public pages; admin.html keeps the text
   one. 34px tall, 3:1, so 102x34 with both dimensions set to reserve the box.

   Light mode: the artwork's LAIVY letters are near-ivory and would all but
   vanish on the pale header, so on the light theme only, the logo sits in a
   tight pill of the artwork's own near-black. It is the smallest change that
   keeps BOTH words legible without touching the artwork's colours. Dark themes
   show the bare wordmark with no pill. */
.logo-brand{
  display:inline-flex; align-items:center; flex-shrink:0;
  line-height:0; padding:0; border-radius:11px; text-decoration:none;
}
.logo-brand .logo-img{display:block; height:34px; width:102px;}
.logo-brand:focus-visible{outline:2px solid var(--accent-2); outline-offset:3px;}
[data-theme="light"] .logo-brand{
  background:#130A11; padding:5px 9px;
  box-shadow:0 1px 5px rgba(42,16,24,0.16);
}

/* ---------- Small icon / ghost buttons ---------- */
.icon-btn{
  width:2.2rem; height:2.2rem; border-radius:50%;
  border:1px solid var(--glass-border);
  background:var(--glass-hi);
  color:var(--text);
  cursor:pointer; font-size:0.95rem;
  display:inline-flex; align-items:center; justify-content:center;
  transition:transform 0.2s var(--ease), border-color 0.2s var(--ease);
}
.icon-btn:hover{transform:translateY(-1px); border-color:var(--accent-2);}

/* ---------- Color-theme swatch picker ---------- */
/* Four small round swatches, each showing its own theme gradient (fixed, so
   they always advertise the color they switch to). The active one wears a
   ring in the current text color so it reads in both light and dark. */
.palette{display:inline-flex; align-items:center; gap:0.4rem;}
.swatch{
  width:1.15rem; height:1.15rem; border-radius:50%;
  border:none; padding:0; cursor:pointer;
  box-shadow:0 0 0 1px var(--glass-border);
  transition:transform 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.swatch:hover{transform:translateY(-1px) scale(1.1);}
.swatch:focus-visible{outline:2px solid var(--accent-2); outline-offset:2px;}
.swatch.is-active{
  transform:scale(1.12);
  box-shadow:0 0 0 2px var(--bg), 0 0 0 3px var(--text);
}
/* Swatch fills are deliberately DEEPER jewel tones than the live accents, so
   the picker reads as a set of dark, elegant gems rather than bright circles.
   These colors are cosmetic to the swatch only; they do not affect theming. */
.swatch.bordeaux{background:linear-gradient(135deg, #4A0F22, #7A1B3C);}
.swatch.emerald{background:linear-gradient(135deg, #063326, #0C5A42);}
.swatch.royal{background:linear-gradient(135deg, #10184A, #232C7A);}
.swatch.gold{background:linear-gradient(135deg, #4A3410, #8A6420);}

@media (max-width: 460px){
  .swatch{width:1.05rem; height:1.05rem;}
  .palette{gap:0.32rem;}
}

/* ---------- Top navigation (every page) ----------
   Three understated text links in the header: body font, small, muted, with no
   background, border or icon. The current page sits in the normal text color
   under a hairline rule. Below 900px the header stacks, so the nav drops to its
   own line directly beneath the wordmark rather than squeezing the theme
   controls. */
.site-nav{display:flex; align-items:center; gap:1.1rem; flex-shrink:0; margin-right:auto;}
.site-nav a{
  font-family:var(--font-ui); font-size:0.82rem; letter-spacing:0.02em;
  color:var(--text-faint); text-decoration:none; white-space:nowrap;
  padding-bottom:2px; border-bottom:1px solid transparent;
  transition:color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.site-nav a:hover{color:var(--text-dim);}
.site-nav a.is-current{color:var(--text); border-bottom-color:currentColor;}
.site-nav a:focus-visible{outline:2px solid var(--accent-2); outline-offset:3px; border-radius:3px;}
@media (max-width:900px){
  .site-nav{flex-basis:100%; margin-right:0; gap:1.25rem;}
}
/* The account slot sits inside the nav and now holds two things (the
   "Share your music" link, then Sign in or the chip), so it lays them out
   with the nav's own spacing. */
#account-slot{display:flex; align-items:center; gap:inherit;}
/* The contribute link is the one nav item in the accent colour: the site's
   purpose, said once in every header, without a box or a badge. */
.site-nav a.nav-share{color:var(--invite-accent);}
.site-nav a.nav-share:hover{color:var(--text);}
.site-nav a.nav-share.is-current{color:var(--invite-accent); border-bottom-color:currentColor;}
/* Four links plus "Sign in or join" is wider than a phone; let the account
   slot drop to a second line rather than overflow. */
@media (max-width:560px){
  .site-nav{flex-wrap:wrap; row-gap:0.55rem;}
}

.quiet-link{
  font-family:var(--font-ui);
  font-size:0.82rem;
  color:var(--text-dim);
  text-decoration:none;
  letter-spacing:0.02em;
  transition:color 0.2s var(--ease);
}
.quiet-link:hover{color:var(--text);}

/* ---------- Share / Install buttons, toast, iOS hint ---------- */
.pill-btn{
  font-family:var(--font-ui); font-size:0.72rem; letter-spacing:0.02em;
  padding:0.4rem 0.8rem; border-radius:16px; cursor:pointer; white-space:nowrap;
  background:var(--chip-bg); border:1px solid var(--chip-border); color:var(--text-dim);
  transition:color 0.2s var(--ease), border-color 0.2s var(--ease), filter 0.2s var(--ease);
}
.pill-btn:hover{color:var(--text); border-color:var(--accent-2);}
.pill-btn.accent{background:var(--accent-grad); border-color:transparent; color:#fff;}
.pill-btn.accent:hover{filter:brightness(1.08); color:#fff;}
.pill-btn[hidden]{display:none;}

.laivy-toast{
  position:fixed; left:50%; bottom:1.4rem; transform:translateX(-50%) translateY(0.8rem);
  background:var(--bg-2); color:var(--text); border:1px solid var(--glass-border);
  padding:0.6rem 1rem; border-radius:14px; font-family:var(--font-ui); font-size:0.82rem;
  box-shadow:0 12px 30px rgba(0,0,0,0.4); opacity:0; pointer-events:none; z-index:200;
  transition:opacity 0.25s var(--ease), transform 0.25s var(--ease);
}
.laivy-toast.show{opacity:1; transform:translateX(-50%) translateY(0);}

.ios-hint{
  position:fixed; left:1rem; right:1rem; bottom:1rem; margin:0 auto; max-width:26rem;
  display:flex; align-items:center; gap:0.7rem; z-index:150;
  background:var(--bg-2); border:1px solid var(--glass-border); border-radius:14px;
  padding:0.7rem 0.9rem; box-shadow:0 12px 30px rgba(0,0,0,0.4);
  font-family:var(--font-ui); font-size:0.82rem; color:var(--text);
}
.ios-hint strong{color:var(--accent-2); font-weight:600;}
.ios-hint-close{margin-left:auto; background:none; border:none; color:var(--text-faint); font-size:1.15rem; line-height:1; cursor:pointer; padding:0 0.2rem;}
.ios-hint[hidden]{display:none;}

/* ---------- Chips and pills ---------- */
.chip{
  font-family:var(--font-ui);
  font-size:0.68rem;
  letter-spacing:0.04em;
  text-transform:uppercase;
  padding:0.3rem 0.66rem;
  border-radius:20px;
  background:var(--chip-bg);
  border:1px solid var(--chip-border);
  color:var(--text-dim);
  cursor:pointer;
  white-space:nowrap;
  transition:all 0.2s var(--ease);
}
.chip:hover{color:var(--text); border-color:var(--accent-2);}
.chip.active{
  background:var(--accent-grad);
  border-color:transparent;
  color:#fff;
}

/* ---------- Ambient background wash ---------- */
.ambient{
  position:absolute; inset:0;
  overflow:hidden;
  z-index:0;
  pointer-events:none;
  border-radius:inherit;
  opacity:var(--ambient-opacity);
  transition:opacity 0.6s var(--ease);
}
.ambient .blob{
  position:absolute;
  width:60%; aspect-ratio:1;
  border-radius:50%;
  filter:blur(var(--ambient-blur));
  animation:drift 24s var(--ease) infinite alternate;
  animation-play-state:paused;
}
.ambient.is-playing .blob{animation-play-state:running;}
.ambient .blob.b1{background:radial-gradient(circle, var(--accent-2), transparent 68%); top:-12%; left:-10%;}
.ambient .blob.b2{background:radial-gradient(circle, var(--accent-1), transparent 68%); bottom:-18%; right:-12%; animation-duration:31s;}
.ambient .blob.b3{background:radial-gradient(circle, var(--wash-2), transparent 68%); top:35%; left:38%; animation-duration:38s; width:50%;}

@keyframes drift{
  0%{transform:translate(0,0) scale(1);}
  50%{transform:translate(8%, 6%) scale(1.14);}
  100%{transform:translate(-6%, -8%) scale(0.96);}
}

/* ---------- Playing equalizer ---------- */
.eq{display:inline-flex; align-items:flex-end; gap:2px; height:14px;}
.eq span{
  width:3px; background:var(--accent-2); border-radius:2px;
  animation:eqbar 0.9s var(--ease) infinite;
}
.eq span:nth-child(1){animation-delay:-0.1s;}
.eq span:nth-child(2){animation-delay:-0.45s;}
.eq span:nth-child(3){animation-delay:-0.25s;}
.eq span:nth-child(4){animation-delay:-0.6s;}
@keyframes eqbar{
  0%,100%{height:25%;}
  50%{height:100%;}
}

/* ---------- Scrollbars ---------- */
*::-webkit-scrollbar{width:9px; height:9px;}
*::-webkit-scrollbar-thumb{background:var(--line); border-radius:8px;}
*::-webkit-scrollbar-thumb:hover{background:var(--text-faint);}

/* ============================================================
   Account UI: sign-in link, chip + menu, modal, onboarding panel, settings.
   Shared by index.html, admin.html and about.html (all three load this file).
   Everything here is built from theme tokens, so it recolors with the palette
   and works in light and dark like the rest of the site.
   ============================================================ */
.lv-signin-link{
  background:none; border:0; padding:0.3rem 0.1rem; cursor:pointer;
  font-family:var(--font-ui); font-size:0.92rem; color:var(--text);
  border-bottom:1px solid var(--chip-border);
}
.lv-signin-link:hover{ color:var(--accent-2); border-bottom-color:var(--accent-2); }
.lv-signin-link:focus-visible{ outline:2px solid var(--accent-2); outline-offset:3px; }

/* The wrap carries the stacking context the menu is positioned against. It
   needs its own z-index, and one high enough to clear the dock: the menu's
   own z-index only competes INSIDE whatever context its ancestors create, so
   a bare z-index on .lv-menu could never lift it above the loves row (150) or
   the mini-player (200). This is why the menu was rendering underneath the
   now-playing panel. */
.lv-chip-wrap{ position:relative; display:inline-flex; z-index:300; }
.lv-chip{
  width:2rem; height:2rem; border-radius:50%; cursor:pointer; overflow:hidden;
  display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--glass-border); background:var(--accent-grad);
  color:#fff; font-family:var(--font-ui); font-weight:700; font-size:0.85rem;
  text-shadow:0 1px 2px rgba(0,0,0,0.45); padding:0;
}
.lv-chip img{ width:100%; height:100%; object-fit:cover; display:block; }
.lv-chip:focus-visible{ outline:2px solid var(--accent-2); outline-offset:2px; }

.lv-menu{
  position:absolute; top:calc(100% + 0.5rem); right:0; z-index:301; min-width:11rem;
  display:flex; flex-direction:column; padding:0.35rem; border-radius:12px;
  background:var(--glass-hi); border:1px solid var(--glass-border);
  box-shadow:0 14px 40px rgba(0,0,0,0.34);
}
.lv-menu[hidden]{ display:none; }
/* The account-chip menu hangs over the homepage banner, where the shared
   --glass-hi fill (7% white in dark) is see-through. Same solid treatment as
   .lv-menu.mood-menu: --bg-2 is an opaque hex in all eight theme x lighting
   combinations. Stacking (wrap 300 / menu 301) is unchanged. */
.lv-menu.account-menu{
  background:var(--bg-2);
  box-shadow:0 18px 48px rgba(0,0,0,0.45);
  pointer-events:auto;
}
.lv-menu a, .lv-menu button{
  text-align:left; background:none; border:0; cursor:pointer; text-decoration:none;
  padding:0.5rem 0.65rem; border-radius:8px; color:var(--text);
  font-family:var(--font-ui); font-size:0.9rem;
}
.lv-menu a:hover, .lv-menu button:hover{ background:rgba(var(--accent-1-rgb), 0.14); color:var(--accent-2); }

.lv-modal-backdrop{
  position:fixed; inset:0; z-index:200; display:flex; align-items:center; justify-content:center;
  padding:1.2rem; background:rgba(0,0,0,0.55); backdrop-filter:blur(3px);
}
.lv-modal-backdrop[hidden]{ display:none; }
.lv-modal, .lv-onboard{
  position:relative; width:100%; max-width:24rem; max-height:90vh; overflow:auto;
  padding:1.5rem 1.4rem 1.2rem; border-radius:18px;
  background:var(--bg-2); border:1px solid var(--glass-border);
  box-shadow:0 24px 70px rgba(0,0,0,0.45); color:var(--text);
}
.lv-onboard{
  position:fixed; z-index:150; right:1.1rem; bottom:1.1rem; max-width:22rem;
}
@media (max-width:560px){ .lv-onboard{ left:0.8rem; right:0.8rem; bottom:0.8rem; max-width:none; } }
.lv-onboard-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:0.6rem; }
.lv-onboard-actions{ display:flex; gap:0.6rem; margin-top:0.9rem; flex-wrap:wrap; }

.lv-modal-x{
  position:absolute; top:0.6rem; right:0.7rem; background:none; border:0; cursor:pointer;
  font-size:1.5rem; line-height:1; color:var(--text-dim); padding:0.15rem 0.35rem; border-radius:8px;
}
.lv-onboard .lv-modal-x{ position:static; }
.lv-modal-x:hover{ color:var(--text); }
.lv-modal-title, .lv-onboard h2{
  font-family:var(--font-display); font-weight:600; margin:0 0 0.15rem; font-size:1.3rem; color:var(--text);
}
.lv-modal-sub{ margin:0 0 1rem; color:var(--text-dim); font-size:0.9rem; }
/* FIX-3: the one-line reason a gated action opened this modal for (e.g. "Sign
   in to rate the words and the music"). Hidden with no inline style attribute
   needed -- the [hidden] attribute set in JS is enough. */
.lv-modal-reason{ margin:0 0 1rem; padding:0.55rem 0.75rem; border-radius:10px;
  background:var(--chip-bg); border:1px solid var(--chip-border); color:var(--text); font-size:0.88rem; }

.lv-label{ display:block; margin:0.8rem 0 0.3rem; font-family:var(--font-ui); font-size:0.82rem; color:var(--text-dim); }
.lv-label span{ opacity:0.75; }
.lv-input{
  width:100%; padding:0.6rem 0.7rem; border-radius:10px; font-family:var(--font-ui); font-size:0.95rem;
  background:var(--glass); border:1px solid var(--chip-border); color:var(--text);
}
.lv-input:focus{ outline:2px solid var(--accent-2); outline-offset:1px; }
.lv-textarea{ resize:vertical; min-height:5rem; }
.lv-handle-row{ display:flex; align-items:center; gap:0.35rem; }
.lv-handle-pre{ font-family:var(--font-ui); font-size:0.78rem; color:var(--text-faint); white-space:nowrap; }
.lv-handle-row .lv-input{ flex:1 1 auto; min-width:0; }

.lv-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:0.5rem;
  width:100%; margin-top:0.75rem; padding:0.7rem 1rem; border-radius:11px; cursor:pointer;
  font-family:var(--font-ui); font-size:0.95rem; border:1px solid transparent; line-height:1.2;
}
.lv-btn.filled{ background:var(--accent-grad); color:#fff; }
.lv-btn.filled:hover{ filter:brightness(1.07); }
.lv-btn.outlined{ background:none; border-color:var(--chip-border); color:var(--text); }
.lv-btn.outlined:hover{ border-color:var(--accent-2); }
.lv-btn.ghost{ background:none; border-color:transparent; color:var(--text-dim); width:auto; }
.lv-btn.ghost:hover{ color:var(--text); }
.lv-btn.danger{ background:#8d2230; color:#fff; width:auto; }
.lv-btn.danger:disabled{ opacity:0.45; cursor:not-allowed; }
.lv-onboard-actions .lv-btn{ width:auto; margin-top:0; }

.lv-or{ display:flex; align-items:center; gap:0.7rem; margin:1rem 0 0.2rem; color:var(--text-faint); font-size:0.8rem; }
.lv-or::before, .lv-or::after{ content:''; flex:1; height:1px; background:var(--chip-border); }

.lv-err{ margin:0.6rem 0 0; color:#ff8497; font-size:0.85rem; }
.lv-ok{ margin:0.6rem 0 0; color:var(--accent-3); font-size:0.85rem; }
.lv-hint{ margin:0.3rem 0 0; color:var(--text-faint); font-size:0.78rem; }
.lv-hint.is-ok{ color:var(--accent-3); }
.lv-hint.is-bad{ color:#ff8497; }
.lv-sent{ margin:0.4rem 0; font-size:0.95rem; }
.lv-modal-foot{ margin:1rem 0 0; color:var(--text-faint); font-size:0.78rem; }
.lv-modal-foot a{ color:var(--accent-3); }

.lv-settings{ max-width:34rem; margin:0 auto; padding:1.4rem 1.5rem 5rem; }
.lv-settings h1{ font-family:var(--font-display); font-weight:600; font-size:1.8rem; margin:0 0 1rem; }
.lv-rule{ border:0; border-top:1px solid var(--chip-border); margin:2rem 0 1.2rem; }
.lv-danger-h{ font-family:var(--font-display); font-size:1.1rem; margin:0 0 0.3rem; color:var(--text); }
.lv-auth-wait{ padding:4rem 1.5rem; text-align:center; color:var(--text-dim); font-family:var(--font-ui); }

/* ARTIST-3: My page's neutral section heading -- same size as .lv-danger-h,
   none of its red-flag connotation. Used by My songs/My picks/Following. */
.lv-section-h{ font-family:var(--font-display); font-weight:600; font-size:1.1rem; margin:0 0 0.3rem; color:var(--text); }

/* ARTIST-3: My picks -- the add-search and each pick row. */
.lv-pick-search{ position:relative; margin-top:0.5rem; }
.lv-pick-results{
  position:absolute; left:0; right:0; top:100%; z-index:5; margin-top:0.25rem;
  background:var(--bg-2); border:1px solid var(--chip-border); border-radius:10px;
  max-height:16rem; overflow-y:auto; box-shadow:0 10px 28px rgba(0,0,0,0.28);
}
.lv-pick-results:empty{ display:none; }
.lv-pick-result{
  display:block; width:100%; text-align:left; padding:0.55rem 0.8rem; border:none; background:none;
  color:var(--text); font-family:var(--font-ui); font-size:0.9rem; cursor:pointer;
}
.lv-pick-result:hover{ background:var(--glass-hi); }
.lv-pick-by{ color:var(--text-faint); font-size:0.82rem; font-style:italic; }
.lv-pick-row{
  padding:0.8rem 0; border-bottom:1px solid var(--line);
}
.lv-pick-row:last-child{ border-bottom:none; }
.lv-pick-head{ display:flex; align-items:center; justify-content:space-between; gap:0.8rem; }
.lv-pick-main{ display:flex; flex-direction:column; min-width:0; }
.lv-pick-title{ font-family:var(--font-ui); font-weight:600; font-size:0.95rem; color:var(--text); }
.lv-pick-reorder{ display:flex; gap:0.3rem; flex-shrink:0; }
.lv-pick-move{
  width:1.8rem; height:1.8rem; border-radius:50%; border:1px solid var(--chip-border);
  background:var(--chip-bg); color:var(--text-dim); cursor:pointer; font-size:0.9rem; line-height:1;
}
.lv-pick-move:hover:not(:disabled){ border-color:var(--accent-2); color:var(--text); }
.lv-pick-move:disabled{ opacity:0.35; cursor:not-allowed; }
.lv-pick-note{ margin-top:0.5rem; }
.lv-pick-row .lv-btn.lv-pick-remove{ width:auto; margin-top:0.4rem; padding:0.4rem 0.9rem; font-size:0.82rem; }

/* ARTIST-3: Following list. */
.lv-following-row{ display:flex; align-items:center; gap:0.7rem; padding:0.6rem 0; border-bottom:1px solid var(--line); }
.lv-following-row:last-child{ border-bottom:none; }
.lv-following-avatar{
  width:36px; height:36px; flex:0 0 36px; border-radius:50%; object-fit:cover; display:block;
  border:1px solid var(--glass-border);
}
.lv-following-avatar.is-initial{
  display:flex; align-items:center; justify-content:center;
  background:var(--accent-grad); color:#fff; font-family:var(--font-display); font-weight:600; font-size:0.95rem;
}
.lv-following-name{ flex:1 1 auto; min-width:0; color:var(--text); font-family:var(--font-ui); font-weight:600; font-size:0.92rem; text-decoration:none; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.lv-following-name:hover{ color:var(--accent-2); }
.lv-following-unfollow{ width:auto; margin-top:0; padding:0.35rem 0.8rem; font-size:0.8rem; flex-shrink:0; }

/* My dedications (DED-2) */
.lv-ded-row{ display:flex; align-items:center; gap:0.7rem; padding:0.7rem 0; border-bottom:1px solid var(--line); flex-wrap:wrap; }
.lv-ded-row:last-child{ border-bottom:none; }
.lv-ded-main{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:0.1rem; }
.lv-ded-recipient{ font-family:var(--font-ui); font-weight:600; font-size:0.92rem; color:var(--text); }
.lv-ded-meta{ font-family:var(--font-ui); font-size:0.78rem; color:var(--text-faint); }
.lv-ded-status{ font-family:var(--font-ui); font-size:0.68rem; text-transform:uppercase; letter-spacing:0.05em;
  color:var(--accent-3); flex-shrink:0; }
.lv-ded-status.is-removed{ color:var(--text-faint); }
.lv-ded-actions{ display:flex; gap:0.4rem; flex-wrap:wrap; }
.lv-ded-actions .lv-btn{ width:auto; margin-top:0; padding:0.35rem 0.8rem; font-size:0.8rem; flex-shrink:0; }

/* ============================================================
   Public artist page (/artist/<handle>), 1A-3.
   Built from theme tokens like everything else, so it follows the palette and
   works in light and dark. No play counts appear here by design.
   ============================================================ */
.artist-wrap{ max-width:1100px; margin:0 auto; padding:1.4rem 1.5rem 6rem; }
.artist-loading{ color:var(--text-dim); font-family:var(--font-ui); padding:3rem 0; text-align:center; }
.artist-missing{ text-align:center; padding-top:4rem; }
.artist-missing h1{ font-family:var(--font-display); font-weight:600; font-size:1.9rem; margin:0 0 0.2rem; }
.artist-he{ color:var(--text-dim); font-family:var(--font-heb); }

.artist-head{ display:flex; gap:1.6rem; align-items:flex-start; margin:0 0 2.2rem; }
.artist-avatar{
  width:160px; height:160px; flex:0 0 160px; border-radius:50%; object-fit:cover; display:block;
  border:1px solid var(--glass-border); box-shadow:0 12px 34px rgba(0,0,0,0.30);
}
.artist-avatar.is-initial{
  display:flex; align-items:center; justify-content:center;
  background:var(--accent-grad); color:#fff;
  font-family:var(--font-display); font-weight:600; font-size:4rem;
  text-shadow:0 2px 6px rgba(0,0,0,0.4);
}
.artist-head-text{ min-width:0; flex:1 1 auto; }
.artist-name{ font-family:var(--font-display); font-weight:600; font-size:2.1rem; margin:0.2rem 0 0; line-height:1.15; }
.artist-name-he{ font-family:var(--font-heb); font-size:1.25rem; color:var(--text-dim); margin-top:0.15rem; }
.artist-handle{ font-family:var(--font-ui); font-size:0.85rem; color:var(--text-faint); margin-top:0.35rem; }
.artist-bio{ font-family:var(--font-ui); font-size:0.97rem; line-height:1.65; color:var(--text); margin:0.9rem 0 0; max-width:44rem; }
/* ---------- BADGE-3: the artist trophy case ----------
   Richer than a song row -- this is the showcase -- but the same quiet
   honor-mark family: no animation, nothing interactive, theme tokens only.
   The glyph uses --star-accent, not --accent-2, for the same reason BADGE-2
   switched: accent-2 dies on a light ground (1.92:1 on gold/light). */
.trophy-case{ margin:1.6rem 0 1.9rem; }
.trophy-case .rail-title-h{ margin-bottom:0.75rem; }
.trophy-list{
  list-style:none; margin:0; padding:0;
  display:flex; flex-wrap:wrap; gap:0.5rem;
}
.trophy{
  display:inline-flex; align-items:center; gap:0.45rem;
  padding:0.5rem 0.85rem; border-radius:var(--radius-sm);
  background:linear-gradient(135deg,
    rgba(var(--accent-1-rgb),0.10), rgba(var(--accent-2-rgb),0.16));
  border:1px solid rgba(var(--accent-2-rgb),0.30);
  cursor:default; user-select:none;
}
.trophy .badge-ic{ width:1.45rem; height:1.45rem; color:var(--badge-gold); flex-shrink:0; }
.trophy-t{
  font-family:var(--font-ui); font-size:0.8rem; font-weight:600;
  letter-spacing:0.02em; color:var(--text); white-space:nowrap;
}

/* ARTIST-2: mood chips, one per distinct channel the artist's approved songs
   carry, ordered by how many of their songs are in it. Pill-sized (the
   --chip-* tokens already exist for exactly this), not the homepage's bigger
   .mood-card -- the header has a name and a bio to carry too. */
.artist-mood-chips{ display:flex; flex-wrap:wrap; gap:0.5rem; margin-top:0.9rem; }
.mood-chip{
  display:inline-flex; align-items:center; padding:0.32rem 0.75rem; border-radius:999px;
  background:var(--chip-bg); border:1px solid var(--chip-border); color:var(--text-dim);
  font-family:var(--font-ui); font-size:0.78rem; text-decoration:none; transition:color 0.18s var(--ease), border-color 0.18s var(--ease);
}
.mood-chip:hover{ color:var(--accent-2); border-color:rgba(var(--accent-2-rgb), 0.4); }

.artist-actions{ display:flex; gap:0.7rem; flex-wrap:wrap; align-items:center; margin-top:1.2rem; }
.artist-actions .home-primary-btn{ text-decoration:none; }
.artist-follow-slot:empty{ display:none; }
.artist-follow-slot{ display:flex; flex-direction:column; gap:0.3rem; }

/* ARTIST-3: Follow. The count sits beside the button when there is one,
   or stands alone (plain text, no button) on the owner's own page. */
.follow-widget{ display:flex; align-items:center; gap:0.6rem; }
.follow-count{ font-family:var(--font-ui); font-size:0.85rem; color:var(--text-dim); }
.follow-err{ font-family:var(--font-ui); font-size:0.8rem; color:var(--accent-1); margin:0; max-width:22rem; }

/* FOLLOW-2: compact pill for the song page / now-playing "by <artist>" line.
   Sized to match .badge-pill (index.html's own <style> block) so it sits
   beside "#1 this week"/"New" without shouting. The slot reserves "Following"
   width (its widest state) up front so the async mount never shifts the row. */
.follow-pill-slot{ display:inline-flex; align-items:center; min-width:4.6rem; min-height:1.5rem; vertical-align:middle; }
.follow-pill-slot.is-empty{ min-width:0; min-height:0; }
.follow-pill{
  display:inline-flex; align-items:center; justify-content:center;
  padding:0.22rem 0.55rem; border-radius:999px;
  background:rgba(var(--accent-2-rgb),0.10);
  border:1px solid rgba(var(--accent-2-rgb),0.26);
  color:var(--text); cursor:pointer;
  font-family:var(--font-ui); font-size:0.68rem; font-weight:600;
  letter-spacing:0.03em; white-space:nowrap;
}
.follow-pill.is-following{ background:rgba(var(--accent-2-rgb),0.20); }
.follow-pill:disabled{ opacity:0.6; cursor:default; }
.follow-err.is-compact{ font-size:0.72rem; max-width:14rem; }
@media (max-width:410px){
  .follow-pill{ font-size:0.64rem; }
}

/* Five tiles, always rendered (0 shows as 0 -- no "hide a zero" logic). No
   raw play count ever appears here, same house rule BADGE-1/2 already keep:
   the strip is songs/weeks/hearts/comments/since, never plays. */
.artist-stat-strip{
  display:flex; flex-wrap:wrap; gap:1.6rem; margin:1.6rem 0 0;
  padding:1.1rem 1.3rem; border-radius:var(--radius-sm);
  background:var(--glass-bg); border:1px solid var(--glass-border);
}
.stat-tile{ display:flex; flex-direction:column; gap:0.15rem; min-width:5.5rem; }
.stat-tile-n{ font-family:var(--font-display); font-weight:600; font-size:1.5rem; color:var(--text); line-height:1; }
.stat-tile-k{ font-family:var(--font-ui); font-size:0.74rem; letter-spacing:0.02em; color:var(--text-faint); }

.artist-empty{ font-family:var(--font-ui); color:var(--text-dim); padding:2rem 0; line-height:1.8; }

/* ARTIST-2: the catalog's own head (title + sort, no Play-all -- that moved
   to the header) and the grid itself. Reuses .rail-item (290px cards) but
   wraps instead of scrolling: "every approved song" is meant to be browsed,
   not skimmed, so it is not a .rail-scroll. */
.catalog-head{ display:flex; align-items:center; justify-content:space-between; gap:1rem; flex-wrap:wrap; margin-bottom:0.9rem; }
.catalog-grid{ display:flex; flex-wrap:wrap; gap:1.4rem 1.1rem; }

/* ARTIST-2: "What listeners say" -- three most recent approved comments on
   this artist's approved songs. Distinct from the global .feed-item: that one
   plays the song in place and never clamps; this one is a real link to the
   song's own page (so a listener reading an artist's page can open it in a
   new tab) and clamps to three lines so one long comment cannot push the
   other two below the fold. */
.artist-listeners{ margin:2.2rem 0; }
.listener-list{ display:flex; flex-direction:column; gap:1.1rem; }
.listener-item{ padding:0.15rem 0 0.15rem 0.95rem; border-left:2px solid transparent; border-image:var(--accent-grad) 1; }
.listener-body{
  font-family:var(--font-display); font-size:1.0rem; line-height:1.55; color:var(--text); margin:0;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}
.listener-meta{ margin-top:0.3rem; font-family:var(--font-ui); font-size:0.78rem; color:var(--text-faint); }
.listener-who{ color:var(--text-dim); font-style:italic; }
.listener-who a{ color:inherit; text-decoration:none; }
.listener-who a:hover{ color:var(--accent-2); text-decoration:underline; }
.listener-song{ color:var(--accent-2); text-decoration:none; font-weight:600; }
.listener-song:hover{ text-decoration:underline; }

/* PROFILE-1: the public member page, /u/<handle>. Reuses every .artist-*
   token above as-is (same header, same stat strip) -- only the list
   sections below (favorites/comments/dedications/ideas/following) are new,
   since none of .listener-item's quote styling fits a plain link row. */
.member-since{ font-family:var(--font-ui); font-size:0.82rem; color:var(--text-faint); margin:0.5rem 0 0; }
.member-section{ margin:2.2rem 0; }
.member-list{ display:flex; flex-direction:column; gap:0.9rem; }
.member-row{
  display:block; padding:0.7rem 0.9rem; border-radius:var(--radius-sm);
  background:var(--glass-bg); border:1px solid var(--glass-border); color:var(--text);
  text-decoration:none; font-family:var(--font-ui);
}
a.member-row:hover{ border-color:rgba(var(--accent-2-rgb),0.4); }
.member-row-song, .member-row-artist{ display:flex; align-items:center; gap:0.7rem; }
.member-row-text{ display:flex; flex-direction:column; gap:0.15rem; min-width:0; }
.member-row-title{ font-weight:600; color:var(--text); }
a .member-row-title{ text-decoration:none; }
.member-row-note{ font-size:0.82rem; color:var(--text-dim); }
.member-row-body{ font-size:0.9rem; line-height:1.5; color:var(--text-dim); margin:0.3rem 0 0; }
.member-row-meta{ display:flex; gap:0.9rem; font-size:0.78rem; color:var(--text-faint); margin-top:0.35rem; }
.member-row-meta a{ color:var(--accent-2); text-decoration:none; }
.member-row-cover{ width:48px; height:48px; border-radius:6px; object-fit:cover; flex:0 0 48px; }
.member-row-artist .artist-avatar{ width:40px; height:40px; flex:0 0 40px; font-size:1.1rem; }

/* ARTIST-2: "If you like <name>, try these" -- recommend_for_artist, live
   only (no snapshot fallback: a stale recommendation is worse than none). */
.try-these-sub{ font-family:var(--font-ui); font-style:italic; font-size:0.82rem; color:var(--text-dim); margin:-0.4rem 0 0.9rem; }

.artist-private{ margin-top:3rem; padding-top:1.4rem; border-top:1px solid var(--chip-border); }
.artist-private-note{ font-family:var(--font-ui); font-size:0.82rem; color:var(--text-faint); margin:0.2rem 0 1rem; }
.artist-private-row{
  display:flex; align-items:center; justify-content:space-between; gap:1rem;
  padding:0.7rem 0.9rem; border-radius:10px; margin-bottom:0.5rem;
  background:var(--glass); border:1px solid var(--chip-border);
}
.artist-private-title{ font-family:var(--font-ui); font-size:0.95rem; color:var(--text); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.artist-private-pill{
  flex-shrink:0; padding:0.2rem 0.6rem; border-radius:999px;
  font-family:var(--font-ui); font-size:0.72rem; letter-spacing:0.04em; text-transform:uppercase;
  background:rgba(var(--accent-1-rgb), 0.18); color:var(--accent-3); border:1px solid var(--chip-border);
}
.artist-private-pill[data-status="removed"]{ background:rgba(0,0,0,0.22); color:var(--text-faint); }
/* Needs changes asks for action, so it reads warmer than a plain grey. */
.artist-private-pill[data-status="rejected"]{ background:rgba(var(--accent-1-rgb), 0.30); color:var(--text); border-color:rgba(var(--accent-1-rgb), 0.5); }
.artist-private-pill[data-status="draft"]{ background:var(--chip-bg); color:var(--text-dim); }
.artist-private-row.has-reason{ flex-wrap:wrap; }
.artist-private-reason{ flex-basis:100%; font-family:var(--font-ui); font-size:0.82rem; color:var(--text-dim); }
.artist-private-reason summary{ cursor:pointer; color:var(--accent-2); width:max-content; }
.artist-private-reason p{ margin:0.3rem 0 0; line-height:1.5; }

@media (max-width:760px){
  .artist-wrap{ padding:1rem 12px 6rem; }
  .artist-head{ flex-direction:column; align-items:center; text-align:center; gap:1.1rem; }
  .artist-avatar{ width:120px; height:120px; flex-basis:120px; }
  .artist-avatar.is-initial{ font-size:3rem; }
  .artist-name{ font-size:1.7rem; }
  .artist-actions{ justify-content:center; }
  .artist-bio{ text-align:left; }
  .artist-mood-chips{ justify-content:center; }
  .artist-stat-strip{ gap:1.1rem 1.4rem; justify-content:center; text-align:center; }
  .stat-tile{ align-items:center; }
  .catalog-grid, .artist-mood-chips{ justify-content:center; }
}

/* Avatar picker in /settings */
.lv-avatar-row{ display:flex; align-items:center; gap:1rem; margin:1rem 0 0.4rem; }
.lv-avatar-prev{
  width:72px; height:72px; flex:0 0 72px; border-radius:50%; object-fit:cover; display:flex;
  align-items:center; justify-content:center; border:1px solid var(--glass-border);
  background:var(--accent-grad); color:#fff; font-family:var(--font-display); font-weight:600; font-size:1.8rem;
}

/* ============================================================
   Artist attribution (1A-4). Present on song rows, the hero, rail cards, the
   now-playing panel and the mini-player. Muted by design: attribution is
   information, not a heading.
   ============================================================ */
.artist-link{
  color:var(--text-dim); text-decoration:none; cursor:pointer;
  border-bottom:1px solid transparent;
}
.artist-link:hover{ color:var(--accent-2); border-bottom-color:var(--accent-2); }
.artist-link:focus-visible{ outline:2px solid var(--accent-2); outline-offset:2px; border-radius:3px; }
.attr-he{ font-family:var(--font-heb); color:var(--text-faint); font-size:0.94em; }

/* Song row: shares the muted meta line with the category, ahead of it. */
.row-artist{
  font-family:var(--font-ui); font-size:0.62rem; letter-spacing:0.05em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:8.5rem;
}
.row-meta-dot{ color:var(--text-faint); font-size:0.62rem; }
/* HERO-2: "by <name>" on rows. The "by" matches the name's small meta type;
   the name keeps its own truncation. */
.row-by{ display:inline-flex; align-items:baseline; gap:0.3em; min-width:0;
  font-family:var(--font-ui); font-size:0.62rem; letter-spacing:0.05em; color:var(--text-faint); white-space:nowrap; }

/* Hero: a "by" line straight under the title, above the category. */
.hero-by{
  font-family:var(--font-ui); font-size:0.8rem; color:var(--text-dim); margin-top:0.35rem;
}

/* Rail card line 2. The card keeps exactly one caption line under the title, so
   adding the artist costs no height. */
.rail-sub .artist-link{ color:var(--text-dim); }

/* Now-playing: avatar + name, the whole row is the link. */
.now-artist{ margin-top:0.5rem; display:flex; align-items:center; gap:0.5rem; }
.now-artist:empty{ display:none; }
.now-artist-by{ font-family:var(--font-ui); font-size:0.88rem; color:var(--text-dim); }
.now-artist-row{ display:inline-flex; align-items:center; gap:0.5rem; border-bottom:0; }
.now-artist-av{
  width:28px; height:28px; border-radius:50%; object-fit:cover; flex-shrink:0; display:block;
  border:1px solid var(--glass-border);
}
.now-artist-av.is-initial{
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--accent-grad); color:#fff;
  font-family:var(--font-display); font-weight:600; font-size:0.8rem;
  text-shadow:0 1px 2px rgba(0,0,0,0.45);
}
.now-artist-name{ font-family:var(--font-ui); font-size:0.88rem; }
.now-artist-row:hover .now-artist-name{ color:var(--accent-2); }
/* SONG-1: "Idea by [name]", shown only when songs.idea_credit is set. */
.now-idea-credit{ margin-top:0.3rem; font-family:var(--font-ui); font-size:0.8rem; color:var(--text-faint); font-style:italic; }

@media (max-width:900px){
  .row-artist{ max-width:5.5rem; }
}

/* ============================================================
   Ratings (1A-6): the words and the music, 1 to 5, in the dock.
   The heart keeps its own .love-btn styling, untouched.
   The number of raters is never rendered anywhere here.
   ============================================================ */
/* The block is meant to read as an invitation to rate, not as a disabled
   footnote. Everything here is lit: bright labels, bright icons, and empty
   stars drawn as ACCENT OUTLINES rather than grey. The one place that stays
   deliberately dim is the signed-out state, because there it is true. */
.rating-rows{ display:flex; flex-direction:column; gap:0.5rem; margin:0.2rem 0 0.8rem; }
.rating-row{
  display:flex; align-items:center; gap:0.7rem; flex-wrap:wrap;
  font-family:var(--font-ui); font-size:0.95rem; color:var(--text);
}
/* Icon + label as one unit, so they wrap together. The min-width that used to
   sit on the label lives here now, which is what keeps the two rows' stars
   aligned with each other. */
.rating-head{ display:inline-flex; align-items:center; gap:0.45rem; min-width:7.4rem; }
.rating-label{ color:var(--text); }
/* Still decorative and still unclickable three ways over -- no pointer events,
   no cursor change, no handler -- but no longer faint.
   COLOUR CHOICE: the bright foreground, NOT the accent. The accent now belongs
   to the stars alone, and that is what makes the stars read as the interactive
   thing in the row. An accent-coloured icon sitting beside accent stars would
   look like a second tappable control, which is the confusion this icon was
   restored without. currentColor is what themes it. */
.rating-ic{
  width:1.35rem; height:1.35rem; flex-shrink:0;
  color:var(--text); pointer-events:none; cursor:default;
}
.rating-stars{ display:inline-flex; gap:0; }
/* The button is the touch target, the glyph inside is what you see: 2rem of
   star plus 0.375rem of padding each side gives a 44px square hit area. The
   padding also spaces the row, so the flex gap stays zero. */
.star{
  background:none; border:0; padding:0.375rem; cursor:pointer; line-height:0;
  border-radius:9px; -webkit-tap-highlight-color:transparent;
}
.star:focus-visible{ outline:2px solid var(--star-accent); outline-offset:-2px; }

/* EMPTY: a crisp accent outline. Never pre-filled -- an unrated song must not
   look scored -- but never grey either. var(--accent-2) means gold, emerald,
   royal or bordeaux follows the active theme in both lightings. */
.star-ic{
  width:2rem; height:2rem; fill:none;
  stroke:var(--star-accent); stroke-width:1.6;
  stroke-linejoin:round; transition:fill 0.12s var(--ease), stroke 0.12s var(--ease);
}
/* SUBMITTED: the rater's own score, filled solid and persistent. */
.star-ic.is-on{ fill:var(--star-accent); stroke:var(--star-accent); }

/* HOVER PREVIEW, pointer devices only: the star under the pointer and every
   star before it fill solid, which is the behaviour every star rating has. The
   position comes from data-preview on the row, set in JS -- the pure-CSS
   version of this needs the DOM reversed with row-reverse, which would put the
   keyboard tab order at 5,4,3,2,1. */
@media (hover:hover){
  .rating-row:not(.is-locked) .rating-stars[data-preview="1"] .star:nth-child(-n+1) .star-ic,
  .rating-row:not(.is-locked) .rating-stars[data-preview="2"] .star:nth-child(-n+2) .star-ic,
  .rating-row:not(.is-locked) .rating-stars[data-preview="3"] .star:nth-child(-n+3) .star-ic,
  .rating-row:not(.is-locked) .rating-stars[data-preview="4"] .star:nth-child(-n+4) .star-ic,
  .rating-row:not(.is-locked) .rating-stars[data-preview="5"] .star:nth-child(-n+5) .star-ic{
    fill:var(--star-accent); stroke:var(--star-accent);
  }
  /* and the ones after the pointer drop back to an outline, even if submitted,
     so the preview reads as "this is what you would give". */
  .rating-row:not(.is-locked) .rating-stars[data-preview] .star .star-ic{ fill:none; }
}

/* SIGNED OUT: the one state that should read as not-yet-available, and it has
   to be obviously different from the lit eligible row above. Grey, not accent,
   and dimmed -- while the whole row stays tappable so it can open the modal. */
.rating-row.is-locked .star-ic{ stroke:var(--text-faint); fill:none; opacity:0.55; }
.rating-row.is-locked .rating-label,
.rating-row.is-locked .rating-ic{ color:var(--text-dim); }

.rating-avg{ color:var(--text); font-variant-numeric:tabular-nums; font-size:1.05rem; font-weight:600; }
/* Quieter than the labels, but legible -- it was a ghost at --text-faint. */
.rating-note{ color:var(--text-dim); font-size:0.82rem; }
.rating-msg{ font-size:0.74rem; color:var(--accent-3); margin-top:0.2rem; }
.rating-msg.is-bad{ color:#ff8497; }
/* On a phone the label and the stars hold one line and the average/hint drops
   below, which is what keeps a 5x40px row from overflowing a narrow dock. */
@media (max-width:560px){
  .rating-row{ gap:0.45rem; font-size:0.9rem; }
  .rating-head{ min-width:6.2rem; gap:0.4rem; }
  .rating-ic{ width:1.2rem; height:1.2rem; }
  .rating-note, .rating-avg{ flex-basis:100%; margin-left:0.15rem; }
  /* Still a 44px target on a phone: 1.85rem of glyph + 0.33rem each side. */
  .star{ padding:0.45rem; }
  .star-ic{ width:1.85rem; height:1.85rem; }
}

/* ============================================================
   DED-2: the dedication page (/d/<code>). A short, personal, single-column
   page -- no play counts, no rail of other songs, just this one dedication.
   ============================================================ */
.ded-page{ max-width:28rem; margin:0 auto; padding:2.4rem 1.5rem 6rem; text-align:center; }
.ded-page-loading{ color:var(--text-dim); font-family:var(--font-ui); padding:3rem 0; }
.ded-page-missing{ padding-top:4rem; }
.ded-page-missing h1{ font-family:var(--font-display); font-weight:600; font-size:1.6rem; margin:0 0 0.6rem; }
.ded-page-cover{
  width:220px; height:220px; border-radius:16px; object-fit:cover; display:block; margin:0 auto 1.4rem;
  border:1px solid var(--glass-border); box-shadow:0 16px 40px rgba(0,0,0,0.35);
}
.ded-page-headline{ font-family:var(--font-display); font-weight:600; font-size:1.9rem; margin:0 0 0.3rem; color:var(--text); }
.ded-page-line{ font-family:var(--font-ui); font-size:0.95rem; color:var(--text-dim); margin-bottom:1.1rem; }
.ded-page-message{
  font-family:var(--font-display); font-style:italic; font-size:1.15rem; line-height:1.55;
  color:var(--text); margin:0 0 1rem; white-space:pre-line;
}
.ded-page-sender{ font-family:var(--font-ui); font-size:0.88rem; color:var(--text-faint); margin-bottom:0.3rem; }
.ded-page-sender a{ color:inherit; text-decoration:none; }
.ded-page-sender a:hover{ color:var(--accent-2); text-decoration:underline; }
.ded-page-song{ font-family:var(--font-display); font-style:italic; font-size:1.05rem; color:var(--accent-2); margin-bottom:1.6rem; }
.ded-page-play{ margin:0 auto; width:auto; padding:0.8rem 1.8rem; }
.ded-page-play svg{ width:1.1rem; height:1.1rem; }
/* SHARE-1: Share sits under Play, same width rule. */
.ded-page-share{ margin:0.7rem auto 0; width:auto; padding:0.65rem 1.6rem; }
.ded-page-link{ display:block; margin-top:0.9rem; }
.ded-page-cta{ margin-top:3rem; padding-top:1.6rem; border-top:1px solid var(--chip-border); }
.ded-page-cta p{ font-family:var(--font-display); font-style:italic; color:var(--text-dim); margin:0 0 0.9rem; }
.ded-page-cta .home-primary-btn{ width:auto; margin:0 auto; }
@media (max-width:560px){
  .ded-page{ padding:1.8rem 1.1rem 5rem; }
  .ded-page-cover{ width:180px; height:180px; }
  .ded-page-headline{ font-size:1.6rem; }
}
