/* band_page — the public catalog at /:slug. Neutral "quiet tool" skin;
   bands recolor via CSS variables (--bp-accent from Visuals), never layout.
   Native nesting: everything inside .band-page{} compiles to ".band-page X". */

body.band-page{
  /* Contrast (audit 2026-08-02): --bp-faint was #9b9da3 = 2.71:1 on white and
     #71706b = 3.43:1 on the dark card — both under WCAG AA for the small text
     it carries (section labels, table headers, fineprint, dock timestamps).
     Raised to 4.68:1 / 5.39:1, still a tier below --bp-muted so the hierarchy
     reads the same. */
  --bp-ink:#1d1d1b; --bp-muted:#66686d; --bp-faint:#727479; --bp-prose:#2c2d30;
  --bp-page:#faf9f7; --bp-card:#fff; --bp-line:#e4e2dc;
  --bp-accent:#c9145c; --bp-accent-ink:#fff; --bp-accent-soft:#fbe9f0;
  --bp-ok:#16a34a; --bp-hot:#ea580c; --bp-r:12px;
  --bp-topbar:rgba(255,255,255,.86);
  --bp-scrim:rgba(255,255,255,.78);
  --bp-veil-a:rgba(255,255,255,.15); --bp-veil-b:rgba(255,255,255,.7);
  --bp-font-body:"Instrument Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --bp-font-head:var(--bp-font-body);
  --bp-head-transform:none; --bp-head-spacing:-.02em;
  background:var(--bp-page); color:var(--bp-ink); min-height:100vh; line-height:1.55;
  font-family:var(--bp-font-body);
  padding-block-end:calc(var(--player-h) + env(safe-area-inset-bottom, 0px));
  /* anchors, and the deep-linked track's scrollIntoView, must land above the
     bar rather than under it */
  scroll-padding-bottom:calc(var(--player-h) + 1rem);
}
/* One source of truth for how much room the fixed player takes. It lives on
   <html>, NOT on body: a custom property redeclared on body shadows the
   inherited one, and every [data-player] rule below would silently do
   nothing. Consumed by the page padding, the scroll padding and the dock. */
html{--player-h:0px}
html[data-player="bar"]{--player-h:64px}
html[data-player="pill"]{--player-h:52px}
@media (max-width:700px){ html[data-player="bar"]{--player-h:56px} }
/* WCAG 1.4.10 reflow: at 400% zoom or on a short landscape phone the bar
   drops its secondary controls rather than eating the viewport. */
@media (max-width:340px), (max-height:430px){ html[data-player="bar"]{--player-h:48px} }
/* Collapsed: the bar shrinks to a pill, the music keeps playing. These live at
   top level on purpose — nested under .band-page they would compile to
   ".band-page html[...]" and match nothing. */
html[data-player="pill"] .player{gap:var(--s3)}
/* the transport sheds everything except play — a collapsed bar you cannot
   pause without expanding first is a worse control than no control */
html[data-player="pill"] .transport{flex:none}
html[data-player="pill"] #btn-prev,
html[data-player="pill"] #btn-next,
html[data-player="pill"] .seek,
html[data-player="pill"] .clock,
html[data-player="pill"] #btn-share{display:none}
html[data-player="pill"] #btn-collapse{margin-left:auto}

.visually-hidden{position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0}

/* Dark mode — "more metal". Same layout, flipped palette; the Visuals theme
   panel switches this via the theme-dark body class. */
body.band-page.theme-dark{
  --bp-ink:#e9e7e2; --bp-muted:#a3a19c; --bp-faint:#93918b; --bp-prose:#dcdad5;
  --bp-page:#131315; --bp-card:#1c1c1f; --bp-line:#2c2c31;
  --bp-accent-soft:#2a2317;
  --bp-topbar:rgba(19,19,21,.86);
  --bp-scrim:rgba(28,28,31,.8);
  --bp-veil-a:rgba(19,19,21,.15); --bp-veil-b:rgba(19,19,21,.82);
}
/* Contrast guard (fixes-round2 §10): translucent cards sit on an unknown
   image, so the surface under text gets pinned near the card token — blur,
   and below ~70% opacity a minimum tint too. The text ramps above were
   audited AA against the card token; the guard keeps that assumption true
   across the whole card-opacity slider, worst-case image included. */
body.band-page.cards-glass .card{backdrop-filter:blur(12px)}
body.band-page.cards-scrim .card{position:relative; backdrop-filter:blur(18px) saturate(.85)}
body.band-page.cards-scrim .card::before{content:""; position:absolute; inset:0;
  background:var(--bp-scrim); pointer-events:none}
body.band-page.cards-scrim .card > *{position:relative}
/* Under glass the surface can drift toward mid-gray, where the quiet ramps
   die first — muted/faint step toward ink (checked ≥4.5:1 on the scrim
   floor, worst-case image), links keep the accent hue but leave the muted
   brightness range entirely. Hierarchy survives; illegibility doesn't. */
body.band-page.cards-glass{--bp-muted:#43454a; --bp-faint:#4c4e53}
body.band-page.theme-dark.cards-glass{--bp-muted:#cfcdc8; --bp-faint:#c5c3be}
@supports (color: color-mix(in oklab, red, white)) {
  body.band-page.cards-glass:not(.theme-dark) .card a:not(.cta):not(.btn){
    color:color-mix(in oklab, var(--bp-accent) 60%, #000)}
}

/* Cover photo: full-bleed hero strip under the topbar. */
.band-page .hero{max-height:360px; overflow:hidden; border-bottom:1px solid var(--bp-line)}
.band-page .hero img{width:100%; height:360px; object-fit:cover; display:block}
@media (max-width:700px){ .band-page .hero, .band-page .hero img{height:200px} }

body.band-page::after{content:none}
body.band-page h1, body.band-page h2, body.band-page h3{
  font-family:var(--bp-font-head); text-transform:var(--bp-head-transform);
  text-shadow:none; letter-spacing:var(--bp-head-spacing)}
body.band-page a{color:var(--bp-accent)}
/* A band accent picked against white (the default) lands around 4.1:1 on the
   dark card — text links sat just under AA. Lift the accent for links only, so
   the band's colour still owns buttons and marks (audit 2026-08-02). */
@supports (color: color-mix(in oklab, red, white)) {
  body.band-page.theme-dark a:not(.cta):not(.btn){color:color-mix(in oklab, var(--bp-accent) 70%, #fff)}
}
body.band-page a:hover{text-underline-offset:3px}
body.band-page :focus-visible{outline:2px solid var(--bp-accent); outline-offset:2px; border-radius:4px}

@keyframes bp-pulse{0%{box-shadow:0 0 0 0 rgba(234,88,12,.35)} 100%{box-shadow:0 0 0 8px rgba(234,88,12,0)}}

.band-page{
  /* With a cycle banner up top, the topbar would stack a second identity
     and a second primary CTA right under the banner's (founder, 2026-08-07).
     player.js docks it (tb-dock) while the banner exists and slides it in
     (tb-in) once the banner scrolls away — Follow stays reachable
     mid-page, never doubled at the top. No JS = topbar stays as-is. */
  .topbar.tb-dock{position:fixed; top:0; left:0; right:0; z-index:20;
    transform:translateY(-110%); transition:transform 200ms var(--ease-out)}
  .topbar.tb-dock.tb-in{transform:translateY(0)}
  .topbar{position:sticky; top:0; z-index:10;
    background:var(--bp-topbar); backdrop-filter:blur(8px); border-bottom:1px solid var(--bp-line);

    .in{max-width:1080px; margin:0 auto; padding:var(--s3) var(--s5);
      display:flex; align-items:center; gap:var(--s5)}
    /* header = identity + Follow, nothing between (fixes-round2 §12);
       the .cta look itself comes from the button atom above */
    .cta{margin-left:auto}
    .cta.is-following{background:var(--bp-accent-soft); color:var(--bp-accent)}
    .cta.is-following:hover{color:var(--bp-accent)}
  }
  .brand{font-weight:700; font-size:.9375rem; letter-spacing:-.01em; color:var(--bp-ink)}
  /* identity slot: logo at a fixed height, or the name in the page's type */
  .brand-mark{display:block; height:26px; width:auto; max-width:180px; object-fit:contain}
  .brand-name{font-family:var(--bp-font-head, inherit); text-transform:var(--bp-head-transform, none);
    letter-spacing:var(--bp-head-spacing, -.01em); font-size:1.0625rem}

  .wrap{max-width:1080px; margin:0 auto; padding:var(--s6) var(--s5) var(--s7)}
  .grid{display:grid; grid-template-columns:320px 1fr; gap:var(--s5); align-items:start}
  .rail{position:sticky; top:72px}

  .card{background:var(--bp-card); border:1px solid var(--bp-line); border-radius:var(--bp-r);
    box-shadow:0 1px 2px rgba(17,18,20,.05); margin-bottom:var(--s4); overflow:hidden; padding:0}
  .card .pad{padding:var(--s5)}
  .label{font-family:var(--bp-font-head); font-size:.75rem; font-weight:600; letter-spacing:.05em; text-transform:uppercase;
    color:var(--bp-faint); margin-bottom:var(--s3)}

  .photo{width:100%; aspect-ratio:4/3; object-fit:cover; display:block; background:var(--bp-line)}
  /* infobox photo (2026-07-29): square, full rail width, focal-point crop.
     2026-08-24: rounded. Shape now carries entity type on this page — the
     band is a rounded surface, releases are square-cornered art. A circular
     mask was the other option and is one value away, but it centre-crops
     hard, which is a gamble on a band photo with five people in it. */
  .photo.sq{aspect-ratio:1/1; border-radius:var(--bp-r)}
  .ib-photo{position:relative; border-radius:var(--bp-r); overflow:hidden; margin:var(--s4) var(--s4) 0}
  /* optional logo overlay on the lower third; scrim tone opposes the logo
     tone so black-or-white marks stay legible on any photo */
  .ib-logo{position:absolute; left:0; right:0; bottom:0; height:40%; pointer-events:none;
    display:flex; align-items:flex-end; justify-content:center; padding:0 8% 6%}
  .ib-logo img{max-height:70%; max-width:72%; object-fit:contain} /* 70% of 40% ≈ the 28% cap */
  .ib-logo.tone-light{background:linear-gradient(180deg, rgba(0,0,0,0), rgba(0,0,0,.55))}
  .ib-logo.tone-dark{background:linear-gradient(180deg, rgba(255,255,255,0), rgba(255,255,255,.6))}
  .prev-wrap summary{cursor:pointer; list-style:none}
  .prev-wrap summary::-webkit-details-marker{display:none}
  .prev-wrap summary::after{content:"▸"; margin-left:6px; font-size:.7em}
  .prev-wrap[open] summary::after{content:"▾"}
  .booking-line{margin-top:var(--s3)}
  h1{font-size:1.75rem; font-weight:700; line-height:1.2}
  .meta-line{color:var(--bp-muted); font-size:.9375rem; margin-top:var(--s1)}
  .status{display:flex; flex-direction:column; gap:6px; margin-top:var(--s3)}
  .badge{display:inline-flex; align-items:center; gap:6px;
    font-size:.8125rem; font-weight:600; color:var(--bp-ok)}
  .badge::before{content:""; width:7px; height:7px; border-radius:50%; background:var(--bp-ok)}
  .badge.hot{color:var(--bp-hot)}
  .badge.hot::before{background:var(--bp-hot); animation:bp-pulse 1.6s ease-out infinite}

  .follow-row{display:flex; flex-direction:column; gap:var(--s2); margin-top:var(--s4)}
  .follow-row input{font:inherit; font-size:.9375rem; color:var(--bp-ink);
    border:1px solid var(--bp-line); border-radius:8px; padding:11px 12px; width:100%; background:var(--bp-page)}
  .follow-row input::placeholder{color:var(--bp-faint)}
  /* ---- THE button atom (design-system doc 09 §atoms) ----
     One implementation for every padded control on the page. Controls
     never inherit the page's reading line-height: inline-flex, centered
     on both axes, line-height 1 — the 2026-08-07 cycle-CTA float was a
     min-height box with top-hanging text, the exact bug this bans.
     Variants change size/width ONLY; colors come from the page tokens. */
  .btn, .cta{display:inline-flex; align-items:center; justify-content:center; gap:8px;
    font:inherit; font-size:.9375rem; font-weight:600; line-height:1; cursor:pointer;
    text-align:center; text-decoration:none; white-space:nowrap; border:none; border-radius:8px;
    background:var(--bp-accent); color:var(--bp-accent-ink); padding:12px 18px; min-height:44px}
  :is(.btn, .cta):hover{filter:brightness(1.08); text-decoration:none; color:var(--bp-accent-ink)}
  .btn.wide{width:100%}
  :is(.btn, .cta).small, .cta{font-size:.875rem; padding:8px 14px; min-height:36px}
  /* outline variant — one primary per card stays true: the release header has
     a filled Play and everything beside it is quiet */
  .btn.ghost{background:transparent; color:var(--bp-ink); border:1px solid var(--bp-line)}
  .btn.ghost:hover{background:var(--bp-page); color:var(--bp-ink); border-color:var(--bp-muted); filter:none}
  .fineprint{font-size:.75rem; color:var(--bp-faint); margin-top:var(--s2)}
  .links{display:grid; grid-template-columns:1fr 1fr; gap:var(--s2) var(--s4); font-size:.9375rem}
  dl.facts{font-size:.875rem; display:grid; grid-template-columns:auto 1fr; gap:var(--s2) var(--s4)}
  dl.facts dt{color:var(--bp-muted); font-weight:500; white-space:nowrap}
  dl.facts dd{margin:0}

  .mrow{display:flex; align-items:center; gap:var(--s3); padding:var(--s2) 0}
  .mrow:first-of-type{padding-top:0}
  .mrow .nm{font-weight:600; font-size:.9375rem; line-height:1.3}
  .mrow .rl{color:var(--bp-muted); font-size:.8125rem}
  /* previous members: same row, dimmed — opacity keeps every theme legal */
  .mrow.former{opacity:.55}
  .former-label{margin-top:var(--s3)}
  .ava{width:28px; height:28px; border-radius:50%; background:var(--bp-accent-soft);
    color:var(--bp-accent); display:inline-flex; align-items:center; justify-content:center;
    font-size:.75rem; font-weight:700; flex:none}
  .ava.big{width:44px; height:44px; font-size:1rem; border:1px solid var(--bp-line)}

  /* ---- the player, now a fixed bottom bar ----
     Left = identity, centre = transport, right = context: the layout every
     streaming player converges on. The seek slider is a real <input
     type=range> — it gets role, keyboard and touch-AT support for free, which
     the old div[role=slider] never had. */
  .player[hidden]{display:none}
  .player{position:fixed; left:0; right:0; bottom:0; z-index:70; /* above the focused panel (60): the song plays from that view */
    display:flex; align-items:center; gap:var(--s4);
    box-sizing:border-box;
    /* exactly what --player-h promises, border and inset included — otherwise
       the bar is a pixel taller than the padding it reserves */
    height:calc(var(--player-h) + env(safe-area-inset-bottom, 0px));
    padding:0 var(--s4) env(safe-area-inset-bottom, 0px) var(--s3);
    background:var(--bp-topbar); backdrop-filter:blur(10px);
    border-top:1px solid var(--bp-line)}
  .now-art-btn{flex:none; padding:0; border:none; background:none; cursor:pointer; line-height:0;
    border-radius:4px}
  .art{width:48px; height:48px; border-radius:4px; object-fit:cover; flex:none; display:block;
    background:linear-gradient(135deg,var(--bp-accent-soft),var(--bp-accent))}
  .now{min-width:0; flex:1 1 12rem}
  .now .t{font-weight:600; font-size:.9375rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
  .now .a{color:var(--bp-muted); font-size:.75rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
  .transport{display:flex; align-items:center; gap:var(--s3); flex:2 1 26rem; justify-content:center}
  .pbar-right{display:flex; align-items:center; gap:var(--s2); flex:none}
  .tbtn{width:34px; height:34px; border-radius:50%; cursor:pointer; flex:none;
    display:inline-flex; align-items:center; justify-content:center;
    background:transparent; border:1px solid transparent; color:var(--bp-ink); font-size:.875rem}
  .tbtn:hover{border-color:var(--bp-line); background:var(--bp-page)}
  .tbtn.play{width:38px; height:38px; background:var(--bp-accent); color:var(--bp-accent-ink); border:none; font-size:.9375rem}
  .tbtn.play:hover{background:var(--bp-accent); filter:brightness(1.08)}
  .seek{flex:1 1 auto; min-width:80px; max-width:26rem; height:4px; margin:0; padding:0;
    -webkit-appearance:none; appearance:none; background:var(--bp-line); border-radius:2px; cursor:pointer}
  .seek::-webkit-slider-thumb{-webkit-appearance:none; width:12px; height:12px; border-radius:50%;
    background:var(--bp-accent); border:none}
  .seek::-moz-range-thumb{width:12px; height:12px; border-radius:50%; background:var(--bp-accent); border:none}
  .clock{font-size:.75rem; color:var(--bp-muted); font-variant-numeric:tabular-nums;
    white-space:nowrap; min-width:2.5rem; text-align:center}
  /* mobile: no room for a seek slider, but silence with no feedback is worse
     than no controls — a hairline along the bar's top edge carries progress
     while the row it came from is scrolled away. */
  .phair{position:absolute; left:0; top:0; height:2px; width:0; background:var(--bp-accent); display:none}
  @media (max-width:700px){
    .player{gap:var(--s3); padding:0 var(--s3)}
    .transport .seek, .transport .clock, .transport #btn-prev{display:none}
    .pbar-right #btn-share{display:none}
    .phair{display:block}
    .art{width:40px; height:40px}
  }
  @media (max-width:340px), (max-height:430px){
    .art{width:34px; height:34px}
  }

  /* strict context: the end of what you started is a stop, plus one explicit
     tap onward. Never an automatic jump into other material. */
  .pend[hidden]{display:none}
  /* .btn is display:inline-flex, which beats the UA's [hidden] — without this
     endMore.hidden is a no-op and "Play more" survives the playlist's own end */
  .pend .btn[hidden]{display:none}
  .pend{position:fixed; left:50%; transform:translateX(-50%); z-index:41;
    bottom:calc(var(--player-h) + env(safe-area-inset-bottom, 0px) + 10px);
    display:flex; align-items:center; gap:var(--s4); max-width:calc(100vw - 32px);
    padding:10px 10px 10px var(--s4); border-radius:999px;
    background:var(--bp-card); border:1px solid var(--bp-line);
    box-shadow:0 10px 34px rgba(17,18,20,.18); font-size:.875rem}
  .pend span{color:var(--bp-muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
  .pend .btn{padding:7px 14px; min-height:0; font-size:.8125rem; white-space:nowrap}

  table.tracks{width:100%; border-collapse:collapse}
  table.tracks th{font-size:.6875rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
    color:var(--bp-faint); text-align:left; padding:var(--s2) var(--s4);
    background:var(--bp-page); border-bottom:1px solid var(--bp-line)}
  table.tracks td{padding:var(--s3) var(--s4); border-bottom:1px solid var(--bp-line);
    font-size:.9375rem; vertical-align:middle; height:52px}
  table.tracks tr:last-child td{border-bottom:0}
  table.tracks tbody tr{cursor:pointer}
  table.tracks tbody tr:hover{background:var(--bp-page)}
  table.tracks tr.on{background:var(--bp-accent-soft)}
  table.tracks tr.on .name{font-weight:600; color:var(--bp-accent)}
  table.tracks .alb{color:var(--bp-muted); font-size:.875rem; white-space:nowrap}
  /* a thumbnail earns its place in the mixed playlist view; inside a release
     it would be the same cover on every row, so it isn't rendered there */
  .alb-thumb{display:inline-block; width:40px; height:40px; border-radius:4px; overflow:hidden;
    vertical-align:middle; margin-right:var(--s3); background:var(--bp-line)}
  .alb-thumb img{width:100%; height:100%; object-fit:cover; display:block}

  /* ---- the play slot ----
     Index at rest → ▶ on hover → equaliser while playing. Where there is no
     hover the ▶ is permanent: a hover-only affordance simply does not exist
     on a phone, which is how the rows ended up looking un-playable. */
  table.tracks th.pos-h{width:44px}
  .pslot{position:relative; display:grid; place-items:center; width:24px; height:24px;
    margin:0 auto; color:var(--bp-faint); font-variant-numeric:tabular-nums; font-size:.8125rem}
  .pslot .glyph{display:none; color:var(--bp-accent); line-height:0}
  .pslot .g-eq{display:none; gap:2px; align-items:flex-end; height:13px}
  .pslot .g-eq i{width:2.5px; border-radius:1px; background:currentColor;
    animation:bp-eq .72s ease-in-out infinite alternate}
  .pslot .g-eq i:nth-child(1){height:6px; animation-delay:-.2s}
  .pslot .g-eq i:nth-child(2){height:13px; animation-delay:-.5s}
  .pslot .g-eq i:nth-child(3){height:9px}
  @keyframes bp-eq{from{transform:scaleY(.35)}to{transform:scaleY(1)}}
  tr:hover .pslot .idx{visibility:hidden}
  tr:hover .pslot .glyph{display:block; position:absolute}
  @media (hover:none){
    .pslot .idx{visibility:hidden}
    .pslot .glyph{display:block; position:absolute}
  }
  /* playing vs paused differ by GLYPH, not only by the band's accent colour —
     under prefers-reduced-motion the bars freeze and colour would otherwise be
     the sole differentiator (WCAG 1.4.1) */
  tr.on .pslot .idx{visibility:hidden}
  tr.on .pslot .glyph{display:block; position:absolute}
  tr.on.playing .pslot .g-play{display:none}
  tr.on.playing .pslot .g-eq{display:flex}
  @media (prefers-reduced-motion: reduce){
    .pslot .g-eq i{animation:none; transform:scaleY(.8)}
  }
  table.tracks .len{color:var(--bp-muted); font-size:.875rem; font-variant-numeric:tabular-nums; text-align:right}
  table.tracks th.r, table.tracks td.r{text-align:right}
  /* The playlist table is the only six-column one (the album views carry four),
     and "From" cannot wrap, so a release called "The Authority of Đađa" is an
     unbreakable ~150px block. A table's width:100% is a FLOOR, not a cap: past
     its min-content width the table grows, and with no overflow-x container
     anywhere on this page it drags the document with it. That is why unrelated
     cards appeared clipped at the same right edge on a phone.
     From and Year are the two least useful columns on a narrow screen, and in
     the album view the release name is already the heading above the table. */
  @media (max-width:700px){
    table.tracks .alb, table.tracks .alb-h,
    table.tracks .yr,  table.tracks .yr-h{display:none}
  }
  /* ---- layers: Playlist / Albums (fixes-round2 §13) ---- */
  .ptabs{display:flex; gap:var(--s2); padding:var(--s3) var(--s5) 0; border-bottom:1px solid var(--bp-line)}
  .ptab{font:inherit; font-size:.875rem; font-weight:600; cursor:pointer; background:none;
    border:none; border-bottom:2px solid transparent; color:var(--bp-muted); padding:8px 4px 10px; margin-right:var(--s3)}
  .ptab:hover{color:var(--bp-ink)}
  .ptab.on{color:var(--bp-ink); border-bottom-color:var(--bp-accent)}
  .yr{color:var(--bp-muted); font-size:.875rem; font-variant-numeric:tabular-nums}
  table.tracks .pos{color:var(--bp-faint); font-size:.8125rem; width:28px; font-variant-numeric:tabular-nums}
  tr.focused .name{text-decoration:underline; text-underline-offset:3px}

  .alb-controls{display:flex; gap:var(--s5); padding:var(--s3) var(--s5) 0}
  .alb-controls label{display:inline-flex; align-items:baseline; gap:6px}
  .alb-controls select{font:inherit; font-size:.8125rem; color:var(--bp-ink);
    background:var(--bp-card); border:1px solid var(--bp-line); border-radius:6px; padding:3px 6px}
  .albums-grid{display:grid; grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));
    gap:var(--s4); padding:var(--s4) var(--s5) var(--s5)}
  /* author display beats the UA's [hidden] — both of these are toggled by
     script, so they have to say so explicitly (the consent-banner lesson) */
  .alb-card[hidden]{display:none}
  .tbtn[hidden]{display:none}
  .alb-card{display:flex; flex-direction:column; gap:6px}
  .alb-art{position:relative; display:block; border-radius:8px; overflow:hidden}
  .alb-cover{width:100%; aspect-ratio:1; object-fit:cover; border-radius:8px;
    border:1px solid var(--bp-line); display:block}
  .alb-cover-empty{display:flex; align-items:center; justify-content:center; font-size:2rem;
    color:var(--bp-faint); background:var(--bp-page)}
  /* the cover and the title open the release; the round button plays it */
  .alb-open{position:absolute; inset:0; z-index:1; border:none; background:none; cursor:pointer;
    border-radius:8px}
  button.alb-title{position:static; inset:auto; font:inherit; font-size:.9375rem; font-weight:600;
    text-align:left; background:none; border:none; padding:0; cursor:pointer; color:var(--bp-ink);
    line-height:1.3; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden}
  .alb-card:hover .alb-title{color:var(--bp-accent)}
  @media (hover:hover){ .alb-art:hover .alb-cover{filter:brightness(.84)} }
  /* Always visible, full opacity, on its own scrim. A hover-revealed control
     is invisible on touch, and a translucent one over user-uploaded artwork
     has contrast nobody can predict. Offset from the corner rather than
     centred, so it doesn't land on a face. */
  .alb-play{position:absolute; right:10px; bottom:10px; z-index:2;
    width:44px; height:44px; border-radius:50%; border:none; cursor:pointer;
    display:grid; place-items:center; color:#fff; background:rgba(17,18,20,.78);
    box-shadow:0 2px 10px rgba(0,0,0,.4)}
  .alb-play:hover{background:var(--bp-accent)}
  .alb-play .g-pause{display:none}
  .alb-play.playing{background:var(--bp-accent)}
  .alb-play.playing .g-play{display:none}
  .alb-play.playing .g-pause{display:block}
  .alb-meta{font-size:.75rem; color:var(--bp-muted); font-variant-numeric:tabular-nums}
  .alb-back{font:inherit; cursor:pointer; background:none; border:none; color:var(--bp-muted);
    padding:var(--s3) var(--s5) 0; display:block}
  .alb-back:hover{color:var(--bp-ink)}

  /* ---- release header: the arrival signal ---- */
  .album-head{display:flex; align-items:flex-start; gap:var(--s5); padding:var(--s4) var(--s5)}
  .ah-cover{width:280px; height:280px; flex:none; border-radius:8px; object-fit:cover;
    border:1px solid var(--bp-line)}
  .ah-cover-empty{display:flex; align-items:center; justify-content:center; font-size:4rem;
    color:var(--bp-faint); background:var(--bp-page)}
  .ah-body{min-width:0; padding-top:2px}
  .ah-eyebrow{font-size:.75rem; font-weight:600; letter-spacing:.09em; text-transform:uppercase;
    color:var(--bp-muted); margin:0 0 6px; font-variant-numeric:tabular-nums}
  .ah-title{display:block; font-family:var(--bp-font-head); font-size:1.75rem; font-weight:700;
    line-height:1.1; letter-spacing:var(--bp-head-spacing); text-wrap:balance; margin-bottom:var(--s4)}
  .ah-actions{display:flex; flex-wrap:wrap; gap:var(--s3); align-items:center}
  .ah-actions .btn{padding:10px 18px; min-height:0; gap:8px}
  .ah-play .g-pause{display:none}
  .ah-play.playing .g-play{display:none}
  .ah-play.playing .g-pause{display:block}
  .ah-desc{color:var(--bp-prose); font-size:.875rem; max-width:56ch; margin-top:var(--s4)}
  .album-foot{display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
    padding:var(--s3) var(--s5) var(--s4); font-size:.8125rem; color:var(--bp-faint); flex-wrap:wrap}
  @media (max-width:700px){
    .album-head{flex-direction:column; gap:var(--s4)}
    .ah-cover{width:200px; height:200px}
    .ah-title{font-size:1.375rem}
  }

  /* ---- share (fixes-round2 §7): quiet affordance, tiny popover ---- */
  /* :not(.btn) — the release header's Share is a labelled button atom and must
     keep the atom's padding and centering; only the bare icon affordances on
     rows and in the bar take the quiet treatment. */
  .share-btn:not(.btn){font:inherit; cursor:pointer; background:none; border:none; color:var(--bp-faint);
    padding:4px; border-radius:6px; line-height:0}
  .share-btn svg{width:15px; height:15px}
  .share-btn:not(.btn):hover{color:var(--bp-accent); background:var(--bp-page)}
  table.tracks .sh{width:34px}
  @media (hover:hover){
    table.tracks .share-btn{opacity:0}
    table.tracks tr:hover .share-btn, table.tracks .share-btn:focus-visible{opacity:1}
  }
  .now-share{margin-left:auto; align-self:flex-start}
  /* components that toggle the hidden attribute AND set display must keep
     [hidden] winning — author display rules beat the UA's (the consent-
     banner lesson, relearned 2026-08-06 on the EQ panel) */
  .share-pop[hidden]{display:none}
  .share-pop{position:absolute; z-index:80; display:flex; flex-direction:column; min-width:150px;
    background:var(--bp-card); border:1px solid var(--bp-line); border-radius:10px;
    box-shadow:0 10px 34px rgba(17,18,20,.18); padding:var(--s2)}
  .share-pop a, .share-pop button{font:inherit; font-size:.875rem; text-align:left; cursor:pointer;
    background:none; border:none; color:var(--bp-ink); padding:7px 10px; border-radius:6px; text-decoration:none}
  .share-pop a:hover, .share-pop button:hover{background:var(--bp-page); text-decoration:none}
  .sp-tick{color:var(--bp-ok); font-size:.875rem; font-weight:600; padding:7px 10px}

  .chip{font-size:.8125rem; font-weight:600; color:var(--bp-accent);
    border:1px solid var(--bp-line); border-radius:999px; padding:4px 12px}
  .chip:hover{background:var(--bp-accent); color:#fff; border-color:var(--bp-accent); text-decoration:none}
  .buyline{display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
    padding:var(--s4) var(--s5); background:var(--bp-page); flex-wrap:wrap}
  .buyline .note{font-size:.8125rem; color:var(--bp-muted)}
  /* .btn-lg retired 2026-08-07 — unused since the buy row left; the atom
     above is the only button implementation on this page */

  .show{display:flex; align-items:baseline; gap:var(--s4);
    padding:var(--s3) var(--s5); border-bottom:1px solid var(--bp-line); font-size:.9375rem}
  .show:last-child{border-bottom:0}
  .show .d{font-weight:600; width:7rem; white-space:nowrap; font-variant-numeric:tabular-nums}
  .show .v{flex:1}
  .show .c{color:var(--bp-muted); font-size:.875rem; white-space:nowrap}

  .sync{margin-left:var(--s2); font-size:.6875rem; font-weight:600; letter-spacing:.03em;
    color:var(--bp-muted); background:var(--bp-page); border:1px solid var(--bp-line);
    border-radius:999px; padding:3px 10px; vertical-align:1px}
  .vgrid{display:grid; grid-template-columns:1fr 1fr; gap:var(--s4); padding:var(--s4) var(--s5) var(--s5)}
  .vid.main{grid-column:1/-1}
  .thumb{position:relative; aspect-ratio:16/9; border-radius:8px; overflow:hidden;
    background:var(--bp-line) center/cover no-repeat; display:block}
  .thumb::after{content:"▶"; position:absolute; inset:0; display:flex; align-items:center;
    justify-content:center; color:#fff; font-size:1.5rem; background:rgba(17,18,20,.25);
    text-shadow:0 1px 4px rgba(0,0,0,.4); transition:background .15s}
  .thumb:hover::after{background:rgba(17,18,20,.45)}
  .vid .vt{font-weight:600; font-size:.9375rem; margin-top:var(--s2); line-height:1.35; color:var(--bp-ink)}
  .vid.main .vt{font-size:1.0625rem}
  .vid .vm{color:var(--bp-muted); font-size:.8125rem; margin-top:2px}
  .vid:hover{text-decoration:none}

  /* Was a hardcoded #2c2d30 — fine on white, 1.2:1 on the dark card, i.e. the
     band's bio was invisible in dark mode. Exactly the failure CLAUDE.md's
     "keep new band-page colors as vars or dark mode breaks" rule is about. */
  .prose p{max-width:64ch; margin-bottom:var(--s3); color:var(--bp-prose)}
  .prose p:last-child{margin-bottom:0}
  .empty{color:var(--bp-muted); font-style:italic}

  /* photos section */
  .bp-gallery{display:grid; grid-template-columns:repeat(3,1fr); gap:8px; padding:var(--s4)}
  .bp-gallery img{width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:8px; display:block}
  /* visuals v2: figure wrapper carries the credit, quiet and small */
  .bp-ph{position:relative; margin:0}
  .ph-credit{position:absolute; right:6px; bottom:6px; font-size:.6875rem; color:#fff;
    background:rgba(17,18,20,.55); padding:2px 8px; border-radius:6px}

  /* floating channel dock */
  /* the dock rides above the player rather than fighting it — two fixed
     bottom-right elements is the likeliest visual regression here */
  .dock{position:fixed; right:20px; z-index:30;
    bottom:calc(20px + var(--player-h) + env(safe-area-inset-bottom, 0px));
    width:340px; max-width:calc(100vw - 32px);
    background:var(--bp-card); border:1px solid var(--bp-line); border-radius:14px;
    box-shadow:0 12px 40px rgba(17,18,20,.18); display:flex; flex-direction:column; overflow:hidden}
  .dock>header{display:flex; align-items:center; gap:var(--s2);
    padding:var(--s3) var(--s4); border-bottom:1px solid var(--bp-line)}
  .dock>header b{font-size:.875rem}
  .dock>header .hotdot{width:8px; height:8px; border-radius:50%; background:var(--bp-hot);
    animation:bp-pulse 1.6s ease-out infinite; flex:none}
  .dock>header time{font-size:.75rem; color:var(--bp-faint)}
  .dock>header .min{margin-left:auto; border:none; background:none; color:var(--bp-faint);
    font-size:1.1rem; cursor:pointer; line-height:1; padding:2px 6px}
  .dock>header .min:hover{color:var(--bp-ink)}
  .dock.minimized .dockbody, .dock.minimized .dockcta{display:none}
  .dock.minimized>header{border-bottom:0}
  .dockbody{max-height:44vh; overflow:auto; padding:var(--s4); display:flex; flex-direction:column; gap:var(--s3)}
  .msg{display:flex; gap:var(--s2); align-items:flex-start}
  .msg .ava{width:24px; height:24px; font-size:.6875rem}
  .msg .bubble{background:var(--bp-page); border:1px solid var(--bp-line); border-radius:10px;
    padding:var(--s2) var(--s3); font-size:.875rem; min-width:0}
  .msg .who2{font-size:.75rem; color:var(--bp-muted); margin-bottom:2px}
  .msg .who2 b{color:var(--bp-ink)}
  .lockzone{position:relative; display:flex; flex-direction:column; gap:var(--s3)}
  .lockzone .msg{filter:blur(5px); pointer-events:none; user-select:none}
  .veil{position:absolute; inset:0; z-index:1; display:flex; align-items:center; justify-content:center;
    background:linear-gradient(180deg, var(--bp-veil-a), var(--bp-veil-b));
    font-size:.8125rem; font-weight:600; color:var(--bp-ink); text-align:center; padding:var(--s3)}
  .clip{display:inline-flex; align-items:center; gap:var(--s2); margin-top:var(--s3);
    border:1px solid var(--bp-line); border-radius:8px; padding:var(--s2) var(--s3);
    font-size:.875rem; background:var(--bp-card); flex-wrap:wrap}
  .clip audio{height:30px; max-width:200px}
  .clip .nodl{font-style:normal; font-size:.6875rem; color:var(--bp-faint);
    border-left:1px solid var(--bp-line); padding-left:var(--s2); margin-left:var(--s1)}
  .dockcta{padding:var(--s3) var(--s4); border-top:1px solid var(--bp-line)}
  .dockcta .btn{padding:10px; min-height:40px; font-size:.875rem}
  .dockcta small{display:block; text-align:center; color:var(--bp-faint); font-size:.6875rem; margin-top:var(--s1)}

  footer{border-top:1px solid var(--bp-line); margin-top:var(--s6); background:none; padding:0}
  footer .in{max-width:1080px; margin:0 auto; padding:var(--s5);
    display:flex; justify-content:space-between; gap:var(--s4); flex-wrap:wrap;
    font-size:.8125rem; color:var(--bp-muted)}

  .toast{position:fixed; left:50%; bottom:24px; transform:translateX(-50%); z-index:60;
    background:var(--bp-ink); color:#fff; font-size:.875rem; padding:10px 18px; border-radius:999px;
    box-shadow:0 8px 30px rgba(0,0,0,.3)}
}

/* the cycle band — the release's temporary shout, above everything.
   Dark treatment over the art works on both themes; vars only for accents. */
.band-page .cycle-band{position:relative; overflow:hidden; background:#141416; color:#f4f1ea}
.band-page .cycle-bg{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; opacity:.6}
.band-page .cycle-bg.blurred{filter:blur(28px) brightness(.7); transform:scale(1.15)}
.band-page .cycle-band::before{content:""; position:absolute; inset:0;
  background:linear-gradient(90deg, rgba(10,10,12,.82), rgba(10,10,12,.45) 55%, rgba(10,10,12,.7))}
.band-page .cycle-in{position:relative; max-width:1200px; margin:0 auto; padding:18px 24px;
  display:flex; align-items:center; gap:18px}
.band-page .cycle-art{width:72px; height:72px; border-radius:8px; object-fit:cover; flex:none;
  box-shadow:0 6px 20px rgba(0,0,0,.5)}
.band-page .cycle-text{display:flex; flex-direction:column; gap:2px; min-width:0}
.band-page .cycle-when{font-size:.6875rem; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:var(--bp-accent, #c9145c)}
.band-page .cycle-band.live .cycle-when{color:#7fd18a}
.band-page .cycle-headline{font-size:1.25rem; font-weight:800; letter-spacing:-.01em; line-height:1.15;
  font-family:var(--bp-font-head, inherit)}
.band-page .cycle-tagline{font-size:.875rem; opacity:.85}
.band-page .cycle-edition{font-size:.75rem; opacity:.65}
.band-page .cycle-cta{margin-left:auto; display:flex; gap:10px; flex:none}
/* sizing/centering come from the .btn.small atom; only the weight is ours */
.band-page .cycle-go{font-weight:700}
@media (max-width:700px){
  .band-page .cycle-in{flex-wrap:wrap; gap:12px}
  .band-page .cycle-cta{margin-left:0; width:100%}
  .band-page .cycle-cta .btn{flex:1; text-align:center}
}

/* mobile — canonical breakpoints 560/700/860 */
@media (max-width:860px){
  .band-page .grid{grid-template-columns:1fr}
  .band-page .rail{position:static}
  /* infobox collapses above the spine: photo stays square + full width,
     the fact list reads as a two-column table */
  .band-page dl.facts{grid-template-columns:auto 1fr; column-gap:var(--s5)}
}
@media (max-width:560px){
  .band-page .vgrid{grid-template-columns:1fr}

  /* Mobile scale pass. Until now nothing but layout changed below 700px: type,
     controls and padding were identical on a 390px phone and a 1440px desktop,
     and page padding plus card padding spent 96px (a quarter of the screen)
     before a word was rendered.
     min-height:44px on controls is NOT reduced. It is the WCAG 2.5.5 / HIG
     touch-target floor, and a button that feels chunky is a smaller problem
     than one a thumb misses. What comes down is padding and type, which is
     what actually reads as oversized. */
  .band-page .wrap{padding:var(--s5) var(--s4) var(--s6)}
  .band-page .card .pad{padding:var(--s4)}
  .band-page .album-head,
  .band-page .album-foot,
  .band-page .vgrid,
  .band-page .bp-gallery{padding-left:var(--s4); padding-right:var(--s4)}
  .band-page table.tracks th,
  .band-page table.tracks td{padding-left:var(--s3); padding-right:var(--s3)}

  .band-page h1{font-size:1.375rem}
  .band-page .btn, .band-page .cta{padding:10px 14px; font-size:.875rem}
  .band-page footer .in{padding:var(--s4)}
}

/* cover-palette recoloring: the player wears the sleeve of the playing song.
   Kept AFTER the nested block — same-specificity ties resolve by order. */
.band-page .player{transition:background .5s}
.band-page .player .tbtn.play{
  background:var(--pl-accent, #111214); color:var(--pl-contrast, #fff);
  border-color:var(--pl-accent, #111214); transition:background .5s, color .5s;
}
.band-page .player .seek i{background:var(--pl-accent, var(--bp-accent, #111214)); transition:background .5s}
.band-page .player .seek b{border-color:var(--pl-accent, var(--bp-accent, #111214))}
.band-page #tracklist tr.on{box-shadow:inset 4px 0 0 var(--pl-accent, var(--bp-accent, #111214))}

/* ---- the focused view (Pages slice 2) ----
   One song, full screen. An overlay on the band page rather than a second
   document, so the player underneath never stops. Fixed and document-level for
   the same reason the bar is: a backdrop-filtered card would otherwise become
   its containing block. */
.focused[hidden]{display:none}
.focused{position:fixed; inset:0; z-index:60; display:flex; align-items:center; justify-content:center; padding-bottom:var(--player-h, 0px);
  padding:var(--s5); overflow-y:auto}
.focused-scrim{position:absolute; inset:0; background:var(--bp-scrim); backdrop-filter:blur(14px)}
.focused-panel{position:relative; width:100%; max-width:1040px; display:grid; gap:var(--s6);
  grid-template-columns:minmax(0,480px) minmax(0,1fr); align-items:start;
  background:var(--bp-card); border:1px solid var(--bp-line); border-radius:16px;
  padding:var(--s6); box-shadow:0 24px 80px rgba(17,18,20,.28)}
.focused-x{position:absolute; top:var(--s4); right:var(--s4); width:44px; height:44px;
  display:inline-flex; align-items:center; justify-content:center; border-radius:50%;
  color:var(--bp-muted); text-decoration:none; font-size:1.125rem; line-height:1}
.focused-x:hover{background:var(--bp-page); color:var(--bp-ink); text-decoration:none}
.focused-art{min-width:0}
/* the cover at roughly 12x the 40px row thumbnail — the entire point of this view */
.focused-cover{width:100%; aspect-ratio:1; object-fit:cover; display:block;
  border-radius:12px; border:1px solid var(--bp-line)}
.focused-cover-empty{display:flex; align-items:center; justify-content:center;
  font-size:5rem; color:var(--bp-faint); background:var(--bp-page)}
.focused-body{min-width:0; display:flex; flex-direction:column; gap:var(--s3)}
.focused-eyebrow{font-size:.8125rem; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
  color:var(--bp-muted); margin:0}
.focused-title{font-family:var(--bp-font-head); font-size:2.25rem; line-height:1.05;
  letter-spacing:var(--bp-head-spacing); text-wrap:balance; margin:0}
.focused-band{color:var(--bp-muted); margin:0}
.focused-actions{display:flex; flex-wrap:wrap; gap:var(--s3); margin-top:var(--s3)}
.focused-play .g-pause{display:none}
.focused-play.playing .g-play{display:none}
.focused-play.playing .g-pause{display:block}
.focused-sub{font-size:.75rem; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--bp-muted); margin:var(--s5) 0 var(--s2)}
.lyrics-body{color:var(--bp-prose); line-height:1.75; max-width:52ch}
.lyrics-body p{margin:0 0 1em}
.focused-down{align-self:flex-start; margin-top:var(--s5); color:var(--bp-muted); font-size:.875rem}

@media (max-width:860px){
  .focused{padding:0; align-items:stretch}
  .focused-panel{grid-template-columns:1fr; border-radius:0; border:0; min-height:100%;
    padding:var(--s6) var(--s5) calc(var(--player-h) + env(safe-area-inset-bottom, 0px) + var(--s6))}
  .focused-cover{width:min(72vw, 340px); margin:0 auto}
  .focused-title{font-size:1.75rem}
}
/* while the panel is open the page behind must not scroll under it */
html[data-focused] body.band-page{overflow:hidden}

/* ---- the Release page (/now) — the same frame, different contents ---- */
.focused-tagline{color:var(--bp-prose); font-size:1.0625rem; margin:0; max-width:46ch}
.focused-edition{color:var(--bp-muted); font-size:.875rem; margin:0}
.focused-follow{margin-top:var(--s6); padding-top:var(--s5); border-top:1px solid var(--bp-line)}
.focused-follow-line{margin:0 0 var(--s3); font-weight:600}
.focused-follow-form{display:flex; gap:var(--s3); flex-wrap:wrap; align-items:center}
.focused-follow-form input[type=email]{flex:1 1 14rem; min-width:0}
.focused-follow .side-note{display:block; margin-top:var(--s2); font-size:.8125rem; color:var(--bp-faint)}

/* ---- the Links page (/links) and the Chat page (/chat) ----
   Both inherit the band's brand through the same vars as everything else, so a
   page switched on already looks like the band with zero overrides set. */
.links-page, .chat-page{max-width:640px; margin:0 auto; padding:var(--s7) var(--s5)}
.links-head{text-align:center; margin-bottom:var(--s6)}
.links-logo{max-height:72px; width:auto; margin:0 auto var(--s4); display:block}
.links-photo{width:96px; height:96px; border-radius:50%; object-fit:cover; margin:0 auto var(--s4); display:block}
.links-name{font-family:var(--bp-font-head); font-size:1.75rem; margin:0 0 var(--s2);
  letter-spacing:var(--bp-head-spacing)}
.links-bio{color:var(--bp-muted); margin:0; max-width:44ch; margin-inline:auto}
.links-list{list-style:none; margin:0; padding:0; display:grid; gap:var(--s3)}
.link-card{display:flex; align-items:center; gap:var(--s4); min-height:56px;
  padding:var(--s3) var(--s4); border-radius:12px; text-decoration:none;
  background:var(--bp-card); border:1px solid var(--bp-line); color:var(--bp-ink)}
.link-card:hover{border-color:var(--bp-accent); text-decoration:none; color:var(--bp-ink)}
.lc-thumb{flex:none; width:40px; height:40px; border-radius:8px; overflow:hidden}
.lc-thumb img{width:100%; height:100%; object-fit:cover; display:block}
.lc-label{flex:1; min-width:0; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.lc-go{flex:none; color:var(--bp-muted)}
.links-empty{color:var(--bp-muted); text-align:center}
.links-back{text-align:center; margin-top:var(--s6)}
.links-back a{color:var(--bp-muted); font-size:.875rem}

.chat-head{margin-bottom:var(--s5)}
.chat-title{font-family:var(--bp-font-head); font-size:1.625rem; margin:0 0 var(--s2)}
.chat-list{list-style:none; margin:0; padding:0; display:grid; gap:var(--s4)}
.chat-msg{background:var(--bp-card); border:1px solid var(--bp-line); border-radius:12px; padding:var(--s4)}
.cm-meta{color:var(--bp-faint); font-size:.75rem; margin-bottom:var(--s2)}
.cm-body{color:var(--bp-prose); line-height:1.65}
.cm-body p{margin:0 0 .75em}
.cm-body.blurred{filter:blur(5px); user-select:none; pointer-events:none}
.cm-clip{width:100%; margin-top:var(--s3)}
.chat-unlock{margin-top:var(--s5); padding:var(--s5); border-radius:12px;
  background:var(--bp-card); border:1px solid var(--bp-line); text-align:center}
.chat-unlock form{display:flex; gap:var(--s3); justify-content:center; flex-wrap:wrap; margin-top:var(--s3)}

/* ---- the shop (payments MVP, 2026-09-08): prices on the page, the math
   only on the checkout step ---- */
/* the shop is its own kind of block (founder, 2026-09-08): an accent header
   band, a wash under the tiles, and tiles that are boxes with a full-width
   Buy. The typical storefront pattern, in the band's colour. */
.band-page .card.shop-section{overflow:hidden;
  background:color-mix(in srgb, var(--bp-accent, #c9145c) 9%, var(--bp-card, #fff));
  border-color:color-mix(in srgb, var(--bp-accent, #c9145c) 35%, var(--bp-line, #e2e2de))}
/* one strip, not a banner (founder, 2026-09-08): title and line on one row,
   the Stripe note at the right, about 44px tall */
.shop-head{display:flex; align-items:baseline; gap:12px; padding:10px var(--s5); background:var(--bp-accent, #c9145c); color:#fff; min-width:0}
.shop-head-title{font-family:var(--bp-font-head); font-size:1.0625rem; font-weight:700; letter-spacing:-.01em; line-height:1.2; flex:none}
.shop-head-line{font-size:.8125rem; opacity:.85; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.shop-head-note{margin-left:auto; font-size:.625rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; opacity:.8; white-space:nowrap; flex:none}
.shop-pad{padding-top:var(--s5) !important}
.shop-grid{display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:var(--s4)}
.shop-card{display:flex; flex-direction:column; background:var(--bp-card, #fff); border:1px solid var(--bp-line, #e2e2de); border-radius:12px; overflow:hidden;
  color:var(--bp-ink, inherit); box-shadow:0 1px 2px rgba(0,0,0,.06); transition:transform 160ms var(--ease-out), box-shadow 160ms ease}
@media (hover:hover) and (pointer:fine){ .shop-card:hover{transform:translateY(-2px); box-shadow:0 8px 20px rgba(0,0,0,.12)} }
.shop-card a{text-decoration:none; color:inherit}
.shop-card .shop-card-cover{position:relative; border-radius:0} /* the tile carries the corners */
.shop-card.is-soldout .shop-card-cover img{filter:grayscale(1); opacity:.7}
.shop-ribbon{position:absolute; top:10px; left:10px; background:var(--bp-accent, #c9145c); color:#fff; font-size:.6875rem; font-weight:700;
  letter-spacing:.04em; text-transform:uppercase; padding:4px 8px; border-radius:6px}
.shop-ribbon.is-off{background:#1d1d1b}
.shop-card-body{display:flex; flex-direction:column; gap:4px; padding:12px 14px 14px}
.shop-card-kind{font-size:.6875rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; opacity:.6}
.shop-buy-btn{margin-top:10px; width:100%; justify-content:center; background:var(--bp-accent, #c9145c); color:#fff; border-color:transparent}
.shop-buy-btn:hover{filter:brightness(1.08); color:#fff}
.shop-buy-btn.is-off{background:transparent; color:inherit; opacity:.6; border:1px solid var(--bp-line, #cfcfc9); cursor:default}
.shop-card-cover, .shop-cover{display:block; aspect-ratio:1; border-radius:10px; overflow:hidden; background:var(--bp-line, #e2e2de)}
.shop-card-cover img, .shop-cover img{width:100%; height:100%; object-fit:cover; display:block}
.shop-cover-empty{display:block; width:100%; height:100%; background:linear-gradient(135deg, var(--bp-line, #e2e2de), transparent)}
.shop-card-title{font-weight:600; line-height:1.25}
.shop-price{display:inline-flex; align-items:baseline; gap:8px; flex-wrap:wrap}
.shop-price s{opacity:.55}
.shop-off{font-size:.75rem; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--bp-accent, inherit)}
.shop-soldout{font-size:.8125rem; font-weight:600; opacity:.7}
.shop-page{max-width:880px; margin:0 auto; padding:var(--s7) var(--s5)}
.shop-product{display:grid; grid-template-columns:minmax(200px, 360px) 1fr; gap:var(--s6); align-items:start}
.shop-kind{font-size:.75rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; opacity:.7}
.shop-title{font-family:var(--bp-font-head); font-size:1.75rem; margin:6px 0 10px; line-height:1.15}
.shop-body .shop-price b{font-size:1.375rem}
.shop-desc{margin:var(--s4) 0; line-height:1.55}
.shop-tracks{margin:var(--s3) 0 var(--s4) 1.2rem; line-height:1.7; opacity:.9}
.shop-buy{display:flex; flex-direction:column; gap:var(--s3); align-items:flex-start; margin-top:var(--s4)}
.shop-buy label{display:flex; gap:8px; align-items:center; font-size:.9375rem}
.shop-buy select{font:inherit; padding:8px 10px; border-radius:8px; border:1px solid var(--bp-line, #cfcfc9); background:var(--bp-card, #fff); color:inherit}
.shop-buy .note, .shop-quote .note{font-size:.8125rem; opacity:.75; max-width:42ch}
.shop-more{font-family:var(--bp-font-head); font-size:1.125rem; margin:var(--s7) 0 var(--s4)}
.shop-math{width:100%; max-width:480px; border-collapse:collapse; margin:var(--s4) 0}
.shop-math td{padding:8px 0; border-bottom:1px solid var(--bp-line, #e2e2de)}
.shop-math td:last-child{text-align:right; font-variant-numeric:tabular-nums}
.shop-math .is-sub td{font-size:.875rem; opacity:.75; padding-left:12px}
.shop-math .is-total td{font-weight:700; font-size:1.125rem; border-bottom:0}
.shop-cover-fees{display:flex; gap:10px; align-items:flex-start; margin:0 0 var(--s4); font-size:.9375rem; max-width:480px}
.shop-quote{display:flex; flex-direction:column; align-items:flex-start; gap:var(--s2)}
.shop-downloads{list-style:none; margin:var(--s4) 0; padding:0; display:flex; flex-direction:column; gap:8px}
.shop-downloads li{display:flex; align-items:center; justify-content:space-between; gap:var(--s3); padding:8px 0; border-bottom:1px solid var(--bp-line, #e2e2de)}
.shop-note{margin-top:var(--s4); padding:var(--s3) var(--s4); border-radius:10px; background:var(--bp-card, #fff); border:1px solid var(--bp-line, #e2e2de)}
@media (max-width:640px){ .shop-product{grid-template-columns:1fr} .shop-grid{grid-template-columns:repeat(2, 1fr); gap:var(--s3)} .shop-head{padding:9px var(--s4)} .shop-head-line, .shop-head-note{display:none} .shop-card-body{padding:10px 10px 12px} }
