/* ─────────────────────────────────────────────────────────────
   sigzmusic.com — /music
   Editorial "liner notes" music page (lane E of the redesign fleet,
   wired to the real API + the real playback engine in /js/player.js).

   Unified token set bridging the landing (#0A0B0D) and the older
   shell (#0d0d10): --bg is the midpoint of the two.
   ───────────────────────────────────────────────────────────── */
:root{
  --bg:#0B0C0F;
  --bg-soft:#0E0F13;
  --bg-raise:#14151B;
  --bg-hover:#1C1D25;
  --line:#23242C;
  --line-soft:#1A1B22;
  --text:#F2F2F4;
  --text-2:#A5A8B0;
  --text-3:#7C7F8A;
  --lime:#C6F53C;          /* now-playing indicator ONLY */
  --nc:#35E0FF;            /* current accent; JS swaps it per release */
  --topbar:64px;
  --player:84px;
  --gutter:clamp(18px,4.5vw,56px);
  --ease:cubic-bezier(.22,.61,.21,1);
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  font-family:"Archivo",system-ui,-apple-system,sans-serif;
  background:var(--bg);color:var(--text);
  font-size:15px;line-height:1.55;
  -webkit-font-smoothing:antialiased;
  padding-bottom:var(--player);
}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
a{color:inherit;text-decoration:none}
img{display:block}
::selection{background:var(--nc);color:#0A0B0D}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:-9999px;top:0;z-index:200;background:var(--text);color:#0A0B0D;padding:10px 16px;font-weight:700}
.skip:focus{left:0}
:focus-visible{outline:2px solid var(--nc);outline-offset:3px;border-radius:2px}

/* ── masthead: one control bar ─────────────────────────────── */
.topbar{
  position:sticky;top:0;z-index:60;
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:13px var(--gutter);
  /* viewport-fit=cover: keep the bar clear of the status bar / notch.
     env() is 0px on desktop, so this is a no-op there. */
  padding-top:calc(13px + env(safe-area-inset-top,0px));
  /* landscape notch: left and right insets differ, pad each side with its own */
  padding-inline:max(var(--gutter),env(safe-area-inset-left,0px)) max(var(--gutter),env(safe-area-inset-right,0px));
  background:rgba(11,12,15,.86);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line-soft);
}
.wm{font-weight:900;font-stretch:125%;font-size:22px;letter-spacing:.06em;flex:none;margin-right:4px}
.topsearch{
  display:flex;align-items:center;gap:9px;flex:0 1 300px;min-width:190px;
  background:var(--bg-raise);border:1px solid var(--line);border-radius:999px;
  padding:8px 14px;transition:border-color .15s var(--ease);
}
.topsearch:focus-within{border-color:var(--text-3)}
.topsearch svg{width:15px;height:15px;stroke:var(--text-3);fill:none;stroke-width:2;stroke-linecap:round;flex:none}
.topsearch input{flex:1;min-width:0;background:none;border:0;outline:none;color:var(--text);font:inherit;font-size:14px}
.topsearch input::placeholder{color:var(--text-3)}
.topsearch .clear-q{display:none;color:var(--text-3);font-size:16px;line-height:1;padding:2px 4px}
.topsearch .clear-q:hover{color:var(--text)}
body.searching .topsearch .clear-q{display:block}

.topctl{display:flex;align-items:center;gap:10px;min-width:0;overflow-x:auto;scrollbar-width:none}
.topctl::-webkit-scrollbar{display:none}
.chips{display:flex;gap:6px;flex:none}
/* The chips are <a>/<span> now, not buttons: inline-flex so the padding and the
   44px min-height in the ≤700 block below apply the same way they did to a
   button, whatever the tag. */
.chip{
  display:inline-flex;align-items:center;justify-content:center;
  flex:none;padding:7px 15px;border-radius:999px;font-size:13px;font-weight:650;
  color:var(--text-2);background:var(--bg-raise);border:1px solid var(--line);
  transition:color .13s var(--ease),background .13s var(--ease),border-color .13s var(--ease);
}
a.chip:hover{color:var(--text);background:var(--bg-hover)}
/* aria-current marks the page you are on; aria-pressed is kept so any chip that
   is still a toggle looks the same. Neither is hoverable-away. */
.chip[aria-current="page"],.chip[aria-pressed="true"]{background:var(--text);color:#0A0B0D;border-color:var(--text)}
.chip[aria-current="page"]{cursor:default}
.top-actions{margin-left:auto;display:flex;align-items:center;gap:8px;flex:none}
.btn{
  display:inline-flex;align-items:center;gap:8px;
  padding:8px 16px;border-radius:999px;font-size:13px;font-weight:700;
  background:var(--bg-raise);border:1px solid var(--line);color:var(--text);
  transition:background .13s var(--ease),border-color .13s var(--ease),transform .13s var(--ease);
}
.btn:hover{background:var(--bg-hover);border-color:var(--text-3);transform:translateY(-1px)}
.btn svg{width:15px;height:15px;fill:currentColor;flex:none}
.btn .stroke,.icon-btn .stroke{fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
#qCount{
  min-width:20px;height:20px;border-radius:10px;display:inline-grid;place-items:center;
  background:var(--bg-hover);font-size:11.5px;font-weight:800;padding:0 6px;font-variant-numeric:tabular-nums;
}

/* ── hero ─────────────────────────────────────────────────── */
.hero{
  position:relative;isolation:isolate;overflow:hidden;
  min-height:min(56vh,470px);display:flex;align-items:stretch;
  border-bottom:1px solid var(--line);
}
.hero-bg{position:absolute;inset:0;z-index:-2;
  background:
    radial-gradient(120% 160% at 85% 10%,color-mix(in srgb,var(--acc,#35E0FF) 20%,transparent),transparent 55%),
    radial-gradient(90% 120% at 10% 100%,color-mix(in srgb,var(--acc,#35E0FF) 8%,transparent),transparent 60%),
    linear-gradient(160deg,#0D1016 0%,#0B0C0F 70%);
}
.hero-bg img{width:100%;height:100%;object-fit:cover;opacity:.5;filter:saturate(.9)}
.hero-scrim{position:absolute;inset:0;z-index:-1;
  background:linear-gradient(100deg,rgba(11,12,15,.94) 0%,rgba(11,12,15,.72) 46%,rgba(11,12,15,.30) 100%)}
.hero-in{
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:clamp(24px,4vw,64px);
  align-items:end;width:100%;padding:clamp(28px,5vh,52px) var(--gutter);
}
.kicker{
  font-size:11.5px;font-weight:750;letter-spacing:.24em;color:var(--text-3);
  text-transform:uppercase;display:flex;align-items:center;gap:12px;
}
.kicker::after{content:"";height:1px;width:44px;background:var(--line)}
.hero h1{
  font-weight:900;font-stretch:125%;letter-spacing:-.01em;line-height:.94;
  font-size:clamp(64px,11vw,148px);margin:14px 0 12px;color:var(--text);
  text-shadow:0 0 60px color-mix(in srgb,var(--acc,#35E0FF) 25%,transparent);
}
.hero-boot{opacity:.25}
.hero .dek{max-width:52ch;color:var(--text-2);font-size:clamp(14px,1.3vw,16.5px)}
.hero .dek b{color:var(--text);font-weight:650}
.hero-actions{display:flex;align-items:center;gap:16px;margin-top:24px;flex-wrap:wrap}
.play-big{
  display:inline-flex;align-items:center;gap:12px;
  background:var(--acc,var(--nc));color:#06090B;border-radius:999px;
  padding:14px 26px 14px 20px;font-weight:800;font-size:13px;letter-spacing:.14em;
  transition:transform .16s var(--ease),box-shadow .16s var(--ease);
  box-shadow:0 10px 34px color-mix(in srgb,var(--acc,var(--nc)) 22%,transparent);
}
.play-big:hover{transform:translateY(-2px) scale(1.02)}
.play-big svg{width:16px;height:16px;fill:currentColor}
.btn-ghost{
  display:inline-flex;align-items:center;gap:10px;
  border:1px solid var(--line);border-radius:999px;padding:13px 22px;
  color:var(--text-2);font-weight:700;font-size:12px;letter-spacing:.14em;
  transition:color .15s var(--ease),border-color .15s var(--ease),background .15s var(--ease);
}
.btn-ghost:hover{color:var(--text);border-color:var(--text-3);background:var(--bg-raise)}
/* order:1 because the cover is FIRST in the markup (it is a float at ≤700px);
   in this grid, order alone puts it back in the right-hand column. */
.hero-cover{width:clamp(180px,22vw,300px);flex:none;order:1}
.cov{
  position:relative;display:block;aspect-ratio:1;border-radius:6px;overflow:hidden;
  background:linear-gradient(150deg,color-mix(in srgb,var(--acc,var(--nc)) 42%,#0B0C0F),#0B0C0F 78%);
  box-shadow:0 18px 50px rgba(0,0,0,.55);
}
.cov-fb{
  position:absolute;inset:0;display:grid;place-items:center;text-align:center;
  padding:12px;font-weight:850;font-stretch:118%;font-size:13px;letter-spacing:.12em;
  color:rgba(255,255,255,.85);text-transform:uppercase;line-height:1.3;
}
.cov img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* .hero-meta (TRACKS · MIN · PLAYS) and .shelf-head/.jump (the ALBUMS · MIXES
   jump nav) were removed 2026-08-21 — nothing on this page emits either any
   more, so their rules are gone with them. .hero-meta still exists on the
   LANDING page (public/js/views.js), which is styled entirely inline and never
   loads this file, so it is unaffected. */

/* ── shelf / catalogue index ──────────────────────────────── */
/* With the jump nav gone, above 700px this section holds nothing but the
   results well — which is display:none until there is a query. So it draws no
   band and no rule of its own; without this the hero would be followed by
   ~90px of empty page and a stray hairline. Searching gives it real content
   and the spacing comes back. On a phone .topctl is moved in here, so the
   ≤700 block re-states both padding rules at equal specificity. */
.shelf{padding:0 var(--gutter)}
body.searching .shelf{padding:clamp(30px,5vh,52px) var(--gutter) clamp(40px,6vh,72px);border-bottom:1px solid var(--line)}
.h-sec{font-weight:880;font-stretch:122%;font-size:clamp(30px,3.6vw,44px);letter-spacing:-.01em;line-height:1;margin-top:10px}

/* .grid is also the search-results card layout (.res-grid) — kept even though
   the shelf that introduced it is gone. */
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:clamp(14px,1.8vw,24px)}
.card{
  position:relative;text-align:left;border-radius:8px;padding:10px;display:block;width:100%;
  cursor:pointer;transition:background .15s var(--ease);
}
.card:hover{background:var(--bg-raise)}
.card .cov{border-radius:6px;margin-bottom:14px;transition:transform .2s var(--ease),box-shadow .2s var(--ease)}
.card:hover .cov{transform:translateY(-3px);box-shadow:0 22px 46px rgba(0,0,0,.6),0 0 0 1px color-mix(in srgb,var(--acc) 55%,transparent)}
.card .c-title{font-weight:750;font-size:16px;letter-spacing:.01em}
.card .c-meta{color:var(--text-3);font-size:12.5px;margin-top:3px;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.tag-demo{
  display:inline-block;font-size:9.5px;font-weight:850;letter-spacing:.16em;
  border:1px dashed var(--text-3);color:var(--text-2);border-radius:4px;padding:2px 6px;
}
.nowtag{display:none;align-items:center;gap:6px;color:var(--lime);font-size:10px;font-weight:800;letter-spacing:.16em;margin-top:6px}
.card.now .nowtag{display:inline-flex}
.eq{display:inline-flex;align-items:flex-end;gap:2px;height:11px;width:14px}
.eq i{width:3px;height:4px;background:var(--lime);border-radius:1px}
body.playing .eq i{animation:eq 1s ease-out infinite}
body.playing .eq i:nth-child(2){animation-delay:.25s}
body.playing .eq i:nth-child(3){animation-delay:.5s}
@keyframes eq{0%,100%{height:4px}45%{height:11px}}

/* ── search results ───────────────────────────────────────── */
.results{display:none}
body.searching .results{display:block}
body.searching #editorial,body.searching .colophon{display:none}
.res-h{
  font-size:11.5px;font-weight:780;letter-spacing:.22em;color:var(--text-3);
  margin:26px 0 12px;display:flex;align-items:center;gap:12px;
}
.res-h::after{content:"";flex:1;height:1px;background:var(--line-soft)}
.res-none{color:var(--text-3);padding:40px 0;font-size:15px}
.res-none b{color:var(--text)}
.res-grid{margin-bottom:8px}

/* ── track rows ───────────────────────────────────────────── */
.tr{
  display:grid;grid-template-columns:38px minmax(0,1fr) auto auto 34px;gap:14px;align-items:center;
  padding:9px 12px;border-radius:6px;color:var(--text-2);cursor:pointer;
  box-shadow:inset 0 -1px 0 rgba(255,255,255,.04);
  transition:background .12s var(--ease),color .12s var(--ease);
}
.tr:hover{background:var(--bg-raise);color:var(--text)}
.tr .tr-n{color:var(--text-3);font-variant-numeric:tabular-nums;font-size:13px;text-align:right}
.tr .tr-t{color:var(--text);font-weight:560;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tr .tr-alb{color:var(--text-3);font-weight:400}
.tr .tr-p,.tr .tr-d{font-variant-numeric:tabular-nums;font-size:12.5px;color:var(--text-3)}
.tr .tr-q{
  width:28px;height:28px;border-radius:50%;display:grid;place-items:center;
  color:var(--text-3);font-size:16px;opacity:0;transition:opacity .14s,background .14s,color .14s;
}
.tr:hover .tr-q,.tr .tr-q:focus-visible{opacity:1}
@media (hover:none){.tr .tr-q{opacity:1}}
.tr .tr-q:hover{background:var(--bg-hover);color:var(--text)}
.tr.now .tr-t,.tr.now .tr-d,.tr.now .tr-p,.tr.now .tr-n{color:var(--lime)}

.empty{border:1px dashed var(--line);border-radius:10px;padding:22px;color:var(--text-3);font-size:13.5px;line-height:1.6}
.empty b{color:var(--text-2)}
.empty .btn-ghost{margin-top:14px;padding:10px 18px;font-size:11px}

/* ── chapters ─────────────────────────────────────────────── */
.chapter{
  position:relative;padding:clamp(56px,10vh,110px) var(--gutter) clamp(40px,7vh,80px);
  border-bottom:1px solid var(--line);overflow:hidden;isolation:isolate;
}
/* the outlined year IS the chapter heading (an <h2>), not a decoration —
   hence margin:0, which a heading would otherwise bring with it.

   Size comes from --ghost-fs, set by the chapter ANCESTOR, never by a rule on
   .ghost itself. A record chapter and a mix series chapter want different sizes,
   and the mobile override below is a plain `.ghost` selector: any `.x .ghost`
   rule would out-specify it and silently keep its desktop size on phones, which
   is exactly what happened. Setting an inherited variable sidesteps specificity. */
.chapter{--ghost-fs:clamp(140px,24vw,320px)}
.ghost{
  position:absolute;top:clamp(8px,2vh,24px);right:-.02em;z-index:-1;margin:0;
  font-weight:900;font-stretch:125%;font-size:var(--ghost-fs,clamp(140px,24vw,320px));line-height:.8;
  color:transparent;-webkit-text-stroke:1px color-mix(in srgb,var(--acc) 26%,transparent);
  user-select:none;letter-spacing:-.02em;pointer-events:none;
}
.rec{display:grid;grid-template-columns:clamp(150px,17vw,230px) minmax(0,1fr);gap:clamp(20px,3vw,44px);padding:clamp(22px,3.5vh,36px) 0}
.rec + .rec{border-top:1px solid var(--line-soft)}
.rec .cov{border-radius:6px}
.rec-meta{font-size:11px;font-weight:780;letter-spacing:.22em;color:var(--text-3);display:flex;gap:14px;flex-wrap:wrap}
.rec-meta em{font-style:normal;color:var(--acc)}
.rec h3{font-weight:880;font-stretch:122%;font-size:clamp(32px,4.6vw,60px);line-height:1;letter-spacing:-.01em;margin:10px 0 10px}
.rec-story{
  color:var(--text-3);font-size:14.5px;max-width:56ch;margin-top:10px;
  border-left:2px solid color-mix(in srgb,var(--acc) 55%,transparent);padding-left:16px;
}
.rec-actions{display:flex;align-items:center;gap:12px;margin-top:20px;flex-wrap:wrap}
.rec-play{
  display:inline-flex;align-items:center;gap:10px;
  background:var(--text);color:#0A0B0D;border-radius:999px;
  padding:11px 22px 11px 17px;font-weight:800;font-size:11.5px;letter-spacing:.14em;
  transition:transform .15s var(--ease),background .15s;
}
.rec-play:hover{transform:translateY(-1px) scale(1.02);background:var(--acc)}
.rec-play svg{width:13px;height:13px;fill:currentColor}
.tl-toggle{
  color:var(--text-3);font-size:11.5px;font-weight:750;letter-spacing:.16em;
  padding:11px 4px;border-bottom:1px solid transparent;transition:color .15s var(--ease);
}
.tl-toggle:hover{color:var(--text)}
/* full-bleed inside the section: spans the cover column too, no width cap */
.tl{grid-column:1/-1;margin-top:0;max-width:none}
.tl[hidden]{display:none}

/* ── mixes ────────────────────────────────────────────────── */
.mixes{position:relative;padding:clamp(56px,9vh,100px) var(--gutter);border-bottom:1px solid var(--line);isolation:isolate;overflow:hidden}
.mixes-bg{position:absolute;inset:0;z-index:-1;background:radial-gradient(100% 140% at 90% 0%,rgba(157,92,255,.10),transparent 55%),var(--bg)}
/* Each series renders as a .rec block — the same shape as a record — so this is a
   plain column, not a card grid. The .rec rules above supply the layout. */
.mixgrid{display:block;margin-top:34px}
.mixgrid .empty{max-width:620px}

/* A series chapter: the same outlined year as the record chapters, but WITHOUT
   .chapter's own gutter padding — .mixes already supplies the gutter, and applying
   it twice would inset these blocks past the records they sit beneath.
   isolation:isolate gives the ghost its own stacking context, so its z-index:-1
   puts it behind this block's content rather than behind .mixes-bg. */
.mix-chapter{position:relative;overflow:hidden;isolation:isolate;padding:clamp(30px,5vh,64px) 0 clamp(10px,2vh,24px)}
/* A record chapter's year spans several records, so it can be enormous. A series
   chapter holds ONE block, where type that size runs straight through the title.
   Roughly a third of the size keeps it beside the heading instead of behind it —
   set as the inherited variable, so the mobile override still applies. */
.mix-chapter{--ghost-fs:clamp(56px,7.5vw,116px)}
.mix-chapter .ghost{top:clamp(4px,1vh,14px)}
.mix-chapter + .mix-chapter{border-top:1px solid var(--line-soft)}
.mix-chapter .rec{padding-top:0}

/* ── colophon ─────────────────────────────────────────────── */
/* Two rows on the left — the domain with the streaming links under it — and
   the counts held to the right. The old 120px of bottom padding was dead
   space: <body> already reserves var(--player) for the bar, so this only has
   to add a little breathing room of its own. */
.colophon{
  padding:30px var(--gutter) 26px;color:var(--text-3);font-size:12.5px;
  display:grid;grid-template-columns:1fr auto;
  grid-template-areas:"brand counts" "socials counts";
  gap:8px 20px;align-items:center;letter-spacing:.06em;
}
.colophon > span:first-child{grid-area:brand}
.colophon .socials{grid-area:socials;margin-left:-11px}  /* the 44px buttons carry their own padding */
.colophon #colophonCounts{grid-area:counts;text-align:right}
/* the socials moved here when the About section was removed (2026-08-18) */
/* icon links, 44px of tap around a 22px mark, greyed until hovered */
.socials{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.socials a{
  display:grid;place-items:center;width:44px;height:44px;border-radius:50%;
  color:var(--text-3);transition:color .15s var(--ease),background .15s var(--ease);
}
.socials a svg{width:22px;height:22px;display:block}
.socials a:hover{color:var(--text);background:var(--bg-raise)}

/* ── queue drawer ─────────────────────────────────────────── */
.queue{
  position:fixed;top:calc(var(--topbar) + 12px);bottom:calc(var(--player) + 12px);
  right:max(12px,env(safe-area-inset-right,0px));
  width:min(360px,calc(100vw - 24px - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px)));z-index:70;
  background:#101116;border:1px solid var(--line);border-radius:14px;
  box-shadow:0 24px 70px rgba(0,0,0,.6);
  transform:translateX(calc(100% + 24px));transition:transform .28s var(--ease);
  display:flex;flex-direction:column;overflow:hidden;
}
.queue.open{transform:translateX(0)}
.q-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 18px;border-bottom:1px solid var(--line-soft)}
.q-head h2{font-size:11px;font-weight:800;letter-spacing:.24em;color:var(--text-3)}
.q-head .q-clear{font-size:11px;font-weight:700;letter-spacing:.12em;color:var(--text-3)}
.q-head .q-clear:hover{color:var(--text)}
.q-list{flex:1;overflow-y:auto;padding:8px}
.q-row{
  display:grid;grid-template-columns:40px minmax(0,1fr) auto auto;gap:12px;align-items:center;
  width:100%;text-align:left;padding:8px;border-radius:8px;color:var(--text-2);cursor:pointer;
  transition:background .13s var(--ease);
}
.q-row:hover{background:var(--bg-raise);color:var(--text)}
.q-row .cov{width:40px;border-radius:4px;box-shadow:none}
.q-row .qt{display:block;font-size:13.5px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.q-row .qs{display:block;font-size:11.5px;color:var(--text-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.q-row.now .qt{color:var(--lime)}
.q-row .qd{font-size:12px;font-variant-numeric:tabular-nums;color:var(--text-3)}
.q-row .qx{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;color:var(--text-3);font-size:14px;opacity:0;transition:opacity .14s,color .14s,background .14s}
.q-row:hover .qx,.qx:focus-visible{opacity:1}
@media (hover:none){.q-row .qx{opacity:1}}
.q-row .qx:hover{color:var(--text);background:var(--bg-hover)}
.q-empty{padding:30px 20px;color:var(--text-3);font-size:13px;line-height:1.7}
.q-empty b{color:var(--text-2)}

/* ── toast ────────────────────────────────────────────────── */
.toast{
  position:fixed;left:50%;bottom:calc(var(--player) + 18px);transform:translate(-50%,12px);
  background:#17181F;border:1px solid var(--line);color:var(--text);
  border-radius:999px;padding:11px 22px;font-size:13px;font-weight:600;
  opacity:0;pointer-events:none;transition:opacity .2s var(--ease),transform .2s var(--ease);
  z-index:90;box-shadow:0 14px 40px rgba(0,0,0,.5);max-width:86vw;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.toast.show{opacity:1;transform:translate(-50%,0)}

/* ── player bar ───────────────────────────────────────────── */
.player{
  position:fixed;left:0;right:0;bottom:0;height:var(--player);z-index:80;
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr) minmax(0,1fr);
  align-items:center;gap:20px;
  /* the bottom inset keeps the controls off the home indicator; 0px on desktop */
  padding:0 max(clamp(14px,2.5vw,28px),env(safe-area-inset-right,0px)) env(safe-area-inset-bottom,0px) max(clamp(14px,2.5vw,28px),env(safe-area-inset-left,0px));
  background:rgba(11,12,15,.92);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  border-top:1px solid var(--line);
}
.p-now{display:flex;align-items:center;gap:14px;min-width:0}
.p-now .cov{width:54px;height:54px;border-radius:5px;box-shadow:none;flex:none}
.p-now-txt{min-width:0}
.p-t{font-weight:650;font-size:14.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.p-s{color:var(--text-3);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
body.playing .p-t{color:var(--lime)}
/* 2026-08-18 (Luc): the now-playing block is not clickable on desktop — it is
   just the song, the artist, the record and the date. The expand control only
   exists ≤700px, where the sheet is the sole route to seek/prev/next/AirPlay;
   the ≤700 block below re-declares display for it. */
/* 🔴 must out-specify .icon-btn{display:grid} five lines below: the button is
   class="icon-btn p-expand", so a bare .p-expand rule loses on source order
   and the arrow stayed visible on desktop. .p-now .p-expand is (0,2,0). */
.p-now .p-expand{display:none}
body.player-empty .p-expand{display:none}
.p-expand svg{width:16px;height:16px}
.p-mid{display:flex;flex-direction:column;align-items:center;gap:7px;min-width:0}
.p-btns{display:flex;align-items:center;gap:16px}
.icon-btn{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;color:var(--text-2);transition:color .13s,background .13s;flex:none}
/* player.js hides unsupported controls via the hidden attribute; display:grid above would defeat it */
.icon-btn[hidden]{display:none}
.icon-btn:hover{color:var(--text);background:var(--bg-hover)}
.icon-btn svg{width:17px;height:17px;fill:currentColor}
.p-toggle{width:44px;height:44px;border-radius:50%;background:var(--text);color:#0A0B0D;display:grid;place-items:center;transition:transform .13s var(--ease)}
.p-toggle:hover{transform:scale(1.06)}
.p-toggle svg{width:17px;height:17px;fill:currentColor}
.p-toggle .ic-pause{display:none}
body.playing .p-toggle .ic-play{display:none}
body.playing .p-toggle .ic-pause{display:block}
.p-scrub{display:flex;align-items:center;gap:10px;width:100%;max-width:520px}
.p-scrub span,.p-scrub-m span{font-size:11px;color:var(--text-3);font-variant-numeric:tabular-nums;min-width:34px}
.p-scrub span:last-child,.p-scrub-m span:last-child{text-align:right}
/* touch-action:none so a horizontal drag on the bar scrubs instead of being
   swallowed by the page's scroll gesture */
.bar{flex:1;height:4px;border-radius:2px;background:#2A2B33;position:relative;cursor:pointer;touch-action:none}
.bar .fill{position:absolute;left:0;top:0;bottom:0;width:0%;border-radius:2px;background:var(--lime);transition:width .18s linear}
/* the thumb: a white dot riding the end of the fill, so the position is
   readable at a glance and there is something to grab */
.bar .fill::after{
  content:"";position:absolute;right:-6px;top:50%;
  width:12px;height:12px;margin-top:-6px;border-radius:50%;
  background:#FFF;box-shadow:0 1px 6px rgba(0,0,0,.55);
  transition:transform .12s var(--ease);
}
/* during a drag the fill must not lerp behind the finger, and the thumb grows */
body.scrubbing .bar .fill{transition:none}
body.scrubbing .bar .fill::after{transform:scale(1.3)}
/* the mobile scrubber row. Declared here, ABOVE the responsive block: as a
   later sibling rule its display:none used to win over the @media override,
   so the phone player shipped with no seek bar and no times at all. */
.p-scrub-m{display:none;align-items:center;gap:10px;padding:0 clamp(14px,2.5vw,28px) 4px}
.p-right{display:flex;align-items:center;justify-content:flex-end;gap:6px}
.vol{
  width:88px;height:4px;-webkit-appearance:none;appearance:none;border-radius:2px;
  background:linear-gradient(var(--lime),var(--lime)) no-repeat #2A2B33;background-size:72% 100%;cursor:pointer;
}
.vol::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--text)}
.vol::-moz-range-thumb{width:12px;height:12px;border:0;border-radius:50%;background:var(--text)}

/* player.js error banner (injected into #player) */
.player-error{
  position:absolute;left:50%;transform:translateX(-50%);bottom:calc(100% + 10px);
  display:flex;align-items:center;gap:12px;max-width:min(92vw,520px);
  background:#2A1417;border:1px solid #5B2630;color:#FFD9DE;
  border-radius:999px;padding:9px 14px 9px 18px;font-size:13px;
}
.player-error-close{color:inherit;opacity:.7}
.player-error-close:hover{opacity:1}

/* ── now-playing sheet ────────────────────────────────────── */
.sheet{
  position:fixed;inset:0;z-index:95;background:var(--bg);
  padding:clamp(20px,4vh,44px) var(--gutter) calc(var(--player) + 24px);
  display:flex;flex-direction:column;align-items:center;gap:14px;overflow-y:auto;
}
.sheet[hidden]{display:none}
.sheet-close{align-self:flex-start}
.sheet-close svg{width:20px;height:20px}
.sheet-art{width:min(300px,62vw);border-radius:10px;margin-top:6px}
.sheet-title{font-weight:880;font-stretch:122%;font-size:clamp(26px,4vw,44px);line-height:1.05;text-align:center;letter-spacing:-.01em}
.sheet-sub{color:var(--text-3);font-size:13px;letter-spacing:.16em;font-weight:700;text-transform:uppercase}
.sheet-times{display:flex;gap:14px;color:var(--text-3);font-size:12px;font-variant-numeric:tabular-nums}
.sheet-tl{width:min(720px,100%);margin-top:8px}
.sheet-tl .tl-row{
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:12px;align-items:center;
  width:100%;text-align:left;padding:9px 12px;border-radius:6px;color:var(--text-2);cursor:pointer;
  box-shadow:inset 0 -1px 0 rgba(255,255,255,.04);
}
.sheet-tl .tl-row:hover{background:var(--bg-raise);color:var(--text)}
.sheet-tl .tl-row.now{color:var(--lime)}
.sheet-skips{display:flex;gap:12px;margin-top:6px}
.skip-btn{
  border:1px solid var(--line);border-radius:999px;padding:10px 18px;
  color:var(--text-2);font-weight:700;font-size:12px;letter-spacing:.1em;
}
.skip-btn:hover{color:var(--text);border-color:var(--text-3);background:var(--bg-raise)}

/* ── boot / error ─────────────────────────────────────────── */
.boot,.fatal{padding:60px var(--gutter);color:var(--text-3);font-size:13px;letter-spacing:.18em;font-weight:700}
.fatal{color:#FFD9DE}
.fatal .btn-ghost{margin-top:16px;letter-spacing:.1em}

/* ── responsive ───────────────────────────────────────────── */
@media (max-width:1020px){
  .topsearch{order:3;flex:1 1 100%}
  .topctl{order:4}
  /* display type is set in vw — a single long word outruns its column and gets
     clipped by the section's overflow:hidden. Let the big faces break instead. */
  .hero h1,.ghost,.rec h3,.h-sec,.card .c-title{overflow-wrap:break-word}
}
@media (max-width:700px){
  /* ══ PHONE SHELL ═══════════════════════════════════════════════════
     Designed for 390×844, not shrunk from 1440. Two pinned surfaces,
     both as thin as a 44px touch target allows:

       top    53px  SIGZ + search        (hides on scroll-down)
       bottom 57px  artwork + title + ONE play button

     110px of 844 = 13.0% at rest, 6.8% while scrolling down. Everything
     else that used to be pinned now lives where it belongs: the filter
     strip inside the catalogue section it filters, and the transport +
     scrubber + AirPlay inside the now-playing sheet, one tap away.

     --player / --topbar are read by the toast, the sheet, the body's
     bottom padding and every in-page anchor's scroll-margin. Each has
     TWO values. The bare :root pair is the NO-MODULE fallback and
     describes the markup exactly as authored (two-row masthead, three-
     across player + scrub row). music-app.js puts .mshell on <html> the
     moment BOTH placement passes report success, and the .mshell pair is
     the real phone shell. Every rule below that would REMOVE something is
     gated on .mshell, so a page whose module never ran — or whose moves
     failed and were rolled back — keeps all of its controls.
     --topbar is a first-paint fallback either way: measureBar() writes
     the measured height into an inline style on <html>, which wins. */
  :root{--player:calc(90px + env(safe-area-inset-bottom,0px));--topbar:calc(110px + env(safe-area-inset-top,0px))}
  :root.mshell{--player:calc(57px + env(safe-area-inset-bottom,0px));--topbar:calc(53px + env(safe-area-inset-top,0px))}

  /* ── masthead: the wordmark and the search field, nothing else ──
     4px + 44px target + 4px + 1px border = 53px. The queue button is
     gone from the phone entirely and music-app.js moves the filter
     strip and shuffle down into the catalogue section, so what is left
     pinned is exactly what Luc asked to keep. */
  .topbar{
    gap:8px 10px;padding:4px var(--gutter);
    padding-top:calc(4px + env(safe-area-inset-top,0px));
    padding-inline:max(var(--gutter),env(safe-area-inset-left,0px)) max(var(--gutter),env(safe-area-inset-right,0px));
    transition:transform .2s var(--ease);will-change:transform;
  }
  /* hide on scroll-down / slide back on scroll-up. The bar is sticky, so
     it keeps its box in the flow: translating it moves no other pixel and
     neither the hide nor the reveal can jump the layout. -100% is exactly
     the bar's own height now that nothing else lives in it. */
  .topbar.is-hidden{transform:translateY(-100%)}
  @media (prefers-reduced-motion:reduce){
    /* music-app.js never sets .is-hidden when motion is reduced; this is
       the belt-and-braces if the class outlives a mid-scroll change. */
    .topbar.is-hidden{transform:none}
  }
  .wm{font-size:19px;margin-right:0}
  .topsearch{order:1;flex:1 1 0;min-width:0;min-height:44px;padding:0 14px}
  /* 16px or iOS Safari zooms the whole page on focus and the layout jumps */
  .topsearch input{font-size:16px}
  .topsearch .clear-q{padding:0 8px;min-height:44px;font-size:20px;line-height:44px}
  /* the two order/flex groups below describe the HEADER-RESIDENT layout,
     i.e. the no-module fallback: search on its own row, strip under it.
     Once the strip has been moved into <main> they are inert on it. */
  .top-actions{order:2;flex:none;margin-left:0}
  .topctl{order:3;flex:1 1 100%;overflow-x:auto;overscroll-behavior-x:contain}
  /* module alive: shuffle has been moved into the catalogue strip and the
     queue is gone from the phone, so the whole action group leaves. */
  .mshell .topbar .top-actions{display:none}

  /* the slim bar overlays content whenever it slides back, so in-page
     jumps (the skip link, smooth scroll, any hand-typed hash) must land
     below it. The only link target left on the page is the skip link
     (#index) — #about/#start went 2026-08-18 and the ALBUMS · MIXES jump
     nav that pointed at #chapters/#mixes went 2026-08-21 — but both
     sections still exist and are still legal anchors, so they keep their
     offset rather than landing under the bar for a /home#mixes deep link. */
  #top,#index,#chapters,#mixes{scroll-margin-top:calc(var(--topbar) + 10px)}

  /* ── filters live with the section they used to filter ──────────
     On a phone the chips are not chrome, they are catalogue controls —
     so music-app.js moves .topctl out of the <header> and into #index,
     directly above #results (the card shelf that used to sit there was
     removed 2026-08-20). One 44px row aligned to the section gutter, and
     it scrolls away with the page like everything else. (Sort and shuffle
     were removed from the page 2026-08-18; the strip is the filter chips
     now.) */
  .shelf > .topctl{margin:0 0 16px;padding:0;gap:8px}
  .shelf > .topctl .btn{font-size:13px;padding:0 16px}
  /* searching replaces the grid with results; the grid's filters have
     nothing to act on, so they get out of the way of the answer. */
  body.searching .shelf > .topctl{display:none}
  .btn{min-height:44px;min-width:44px;padding:0 13px;gap:6px;font-size:0}
  .btn svg{width:18px;height:18px}
  #qCount{font-size:11.5px}
  .chip{padding:0 15px;min-height:44px}

  /* ── the queue is gone from the phone ───────────────────────────
     Luc: "I find the queue to be annoying and in the way." The masthead
     button, the duplicate button in the player and the drawer itself all
     leave. The queue FEATURE is untouched: "+" on any card, row, hero or
     chapter still enqueues, still toasts what it did and how many tracks
     are up next, and the queue still decides what plays next — it simply
     has no panel to get in the way. Desktop keeps all three, and
     setQueueOpen() in music-app.js refuses to open the drawer at ≤700
     so the state is unreachable even from a stale event. */
  .mshell #queueTopBtn,.mshell #queueBtn,.mshell .queue{display:none}

  /* ── hero: fit above the fold, say each thing once ──────────────
     The TRACKS · MIN · PLAYS strip used to be visually hidden here because
     it restated the dek. It is gone from the markup entirely as of
     2026-08-21, so there is nothing left to hide. */
  .hero{min-height:0}
  /* 2026-08-18 (Luc: "I don't like that the artwork is on top of the title"):
     the cover no longer stacks above the text with order:-1. It keeps the
     markup's order and sits in its own column beside the name, top-aligned,
     the way a streaming app heads an album. */
  /* The cover FLOATS right and the text wraps around it: the kicker, the
     title and however many lines of the dek sit beside it, and every line
     past its bottom edge gets the full width — which is what neither of the
     two earlier attempts managed. A grid column reserved the width down the
     whole hero (buttons stranded in a 230px column); absolute positioning
     took the cover out of flow entirely, so the dek ran underneath it. */
  .hero-in{display:block;padding-top:22px;padding-bottom:26px}
  /* The float would otherwise start level with the kicker. Push it down by
     exactly the kicker's line box plus the title's top margin, so the top of
     the artwork lines up with the top of the release name (Luc, 2026-08-18).
     11.5px is .kicker's font-size, 1.55 the body line-height, 10px the h1's
     margin-top below — keep these in step if any of the three changes. */
  .hero-cover{
    float:right;width:min(29vw,110px);
    margin:calc(11.5px * 1.55 + 10px) 0 12px 16px;
  }
  .hero h1{font-size:clamp(34px,10.5vw,64px);margin:10px 0 8px}
  /* a flex row does not wrap around a float, it overlaps it — so the buttons
     start below the cover rather than colliding with its bottom corner */
  .hero-actions{clear:right}
  .hero-actions{gap:10px;margin-top:16px}
  .grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
  .rec{grid-template-columns:1fr}
  .rec .cov{width:min(52vw,220px)}

  /* ── player: artwork · title · ONE play button ──────────────────
     Luc: "The player should just show a play button on mobile."
     prev/next, the scrubber, the times and AirPlay are MOVED (not
     cloned, not deleted) by music-app.js into the now-playing sheet,
     which is one tap on the bar. Same nodes, same ids, same handlers,
     same player.js contract — AirPlay in particular is still the one
     real Remote Playback route and is still hidden by its own [hidden]
     attribute when the browser has no support.

     The ungated rule is the no-module fallback: the authored three-
     across bar over a full-width scrubber, 90px. .mshell is the real
     phone bar: one 44px row, 6px of padding either side, 57px. */
  .player{
    grid-template-columns:minmax(0,1fr) auto auto;
    grid-template-rows:auto auto;
    grid-template-areas:"now transport aux" "scrub scrub scrub";
    gap:8px 10px;height:var(--player);
    padding-top:8px;padding-bottom:calc(8px + env(safe-area-inset-bottom,0px));
  }
  .mshell .player{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"now transport";
    gap:0 10px;
    padding-top:6px;padding-bottom:calc(6px + env(safe-area-inset-bottom,0px));
  }
  /* progress IS the bar's top edge: 2px, no row, no layout cost, and it is
     still there while the sheet is open. A full-width hairline scaled on the
     X axis from its LEFT edge — transform-origin is the whole point, without
     it progress would grow outward from the centre. scaleX() is
     compositor-only, so a per-timeupdate repaint costs no layout, and the
     element only exists in the DOM at ≤700 (music-app.js appends it in
     placePlayer and removes it on the desktop path), so no base-level rule is
     needed to keep it out of the desktop grid. It is absolutely positioned,
     so it is not a grid item and consumes no cell. Zero duration →
     scaleX(0). The baseline reduced-motion block neutralises the transition
     along with every other one. */
  .mshell .player .p-hair{
    position:absolute;left:0;top:0;height:2px;width:100%;
    background:var(--lime);pointer-events:none;
    transform:scaleX(0);transform-origin:left center;
    transition:transform .18s linear;
  }
  .p-now{grid-area:now;position:relative;gap:10px}
  .p-now .cov{width:44px;height:44px}
  .p-now-txt{padding-right:26px}
  /* the whole now-playing block opens the sheet; the chevron is the cue.
     Higher specificity than .icon-btn's 44×44 below, so the overlay
     cannot collapse back into a square over the artwork. */
  /* display is re-declared here because the base rule hides it on desktop */
  .p-now .p-expand{display:grid;position:absolute;inset:0;width:auto;height:auto;border-radius:6px;place-items:center end}
  .p-now .p-expand:hover{background:none}
  .p-mid{grid-area:transport;gap:0}
  .p-mid .p-scrub{display:none}
  .p-btns{gap:4px}
  .icon-btn{width:44px;height:44px}
  .p-right{grid-area:aux}
  /* iOS ignores element volume (hardware keys own it) and a 4px slider is
     not a touch target. With AirPlay moved into the sheet and the queue
     gone, nothing is left in the aux cell at all. */
  .vol,#queueBtn{display:none}
  .mshell .p-right{display:none}
  /* the play button becomes a direct child of .player while the sheet is
     closed, and rides back into .p-btns while it is open */
  .mshell .player > .p-toggle{grid-area:transport}
  /* …EXCEPT before the first release loads. player.js's expandSheet() returns
     early while body.player-empty is set and the sheet's only entry point is
     display:none in that state, so the expanded view does not exist yet and
     cannot be AirPlay's only route. Pre-play the aux cell comes back and
     #airplayBtn sits in it, exactly where the baseline had it (#vol and
     #queueBtn stay hidden by the rule above, so the cell holds AirPlay
     alone). music-app.js's placeAirplay() moves it into the expanded view the
     moment a release loads — which is also when the bar gains a title, a
     progress edge and a working expand affordance. Both selectors are (0,3,1)
     and beat the (0,2,0) rules above on specificity, not on order. */
  .mshell body.player-empty .player{
    grid-template-columns:minmax(0,1fr) auto auto;
    grid-template-areas:"now aux transport";
  }
  .mshell body.player-empty .p-right{display:flex}
  .p-scrub-m{grid-area:scrub;display:flex;padding:0}
  /* 4px of ink, 26px of tap: the extra area stays inside the row */
  .p-scrub-m .bar::after{content:"";position:absolute;left:0;right:0;top:-10px;bottom:-12px}

  /* ── now playing: the phone's real player ───────────────────────
     The sheet covers the bar (inset:0, z-index 95 over 80), so the
     transport that moved in here and the play button that moved in with
     it are never on screen twice. .sheet is a flex column, so the skips
     and the tracklist are re-ordered with `order` instead of a second
     DOM move — the relocated rows land straight under the title. */
  /* The expanded view is inset:0 over the notch, and it is the home of the
     transport and of AirPlay, so it has to protect all four safe areas itself
     — the masthead's and the player's insets do nothing for it. Without the
     top inset a 47px notch puts ~27px of the 44px close target under the
     unsafe region; in landscape an inset wider than the 18px gutter puts the
     left edge of the content under it. */
  .sheet{
    gap:12px;
    padding-top:calc(clamp(20px,4vh,44px) + env(safe-area-inset-top,0px));
    padding-inline:max(var(--gutter),env(safe-area-inset-left,0px)) max(var(--gutter),env(safe-area-inset-right,0px));
    padding-bottom:calc(28px + env(safe-area-inset-bottom,0px));
  }
  /* sticky offsets are measured from the scrollport (the padding box), NOT from
     the content box — so top:0 would re-park the close button under the notch
     as soon as the sheet is scrolled. It gets the inset too. */
  .sheet-close{position:sticky;top:env(safe-area-inset-top,0px);width:44px;height:44px;background:var(--bg);z-index:1}
  /* 2026-08-18 (Luc): the expanded view is artwork-first — bigger cover, no
     SIGZ · RECORD · date line, and the transport sits low on the screen. */
  .sheet-art{width:min(460px,88vw,52vh)}
  .mshell .sheet .sheet-sub{display:none}
  .sheet-tl .tl-row{padding:12px}
  /* ±30s live inside .p-btns now, flanking prev/play/next: icon + a small
     "30", 44px of tap, no pill. .sheet-skips is left empty and collapses. */
  .mshell .sheet .p-btns .skip-btn{
    position:relative;display:grid;place-items:center;
    width:44px;height:44px;padding:0;border:0;background:none;border-radius:50%;
    color:var(--text-2);font-size:0;
  }
  .mshell .sheet .p-btns .skip-btn svg{width:26px;height:26px;stroke-width:1.7}
  .mshell .sheet .p-btns .skip-btn .skip-n{
    position:absolute;font-size:8.5px;font-weight:800;letter-spacing:.02em;
    font-variant-numeric:tabular-nums;
  }
  .mshell .sheet .p-btns .skip-btn:active{color:var(--text)}
  .skip-btn{padding:13px 20px}
  /* #sCur/#sDur restate #pCurM/#pDurM, which have just moved in above
     them — one visible source of truth, both still painted by player.js */
  .mshell .sheet .sheet-times{display:none}
  /* margin-top:auto absorbs the free space above the first relocated control,
     so seek + transport + skips + tracklist all ride low on the screen. With
     a full sheet there is no free space and it is a no-op. */
  .mshell .sheet .p-scrub-m{width:min(720px,100%);padding:0;margin-top:auto}
  /* a real seek target now, not a 4px line inside a 57px bar */
  .mshell .sheet .p-scrub-m .bar::after{top:-16px;bottom:-16px}
  .mshell .sheet .p-mid{flex-direction:row;justify-content:center;align-items:center;gap:22px}
  .mshell .sheet .p-btns{gap:14px}
  .mshell .sheet .sheet-skips{order:1}
  .mshell .sheet #sheetTl{order:2}

  /* queue drawer (desktop-only on this breakpoint, but it is still the
     ≤700 layout for a tablet that never gets .mshell): 44px rows */
  .q-head .q-clear{min-height:44px;padding:0 4px}
  .q-row{grid-template-columns:40px minmax(0,1fr) auto 44px}
  .q-row .qx{width:44px;height:44px;font-size:16px}
  .tr{grid-template-columns:28px minmax(0,1fr) auto 44px;gap:10px}
  .tr .tr-p{display:none}
  /* Both set the inherited variable, so the record and series years stay in the
     same proportion on a phone as they do on a desktop instead of one of them
     keeping its desktop size. */
  .chapter{--ghost-fs:clamp(96px,26vw,150px)}
  .mix-chapter{--ghost-fs:clamp(46px,11vw,74px)}

  /* section rhythm: 9–11vh of padding is a whole thumb-swipe of nothing.
     Unlike desktop the section is NOT empty here — music-app.js moves the
     chip strip into it — so it keeps its top padding; the gap below the
     chips is the strip's own 16px margin, hence no bottom padding. Both
     rules must be re-stated at the SAME specificity as their base-level
     counterparts (a bare .shelf here would lose to base
     `body.searching .shelf` and take the desktop clamp on a phone). */
  .shelf{padding:26px var(--gutter) 0}
  body.searching .shelf{padding:26px var(--gutter) 34px}
  /* the counts line does not fit beside the domain on a phone */
  .colophon{grid-template-columns:1fr;grid-template-areas:"brand" "socials" "counts";gap:10px}
  .colophon #colophonCounts{text-align:left}
  .chapter{padding:40px var(--gutter) 32px}
  .rec{padding:24px 0}
  .mixes{padding:40px var(--gutter)}
  .mixgrid{margin-top:22px}

  /* cards: the play button is anchored to the artwork, not to the card box —
     card height moves with the title, and bottom:64px landed on the words. */
  .card .c-title{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  /* no max-height clip here: music-app.js appends the DEMO badge inside .c-meta */
  .card .c-meta{gap:5px;font-size:12px}

  /* ── density: one line everywhere a release describes itself ────
     .c-meta ships as ALBUM · 2019 · 11 tracks · 35 min [DEMO] · N plays —
     alternating value/separator spans (music-app.js:191-193), so children
     1-3 are always "<kind> · <year>" for records AND mixes and everything
     from child 4 on is the tail. .rec-meta is the recommendation row and
     ships six children (year, kind, tracks, duration, plays, accent hex)
     with no separators, so children 3+ are its tail — the previous run
     stopped at 5+ and left four fields wrapping onto two lines at 360px.
     (.hero-meta used to be hidden whole here — it was removed from the page
     on 2026-08-21, so it is no longer in this group.)

     These are visually-hidden, NOT display:none: the track/duration/play
     facts stay in the accessibility tree for screen-reader users, who are
     not the ones asking for a calmer screen. Absolutely positioned with
     every offset auto, so each box keeps its static position and takes
     zero space — no reflow, and nothing still shown gets clipped.

     :not(.tag-demo) is load-bearing. The DEMO badge is appended INSIDE
     .c-meta and always lands at child ≥6, so a naive tail-hide would eat
     it — exactly the clip an earlier merge had to back out. */
  .card .c-meta > *:nth-child(n+4):not(.tag-demo),
  .rec-meta > *:nth-child(n+3){
    position:absolute;width:1px;height:1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap;pointer-events:none;
  }
  /* the badge is the only thing left that can push meta to a second line,
     so shrink it to buy headroom: worst case is a 2-column grid at ~350px,
     where the card's content box is 130px and "ALBUM · 2019 DEMO" ≈118px. */
  .card .c-meta .tag-demo{font-size:9px;padding:1px 4px;letter-spacing:.08em;flex:none}
  .rec-meta{gap:10px}

  /* touch targets — 44px minimum, thumbs are blunt */
  .tr .tr-q{width:44px;height:44px}
  .tl-toggle{padding:14px 4px}
  .rec-play{padding:14px 22px 14px 17px}
  /* .jump a's 44px touch target went with the jump nav itself (2026-08-21). */
  /* .socials a is already a 44px icon button at every width (2026-08-18) —
     the old inline-block + padding rule here fought that and is gone. */
  .empty .btn-ghost{padding:14px 18px}
}
@media (prefers-reduced-motion:reduce){
  *{animation:none !important;transition:none !important;scroll-behavior:auto !important}
}
