﻿/* =============================================================================
   ezmuze central — design layer
   =============================================================================

   Loaded AFTER site.css, which is the original Blazor site's stylesheet (1931
   lines of w3-css-style utilities). This file does not replace it: it restyles
   the handful of classes the pages actually use, so every page gets the new
   look without being rewritten one at a time.

   WHERE THE VALUES COME FROM. Not invented — they are ezmuze studio's own
   tokens, from design-guide.md in the studio repo (§1 colour, §2 typography,
   §3 spacing, §4 corner radius, §5 motion). The site fronts the app, so it
   should look like it was designed by whoever designed the app, rather than
   like a different product from a different decade.

   WHAT MADE IT LOOK "METRO", and what each is replaced with:

     body { font-weight: 100 }        hairline type at every size
       -> 400 body / 600 headings, a real weight hierarchy

     rgba(200,200,200,.7) slabs       full-bleed light-grey and black bands,
     rgba(0,0,0,.7)                   alternating, no edges, no depth
       -> the app's dark surface family, 1px bordered, blurred over the video

     padding: 128px                   fixed slabs that overflow a phone
       -> a fluid clamp() scale, and content capped at a readable measure

     ALL-CAPS NAV                     Windows-8 chrome
       -> sentence case, which is design-guide.md §2's app-wide rule

   CORNER RADIUS IS DELIBERATELY 0. design-guide.md §4 locks the app to hard
   edges everywhere except knobs and switches, on the grounds that hard-edged
   chrome is the DAW convention (Ableton, Reaper). Rounding the website would
   have made it look less like the thing it is selling, not more modern. The
   modern feeling here is meant to come from type, spacing, borders, depth and
   motion instead. Easy to revisit: change --radius.
   ========================================================================== */

:root {
  /* -- surfaces: design-guide.md §1.1 dark palette ------------------------ */
  --backdrop:      #0d0d11;   /* SurfaceBackdrop (13,13,17)  */
  --panel:         #16161c;   /* SurfacePanel    (22,22,28)  */
  --header:        #1e1e26;   /* SurfaceHeader   (30,30,38)  */
  --raised:        #282833;   /* SurfaceRaised   (40,40,51)  */
  --border:        #3c3c4a;   /* SurfaceBorder   (60,60,74)  */

  /* Panels are translucent and blurred rather than opaque, so the backdrop
     behind them is visible through the glass. That used to be a video; it is
     now a painted gradient, and these stay translucent because the depth they
     give is the point, not what happens to be underneath. */
  --panel-a:       rgba(22, 22, 28, 0.82);
  --raised-a:      rgba(40, 40, 51, 0.78);
  --header-a:      rgba(16, 16, 21, 0.86);

  /* -- text -------------------------------------------------------------- */
  --text:          #e9e9f0;
  --text-dim:      #a6a6b6;
  --text-faint:    #7c7c8c;

  /* -- accents ----------------------------------------------------------- */
  --brand:         #ff00d8;   /* .ezmuzePurple, and the logo's magenta       */
  --brand-soft:    #ff78a8;   /* AccentModNegative — brand pink, easier on   */
  --green:         #60e0a0;   /* AccentModPositive — the logo's green        */
  --blue:          #96c8ff;   /* AccentSelection — focus rings, links        */
  --amber:         #ffc440;   /* AccentLiveAutomation                        */

  --positive:      #3a6c4a;   --positive-hover: #4a8a5e;  --positive-press: #60a874;
  --neutral:       #34343c;   --neutral-hover:  #42424c;  --neutral-press:  #50505c;
  --negative:      #783a3a;   --negative-hover: #9e4848;

  /* -- geometry: §3 base grid is 12px, §4 hard edges --------------------- */
  --u: 12px;
  --radius: 0px;
  --measure: 68ch;            /* readable line length for body copy          */
  --page: 1220px;             /* content cap; the backdrop still runs edge to edge */
  --tracklist: 820px;         /* a list of tracks reads at this, not at --page  */

  /* -- motion: §5, 100-150ms, ease-out in ------------------------------- */
  --t: 140ms;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* =============================================================== typography */

/* site.css sets font-weight:100 on body AND every heading, which is where most
   of the thin, washed-out feeling came from. */
body, h1, h2, h3, h4, h5, h6, .button, input, select, textarea, button {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-weight: 400;
  font-style: normal;
}

body, html {
  line-height: 1.55;                 /* was 1.8 — airy to the point of loose */
  color: var(--text);
  background: var(--backdrop);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5 {
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0 0 calc(var(--u) * 0.75);
  color: var(--text);
}

/* Fluid, so a phone gets a proportionate headline rather than a desktop one
   that wraps four times. */
h1 { font-size: clamp(28px, 3.4vw, 42px); }
h2 { font-size: clamp(22px, 2.6vw, 30px); }
h3 { font-size: clamp(19px, 1.9vw, 23px); letter-spacing: -0.01em; }
h4 { font-size: clamp(16px, 1.5vw, 18px); font-weight: 500; color: var(--text-dim); }

p  { margin: 0 0 var(--u); max-width: var(--measure); color: var(--text-dim); }
p:last-child { margin-bottom: 0; }

b, strong { font-weight: 600; }   /* colour inherits: see hero buttons */
hr { border: 0; border-top: 1px solid var(--border); margin: calc(var(--u) * 2.5) 0; }

.jumbo {
  font-size: clamp(44px, 8.5vw, 84px) !important;
  font-weight: 300;
  letter-spacing: -0.035em;
  line-height: 1.02;
  display: inline-block;
}
.xxlarge { font-size: clamp(32px, 7vw, 44px) !important; font-weight: 300; letter-spacing: -0.03em; }

.ezmuzePurple { color: var(--brand); }

a { color: var(--blue); text-decoration: none; transition: color var(--t) var(--ease); }
a:hover { color: #c2ddff; text-decoration: underline; text-underline-offset: 3px; }

/* ================================================================= surfaces */

/* The three alternating band classes. All three are now dark: the light-grey
   band was the single most dated element on the site, and losing it costs
   nothing because the alternation survives as panel-vs-raised. */
.light-greyalpha, .light-grayalpha,
.hover-light-greyalpha:hover, .hover-light-grayalpha:hover {
  background-color: var(--raised-a) !important;
  color: var(--text) !important;
}

.light-blackalpha, .hover-light-blackalpha:hover {
  background-color: var(--panel-a) !important;
  color: var(--text) !important;
}

/* Kept tinted, because the brand hue earning a place on the page is the point
   of having one — but at a fraction of rgba(100,0,200,.6). */
.light-purplealpha, .hover-light-purplealpha:hover,
.lighter-purplealpha, .hover-lighter-purplealpha:hover {
  background-color: rgba(58, 22, 74, 0.72) !important;
  color: var(--text) !important;
}

.container, .slimcontainer, .light-greyalpha, .light-blackalpha, .light-purplealpha {
  -webkit-backdrop-filter: blur(16px) saturate(115%);
  backdrop-filter: blur(16px) saturate(115%);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

/* Fluid padding, and a cap on measure so body copy does not run the full width
   of a 27" display. The video still fills the viewport behind it. */
.container, .slimcontainer {
  width: auto !important;
  max-width: none;
  padding: clamp(20px, 4.5vw, 60px) clamp(16px, 5vw, 64px) !important;
  margin-bottom: 4px;
  box-sizing: border-box;
}

.container > .row,
.container > .row-padding,
.slimcontainer > .row-padding,
.slimcontainer > .row {
  max-width: var(--page);
  margin-inline: auto;
}

/* ================================================================== buttons */

/* No !important on the colours: several pages still pass an inline
   backgroundColor for primary/destructive intent, and an !important here would
   flatten all of them into one grey. Shape, motion and focus are safe to force;
   colour is not. */
.button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 18px;
  min-height: 42px;                  /* comfortably tappable */
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--neutral);
  color: var(--text);
  font-size: 15px;
  line-height: 1.2;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--t) var(--ease),
              border-color var(--t) var(--ease),
              color var(--t) var(--ease),
              transform var(--t) var(--ease);
}

.button:hover, .btn:hover {
  background-color: var(--neutral-hover);
  border-color: #55556a;
  color: var(--text) !important;     /* site.css forces #000 on hover */
  text-decoration: none;
}
.button:active, .btn:active { transform: translateY(1px); background-color: var(--neutral-press); }

.button:focus-visible, .btn:focus-visible,
a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* Intent classes, so pages can stop hand-colouring buttons inline. */
.btn--primary  { background-color: var(--positive); border-color: #4d7f61; color: #eafff2; }
.btn--primary:hover  { background-color: var(--positive-hover); border-color: #61a179; }
.btn--primary:active { background-color: var(--positive-press); }

.btn--ghost { background-color: rgba(255,255,255,0.04); }
.btn--ghost:hover { background-color: rgba(255,255,255,0.09); }

.btn--danger { background-color: var(--negative); border-color: #8f4b4b; color: #ffecec; }
.btn--danger:hover { background-color: var(--negative-hover) !important; }

/* =================================================================== navbar */

/* NOT overflow: hidden any more.
   site.css:507 hid the bar's overflow, which cut the top and bottom off
   anything taller than the bar -- which is exactly what the avatar is.

   The clearfix replaces what that hiding also did: contain floated children.
   Inside .top the bar is laid out with flex and the floats are turned off, so
   there it is belt and braces; it matters for any .bar outside .top, which is
   where the rule it is replacing came from. */
.bar { overflow: visible !important; }
.bar::after { content: ""; display: table; clear: both; }

.bar {
  background: var(--header-a) !important;
  -webkit-backdrop-filter: blur(18px) saturate(120%);
  backdrop-filter: blur(18px) saturate(120%);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 1px 24px rgba(0, 0, 0, 0.45);
}

.bar .bar-item.button {
  min-height: 0;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: 14px;
  padding: 10px 14px;
  border-radius: var(--radius);
}
.bar .bar-item.button:hover {
  background: rgba(255, 255, 255, 0.07);
  color: var(--text) !important;
}

/* The wordmark. */
.bar .bar-item-black {
  background: transparent !important;
  color: var(--text) !important;
  letter-spacing: 0.34em;
  font-size: 15px;
  border: 0;
}

/* site.css swaps the desktop links for a burger at its own 601px breakpoint,
   which leaves 600-900px rendering a row of links with no room for them. */
@media (max-width: 900px) {
  .bar .right.hide-small { display: none !important; }
  .bar .fakeClick.hide-large.hide-medium { display: inline-block !important; }
  .bar .bar-item-player { display: none !important; }
}

/* ==================================================================== forms */

input[type="text"], input[type="password"], input[type="email"],
input[type="date"], input[type="number"], select, textarea, .input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 13px;
  min-height: 42px;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 15px;
  transition: border-color var(--t) var(--ease), background-color var(--t) var(--ease);
}
input:hover, select:hover, textarea:hover { border-color: #4d4d5f; }
input:focus, select:focus, textarea:focus { border-color: var(--blue); background: rgba(0, 0, 0, 0.55); }
::placeholder { color: var(--text-faint); }

/* =================================================================== tables */

table.table, table.table-dark {
  width: 100%;
  border-collapse: collapse;
  background: rgba(0, 0, 0, 0.35) !important;
  border: 1px solid var(--border);
}
table.table th, table.table-dark th {
  text-align: left;
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--text-dim);
  background: var(--header-a);
  border-bottom: 1px solid var(--border);
  padding: 10px 12px;
}
table.table td, table.table-dark td {
  padding: 10px 12px;
  border-bottom: 1px solid rgba(60, 60, 74, 0.5);
}
table.table tbody tr:hover td { background: rgba(255, 255, 255, 0.035); }

/* A data table cannot reflow, so on a phone it scrolls inside its own box
   instead of pushing the whole page sideways. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ================================================================= sections */

/* The home page's feature rows. Were layout <table>s, which cannot stack —
   on a phone they squeezed a screenshot and a paragraph into two columns of
   nothing. Grid, so they become one column when there is no room for two. */
.feature {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(20px, 3.5vw, 48px);
  align-items: center;
  max-width: var(--page);
  margin: 0 auto calc(var(--u) * 3);
}
.feature:last-child { margin-bottom: 0; }
.feature__media img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  background: var(--backdrop);
}
.feature--flip .feature__media { order: -1; }

@media (max-width: 860px) {
  .feature { grid-template-columns: minmax(0, 1fr); gap: var(--u); }
  .feature--flip .feature__media { order: 0; }
  .feature__media { margin-top: 4px; }
}

/* A row of actions that wraps instead of overflowing. */
.actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }

/* ============================================================ housekeeping */

img, video { max-width: 100%; }

/* site.css floats .col; without this the page can scroll sideways on a phone. */
html, body { overflow-x: hidden; }

/* THE BACKDROP, WHICH WAS A 3.4MB VIDEO.
   1.mp4 autoplayed on every page of the site -- the single heaviest thing it
   served, downloaded before anyone had decided to stay, and on a phone's data
   as readily as on a desktop. It was then dimmed to 62% brightness and covered
   by translucent panels, so most of those megabytes arrived to be hidden.

   This is what is left when you keep the part that was doing the work: colour
   and depth behind the glass. Three very soft pools in the brand hues over the
   backdrop, fixed so they do not scroll, and costing nothing to send because
   they are four lines of CSS rather than a file.

   It is on a pseudo-element rather than on body so that the gradients can be
   fixed while the page scrolls normally over them. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -100;
  pointer-events: none;
  /* The palette's own three accents -- brand magenta, the logo's green and
     the selection blue -- at the opacity the video sat at once it had been
     dimmed. Any stronger and the panel text starts fighting it. */
  background:
    radial-gradient(120vw 90vh at 10% -12%, rgba(255, 0, 216, 0.13), transparent 62%),
    radial-gradient(100vw 80vh at 94% 6%,  rgba(150, 200, 255, 0.09), transparent 60%),
    radial-gradient(130vw 100vh at 52% 112%, rgba(96, 224, 160, 0.09), transparent 66%);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 1ms !important; animation-duration: 1ms !important; }
}

/* ============================================================= mobile menu */

/* The old sheet was <h2> links with hand-placed margins, absolutely positioned
   at top:64 with a hardcoded white background — and two of its entries pointed
   at routes that do not exist. This is a real sheet: full width, scrollable if
   the list outgrows the screen, and tappable at 48px a row. */
.mobile-menu {
  position: fixed;
  top: 64px;
  left: 0;
  right: 0;
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  z-index: 10000;
  background: var(--header-a);
  -webkit-backdrop-filter: blur(18px) saturate(120%);
  backdrop-filter: blur(18px) saturate(120%);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5);
  padding: 8px 0 12px;
}

.mobile-menu__item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px clamp(16px, 5vw, 28px);
  min-height: 48px;
  font-size: 17px;
  color: var(--text);
  cursor: pointer;
  border-bottom: 1px solid rgba(60, 60, 74, 0.45);
}
.mobile-menu__item:last-child { border-bottom: 0; }
.mobile-menu__item:hover, .mobile-menu__item:active {
  background: rgba(255, 255, 255, 0.06);
  text-decoration: none;
}
.mobile-menu__item i { width: 22px; text-align: center; color: var(--text-faint); }

/* ===================================================================== hero */

/* Two columns on a desktop, one on a phone. The old hero left the right-hand
   half of a widescreen empty: copy capped at a readable measure and nothing
   beside it. The browser screenshot goes there, which also lets the page prove
   its own headline -- "make music in your browser" next to a picture of exactly
   that. */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: center;
  max-width: var(--page);
  margin: 0 auto;
}

.hero__copy .jumbo { margin-bottom: 2px; }

.hero__badge {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: var(--text-dim);
  margin-bottom: calc(var(--u) * 1.5);
}
.hero__badge b { color: var(--brand-soft); letter-spacing: 0.02em; }
/* Sitting on the download buttons rather than under the lockup: it needs a
   little air above it and none of the paragraph spacing below. */
.hero__badge--above { margin: var(--u) 0 6px; }

/* The page's real headline, so it outranks the section h1s below it. */
.hero__tagline {
  font-size: clamp(24px, 3vw, 34px) !important;
  font-weight: 500;
  margin-bottom: var(--u);
}

.hero__fineprint { font-size: 14px; color: var(--text-faint); margin-top: var(--u); }
.hero__fineprint a { color: var(--brand-soft); }
.hero__fineprint a:hover { color: #ffa8c8; }

/* The hero's case, where the hero leads with the product rather than with a
   screenshot. Centred in its column like the history covers, and outside the
   img rules below: a case is not a picture in a frame, it draws its own
   edges and casts its own shadow. */
.hero__media--case { display: flex; justify-content: center; }

.hero__media img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

@media (max-width: 900px) {
  .hero { grid-template-columns: minmax(0, 1fr); gap: var(--u); }
  .hero__media { margin-top: 8px; }
}

.button__note { color: var(--text-faint); font-weight: 400; }

/* The download row is secondary to the green browser CTA above it. */
.actions .btn--ghost { font-size: 14px; }

/* Breathing room between the hero and the section that follows it inside the
   same container -- they were running together with only a paragraph's margin. */
.hero + .row { margin-top: calc(var(--u) * 4); }
.hero + .row > h1 { margin-top: 0; }

/* A history entry: prose beside its cover. The cover is art, not a screenshot,
   so it gets a column of its own rather than half the width.

   The case sits in the MIDDLE of the entry, not at the top of it. Entries run
   to wildly different lengths -- some four paragraphs, some one -- and with the
   cover pinned to the top a long entry left it stranded against three hundred
   pixels of empty column. Centred, the art reads as the partner of the prose at
   any length. */
/* The cover column grows with the window. It was a flat 280px, so every pixel
   a wide display added went to the prose, and the case -- the one thing on the
   page that is worth looking at large -- stayed the size it is on a laptop.
   The cap stops the measure beside it dropping below a readable line length. */
.feature--history { grid-template-columns: minmax(0, 1fr) minmax(0, clamp(260px, 30vw, 420px)); align-items: center; }
.feature__media--case { display: flex; justify-content: center; }
.feature__media--case .dvd { max-width: 100%; }

@media (max-width: 860px) {
  .feature--history { grid-template-columns: minmax(0, 1fr); }
  /* The cover leads on a phone: it is the most recognisable thing about an
     entry, and scrolling past four paragraphs to reach it is backwards. */
  .feature--history .feature__media { order: -1; margin-bottom: 8px; }
}

/* ============================================================== the lockup */

/* The mark plus the wordmark, as the app draws it.
   - the mark is public/img/ezmuze-mark.svg, generated from LogoMark.cs's own 26
     control points, so the site and the app draw the same curve
   - "ezmuze" is Comfortaa Bold and "studio" Comfortaa Light, which is what
     GustUI's Theme.WordmarkFont / WordmarkLightFont name
   - "studio" is the app's brand magenta (249,0,183), not the site's older
     #FF00D8 -- the lockup should match the product, not the stylesheet */
.lockup { display: flex; align-items: center; gap: clamp(12px, 1.6vw, 22px); }

.lockup__mark {
  width: clamp(54px, 7vw, 86px);
  height: auto;
  flex: 0 0 auto;
}

.lockup__word {
  font-family: Comfortaa, system-ui, sans-serif;
  font-size: clamp(38px, 7vw, 72px);
  line-height: 1;
  letter-spacing: -0.005em;
  white-space: nowrap;
}
.lockup__word b { font-weight: 700; color: var(--text); }
.lockup__word span { font-weight: 300; color: #f900b7; }

/* ============================================================== previews */

.previews { display: grid; gap: var(--u); max-width: 720px; }
.preview {
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.35);
  padding: 12px 14px;
}
.preview__title { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
.preview__title b { font-size: 15px; }
.preview__title span { font-size: 13px; color: var(--text-faint); }
.preview audio { width: 100%; height: 36px; display: block; }

/* ============================================================ pack notice */

/* The "your packs still work" panel. It is the single most reassuring thing on
   the page for anyone who spent money on ezmuze before, so it gets a surface of
   its own rather than being a sentence in a paragraph. */
.keepsake {
  border: 1px solid var(--border);
  border-left: 3px solid var(--green);
  background: rgba(96, 224, 160, 0.05);
  padding: clamp(16px, 2.4vw, 26px);
  max-width: var(--page);
  margin: 0 auto calc(var(--u) * 2);
}
.keepsake h3 { margin-bottom: 8px; }
.keepsake p { max-width: none; }

/* ================================================================== store */

.storebar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.storebar__cats, .storebar__sort { display: flex; flex-wrap: wrap; gap: 8px; }
.storebar__sort { margin-left: auto; }

/* A fixed basis is the whole point: the search field must not be sized by the
   result list it is filtering. */
.storebar__search { flex: 0 1 300px; min-width: 200px; }
.storebar__search .searchbar { position: relative; margin: 0; }
.storebar__search .search_input {
  width: 100%;
  padding-right: 40px;
  margin: 0;
}
.storebar__search .search_icon {
  position: absolute;
  right: 0; top: 0; bottom: 0;
  width: 38px;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-faint);
}
.storebar__search .search_icon:hover { color: var(--text); text-decoration: none; }

@media (max-width: 700px) {
  .storebar__sort { margin-left: 0; width: 100%; }
  .storebar__search { flex: 1 1 100%; }
}

/* ============================================== the legacy search bar, undone */

/* site.css made this an expanding search: the input is width:0 with a
   TRANSPARENT CARET, and only `.searchbar:hover > .search_input` gives it
   250px and a visible caret. So it collapsed the moment the pointer left,
   you could not see what you were typing, and the width animated under the
   cursor. That is the "barmy" behaviour, and it was never the flex layout.
   Specificity has to beat `.searchbar:hover > .search_input` (0,3,0). */
.storebar__search .searchbar,
.storebar__search .searchbar:hover {
  height: auto;
  background: none;
  border-radius: 0;
  padding: 0;
}
.storebar__search .searchbar > .search_input,
.storebar__search .searchbar:hover > .search_input {
  width: 100%;
  padding: 11px 40px 11px 13px;
  line-height: 1.2;
  caret-color: var(--text);
  color: var(--text);
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid var(--border);
  transition: border-color var(--t) var(--ease), background-color var(--t) var(--ease);
}
.storebar__search .searchbar:hover > .search_icon { background: none; color: var(--text); }

/* ==================================================================== vault */

.vault {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(16px, 2.5vw, 32px);
  max-width: var(--page);
  margin: 0 auto;
}
@media (max-width: 820px) { .vault { grid-template-columns: minmax(0, 1fr); } }

.vault__pane {
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.28);
  padding: clamp(12px, 1.6vw, 18px);
  min-width: 0;
}
.vault__head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.vault__head h3 { margin: 0; }
.vault__count {
  font-size: 12px;
  color: var(--text-faint);
  border: 1px solid var(--border);
  padding: 1px 7px;
}
.vault__pane .input { margin-bottom: 10px; }

/* Rows: design-guide.md §9.5 makes list rows a universal spec in the app --
   one height, hover, a selected state. These were bare <p> tags. */
.rowlist { list-style: none; margin: 0; padding: 0; }
.rowlist__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  min-height: 40px;
  color: var(--text);
  border-bottom: 1px solid rgba(60, 60, 74, 0.45);
  cursor: pointer;
  transition: background-color var(--t) var(--ease), color var(--t) var(--ease);
}
.rowlist li:last-child .rowlist__row { border-bottom: 0; }
.rowlist__row:hover { background: rgba(255, 255, 255, 0.06); text-decoration: none; }
.rowlist__row.is-selected {
  background: rgba(150, 200, 255, 0.10);
  box-shadow: inset 2px 0 0 var(--blue);
  color: #fff;
}
.rowlist__row--play i { color: var(--text-faint); font-size: 11px; width: 14px; }
.rowlist__row--play:hover i, .rowlist__row--play.is-selected i { color: var(--green); }
.rowlist__row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rowlist__empty { color: var(--text-faint); padding: 10px; }
/* A line under a heading saying what the list below it is. */
.rowlist__note { color: var(--text-dim); margin: -4px 0 14px; max-width: var(--measure); }

/* =========================================================== the user menu ===
   Your own face, and everything about your account behind it. Bigger than the
   bar it sits on, which is the point -- it is the one thing up there that is
   yours, and hanging below the edge makes it read as a face rather than as
   another button. Nothing else on the bar is allowed to be taller, so nothing
   else needed the bar to stop clipping. */
/* OUT OF THE FLOW, which is the only way it can be taller than the bar. In
   flow it is a floated child like every other item, so containing the floats --
   which is what stops the bar collapsing -- also stretches the bar to fit it,
   and 42px became 54 with nothing hanging over anything. Absolute, and the row
   beside it reserves the width so the two do not overlap.

   It hangs DOWNWARD only: the bar is at the top of the window, so an overhang
   split evenly would put the top of the face off the screen. */
.bar { position: relative; }
#myNavbar .right.hide-small { padding-right: 66px; }

.usermenu__face {
  position: absolute;
  right: 10px;
  top: 3px;
  /* ABOVE THE LINE UNDER THE BAR. .top::after draws that hairline, and a
     pseudo-element paints after its element's content -- so with both of them
     positioned and neither given a z-index, tree order decided, and the line
     went straight across the face. */
  z-index: 2;
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  line-height: 0;
  cursor: pointer;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--header), 0 6px 18px -6px rgba(0, 0, 0, 0.8);
  transition: box-shadow var(--t) var(--ease), transform var(--t) var(--ease);
}
.usermenu__face:hover { transform: translateY(-1px); }
.usermenu__face:hover,
.usermenu__face.is-on {
  box-shadow: 0 0 0 2px var(--green), 0 6px 18px -6px rgba(0, 0, 0, 0.8);
}
.usermenu__face .avatar { display: block; }

.usermenu__panel { min-width: 240px; }

/* Who you are and what you have: facts, not menu items. */
.usermenu__who {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px 8px;
}
.usermenu__name { color: var(--text); font-size: 15px; }
.usermenu__tag { color: var(--text-faint); font-size: 12px; }
.usermenu__tag .fa-xbox { color: var(--green); margin-right: 4px; }

.usermenu__sigma .usermenu__balance {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--green);
}

/* ================================================= making a record ===
   The artist's side of an album. The tiles are the playlist grid, because a
   record and a playlist are the same shape on screen -- a picture, a name and
   a count -- and giving them two looks would say they were different things. */
.albums { margin-bottom: clamp(20px, 3vw, 30px); }
.albums .plnew { margin-top: 12px; }
.pltile.is-open .pltile__name { color: var(--green); }

.albumedit {
  margin-top: 16px;
  padding: 14px;
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.25);
}
.albumedit__head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.albumedit__head h4 { margin: 0; flex: 1 1 auto; }
.albumedit__acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* The position on the record, which is what the numeric prefix means inside an
   album folder. Tabular so a two-digit track does not shift the names. */
.filerow__num {
  min-width: 20px;
  color: var(--text-faint); font-size: 12px;
  font-variant-numeric: tabular-nums; text-align: right;
}

.albumadd {
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.albumadd h5 { margin: 0 0 8px; color: var(--text-dim); font-size: 13px; font-weight: 500; }

/* ======================================================= the vault search ===
   One box for the whole vault, with the two badges inside it. They are inside
   because they are not a second control -- they say what the box is looking at,
   and a filter sitting somewhere else would be a second thing to notice. */
.vsearch { position: relative; max-width: var(--page); margin: 0 auto clamp(20px, 3vw, 32px); }

.vsearch__bar { position: relative; display: flex; align-items: center; }
.vsearch__icon {
  position: absolute; left: 14px;
  color: var(--text-faint); font-size: 14px; pointer-events: none;
}
.vsearch__input {
  width: 100%;
  padding: 14px 96px 14px 40px;      /* room for the icon, and for both badges */
  font-size: 15px;
}

.vsearch__kinds { position: absolute; right: 8px; display: flex; gap: 4px; }
.vsearch__kind {
  appearance: none; border: 0; background: transparent;
  padding: 6px; line-height: 0; cursor: pointer;
  border-radius: var(--radius);
  transition: opacity var(--t) var(--ease), filter var(--t) var(--ease);
  /* OFF is dark, not gone. The control has to say what it would do if you
     pressed it again, and an empty space says nothing. */
  opacity: 0.32;
  filter: grayscale(1);
}
.vsearch__kind:hover { opacity: 0.6; }
.vsearch__kind.is-on { opacity: 1; filter: none; }
.vsearch__kind.is-on:hover { background: rgba(255, 255, 255, 0.07); }
.vsearch__kind img { width: 22px; height: 22px; display: block; }

/* The results. Absolute rather than a portal: this sits at the top of the page
   with nothing above it that clips, and it has to match the box's own width,
   which a panel placed from a rect would not. */
.vsearch__panel {
  position: absolute;
  left: 0; right: 0; top: calc(100% + 6px);
  z-index: 60;
  max-height: min(60vh, 520px);
  overflow-y: auto;
  padding: 6px;
  border: 1px solid var(--border);
  background: #14151a;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.66);
}
.vsearch__none { padding: 14px; color: var(--text-faint); font-size: 14px; }
.vsearch__group + .vsearch__group { border-top: 1px solid rgba(255, 255, 255, 0.07); margin-top: 4px; padding-top: 4px; }
.vsearch__label {
  padding: 8px 10px 4px;
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-faint);
}
.vsearch__hit {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 7px 10px;
  border: 0; background: transparent; color: var(--text);
  text-align: left; cursor: pointer;
  font: inherit;
}
/* Hover and keyboard are the SAME state. Two different highlights moving
   independently is how you end up pressing enter on the wrong row. */
.vsearch__hit.is-at { background: rgba(255, 255, 255, 0.07); }
.vsearch__art {
  width: 34px; height: 34px; overflow: hidden;
  display: grid; place-items: center;
  background: var(--panel); color: var(--text-faint);
}
.vsearch__art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vsearch__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vsearch__sub { color: var(--text-faint); font-size: 12px; white-space: nowrap; }

@media (max-width: 620px) { .vsearch__sub { display: none; } }

/* ============================================================== albums ===
   A record is a subfolder of an artist's, so an album card is a shelf card
   that happens to open a folder rather than a track. It is a <button> and not
   a link because it does not go anywhere: it filters the page you are on, and
   says so by putting ?album= in the address. */
.albumcard {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  text-align: left;
  cursor: pointer;
}
.albumcard__art { display: block; background: var(--panel); }
.albumcard__art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.albumcard:hover .tcard__name { color: var(--text); }

/* Which record you are in. The artist's own header stays above it, because you
   have not left them -- this is a place inside their page, not another page. */
.albumhead {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 20px);
  max-width: var(--tracklist);
  margin: 0 auto clamp(16px, 2.5vw, 24px);
  padding: 12px;
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.25);
}
.albumhead__art {
  width: 112px; height: 112px; flex: none; display: block; object-fit: cover;
  box-shadow: 0 10px 26px -12px rgba(0, 0, 0, 0.9);
}
.albumhead h2 { margin: 0 0 2px; }
.albumhead h2::after { display: none; }
.albumhead__facts { color: var(--text-faint); font-size: 13px; margin: 0 0 10px; }

/* A folder-wide action under its heading -- emptying the trash. */
.cloudgroup__act { display: flex; gap: 8px; align-items: center; margin: 0 0 14px; }


.pager { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.pager span { font-size: 13px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.pager .button { min-height: 34px; padding: 6px 12px; }

/* ============================================================== store rows */

/* The compact list rows and the big cards both sat on the old alternating
   alpha slabs with no edge between them. */
.slimcontainer.light-purplealpha + .slimcontainer,
.storerow {
  border: 1px solid var(--border);
}

/* =============================================================================
   NEON PASS
   =============================================================================
   Taking the cues from the ezmuze studio box art rather than from the app's
   chrome: near-black ground, halftone dots, the green-to-magenta disc, colour
   coded sequencer rows, a spectrum curve running red through blue to green,
   and glowing wires over the top of it.

   This deliberately overrides the restraint above -- flat surfaces, hard
   edges, borders doing all the work. That read as tidy rather than as ezmuze.
   ========================================================================== */

:root {
  --neon-green:   #27f836;
  --neon-magenta: #ff05c7;
  --neon-cyan:    #5ad9ff;
  --neon-violet:  #a06bff;
  --grad-brand:   linear-gradient(120deg, var(--neon-green) 0%, var(--neon-cyan) 42%, var(--neon-magenta) 100%);
  --radius: 6px;                     /* the flat-0 decision is retired */
}

/* -- the ground: halftone, as on the art ---------------------------------- */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  /* Faint, and fading from the top rather than concentrated there: at 0.055
     with a tight mask the only place the dots were NOT covered by a panel was
     the gap under the nav, so they read as a dotted band rather than as
     texture. */
  background-image: radial-gradient(rgba(255, 255, 255, 0.032) 1px, transparent 1px);
  background-size: 5px 5px;
  mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.55) 45%, transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.55) 45%, transparent 100%);
}
.top, .container, .slimcontainer, .mobile-menu { position: relative; z-index: 1; }

/* -- headings in the wordmark's own typeface ------------------------------ */
h1, h2, .jumbo, .xxlarge, .hero__tagline {
  font-family: Comfortaa, system-ui, sans-serif;
  letter-spacing: -0.02em;
}
h1 { font-weight: 700; }

/* A lit rule under the big section headings, the way the spectrum runs across
   the art. */
.row > h1, .container > h1 { position: relative; padding-bottom: 12px; }
.row > h1::after, .container > h1::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 116px; height: 2px;
  background: var(--grad-brand);
  border-radius: 2px;
  box-shadow: 0 0 14px rgba(255, 5, 199, 0.55);
}

hr {
  height: 1px;
  border: 0;
  background: linear-gradient(90deg, transparent, var(--border) 12%, var(--border) 88%, transparent);
}

/* -- surfaces get an edge that catches the light -------------------------- */
.container, .slimcontainer {
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  border-bottom: 1px solid rgba(0, 0, 0, 0.5);
}

.feature__media img,
.hero__media img,
.vault__pane,
.preview,
.keepsake {
  border-radius: var(--radius);
}

/* The screenshots are the best thing on the page: let them glow a little, and
   lift on hover the way the art's panels sit above their own reflections. */
.feature__media img, .hero__media img {
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.6),
              0 0 0 1px rgba(255, 255, 255, 0.06),
              0 0 34px -12px rgba(90, 217, 255, 0.5);
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.feature__media img:hover, .hero__media img:hover {
  transform: translateY(-3px);
  box-shadow: 0 24px 56px rgba(0, 0, 0, 0.65),
              0 0 0 1px rgba(255, 255, 255, 0.10),
              0 0 44px -8px rgba(255, 5, 199, 0.45);
}

/* -- the mark, lit --------------------------------------------------------- */
.lockup__mark {
  filter: drop-shadow(0 0 26px rgba(39, 248, 54, 0.35))
          drop-shadow(0 0 34px rgba(255, 5, 199, 0.30));
}

/* -- buttons ---------------------------------------------------------------- */
.button, .btn { border-radius: var(--radius); }

.btn--primary {
  background: linear-gradient(135deg, #2f7d52, #1f6a63);
  border-color: rgba(39, 248, 54, 0.45);
  color: #eafff2;
  box-shadow: 0 0 0 1px rgba(39, 248, 54, 0.10), 0 8px 24px -10px rgba(39, 248, 54, 0.55);
}
.btn--primary:hover {
  background: linear-gradient(135deg, #3a9a64, #268076);
  border-color: rgba(39, 248, 54, 0.75);
  box-shadow: 0 0 0 1px rgba(39, 248, 54, 0.18), 0 12px 30px -10px rgba(39, 248, 54, 0.7);
}

/* WHY !important IS HERE, AND ONLY HERE.

   site.css line 1389 is

       .button:hover { color: #000 !important; background-color: #ccc !important }

   -- the Blazor site's idea of a hover, applied to every button anywhere.
   !important beats source order, so nothing in this file could get past it.

   The green primary button escaped by accident: its colour is a gradient,
   which is a background-IMAGE, and the image paints over the grey the old
   rule forces underneath. A ghost button's colour is a background-COLOR,
   which is the exact property being overridden -- so it went solid #ccc on
   hover, which is what "washed out and goes white" was.

   The declarations below are !important so they win, rather than the whole
   legacy rule being neutralised: turning it off would change every button
   on the site at once, including ones nothing here has ever styled.

   GREEN, not the cyan it used to hint at. Cyan is --blue, the selection
   colour, and it belongs to focus rings and to what is currently chosen.
   These buttons DO something, and the site's colour for that is the green
   on the play button sitting next to them. */
.btn--ghost {
  /* Was 4.5% white with a 14% border: legible, but only just, and it read
     as disabled next to a filled button. */
  background: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.22);
}
.btn--ghost:hover {
  background-color: rgba(96, 224, 160, 0.15) !important;
  border-color: rgba(96, 224, 160, 0.6);
  color: #eafff2 !important;
  box-shadow: 0 0 0 1px rgba(96, 224, 160, 0.14),
              0 10px 26px -12px rgba(96, 224, 160, 0.8);
}
.btn--ghost:hover i { color: var(--green); }

/* Not a button -- it is the "already in your cloud" state, and it says so.
   Without this the old rule greys it out under the pointer, which reads as
   something you can press. */
.button.is-done:hover {
  background-color: var(--positive) !important;
  color: #fff !important;
}

/* -- nav ------------------------------------------------------------------- */
.bar { border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
.bar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 1px;
  background: var(--grad-brand);
  opacity: 0.5;
}
.top .bar { position: relative; }
.bar .bar-item.button:hover { color: #fff !important; }

/* -- the packs panel, lit down its edge ------------------------------------ */
.keepsake {
  border-left: 0;
  background:
    linear-gradient(90deg, rgba(39, 248, 54, 0.09), rgba(255, 5, 199, 0.05) 42%, transparent 78%),
    rgba(0, 0, 0, 0.32);
  position: relative;
  overflow: hidden;
}
.keepsake::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--grad-brand);
  box-shadow: 0 0 18px rgba(39, 248, 54, 0.55);
}

/* -- previews, like the art's channel rows -------------------------------- */
.preview { position: relative; overflow: hidden; background: rgba(0, 0, 0, 0.34); }
.preview::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--neon-cyan);
  box-shadow: 0 0 16px rgba(90, 217, 255, 0.7);
}
.preview:nth-child(2)::before { background: var(--neon-magenta); box-shadow: 0 0 16px rgba(255, 5, 199, 0.7); }
.preview__title b { font-family: Comfortaa, system-ui, sans-serif; font-weight: 700; }

/* -- vault rows: a lit edge on the selected one --------------------------- */
.rowlist__row.is-selected {
  background: linear-gradient(90deg, rgba(90, 217, 255, 0.14), transparent 62%);
  box-shadow: inset 3px 0 0 var(--neon-cyan);
}
.rowlist__row--play:hover i, .rowlist__row--play.is-selected i {
  color: var(--neon-green);
  filter: drop-shadow(0 0 6px rgba(39, 248, 54, 0.8));
}

.vault__pane, .preview, .keepsake { border-color: rgba(255, 255, 255, 0.09); }

/* -- the brand pink, everywhere it was the older flat magenta -------------- */
.ezmuzePurple { color: var(--neon-magenta); text-shadow: 0 0 22px rgba(255, 5, 199, 0.45); }

/* ============================================================ store cards */

.pgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(392px, 1fr));
  gap: clamp(12px, 1.6vw, 20px);
  max-width: var(--page);
  margin: 0 auto clamp(24px, 4vw, 56px);
  padding-inline: clamp(16px, 5vw, 64px);
}
@media (max-width: 420px) { .pgrid { grid-template-columns: minmax(0, 1fr); } }

.pcard {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 16px;
  align-items: start;
  padding: 16px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: var(--radius);
  background: linear-gradient(150deg, rgba(58, 22, 74, 0.5), rgba(0, 0, 0, 0.4) 55%);
  color: var(--text);
  cursor: pointer;
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease),
              border-color var(--t) var(--ease);
}
.pcard:hover {
  transform: translateY(-2px);
  border-color: rgba(255, 5, 199, 0.45);
  box-shadow: 0 16px 34px -14px rgba(0, 0, 0, 0.8), 0 0 30px -14px rgba(255, 5, 199, 0.8);
  text-decoration: none;
}
.pcard__art { flex: 0 0 auto; }
.pcard__name {
  font-family: Comfortaa, system-ui, sans-serif;
  font-size: 19px;
  margin: 0 0 2px;
}
.pcard__by { font-size: 12px; color: var(--text-faint); margin-bottom: 8px; }
.pcard__pitch {
  font-size: 14px;
  color: var(--text-dim);
  margin: 0;
  max-width: none;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pcard__price {
  font-family: Comfortaa, system-ui, sans-serif;
  font-weight: 700;
  font-size: 15px;
  white-space: nowrap;
  padding: 4px 10px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
}
.pcard__price.is-free {
  color: var(--neon-green);
  border-color: rgba(39, 248, 54, 0.45);
  box-shadow: 0 0 18px -8px rgba(39, 248, 54, 0.9);
}

/* The toolbar band was a full-bleed purple slab of its own. */
.slimcontainer.light-purplealpha:has(.storebar) {
  background: rgba(0, 0, 0, 0.35) !important;
  margin-bottom: clamp(16px, 2.5vw, 28px);
}

/* =============================================================== the player */

.playerbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 9000;
  background: rgba(12, 12, 16, 0.92);
  -webkit-backdrop-filter: blur(22px) saturate(130%);
  backdrop-filter: blur(22px) saturate(130%);
  border-top: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.55);
  padding-bottom: env(safe-area-inset-bottom);
}
/* The lit hairline the rest of the design uses for "this is ezmuze". */
.playerbar::before {
  content: "";
  position: absolute; left: 0; right: 0; top: -1px; height: 1px;
  background: var(--grad-brand);
  opacity: 0.75;
}

.playerbar__inner {
  display: grid;
  /* The meta column carries a cover, two lines of linked text and the stars
     now, so it needs a floor -- at 1fr against a 2fr seek bar the artist name
     was clipped to "madninjaskill:". */
  grid-template-columns: minmax(260px, 1.2fr) auto minmax(160px, 1.6fr) auto;
  align-items: center;
  gap: clamp(10px, 2vw, 26px);
  max-width: var(--page);
  margin: 0 auto;
  padding: 10px clamp(12px, 4vw, 28px);
}

.playerbar__meta { display: flex; align-items: center; gap: 12px; min-width: 0; }
.playerbar__text { min-width: 0; }
.playerbar__track {
  font-weight: 600; font-size: 14px; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.playerbar__artist {
  font-size: 12px; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Four bars that dance while it plays and rest when it does not. */
.playerbar__eq { display: flex; align-items: flex-end; gap: 2px; height: 20px; flex: 0 0 auto; }
.playerbar__eq span {
  width: 3px; height: 4px; border-radius: 1px;
  background: var(--neon-green);
  box-shadow: 0 0 8px rgba(39, 248, 54, 0.8);
}
.playerbar__eq[data-on="true"] span { animation: eq 900ms ease-in-out infinite; }
.playerbar__eq span:nth-child(2) { animation-delay: 140ms; background: var(--neon-cyan);    box-shadow: 0 0 8px rgba(90, 217, 255, 0.8); }
.playerbar__eq span:nth-child(3) { animation-delay: 280ms; background: var(--neon-violet);  box-shadow: 0 0 8px rgba(160, 107, 255, 0.8); }
.playerbar__eq span:nth-child(4) { animation-delay: 420ms; background: var(--neon-magenta); box-shadow: 0 0 8px rgba(255, 5, 199, 0.8); }
@keyframes eq { 0%, 100% { height: 4px } 50% { height: 18px } }

.playerbar__controls { display: flex; align-items: center; gap: 6px; }
.playerbar__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--text-dim);
  cursor: pointer;
  transition: color var(--t) var(--ease), background-color var(--t) var(--ease),
              transform var(--t) var(--ease);
}
.playerbar__btn:hover:not(:disabled) { color: #fff; background: rgba(255, 255, 255, 0.09); }
.playerbar__btn:disabled { opacity: 0.3; cursor: default; }
.playerbar__btn--play {
  width: 44px; height: 44px;
  color: #06210f;
  background: linear-gradient(135deg, var(--neon-green), #12c9a0);
  box-shadow: 0 0 24px -6px rgba(39, 248, 54, 0.8);
}
.playerbar__btn--play:hover:not(:disabled) {
  color: #06210f;
  background: linear-gradient(135deg, #56ff62, #21e3b6);
  transform: scale(1.05);
}

.playerbar__seek { display: flex; align-items: center; gap: 10px; min-width: 0; }

/* Where you are in the queue, on the button that opens it.
   It used to sit after the artist's name, where "Railblade . 5 of 118" read as
   though the number were part of who made the track. */
.playerbar__queuebtn { display: inline-flex; align-items: center; gap: 6px; }
.playerbar__queuebtn.is-on { color: var(--green); }
.playerbar__pos { font-size: 0.78rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.playerbar__queuebtn.is-on .playerbar__pos { color: inherit; }
/* The handle on the drawer: quieter than the list icon and the count, which
   are what you are actually reading. */
.playerbar__queuebtn .fa-chevron-up,
.playerbar__queuebtn .fa-chevron-down { font-size: 0.7em; opacity: 0.75; }

/* The queue, expanding UPWARDS out of the bar.
   Above rather than over: the player is anchored to the bottom of the window,
   and a panel that covered the page would hide the list you were browsing when
   you opened it. */
.playerqueue {
  position: absolute;
  left: 0; right: 0; bottom: 100%;
  max-height: min(52vh, 460px);
  display: flex;
  flex-direction: column;
  background: rgba(12, 12, 16, 0.97);
  border-top: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.55);
}
.playerqueue__head {
  display: flex; align-items: center;
  padding: 8px 14px;
  color: var(--text-dim);
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.playerqueue__list { list-style: none; margin: 0; padding: 4px 0; overflow-y: auto; }
.playerqueue__row > button {
  display: grid;
  grid-template-columns: 28px 32px minmax(0, 1fr) minmax(0, 0.6fr);
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 5px 14px;
  background: transparent;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.playerqueue__row > button:hover { background: rgba(255, 255, 255, 0.06); }
.playerqueue__row.is-current > button { background: rgba(90, 217, 255, 0.10); }
.playerqueue__row.is-current .playerqueue__n { color: var(--neon-cyan, var(--blue)); }
.playerqueue__n { color: var(--text-faint); font-size: 0.8rem; text-align: center; font-variant-numeric: tabular-nums; }
.playerqueue__name, .playerqueue__by { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.playerqueue__by { color: var(--text-dim); font-size: 0.85rem; }

/* The scrubber, drawn as the track's own waveform.
   Taller than a bar because that is the point -- at 4px it would be a bar with
   texture. 34px is enough to read the shape of a song without the player
   growing into the page. */
.waveform {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 34px;
  cursor: pointer;
  overflow: hidden;
}
.waveform__canvas { display: block; width: 100%; height: 100%; }

/* Where you would land. A line rather than a tooltip: the waveform already
   says what is there, so the only missing information is "here". */
.waveform__hover {
  position: absolute;
  top: 0; bottom: 0;
  width: 1px;
  background: rgba(255, 255, 255, 0.55);
  pointer-events: none;
}

/* No peaks -- an undecodable file, or one still being computed. A plain bar,
   so the control never vanishes and the track stays scrubbable. */
.waveform.is-plain { height: 6px; background: rgba(255, 255, 255, 0.12); }
.waveform__plain { position: absolute; inset: 0 auto 0 0; background: var(--brand); }
.playerbar__time {
  font-size: 11px; color: var(--text-faint);
  font-variant-numeric: tabular-nums; flex: 0 0 auto;
}

/* One range style for both seek and volume, filled to --pct. */
.playerbar input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 4px; padding: 0; min-height: 0;
  border: 0; border-radius: 999px;
  background: linear-gradient(90deg, var(--neon-cyan) var(--pct, 0%), rgba(255, 255, 255, 0.16) var(--pct, 0%));
  cursor: pointer;
}
.playerbar input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 12px; border-radius: 50%;
  background: #fff; box-shadow: 0 0 10px rgba(90, 217, 255, 0.9);
}
.playerbar input[type="range"]::-moz-range-thumb {
  width: 12px; height: 12px; border: 0; border-radius: 50%;
  background: #fff; box-shadow: 0 0 10px rgba(90, 217, 255, 0.9);
}

.playerbar__right { display: flex; align-items: center; gap: 8px; }
.playerbar__vol { width: 84px; }
.playerbar__vol-icon { color: var(--text-faint); font-size: 13px; width: 16px; text-align: center; }

/* On a phone: title and transport on one row, seek under it, and the volume
   slider dropped -- the hardware buttons already do that job. */
@media (max-width: 760px) {
  .playerbar__inner {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas: "meta controls close" "seek seek seek";
    gap: 6px 10px;
  }
  .playerbar__meta { grid-area: meta; }
  .playerbar__controls { grid-area: controls; }
  .playerbar__seek { grid-area: seek; }
  .playerbar__right { grid-area: close; }
  .playerbar__vol, .playerbar__vol-icon { display: none; }
}

/* Nothing hides behind the bar. */
body:has(.playerbar) { padding-bottom: 96px; }
@media (max-width: 760px) { body:has(.playerbar) { padding-bottom: 128px; } }

/* ============================================================ vault, again */

/* Both panes scroll instead of paging: 809 artists behind 81 pages of a single
   prev/next pair was the worst interaction on the site. */
.vault__scroll {
  max-height: min(58vh, 560px);
  overflow-y: auto;
  overscroll-behavior: contain;
  border-top: 1px solid rgba(60, 60, 74, 0.5);
  margin-top: 8px;
}
.vault__scroll::-webkit-scrollbar { width: 10px; }
.vault__scroll::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: padding-box;
}
.vault__scroll::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.26);
  background-clip: padding-box;
}

.vault__head { flex-wrap: wrap; }
.vault__playall { margin-left: auto; min-height: 32px; padding: 6px 12px; font-size: 13px; }

/* A-Z jump. Letters with nobody under them stay put but dim, so the strip does
   not reflow while you filter. */
.azbar {
  /* 27 equal columns rather than flex-wrap: as a flex row the letters wrapped
     into a ragged second line whose items then stretched to fill it. */
  display: grid;
  grid-template-columns: repeat(27, minmax(0, 1fr));
  gap: 1px;
  margin: 8px 0 0;
}
.azbar__c {
  min-width: 0;
  padding: 3px 0;
  font-size: 11px;
  border: 0; border-radius: 3px;
  background: transparent; color: var(--text-dim);
  cursor: pointer;
  transition: background-color var(--t) var(--ease), color var(--t) var(--ease);
}
.azbar__c:hover:not(:disabled) { background: rgba(90, 217, 255, 0.18); color: #fff; }
.azbar__c:disabled { color: rgba(255, 255, 255, 0.14); cursor: default; }

.rowlist__icon { width: 16px; text-align: center; color: var(--text-faint); font-size: 11px; flex: 0 0 auto; }
.rowlist__row--play:hover .rowlist__icon { color: var(--neon-green); }

/* The same dancing bars, smaller, on whichever row is playing. */
.eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; }
.eq span {
  width: 2px; height: 3px; border-radius: 1px;
  background: var(--neon-green);
  animation: eq 900ms ease-in-out infinite;
}
.eq span:nth-child(2) { animation-delay: 150ms; }
.eq span:nth-child(3) { animation-delay: 300ms; }

@media (prefers-reduced-motion: reduce) {
  .playerbar__eq span, .eq span { animation: none !important; height: 10px; }
}

/* ========================================================= vault, spotified */

.vaulthead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  max-width: var(--page);
  margin: 0 auto clamp(18px, 3vw, 30px);
}
.vaulthead p { margin: 0; }
.vaulthead__search { max-width: 320px; margin: 0; }

.artistgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: clamp(10px, 1.6vw, 20px);
  max-width: var(--page);
  margin: 0 auto;
}

.artisttile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 14px 8px;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: background-color var(--t) var(--ease), transform var(--t) var(--ease),
              border-color var(--t) var(--ease);
}
.artisttile:hover {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.09);
  transform: translateY(-2px);
}
.artisttile:hover .avatar {
  box-shadow: 0 10px 26px -8px rgba(0, 0, 0, 0.85), 0 0 26px -8px rgba(90, 217, 255, 0.75);
}
.artisttile__name {
  font-size: 13px;
  text-align: center;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* -- the avatar itself ---------------------------------------------------- */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  flex: 0 0 auto;
  color: rgba(255, 255, 255, 0.92);
  font-family: Comfortaa, system-ui, sans-serif;
  font-weight: 700;
  letter-spacing: 0.02em;
  box-shadow: 0 6px 18px -8px rgba(0, 0, 0, 0.8);
  transition: box-shadow var(--t) var(--ease);
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* -- one artist ----------------------------------------------------------- */
.backlink {
  display: inline-flex; align-items: center; gap: 8px;
  background: none; border: 0; padding: 6px 0;
  color: var(--text-dim); font-size: 14px; cursor: pointer;
  transition: color var(--t) var(--ease);
}
.backlink:hover { color: #fff; }

.artisthead {
  display: flex;
  align-items: flex-end;
  gap: clamp(16px, 3vw, 32px);
  max-width: var(--page);
  margin: 8px auto clamp(20px, 3vw, 32px);
  flex-wrap: wrap;
}
/* Takes the width the avatar leaves. Without this it shrank to its own content,
   which meant the actions row had no slack in it -- and the track filter, which
   pushes itself to the far end of that row, had nothing to push against and sat
   flush against the Shuffle button. min-width: 0 so a long name can still
   ellipsis rather than forcing the row wider. */
.artisthead__text { flex: 1 1 320px; min-width: 0; }

.artisthead__kicker {
  font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-faint); margin-bottom: 4px;
}
.artisthead h1 {
  font-size: clamp(30px, 6vw, 64px);
  line-height: 1.02;
  margin: 0 0 6px;
  padding-bottom: 0;
}
/* The gradient rule under section headings would look odd under a name. */
.artisthead h1::after { display: none; }
.artisthead p { margin: 0 0 14px; }
.artisthead__play { min-width: 116px; }

/* The controls over a track list: search on the left, sort on the right, on one
   line the width of the list they act on. The search box was 340px centred over
   a list running the full 1220, which read as an island floating above
   something else rather than as a control belonging to it. */
.tracktools {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: var(--tracklist);
  margin: 0 auto 12px;
}
/* Sort with no search beside it -- a playlist, which is not searched. */
.tracktools--right { justify-content: flex-end; }
.tracksearch { flex: 1 1 auto; min-width: 0; margin: 0; display: block; }

/* Two controls, because they answer two questions: what it is ordered by, and
   which way round. The arrow has nothing to reverse under "As listed", and says
   so by being disabled rather than by quietly doing nothing. */
.tracksort { display: flex; gap: 6px; margin-left: auto; }
.tracksort__pick { white-space: nowrap; }
.tracksort__dir { padding-left: 10px; padding-right: 10px; }
.tracksort__dir:disabled { opacity: 0.4; cursor: default; }
.tracksort__dir:disabled:hover {
  background-color: rgba(255, 255, 255, 0.07) !important;
  border-color: rgba(255, 255, 255, 0.22);
  box-shadow: none;
}
/* The chosen row keeps a tick; the others keep its width so the list does not
   jump as you move down it. */
.sortmenu__item.is-on { color: var(--text); }
/* Hidden, not absent: an icon with no glyph has no width, and the labels
   would sit at two different left edges down the same menu. */
.sortmenu__item .is-blank { visibility: hidden; }

/* Filter a track list by where the audio came from.
   Deliberately plain: these are the same tracks either way, and the control
   should read as a lens over one list rather than as a set of tabs between
   two different kinds of thing. */
/* Sits at the far end of the Play/Shuffle row. margin-left:auto is what
   pushes it there -- the row is a flex line, so the filter takes all the
   slack between itself and the buttons that start it. It wraps onto its own
   line when there is no slack left, which on a phone is most of the time. */
.trackfilter { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }
.trackfilter__btn { padding: 4px 12px; font-size: 0.8rem; display: inline-flex; align-items: center; gap: 6px; }

/* How a track got here: a 3.0 share, or a 360 upload.
   Inline inside the name rather than a grid column, so the three-column .track
   layout is untouched everywhere it is used. vertical-align keeps it sitting on
   the text baseline instead of dragging the row taller. */
.trackbadge {
  /* Size comes from the width/height ATTRIBUTES, not from here: setting it in
     css would override them and silently ignore the size prop. */
  vertical-align: -0.18em;   /* scales with the text it sits in */
  margin-right: 7px;
  border-radius: 3px;
  flex: 0 0 auto;
}
.trackfilter__btn .trackbadge { margin-right: 0; vertical-align: 0; }
.track__num .trackbadge { margin-right: 0; vertical-align: -0.25em; }

/* On the artwork, bottom right.
   The cover already clips its children, so this cannot escape the tile. The
   ring and shadow are there because these two icons land on art whose colours
   are drawn from the track itself -- one of them will always be a bad
   background for one of them. */
.cover__badge {
  position: absolute;
  right: 5%;
  /* TOP right. Bottom right is where the play button lives -- a 42px circle at
     right 8px / bottom 8px on a card -- so the badge sat underneath it. The
     generated art also runs its title text along the bottom, which makes this
     the free corner twice over. */
  top: 5%;
  border-radius: 3px;   /* softens the square 3.0 tile; nothing to the round one */
  /* drop-shadow, NOT box-shadow: box-shadow traces the element's RECTANGLE, so
     a round transparent logo would wear a square halo. This follows the alpha. */
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.7)) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
  pointer-events: none;
  z-index: 2;
}
.trackfilter__btn.is-on { background: var(--brand); border-color: transparent; color: #fff; }

/* A button that has finished and become a statement. Not disabled -- disabled
   reads as "you cannot", and the truth is "you already did". */
.button.is-done {
  background: var(--positive);
  border-color: transparent;
  color: #fff;
  cursor: default;
}

/* "Move or copy to..." on a cloud song. One row per destination with both
   verbs spelled out, rather than a select plus a separate move/copy toggle:
   the question people actually have is "does my original survive", and the
   answer should be on the button they are about to press. */
.filerow__filing {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.28);
}
.filerow__filingrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.filerow__filingname { min-width: 92px; opacity: 0.85; font-size: 0.85rem; }

/* -- the track list ------------------------------------------------------- */
/* A TRACK LIST IS NOT A PAGE WIDE. At the full 1220 the name sat on the far
   left and the plays, rating and buttons on the far right, with the better
   part of nine hundred pixels of nothing in between -- a row you had to read
   across rather than take in. There is nothing to put in that gap: a vault
   track has a name, an artist you already know, and its numbers. So the gap
   goes instead, and the rows read as a list. */
.tracklist {
  list-style: none;
  /* CENTRED, like everything above it. .artisthead is a --page block with auto
     margins, so an artist page is a centred column; a list left-aligned to the
     container's own padding sat well to the left of the header on any screen
     wider than --page, which read as the list having come loose. Both are
     centred, so both keep the same centre line at every width. */
  margin: 0 auto;
  padding: 0;
  max-width: var(--tracklist);
}

/* Stands in for the rows a windowed list is not drawing, so the page keeps
   its real height and the scrollbar keeps its real size. */
.tracklist__gap { list-style: none; }
.track {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--t) var(--ease);
}
.track:hover { background: rgba(255, 255, 255, 0.06); }
.track.is-playing { background: rgba(90, 217, 255, 0.10); color: #fff; }

.track__num {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-faint); font-size: 12px;
  font-variant-numeric: tabular-nums;
}
/* Number normally, play triangle on hover -- the convention every music app
   uses, and it saves a permanent column of icons. */
.track__play { display: none; font-size: 11px; }
.track:hover .track__i { display: none; }
.track:hover .track__play { display: inline; color: var(--neon-green); }
.track.is-playing .track__i { color: var(--neon-cyan); }

.track__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.track__artist { color: var(--text-faint); font-size: 12px; white-space: nowrap; }
@media (max-width: 620px) { .track__artist { display: none; } }

/* ================================================= plays and star ratings */

.track {
  /* room for the stats column now */
  grid-template-columns: 34px minmax(0, 1fr) auto;
}
.track__stats { display: flex; align-items: center; gap: 14px; }
.track__plays {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.track__plays i { font-size: 9px; }

.stars { display: inline-flex; align-items: center; gap: 1px; }
.stars__s {
  background: none;
  border: 0;
  padding: 0 1px;
  font-size: 12px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.16);
  cursor: pointer;
  transition: color var(--t) var(--ease), transform var(--t) var(--ease);
}
.stars__s.is-on { color: rgba(255, 255, 255, 0.45); }
/* Yours is the brand amber; everyone else's average stays neutral, so the two
   readings are never confused. */
.stars.is-mine .stars__s.is-on { color: var(--amber); filter: drop-shadow(0 0 5px rgba(255, 196, 64, 0.6)); }
.stars:not(.is-readonly) .stars__s:hover { transform: scale(1.22); }
.stars:not(.is-readonly):hover .stars__s.is-on { color: var(--amber); }
.stars.is-readonly .stars__s { cursor: default; }
.stars__count {
  font-size: 11px;
  color: var(--text-faint);
  margin-left: 5px;
  font-variant-numeric: tabular-nums;
}

/* The stars are fiddly at a phone's tap size and the row is the important
   target, so they only appear where there is room to hit them. */
@media (max-width: 620px) {
  .stars { display: none; }
  .track__stats { gap: 8px; }
}

/* ========================================================= gamertag claims */

.claimline { display: flex; align-items: center; gap: 8px; font-size: 14px; margin: 0 0 12px; }
.claimline i { color: var(--neon-green); }
.claimline__you { color: var(--text-faint); }

/* Only ever seen by the twenty-odd people whose username IS their gamertag,
   so it is worth making a moment of. */
.claimbox {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 14px 16px;
  margin: 0 0 14px;
  border: 1px solid rgba(39, 248, 54, 0.35);
  border-radius: var(--radius);
  background: linear-gradient(120deg, rgba(39, 248, 54, 0.10), rgba(255, 5, 199, 0.05) 70%, transparent);
  box-shadow: 0 0 30px -14px rgba(39, 248, 54, 0.8);
}
.claimbox p { margin: 2px 0 0; font-size: 13px; }

/* ========================================================== claiming a tag */

.claimpanel {
  max-width: var(--page);
  margin: 0 auto;
  padding: clamp(16px, 2.4vw, 26px);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--radius);
  background:
    linear-gradient(120deg, rgba(39, 248, 54, 0.07), rgba(90, 217, 255, 0.04) 55%, transparent),
    rgba(0, 0, 0, 0.32);
}
.claimpanel h3 { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.claimpanel h3 i { color: var(--neon-green); }
.claimpanel.is-done { background: rgba(0, 0, 0, 0.3); }
.claimpanel p { max-width: 70ch; }

.claimpanel__row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.claimpanel__row .input { max-width: 260px; margin: 0; }

/* The code is the whole point of the panel, so it is the loudest thing in it. */
.claimcode {
  display: flex; align-items: center; gap: 12px;
  margin: 14px 0;
  padding: 12px 16px;
  border: 1px solid rgba(39, 248, 54, 0.4);
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.45);
  width: fit-content;
}
.claimcode code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: clamp(20px, 3vw, 28px);
  letter-spacing: 0.14em;
  color: var(--neon-green);
  text-shadow: 0 0 18px rgba(39, 248, 54, 0.55);
}

.claimsteps { margin: 0 0 12px; padding-left: 20px; color: var(--text-dim); font-size: 14px; }
.claimsteps li { margin-bottom: 5px; }

.claimpanel__hint  { font-size: 13px; color: var(--text-faint); margin-top: 12px; }
.claimpanel__error { font-size: 14px; color: #ff9a9a; margin-top: 12px; }
.claimpanel__warn  {
  font-size: 13px; color: var(--amber);
  display: flex; align-items: flex-start; gap: 8px; margin-top: 12px;
}

/* ======================================================= profile and vault */

/* A claimed artist is shown under their ezmuze name; the tick says the person
   behind it proved the gamertag is theirs. */
.artisttile { position: relative; }
.artisttile__claimed {
  position: absolute;
  top: 12px; right: 14px;
  font-size: 12px;
  color: var(--neon-green);
  filter: drop-shadow(0 0 6px rgba(39, 248, 54, 0.8));
}

.artisthead__gamertag {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--text-faint);
  margin: 0 0 10px;
}
.artisthead__gamertag i { color: var(--neon-green); }

.artisthead__bio {
  max-width: 70ch;
  margin: 0 0 12px;
  color: var(--text-dim);
  white-space: pre-wrap;      /* keep the line breaks people type */
}

.claimline__hint { color: var(--text-faint); font-size: 13px; }
.claimline__hint i { color: var(--text-faint); }

/* ------------------------------------------------------------ edit panel */

.editprofile {
  max-width: var(--page);
  margin: 0 auto 16px;
  padding: clamp(16px, 2.4vw, 24px);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.32);
}
.editprofile h3 { margin-bottom: 14px; }

.editprofile__pic {
  display: flex; align-items: center; gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.editprofile__hint { font-size: 12px; color: var(--text-faint); margin: 8px 0 0; }
.editprofile__error { font-size: 14px; color: #ff9a9a; margin-top: 12px; }

.editprofile__bio { display: block; position: relative; margin-bottom: 14px; }
.editprofile__bio > span:first-child {
  display: block; font-size: 12px; opacity: 0.75; margin-bottom: 4px;
}
.editprofile__bio textarea {
  width: 100%; min-height: 96px; resize: vertical;
  font-family: inherit; font-size: 14px; line-height: 1.5;
}
.editprofile__count {
  position: absolute; right: 10px; bottom: 8px;
  font-size: 11px; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* ================================================== shared cloud songs */

.artisttile__meta { font-size: 11px; color: var(--text-faint); }

.sharedlist { max-width: var(--page); margin: clamp(20px, 3vw, 32px) auto 0; }
.sharedlist h3 { margin-bottom: 4px; }
.sharedlist__note { font-size: 13px; color: var(--text-faint); margin: 0 0 10px; max-width: 70ch; }

/* A project, not a recording: no hover-to-play, no pointer, and the row says
   what it is rather than offering a play button that would do nothing. */
.track.is-notplayable { cursor: default; opacity: 0.82; }
.track.is-notplayable:hover { background: transparent; }
.track.is-notplayable .track__num i { color: var(--neon-violet); font-size: 12px; }
.track.is-notplayable .track__plays {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  padding: 2px 8px;
}

/* ============================================== profile tabs and file lists */

.tabs {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  max-width: var(--page);
  margin: clamp(16px, 2.5vw, 28px) auto 0;
  padding-bottom: 2px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}

.tabs__tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 14px;
  padding: 10px 16px;
  cursor: pointer;
  white-space: nowrap;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
.tabs__tab i { opacity: 0.75; margin-right: 4px; }
.tabs__tab:hover { color: var(--text); background: rgba(255, 255, 255, 0.04); }
.tabs__tab.is-active {
  color: var(--text);
  border-bottom-color: var(--neon-green);
}
.tabs__tab.is-active i { opacity: 1; color: var(--neon-green); }

.tabs__panel { max-width: var(--page); margin: 0 auto; }

.profilesection { margin: clamp(20px, 3vw, 32px) 0; }
.profilesection > h3 { margin-bottom: 6px; }

.productgrid { display: flex; flex-wrap: wrap; gap: 16px; }

/* ------------------------------------------------------------- file lists */

.cloudgroup { margin-bottom: clamp(24px, 3.5vw, 40px); }
.cloudgroup > h3 { margin-bottom: 4px; }
.cloudgroup__count {
  font-size: 12px;
  color: var(--text-faint);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  padding: 1px 8px;
  margin-left: 6px;
  vertical-align: middle;
}
.cloudgroup__note {
  font-size: 13px;
  color: var(--text-faint);
  margin: 0 0 12px;
  max-width: 76ch;
}

.filelist { list-style: none; margin: 0; padding: 0; }

.filerow {
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.filerow:hover { background: rgba(255, 255, 255, 0.03); }

.filerow__main {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  min-height: 44px;
}

.filerow__play {
  flex: none;
  width: 30px; height: 30px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  cursor: pointer;
  font-size: 11px;
  display: grid; place-items: center;
}
.filerow__play:hover { background: var(--neon-green); color: #04140a; border-color: transparent; }

.filerow__name {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0;
}
.filerow__name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filerow__name > i { font-size: 10px; color: var(--text-faint); flex: none; width: 10px; }
.filerow__name--static { cursor: default; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filerow__icon { color: var(--neon-violet); font-size: 12px; margin-right: 2px; }

.filerow__rename { flex: 1 1 auto; min-width: 0; font-size: 14px; padding: 4px 8px; }

/* -- the cloud browser: folders and songs in one listing ------------------ */

/* Up, and the folders. Same row height as a song even though there is no
   40px cover in them, so the list does not jolt as you scroll past. */
.filerow--nav .filerow__folder {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  width: 100%;
  min-height: 58px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 12px;
}
.filerow--nav .filerow__folder:hover { background: rgba(255, 255, 255, 0.06); }
.filerow__foldericon {
  width: 40px;             /* the width a cover would take, so names line up */
  text-align: center;
  font-size: 18px;
  color: var(--blue);
  flex: none;
}
.filerow__foldercount { margin-left: auto; color: var(--text-faint); font-size: 0.85rem; }

/* The ... menu. Anchored to the row rather than floated: there is one open at
   a time and it belongs to a specific song, so it should move with it. */
.filerow__actions { position: relative; }
.filerow__more.is-on { background: var(--neutral); }
.filemenu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  padding: 6px;
  min-width: 190px;
  /* --raised, the token for something sitting on top of a panel. A menu over
     a translucent panel has to be OPAQUE: the row underneath would otherwise
     read through the item you are trying to click. */
  background: var(--raised);
  border: 1px solid var(--border);
  box-shadow: 0 10px 30px -8px rgba(0, 0, 0, 0.8);
}
.filemenu .button { justify-content: flex-start; text-align: left; }

/* Said once, then gone. Sits at the top of the panel rather than floating over
   the corner of the window: the thing it is talking about is right here, and a
   message that travels away from its cause has to work harder to be understood. */
.cloudtoast {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  padding: 10px 12px;
  background: rgba(58, 108, 74, 0.28);
  border-left: 3px solid var(--positive);
  color: var(--text);
  font-size: 0.9rem;
}

/* Whether a shared song has audio yet. */
.filerow__rendered  { color: var(--green); }
.filerow__rendering { color: var(--blue); }
.filerow__waiting   { color: var(--text-faint); }

.filerow__filingnote { color: var(--text-faint); font-size: 0.8rem; }

.filerow__facts {
  display: flex;
  gap: 10px;
  flex: none;
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.filerow__actions { display: flex; gap: 6px; flex: none; flex-wrap: wrap; }

.btn--small { font-size: 12px; padding: 5px 10px; }
.btn--danger {
  border-color: rgba(255, 120, 120, 0.5);
  color: #ffb3b3;
  background: rgba(255, 70, 70, 0.10);
}
.btn--danger:hover { background: rgba(255, 70, 70, 0.22); color: #fff; }

.studiolink i { font-size: 10px; }

/* ----------------------------------------------------------- the expansion */

.filerow__detail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px 24px;
  margin: 0;
  padding: 4px 10px 16px 32px;
}
.filerow__detail > div { min-width: 0; }
.filerow__detail dt {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 2px;
}
.filerow__detail dd { margin: 0; font-size: 13px; color: var(--text-dim); }
.filerow__detail .filerow__uid { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
.filerow__blurb { grid-column: 1 / -1; }
.filerow__blurb dd { white-space: pre-wrap; }

@media (max-width: 720px) {
  /* The actions wrap under the name rather than squeezing it to nothing. */
  .filerow__main { flex-wrap: wrap; row-gap: 8px; }
  .filerow__name, .filerow__name--static, .filerow__rename { flex: 1 1 100%; }
  .filerow__actions { flex: 1 1 100%; }
  .filerow__detail { padding-left: 10px; }
  .tabs { gap: 0; }
  .tabs__tab { padding: 10px 12px; font-size: 13px; }
}

.filerow__formats { grid-column: 1 / -1; }
.filerow__formats dd { display: flex; flex-wrap: wrap; gap: 5px; }
.chip {
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text-dim);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
}

/* --------------------------------------------------- which studio to open */

.buildpick { border: 0; margin: 0 0 16px; padding: 0; }
.buildpick legend { font-size: 12px; opacity: 0.75; padding: 0; margin-bottom: 4px; }
.buildpick .editprofile__hint { margin: 0 0 8px; }

.buildpick__opt {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 12px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--radius);
  margin-bottom: 6px;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}
.buildpick__opt:hover { background: rgba(255, 255, 255, 0.04); }
.buildpick__opt.is-on { border-color: var(--neon-green); background: rgba(39, 248, 54, 0.06); }
.buildpick__opt input { accent-color: var(--neon-green); }
.buildpick__opt span { font-size: 14px; }
.buildpick__opt small { font-size: 12px; color: var(--text-faint); margin-left: auto; }

/* The full product description, rendered from the `=` / `==` / `*` markup it
   is stored in -- see PitchText.tsx. Real block elements now, so no pre-wrap:
   the parser decides where the paragraphs are. */
.pitchlong {
  max-width: 68ch;
  color: var(--text-dim);
  margin-top: 14px;
}

.pitchlong p { margin: 0 0 14px; line-height: 1.6; }

/* The tagline the copy opens with. Sized up and un-dimmed, because it is the
   sentence the rest of the description is an argument for. */
.pitchlong__lead {
  font-size: clamp(15px, 1.5vw, 17px);
  color: var(--text);
  margin-bottom: 18px;
}

.pitchlong__head {
  font-size: 15px;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  color: var(--text);
  margin: 26px 0 10px;
}
.pitchlong__head::before { content: '// '; color: var(--neon-green); }

.pitchlong__list { list-style: none; margin: 0 0 14px; padding: 0; }
.pitchlong__list li {
  position: relative;
  padding-left: 20px;
  line-height: 1.6;
  margin-bottom: 4px;
}
.pitchlong__list li::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 0.62em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--neon-green);
}

/* ========================================================== a product page */

.pdp {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: start;
  max-width: var(--page);
  margin: clamp(20px, 3vw, 36px) auto 0;
}

.pdp__art { flex: none; }

.pdp__body { min-width: 0; }

.pdp__kicker {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 6px;
}
.pdp__kicker i { color: var(--neon-green); margin-right: 5px; }

.pdp__body h1 { margin: 0 0 6px; line-height: 1.05; }

.pdp__by { font-size: 14px; color: var(--text-dim); margin-bottom: 16px; }
.pdp__by a { cursor: pointer; }

.pdp__pitch {
  font-size: clamp(15px, 1.5vw, 17px);
  line-height: 1.55;
  color: var(--text);
  max-width: 62ch;
  margin: 0 0 20px;
}

.pdp__retired { margin-bottom: 20px; }
.pdp__retired p { font-size: 14px; color: var(--text-faint); max-width: 62ch; }

.pdp__demo { display: block; width: min(100%, 420px); margin-bottom: 20px; }

.pdp__nolicence { color: var(--text-faint); font-size: 14px; }

.pdp__long { max-width: var(--page); margin: clamp(22px, 3vw, 34px) auto 0; }

.pdp__related { max-width: var(--page); margin: clamp(26px, 3.5vw, 44px) auto 0; }
.pdp__related h3 { margin-bottom: 12px; }

.pdp__relatedgrid { display: flex; flex-wrap: wrap; gap: 18px; }

.reltile {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  width: 116px;
  color: var(--text-dim);
  font: inherit;
  font-size: 12px;
  text-align: center;
  transition: transform 140ms ease, color 140ms ease;
}
.reltile:hover { transform: translateY(-3px); color: var(--text); }
.reltile span { display: block; margin-top: 8px; line-height: 1.3; }

/* ----------------------------------------------------------- the licences */

.pdp__licences { display: flex; flex-direction: column; gap: 10px; max-width: 620px; }

/* The purple table this replaces was a 43px bar with the name in one cell and
   the price floated to the far right of the other, which on a wide screen put
   the two halves of one decision a third of a metre apart. */
.licence {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  padding: 14px 16px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.28);
  transition: border-color 140ms ease, background 140ms ease;
}
.licence:hover { border-color: rgba(255, 255, 255, 0.20); background: rgba(0, 0, 0, 0.38); }

.licence.is-owned {
  border-color: rgba(39, 248, 54, 0.38);
  background: rgba(39, 248, 54, 0.06);
}

.licence__what { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.licence__name { font-size: 15px; color: var(--text); }
.licence__terms { font-size: 12px; color: var(--text-faint); }

.licence__act { flex: none; }
.licence__act .button { min-width: 116px; justify-content: center; }

.licence__owned {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  color: var(--neon-green);
  padding: 0 10px;
}

.licence__said {
  grid-column: 1 / -1;
  margin: 2px 0 0;
  font-size: 13px;
  color: #ff9a9a;
}
.licence__said.is-good { color: var(--neon-green); }

@media (max-width: 780px) {
  /* The box stops being a column and becomes a header: at this width a 260px
     box beside anything leaves the text too narrow to read. */
  .pdp { grid-template-columns: 1fr; }
  .pdp__art { justify-self: center; }
  .licence { grid-template-columns: 1fr; gap: 10px; }
  .licence__act .button { width: 100%; }
}

/* ============================================ sigma: balance, top up, ledger */

/* The balance where a price is being read, rather than only in the nav corner. */
.sigmabar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 14px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.28);
  font-size: 13px;
  color: var(--text-faint);
}
.sigmabar--compact { padding: 7px 12px; font-size: 12px; }

.sigmabar__amount {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 15px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.sigmabar__amount i { color: var(--neon-green); font-size: 12px; }
.sigmabar__text { flex: 1 1 auto; min-width: 0; }
.sigmabar .button { flex: none; }

/* Not alarming, just honest: you cannot afford the thing you are looking at. */
.sigmabar.is-short { border-color: rgba(255, 190, 90, 0.34); background: rgba(255, 170, 60, 0.07); }
.sigmabar.is-short .sigmabar__text { color: #ffcf94; }

/* The store toolbar puts it on its own line under the categories. */
.storebar .sigmabar { flex: 1 1 240px; max-width: 340px; }

/* ---------------------------------------------------------------- the page */

.sigmahead { max-width: var(--page); margin: clamp(18px, 3vw, 30px) auto 0; }
.sigmahead__text p { max-width: 62ch; color: var(--text-dim); }

.sigmahead__amount {
  font-family: Comfortaa, 'Trebuchet MS', Verdana, sans-serif;
  font-weight: 700;
  font-size: clamp(44px, 8vw, 84px);
  line-height: 1;
  margin: 2px 0 10px;
  background: var(--grad-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-variant-numeric: tabular-nums;
}

.sigmahead__totals {
  display: flex;
  gap: 22px;
  flex-wrap: wrap;
  margin-top: 14px;
  font-size: 13px;
  color: var(--text-faint);
}
.sigmahead__totals strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ top up */

.topup { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }

.topup__amounts { display: flex; flex-wrap: wrap; gap: 8px; }

.topup__amount {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 82px;
  padding: 10px 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.28);
  color: var(--text-dim);
  font: inherit;
  cursor: pointer;
  transition: border-color 130ms ease, background 130ms ease, transform 130ms ease;
}
.topup__amount:hover { transform: translateY(-2px); border-color: rgba(255, 255, 255, 0.24); }
.topup__amount.is-on {
  border-color: var(--neon-green);
  background: rgba(39, 248, 54, 0.08);
  color: var(--text);
}
.topup__sigma { font-size: 17px; font-weight: 700; }
.topup__usd { font-size: 11px; color: var(--text-faint); }

.topup__go { min-height: 44px; }

/* ------------------------------------------------------------------ ledger */

.ledger__dir {
  flex: none;
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 10px;
}
.ledger__dir.is-in  { background: rgba(39, 248, 54, 0.14); color: var(--neon-green); }
.ledger__dir.is-out { background: rgba(255, 255, 255, 0.07); color: var(--text-faint); }

/* align-items:flex-start is doing real work: .filerow__name sets center for
   its normal single-line use, and once this becomes a COLUMN that centres the
   text horizontally instead of vertically. */
.ledger__what { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.ledger__what > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ledger__who { font-size: 11px; color: var(--text-faint); }

.ledger__amount {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  min-width: 84px;
  text-align: right;
}
.ledger__amount.is-in  { color: var(--neon-green); }
.ledger__amount.is-out { color: var(--text-dim); }

@media (max-width: 720px) {
  .ledger__amount { min-width: 0; }
  .topup { flex-direction: column; align-items: stretch; }
  .topup__go { width: 100%; }
}

/* Under the store toolbar, and the same width as it, so the two read as one
   block rather than a bar with a smaller bar tucked under its left corner.
   Full width without right-aligning the contents WOULD put the balance and the
   button at opposite ends of a 1400px screen -- the mistake the old licence row
   made -- so the group sits together at the right, under the sort control it
   belongs beside. */
.storebalance { margin-top: 12px; }
.storebalance .sigmabar { justify-content: flex-end; }
.storebalance .sigmabar__text { flex: 0 0 auto; }

/* ------------------------------------------------------------ supporter badge */

.supbadge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  vertical-align: middle;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #ffb3e6;
  border: 1px solid rgba(255, 5, 199, 0.38);
  background: rgba(255, 5, 199, 0.10);
  border-radius: 999px;
  padding: 3px 10px;
  white-space: nowrap;
}
.supbadge i { font-size: 10px; color: var(--neon-magenta); }
.supbadge__word { line-height: 1; }

/* On an avatar it is a pip in the corner, opposite the claimed tick so the two
   never sit on top of each other. */
.supbadge--avatar {
  position: absolute;
  right: -2px; bottom: -2px;
  padding: 0;
  width: 22px; height: 22px;
  border-radius: 50%;
  justify-content: center;
  background: #17040f;
  border-color: rgba(255, 5, 199, 0.55);
  box-shadow: 0 0 10px rgba(255, 5, 199, 0.55);
}
.supbadge--avatar i { font-size: 9px; }

/* The avatar has to be a positioning context for the pip above -- it was static,
   so the badge would otherwise hang off whichever ancestor happened to be
   positioned, which is the page in most cases.
   
   And it must NOT clip: .avatar is overflow:hidden so a rectangular photo comes
   out circular, which also swallowed a pip sitting on the rim. The clipping
   moves onto the image and the initials, which are the only things that need
   it, leaving the badge free to overhang the edge the way it should. */
.avatar { position: relative; overflow: visible; }
.avatar img, .avatar__initials { border-radius: 50%; }
.avatar__initials { width: 100%; height: 100%; display: grid; place-items: center; }

/* ============================================================ the header bar */

/* THE BAR WAS FULL-BLEED WHILE EVERYTHING ELSE IS IN A var(--page) COLUMN, so
   the wordmark sat hard against the left edge of the screen and the links
   against the right, neither lining up with a single thing below them. The
   painted surface stays full width -- it should -- and its CONTENTS move into
   the same column as the page. */
.top {
  background: var(--header-a);
  -webkit-backdrop-filter: blur(18px) saturate(120%);
  backdrop-filter: blur(18px) saturate(120%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.top::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 1px;
  background: var(--grad-brand);
  opacity: 0.5;
}

.top .bar {
  display: flex;
  align-items: center;
  gap: 2px;
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 4px;
  background: transparent !important;
  border-bottom: 0;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.top .bar::after { display: none; }

/* The legacy layout floated these; flex wants them not to. */
.top .bar .bar-item-black,
.top .bar .bar-item.button { float: none; }
.top .bar .right {
  float: none;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 2px;
}

/* -- the wordmark --------------------------------------------------------- */

.bar .brand {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  font-family: Comfortaa, system-ui, sans-serif;
  font-weight: 700;
  font-size: 19px;
  /* The old 0.34em tracking was set for the single word "ezmuze"; two words at
     that spacing read as two unrelated things rather than one name. */
  letter-spacing: 0.10em;
  padding: 8px 10px;
}
.brand__ezmuze { color: var(--text); }
.brand__central {
  color: var(--neon-violet);
  text-shadow: 0 0 14px rgba(160, 107, 255, 0.45);
}

@media (max-width: 520px) {
  .bar .brand { font-size: 16px; letter-spacing: 0.06em; gap: 6px; }
}

/* ------------------------------------------------------------ sort dropdown */

.sortmenu { position: relative; }
.sortmenu__button { display: inline-flex; align-items: center; gap: 8px; }
.sortmenu__caret { font-size: 10px; opacity: 0.7; }

.sortmenu__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 30;
  min-width: 190px;
  padding: 6px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius);
  background: #14151a;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.6);
}

.sortmenu__item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.sortmenu__item:hover { background: rgba(255, 255, 255, 0.06); color: var(--text); }
.sortmenu__item.is-on { color: var(--text); }
.sortmenu__item i { font-size: 11px; width: 12px; color: var(--neon-green); }
/* Reserves the tick's width so the labels do not shuffle sideways when the
   selection moves. */
.sortmenu__item i.is-hidden { visibility: hidden; }

.sortmenu__rule { height: 1px; margin: 5px 4px; background: rgba(255, 255, 255, 0.10); }

/* ================================================================= footer */

.sitefoot {
  position: relative;
  z-index: 1;
  max-width: var(--page);
  margin: clamp(40px, 6vw, 80px) auto 0;
  padding: clamp(24px, 3vw, 36px) 16px clamp(20px, 3vw, 28px);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--text-faint);
  font-size: 13px;
}

.sitefoot__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 2fr);
  gap: clamp(20px, 4vw, 56px);
  align-items: start;
}

.sitefoot__brand .brand__ezmuze,
.sitefoot__brand .brand__central {
  font-family: Comfortaa, system-ui, sans-serif;
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 0.08em;
}
.sitefoot__brand p { margin: 10px 0 0; max-width: 46ch; line-height: 1.6; }

.sitefoot__links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 20px;
}
.sitefoot__links h4 {
  margin: 0 0 8px;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.sitefoot__links a {
  display: block;
  padding: 3px 0;
  color: var(--text-faint);
  transition: color 120ms ease;
}
.sitefoot__links a:hover { color: var(--text); }

.sitefoot__copy { margin: 14px 0 0; font-size: 12px; color: var(--text-faint); }

@media (max-width: 720px) {
  .sitefoot__inner { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------- legal pages */

.legal { max-width: 74ch; margin: clamp(20px, 3vw, 34px) auto 0; }
.legal h1 { margin-bottom: 4px; }
.legal h3 { margin: clamp(22px, 3vw, 32px) 0 8px; }
.legal p, .legal li { line-height: 1.7; color: var(--text-dim); }
.legal ul { padding-left: 20px; }
.legal li { margin-bottom: 8px; }
.legal strong { color: var(--text); }

.legal__updated { font-size: 12px; color: var(--text-faint); margin-top: 0; }
.legal__lede { font-size: 16px; color: var(--text); }
.legal__seealso { margin-top: 26px; }
.legal__foot {
  margin-top: clamp(26px, 4vw, 40px);
  padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 13px;
  color: var(--text-faint);
}

/* ====================================================== the vault front page */

.vhero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: clamp(20px, 4vw, 44px);
  align-items: center;
  max-width: var(--page);
  margin: clamp(18px, 3vw, 32px) auto clamp(26px, 4vw, 44px);
  padding: clamp(20px, 3vw, 34px);
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, 0.08);
  /* The brand gradient at low opacity, so the hero reads as the loudest thing
     on the page without becoming a colour field. */
  background:
    radial-gradient(120% 140% at 0% 0%, rgba(160, 107, 255, 0.20), transparent 60%),
    radial-gradient(120% 140% at 100% 100%, rgba(39, 248, 54, 0.12), transparent 60%),
    rgba(0, 0, 0, 0.34);
}

.vhero__text { min-width: 0; }
.vhero__text h1 { margin: 2px 0 6px; line-height: 1.03; }
.vhero__facts { font-size: 13px; color: var(--text-dim); margin: 0 0 16px; }
.vhero__facts i { color: var(--neon-green); }

.vhero__tracks {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: grid;
  gap: 2px;
  max-width: 520px;
}
.vhero__tracks button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.vhero__tracks button:hover { background: rgba(255, 255, 255, 0.06); color: var(--text); }
.vhero__tracks i { font-size: 9px; color: var(--neon-green); }

.vhero--playlist { align-items: center; }

/* ---------------------------------------------------------------- sections */

.vsection { max-width: var(--page); margin: 0 auto clamp(28px, 4vw, 46px); }

.vsection__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}
.vsection__head h2 { margin: 0; }
.vsection__note { margin: 4px 0 0; font-size: 13px; color: var(--text-faint); max-width: 68ch; }

/* ------------------------------------------------------------------ charts */

.chart { list-style: none; margin: 0; padding: 0; }

.chartrow {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 10px;
  border-radius: var(--radius);
}
.chartrow:hover { background: rgba(255, 255, 255, 0.04); }
.chartrow.is-playing { background: rgba(39, 248, 54, 0.07); }

.chartrow__rank {
  flex: none;
  width: 22px;
  text-align: right;
  font-size: 13px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.chartrow__play {
  flex: none;
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-size: 11px;
  display: grid; place-items: center;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.chartrow__play:hover { background: var(--neon-green); color: #04140a; border-color: transparent; }

.chartrow__what { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-width: 0; }
.chartrow__name { font-size: 14px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chartrow__by { font-size: 12px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chartrow__by:hover { color: var(--text); }

.chartrow__facts {
  flex: none;
  display: flex;
  gap: 12px;
  font-size: 11px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.chartrow__facts i { font-size: 9px; margin-right: 3px; }

.chartrow__acts { flex: none; display: flex; gap: 4px; }

/* -------------------------------------------------------- the track buttons */

.trackbtn {
  width: 30px; height: 30px;
  border-radius: 50%;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-faint);
  font-size: 12px;
  display: grid; place-items: center;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.trackbtn:hover { background: rgba(255, 255, 255, 0.09); color: var(--text); }
.trackbtn--small { width: 26px; height: 26px; font-size: 11px; }
.trackbtn__said { font-size: 11px; color: var(--neon-green); margin-left: 4px; }

.sharebox { position: relative; display: inline-flex; align-items: center; }

/* A menu rendered into <body> and placed from its button's rect, so nothing
   between the two can clip it. Fixed, not absolute: it is no longer inside
   the thing it belongs to, and the coordinates it is given are the
   viewport's. See components/Popover.tsx. */
.popover {
  position: fixed;
  z-index: 400;
}

/* Reuses the sort menu's items, which is the same thing in a different place. */
.sharemenu {
  min-width: 210px;
  padding: 6px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius);
  background: #14151a;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.6);
}
.sharemenu a.sortmenu__item { text-decoration: none; }
.sharemenu__note { padding: 9px 10px; font-size: 13px; color: var(--text-faint); }
.sharemenu__count { margin-left: auto; font-size: 11px; color: var(--text-faint); }
.sharemenu__new { display: flex; gap: 6px; padding: 6px; }
.sharemenu__new .input { flex: 1 1 auto; min-width: 0; font-size: 13px; padding: 6px 8px; }

/* The row is a click target for playing; the buttons on it are not. */
.track__acts { display: inline-flex; gap: 2px; margin-right: 6px; }

/* --------------------------------------------------------------- playlists */

.plgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 16px;
}

/* No padded box round the art any more: the cover reaches the tile's edges,
   the way a cover does. The frame and the padding were doing the work of
   making a violet icon look deliberate, and there is a picture there now. */
.pltile {
  display: flex;
  flex-direction: column;
  color: var(--text-dim);
  transition: transform 140ms ease;
}
.pltile:hover { transform: translateY(-3px); }
.pltile__link { display: flex; flex-direction: column; gap: 4px; color: inherit; }
.pltile__link:hover { color: inherit; }
/* Outside the link, so rating one does not navigate to it. */
.pltile__rate { display: flex; margin-top: 4px; min-height: 18px; }

/* THE MOSAIC. Nine covers, edge to edge, no gap and no border between them --
   the tiles are the picture, and a line through it would read as nine small
   covers rather than one made of nine. line-height: 0 because the cells are
   inline-level and would otherwise sit on a text baseline with a gap under
   each row that no amount of gap: 0 would remove. */
.plart {
  display: grid;
  grid-template-columns: repeat(var(--plart-cols, 3), 1fr);
  gap: 0;
  width: 100%;
  aspect-ratio: 1 / 1;
  line-height: 0;
  overflow: hidden;
  border-radius: var(--radius);
  box-shadow: 0 10px 26px -12px rgba(0, 0, 0, 0.9);
}
.plart .cover { border-radius: 0; }
.plart--big { width: 190px; }

/* An empty playlist really has nothing to show, so it keeps the old icon. */
.plart--none {
  display: grid;
  grid-template-columns: 1fr;
  place-items: center;
  font-size: 26px;
  color: #0b0c10;
  background: linear-gradient(135deg, var(--neon-violet), var(--neon-magenta));
}

.pltile__name { font-size: 14px; color: var(--text); }
.pltile__count { font-size: 11px; color: var(--text-faint); }

/* A name, not a link that announces itself. The underline and the blue would
   fight a 64px heading; it is plainly clickable from being a name at that
   size, and the hover says the rest. */
/* The artist's name inside one of their records: the way back to everything
   they made. A button because it changes a query parameter rather than going
   anywhere new, and styled as the heading it is -- the underline and the blue
   would fight type this size, and it is plainly clickable from being a name at
   the top of a page you have gone one level into. */
.artisthead__up {
  appearance: none; border: 0; background: transparent; padding: 0;
  font: inherit; color: inherit; cursor: pointer; text-align: left;
}
.artisthead__up:hover { color: var(--brand-soft); }

.vhero__who { color: inherit; text-decoration: none; }
.vhero__who:hover { color: var(--brand-soft); }

.vhero__rate { display: flex; align-items: center; gap: 10px; margin: 2px 0 12px;
               font-size: 13px; color: var(--text-faint); }

.plnew { display: flex; gap: 10px; margin: 16px 0 24px; max-width: 480px; }
.plnew .input { flex: 1 1 auto; min-width: 0; }
.plname { font-size: 28px; padding: 4px 10px; max-width: 460px; }

@media (max-width: 760px) {
  .vhero { grid-template-columns: 1fr; justify-items: start; }
  .chartrow__facts { display: none; }
  .vsection__head { flex-direction: column; align-items: flex-start; }
}

/* ================================================================== shelves */

.shelf { max-width: var(--page); margin: 0 auto clamp(26px, 3.5vw, 42px); }

.shelf__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}
.shelf__title h2 { margin: 0; }
.shelf__note { margin: 4px 0 0; font-size: 13px; color: var(--text-faint); max-width: 70ch; }

.shelf__controls { display: flex; align-items: center; gap: 6px; flex: none; }

.shelf__arrow {
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(0, 0, 0, 0.4);
  color: var(--text-dim);
  font-size: 11px;
  display: grid; place-items: center;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, opacity 120ms ease;
}
.shelf__arrow:hover:not(:disabled) { background: rgba(255, 255, 255, 0.12); color: var(--text); }
/* Hidden rather than greyed at the ends: an arrow pointing at nothing is
   furniture, and on a touch screen it is never used anyway. */
.shelf__arrow:disabled { opacity: 0; pointer-events: none; }

.shelf__rail {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding: 4px 4px 10px;
  margin: 0 -4px;
  /* The scrollbar is noise under a row of covers; the arrows and the drag do
     the job. Still scrollable by every input. */
  scrollbar-width: none;
}
.shelf__rail::-webkit-scrollbar { display: none; }
.shelf__rail > * { scroll-snap-align: start; }

/* --------------------------------------------------------------- a track */

.tcard {
  flex: 0 0 auto;
  width: 168px;
  display: flex;
  flex-direction: column;
  color: var(--text-dim);
}

.tcard__art {
  position: relative;
  width: 168px;
  height: 168px;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 10px 26px -12px rgba(0, 0, 0, 0.9);
}
.tcard__art .cover { display: block; width: 100%; height: 100%; }

/* The play button rides in on hover, the way a cover grid always has. It is
   always present for keyboard and touch -- only its opacity moves. */
.tcard__play {
  position: absolute;
  right: 8px; bottom: 8px;
  width: 42px; height: 42px;
  border-radius: 50%;
  border: 0;
  background: var(--neon-green);
  color: #04140a;
  font-size: 14px;
  display: grid; place-items: center;
  cursor: pointer;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 140ms ease, transform 140ms ease, background 140ms ease;
  box-shadow: 0 8px 20px -6px rgba(0, 0, 0, 0.8);
}
.tcard:hover .tcard__play,
.tcard:focus-within .tcard__play,
.tcard.is-playing .tcard__play { opacity: 1; transform: none; }
.tcard__play:hover { background: #4bff58; }

.tcard__acts {
  position: absolute;
  left: 6px; top: 6px;
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity 140ms ease;
}
.tcard:hover .tcard__acts, .tcard:focus-within .tcard__acts { opacity: 1; }
.tcard__acts .trackbtn {
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  backdrop-filter: blur(6px);
}
.tcard__acts .trackbtn:hover { background: rgba(0, 0, 0, 0.8); }

.tcard.is-playing .tcard__art { outline: 2px solid var(--neon-green); outline-offset: -2px; }

.tcard__name {
  margin: 10px 0 2px;
  font-size: 14px;
  font-weight: 400;
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tcard__by {
  font-size: 12px;
  color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tcard__by:hover { color: var(--text); }
.tcard__facts { margin-top: 4px; display: flex; gap: 10px; font-size: 11px; color: var(--text-faint); }
.tcard__facts i { font-size: 9px; margin-right: 3px; }

/* While the fetch is out, so the page has its shape before it has its data. */
.tcard--ghost {
  height: 168px;
  border-radius: var(--radius);
  background: linear-gradient(100deg, rgba(255,255,255,0.04), rgba(255,255,255,0.08), rgba(255,255,255,0.04));
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { from { background-position: 200% 0 } to { background-position: -200% 0 } }

/* -------------------------------------------------------------- an artist */

.acard {
  flex: 0 0 auto;
  width: 148px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  color: var(--text-dim);
  transition: transform 140ms ease;
}
.acard:hover { transform: translateY(-4px); }
.acard .avatar { box-shadow: 0 10px 26px -12px rgba(0, 0, 0, 0.9); }
.acard__name {
  margin: 12px 0 2px;
  font-size: 14px;
  font-weight: 400;
  color: var(--text);
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acard__count { font-size: 12px; color: var(--text-faint); }

/* ----------------------------------------------------------------- hero */

.vhero__covers { display: flex; gap: 8px; align-self: center; }
.vhero__covers button {
  border: 0; padding: 0; background: none; cursor: pointer; line-height: 0;
  border-radius: 6px; overflow: hidden;
  box-shadow: 0 8px 20px -10px rgba(0, 0, 0, 0.9);
  transition: transform 140ms ease;
}
.vhero__covers button:hover { transform: translateY(-3px); }

.vaultfoot {
  max-width: var(--page);
  margin: 0 auto;
  font-size: 13px;
  color: var(--text-faint);
}

@media (max-width: 900px) {
  .vhero__covers { display: none; }
}
@media (max-width: 620px) {
  .tcard, .tcard__art { width: 140px; }
  .tcard__art { height: 140px; }
  .acard { width: 124px; }
  /* Arrows are a pointer affordance; a thumb just swipes. */
  .shelf__arrow { display: none; }
}

@media (max-width: 620px) {
  /* Avatar sizes are inline (the component takes a px size), so overriding them
     for a narrow screen needs !important. A 168px hero portrait on a 390px
     phone was half the viewport before a single track was visible. */
  .vhero .avatar { width: 104px !important; height: 104px !important; font-size: 34px !important; }
  .vhero { padding: 18px; gap: 16px; }
  .vhero__text h1 { font-size: 30px; }
}

/* ================================================================== covers */

.cover {
  position: relative;
  flex: none;
  border-radius: inherit;
  overflow: hidden;
  line-height: 0;
}
/* Sized by its container rather than by the size prop -- the playlist
   mosaic's cells, which are a third of a box only CSS knows the width of. */
.cover--fill { width: 100%; height: 100%; }

.cover__img, .cover__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
/* An uploaded cover may not be exactly square if it came from somewhere other
   than our own cropper; cover rather than stretch it. */
.cover__img { object-fit: cover; }

/* -------------------------------------------- a cover as a play button */

.filerow__cover, .track__cover {
  position: relative;
  flex: none;
  width: 44px; height: 44px;
  padding: 0;
  border: 0;
  border-radius: 5px;
  overflow: hidden;
  background: transparent;
  cursor: pointer;
}
.track__cover { width: 40px; height: 40px; }

.filerow__coverplay, .track__coverplay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 12px;
  opacity: 0;
  transition: opacity 120ms ease;
}
.filerow:hover .filerow__coverplay,
.track:hover .track__coverplay,
.track.is-playing .track__coverplay,
.filerow__cover:focus-visible .filerow__coverplay { opacity: 1; }

/* ------------------------------------------------------- choosing a cover */

.coverpick {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  padding: 14px 10px 18px 32px;
  border-top: 1px dashed rgba(255, 255, 255, 0.08);
}
.coverpick .cover { border-radius: var(--radius); }
.coverpick__acts { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.coverpick__hint, .coverpick__error {
  flex: 1 1 100%;
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--text-faint);
}
.coverpick__error { color: #ff9a9a; }

.coverpick__check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  padding: 6px 10px;
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--radius);
}
.coverpick__check:hover { border-color: rgba(255, 255, 255, 0.2); color: var(--text); }
.coverpick__check input { accent-color: var(--neon-green); }

@media (max-width: 620px) {
  .coverpick { padding-left: 10px; }
}

/* ============================================================= a track page */

.trackpage {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(22px, 4vw, 48px);
  align-items: end;
  max-width: var(--page);
  margin: clamp(14px, 2vw, 24px) auto clamp(24px, 3vw, 36px);
}
.trackpage__art .cover { border-radius: var(--radius); box-shadow: 0 22px 50px -20px rgba(0,0,0,0.95); }
.trackpage__body { min-width: 0; }
.trackpage__body h1 { margin: 2px 0 12px; line-height: 1.02; }

.trackpage__by {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--text);
  font-size: 15px;
  margin-bottom: 10px;
}
.trackpage__by:hover { color: #fff; }

.trackpage__facts { font-size: 13px; color: var(--text-faint); margin: 0 0 18px; }
.trackpage__actions { margin-bottom: 18px; }
.trackpage__actions .trackbtn { width: 38px; height: 38px; border: 1px solid rgba(255,255,255,0.14); }

.trackpage__rate {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  color: var(--text-faint);
  flex-wrap: wrap;
}

.trackpage__artist {
  display: flex;
  gap: 18px;
  align-items: flex-start;
  max-width: var(--page);
  margin: 0 auto clamp(26px, 4vw, 40px);
  padding: clamp(16px, 2.2vw, 22px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.28);
}
.trackpage__artist h3 { margin: 0 0 4px; }
.trackpage__artistfacts { margin: 0; font-size: 13px; color: var(--text-faint); }
.trackpage__artistfacts i { color: var(--neon-green); }
.trackpage__bio { margin: 10px 0 0; color: var(--text-dim); max-width: 68ch; white-space: pre-wrap; }

/* -------------------------------------- a cover that is also a play button */

.trackcover {
  position: relative;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 5px;
  overflow: hidden;
  background: transparent;
  cursor: pointer;
  line-height: 0;
}
.trackcover__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 12px;
  color: #fff;
  /* ALWAYS THERE, not on hover. A touch screen has no hover, and the whole
     point of a cover this small is that it is the play button -- a control that
     only exists once you have already guessed it is there is not a control. */
  background: rgba(0, 0, 0, 0.42);
  opacity: 1;
  transition: background 120ms ease;
}
.trackcover:hover .trackcover__play { background: rgba(0, 0, 0, 0.66); }

/* The big cards: the play button is always visible too, and only grows on
   hover, rather than appearing from nothing. */
.tcard__play { opacity: 0.86; transform: none; }
.tcard:hover .tcard__play, .tcard:focus-within .tcard__play { opacity: 1; transform: scale(1.06); }
.tcard__art { cursor: pointer; }
.tcard__name { display: block; color: var(--text); }
.tcard__name:hover { color: #fff; text-decoration: underline; }

/* ------------------------------------------------------------- the player */

.playerbar__cover { flex: none; line-height: 0; border-radius: 5px; overflow: hidden; }
.playerbar__cover .cover { border-radius: 5px; }

.playerbar__track { display: block; color: var(--text); }
.playerbar__track:hover { color: #fff; text-decoration: underline; }

.playerbar__artist a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-faint);
}
.playerbar__artist a:hover { color: var(--text); }
.playerbar__artist .avatar { flex: none; }

.playerbar__stars { flex: none; margin-left: 10px; }

@media (max-width: 900px) {
  /* The bar is already tight at this width; the stars are the first thing that
     can wait until you are on the track's own page. */
  .playerbar__stars { display: none; }
}
@media (max-width: 760px) {
  .trackpage { grid-template-columns: 1fr; }
  .trackpage__art { justify-self: center; }
  .trackpage__art .cover { width: 220px !important; height: 220px !important; }
}

/* Sign in, sign up and connecting an app all share the auth card above. */

/* ==================================================================== tips */

/* THE TIP IS THE ACCENT ACTION wherever it appears. It is the one control on a
   profile that moves money to a PERSON rather than to the house, so it carries
   the magenta instead of sitting in the grey ghost set with everything else.
   It was a btn--small: 12px of text adrift in a 42px box, next to a 24px pill
   of its own -- two boxes of different heights arguing with each other. Now
   there is one box, and what you have already given is quiet text beside it. */

.tip {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: 18px 0 4px;
}
.tip--compact { margin: 0; }

.tip__go {
  border-radius: 999px;
  padding: 9px 20px;
  min-height: 38px;
  font-size: 14px;
  color: #ffd9f5;
  background: rgba(255, 5, 199, 0.10);
  border-color: rgba(255, 5, 199, 0.34);
}
.tip__go:hover:not(:disabled) {
  color: #fff !important;
  background: rgba(255, 5, 199, 0.20);
  border-color: rgba(255, 5, 199, 0.62);
  box-shadow: 0 0 26px -10px rgba(255, 5, 199, 0.95);
}
.tip__go:disabled { opacity: 0.6; cursor: default; }
.tip__go i { color: var(--neon-magenta); }
.tip__go:hover i { color: #ff5ad9; }

/* Quiet, because it is a fact about you rather than something to press. */
.tip__given {
  font-size: 13px;
  color: var(--text-faint);
  white-space: nowrap;
}
.tip__given strong { color: var(--neon-magenta); font-weight: 600; }

.tip__said { font-size: 13px; color: #ff9a9a; }
.tip__said.is-good { color: var(--neon-green); }

@media (max-width: 560px) {
  .tip { gap: 10px; }
  .tip__go { padding: 9px 16px; }
}

/* ================================================================== admin */

/* The admin panel used to be the one page that had missed every redesign: a
   purple slab, legacy grey/green buttons with black text, and inline styles on
   every input. It now uses the same shell, tabs, tables and buttons as the
   rest of the site, which is why almost all of this is layout rather than
   colour -- the colours already exist as tokens. */

/* The heading shares the tabs' measure. .container spans the full window on
   purpose -- the video behind it should too -- and everything inside the admin
   page is capped at --page and centred, so a heading left at the container's
   own width started a long way to the left of the panel it titles. */
.admhead, .admerror { max-width: var(--page); margin-inline: auto; }
.admhead { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 4px; }
.admhead h1 { margin: 0; }
.admhead__who { font-size: 13px; color: var(--text-faint); }

/* The dashboard. Numbers first, because the question an admin arrives with is
   usually "is anything obviously wrong". */
.admstats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin: 18px 0 8px;
}
.admstat {
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 12px;
  padding: 14px 16px;
}
.admstat__n { font-size: 26px; line-height: 1.1; font-weight: 600; }
.admstat__l { font-size: 12px; color: var(--text-faint); margin-top: 4px; }
.admstat--money .admstat__n { color: var(--neon-magenta); }
.admstat--warn .admstat__n { color: #ffb3b3; }

/* A section. Not a slab: a hairline card on the page ground, like everywhere
   else on the site. */
.admcard {
  background: rgba(255, 255, 255, 0.022);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  padding: 18px 20px;
  margin-bottom: 20px;
}
.admcard > h3, .admcard > h4 { margin-top: 0; }
.admcard__note { font-size: 13px; color: var(--text-faint); margin-top: 0; }

.admbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 12px 0; }
.admbar--end { justify-content: flex-end; }

/* Fields. One rule instead of the `input` style object that was spread onto
   every control by hand. */
.admfield { display: block; margin-bottom: 12px; }
.admfield > span { display: block; font-size: 12px; color: var(--text-faint); margin-bottom: 4px; }
.admfield input[type="text"], .admfield input[type="date"], .admfield input[type="number"],
.admfield select, .admfield textarea, .adminput {
  width: 100%;
  box-sizing: border-box;
  padding: 9px 11px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.42);
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
}
.admfield textarea { min-height: 120px; resize: vertical; }
.admfield input:focus, .admfield select:focus, .admfield textarea:focus, .adminput:focus {
  outline: none;
  border-color: rgba(90, 217, 255, 0.55);
  box-shadow: 0 0 0 3px rgba(90, 217, 255, 0.12);
}
.admcheck { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; }
.admcheck input { width: 16px; height: 16px; }

/* Two or three fields on a line, one under 720px. */
.admgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0 16px; }

/* The panel that opens under a row. */
.admdrawer {
  margin-top: 16px;
  padding: 18px;
  background: rgba(0, 0, 0, 0.34);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-left: 3px solid var(--neon-cyan);
  border-radius: 10px;
}
.admdrawer h4 { margin: 0 0 4px; }
.admdrawer__sub { font-size: 13px; color: var(--text-faint); margin: 0 0 14px; }
.admdrawer__section { margin-top: 20px; padding-top: 16px; border-top: 1px solid rgba(255,255,255,0.08); }
.admdrawer__section h5 { margin: 0 0 6px; font-size: 14px; }

.admchip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; padding: 3px 10px; border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text-faint);
  white-space: nowrap;
}
.admchip--on { border-color: rgba(80, 220, 140, 0.45); background: rgba(80, 220, 140, 0.10); color: #b6f5cf; }
.admchip--off { opacity: 0.6; }
.admchip--dead { border-color: rgba(255, 120, 120, 0.4); background: rgba(255, 70, 70, 0.10); color: #ffb3b3; }
.admchip button {
  background: none; border: 0; color: inherit; cursor: pointer;
  padding: 0; font-size: 12px; opacity: 0.7;
}
.admchip button:hover { opacity: 1; color: #fff; }

.admchips { display: flex; gap: 6px; flex-wrap: wrap; }

/* Tables: the shared table.table, plus admin-only affordances. */
.admtable { width: 100%; }
.admtable td, .admtable th { vertical-align: middle; }
.admtable tr.is-admin td { background: rgba(80, 220, 140, 0.07); }
.admtable tr.is-hidden { opacity: 0.5; }
.admtable .admtable__right { text-align: right; white-space: nowrap; }
.admnum { font-variant-numeric: tabular-nums; }
.admin-neg { color: #ffb3b3; }
.admin-pos { color: var(--neon-green); }

.admpager { display: flex; align-items: center; gap: 12px; margin-top: 10px; font-size: 13px; }

.admcode {
  display: block; word-break: break-all; font-size: 12px;
  color: var(--neon-cyan); background: rgba(0,0,0,0.5);
  padding: 10px; border-radius: 8px; margin: 6px 0 0;
}

.admerror {
  border: 1px solid rgba(255, 120, 120, 0.45);
  background: rgba(255, 70, 70, 0.12);
  color: #ffd9d9;
  border-radius: 10px;
  padding: 12px 16px;
  margin-bottom: 16px;
}

@media (max-width: 720px) {
  .admcard { padding: 14px; }
  .admstat__n { font-size: 22px; }
}

/* ================================================== signing in and signing up */

/*
 * One card, used by sign in, sign up and connecting an app.
 *
 * What was here before was the legacy Blazor layout, and it was broken rather
 * than merely dated. `.middlechild` is position:fixed with translate(0,-50%),
 * so the form was taken out of the flow entirely: the footer laid itself out as
 * though the page were empty and the form painted on top of it. That is the
 * overlap in the screenshots, and it got worse the more the page was scaled,
 * because a fixed element cannot grow the document it is no longer part of.
 *
 * Everything below is ordinary flow. It is a single column at every width --
 * the old two-column split put the prose beside the fields and then dropped the
 * fields below the fold on a phone -- and every size is clamped or relative, so
 * a browser at 200% gets a bigger card rather than a broken one.
 */
.auth {
  /* A gutter on a phone: edge-to-edge, the card's own border sits half off the
     screen and it stops reading as a card at all. */
  width: min(460px, calc(100% - 28px));
  box-sizing: border-box;
  margin: clamp(24px, 6vw, 72px) auto clamp(40px, 9vw, 96px);
  padding: clamp(20px, 4vw, 34px);
  background: var(--panel-a);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.auth__kicker {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 8px;
}

.auth h1 {
  margin: 0 0 8px;
  font-size: clamp(26px, 5vw, 34px);
  line-height: 1.1;
}

.auth__lede {
  color: var(--text-dim);
  font-size: 15px;
  line-height: 1.55;
  margin: 0 0 22px;
}

.auth__form { display: grid; gap: 16px; }

.auth__field { display: grid; gap: 6px; }
.auth__field > span {
  font-size: 13px;
  color: var(--text-dim);
  letter-spacing: 0.02em;
}
/* 16px so iOS does not zoom the whole page in when the field is focused, which
   on a narrow screen leaves the user scrolled sideways into nothing. */
.auth__field input { font-size: 16px; min-height: 46px; }

/* A fault with one field, under that field, where the thing it is about is. */
.auth__fieldnote {
  font-size: 13px;
  color: var(--brand-soft);
  margin: 0;
}

/*
 * A failed attempt. It reads as part of the form now: the old one was a
 * full-bleed red slab with an <h1> in it, above the page, which pushed the form
 * off screen at the exact moment somebody needed to correct it -- the second
 * screenshot is the whole viewport filled with "Username and password are
 * required" and no visible username box.
 */
.auth__error {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 13px;
  border: 1px solid rgba(255, 107, 107, 0.42);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: rgba(255, 70, 70, 0.11);
  color: #ffc9c9;
  font-size: 14px;
  line-height: 1.45;
}
.auth__error i { color: #ff6b6b; margin-top: 2px; }

/* Full width, and tall enough to hit with a thumb. */
.auth__go {
  width: 100%;
  min-height: 48px;
  font-size: 15px;
  justify-content: center;
}

.auth__alt {
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 14px;
  color: var(--text-faint);
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  justify-content: space-between;
}
.auth__alt a { color: var(--blue); }

/* Connecting an app is the same card with a result state on the end of it. */
.auth--wide { width: min(520px, calc(100% - 28px)); text-align: center; }

/*
 * The popup. No nav, no footer, no background video, so the card centres itself
 * in the window rather than sitting under a page that is not there.
 *
 * It has to survive being a TAB rather than a window -- Firefox turns
 * window.open into a tab by default and any browser may -- so this is centring
 * and min-height rather than anything that assumes 460 by 640. Full width it is
 * the same card with more room around it.
 */
.auth--bare {
  margin: 0 auto;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: 0;
  background: none;
}
.auth--wide .auth__lede { max-width: 42ch; margin-left: auto; margin-right: auto; }
.auth--wide .auth__form { text-align: left; }

.auth__tick {
  width: clamp(58px, 12vw, 74px);
  aspect-ratio: 1;
  margin: 0 auto 18px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: clamp(24px, 5vw, 30px);
  color: #04140a;
  background: linear-gradient(135deg, var(--neon-green), #12c9a0);
  box-shadow: 0 0 44px -8px rgba(39, 248, 54, 0.7);
}

.auth__hint { font-size: 13px; color: var(--text-faint); margin: 16px 0 0; }
.auth__os { font-size: 13px; color: var(--text-faint); margin: 0; }

/* ============================================================= a dvd case */

/*
 * A real box, not a picture of one: six faces in a preserve-3d context at the
 * proportions of a DVD keep case -- 190 x 135 x 14 mm, so the depth is 10.4% of
 * the width. See DvdBox.tsx for why this is CSS and not a 3D model.
 *
 * TURNED TOWARD ITS SPINE, the way a case stands on a shelf. The spine carries
 * the one marking that says what kind of thing this is, so it has to be the
 * edge you can see.
 *
 * --w, --h and --zoom come from the component, so everything below is derived
 * and the case is correct at any size from the 116px add-on tile to the 257px
 * one on the History page.
 */
.dvd {
  --d: calc(var(--w) * 0.104);          /* 14mm of depth */
  --rim: calc(var(--w) * 0.026);        /* the moulded lip around the sleeve */
  --r: calc(var(--w) * 0.032);          /* the corner radius, ~4mm */

  --plastic: hsl(var(--ph) 54% 15%);
  --plastic-lit: hsl(var(--ph) 40% 31%);
  --plastic-dark: hsl(var(--ph) 58% 7%);

  position: relative;
  display: inline-block;
  perspective: calc(var(--w) * 3.4);
  /* Centred, because the resting pose is now square-on and an off-centre
     vanishing point would keystone a face that is supposed to be flat. */
  perspective-origin: 50% 50%;
}

/* One hue of plastic per product type. There used to be a marking on the spine
   to go with it -- a bar for a product, ticks for a pack -- and it went: the
   label is printed in the cover's colour and carries a title, a code and a
   logo, and a decorative stripe down the edge of all that was just clutter. The
   plastic still says what type a thing is. */
.dvd--product { --ph: 282; }            /* the purple products always had */
.dvd--pack    { --ph: 122; }            /* the green loop packs always had */
.dvd--module  { --ph: 202; }            /* new: studio modules were green too */
.dvd--support { --ph: 32; }             /* new: so were supports */

/* Lifted over its neighbours while it is the one being looked at, or the zoom
   slides under the next tile along. */
.dvd:hover { z-index: 3; }

.dvd__box {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform:
    scale3d(var(--z, 1), var(--z, 1), var(--z, 1))
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg));
  /* Slow and eased on the way back to rest, quick while the cursor is driving
     it -- a long ease there would lag the pointer and feel like treacle. */
  transition: transform 460ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.dvd:hover .dvd__box { --z: var(--zoom, 1); transition: transform 110ms linear; }

/*
 * Turns itself square to the reader as the page arrives -- ONCE, and then the
 * class is gone for good (DvdBox drops it on animationend, or on the first
 * movement of a cursor over the case).
 *
 * It is a class rather than a plain rule on .dvd__box because a CSS animation
 * outranks the transform the cursor drives, so it had to be switched off while
 * hovering -- and `animation: none` under :hover meant the animation came BACK
 * when the cursor left, which restarted it. Leaving a case snapped it round to
 * 30 degrees and played the whole settle again instead of easing home from
 * where it actually was.
 */
.dvd__box.is-settling { animation: dvd-settle 1s cubic-bezier(0.22, 0.61, 0.36, 1); }

/*
 * The case shows itself on the way in: it arrives turned to its spine, the way
 * it sits on a shelf, and over a second squares up to the reader.
 *
 * ONLY A `from`. The implicit `to` is the element's own base transform, so the
 * animation always lands exactly where the resting state is rather than on a
 * duplicate of it that could drift out of step with it later. The base carries
 * the scale, so `from` states it too -- interpolating between transform lists of
 * different lengths falls back to matrix maths, which is a different curve.
 */
@keyframes dvd-settle {
  from { transform: scale3d(1, 1, 1) rotateX(2.5deg) rotateY(30deg); }
}

.dvd__face {
  position: absolute;
  top: 50%;
  left: 50%;
  backface-visibility: hidden;
}

/* Every face is centred first, then pushed out to its own plane -- the standard
   way to build a box, and the only one where the maths stays readable. */
.dvd__front, .dvd__back { width: var(--w); height: var(--h); }
.dvd__spine, .dvd__lip { width: var(--d); height: var(--h); }
.dvd__edge { width: var(--w); height: var(--d); }

.dvd__front {
  transform: translate(-50%, -50%) translateZ(calc(var(--d) / 2));
  background: var(--plastic);
  border-radius: var(--r);
  padding: var(--rim);
  box-sizing: border-box;
  /* The lip catches light along the top-left and loses it at the bottom-right,
     which is what stops the border reading as a flat coloured frame. */
  box-shadow:
    inset 1px 1px 0 hsl(var(--ph) 35% 42% / 0.85),
    inset -1px -1px 0 var(--plastic-dark);
}

.dvd__art {
  width: 100%;
  height: 100%;
  /* Stretched, not cropped -- what the original did, and the right call for box
     art: the covers are not all 1.4, and "cover" ate the last word of the
     Discord pack's title. A slightly squashed cover still reads; a cropped one
     loses the thing it says. */
  background-size: 100% 100%;
  border-radius: calc(var(--r) * 0.4);
  /* The sleeve sits UNDER the moulded lip, so the lip shades its edges. */
  box-shadow: inset 0 0 calc(var(--w) * 0.03) rgba(0, 0, 0, 0.55);
}

/* No artwork: a plain sleeve with the name on it, rather than an empty case
   that reads as a failed image. */
.dvd__art--blank {
  background: linear-gradient(160deg, #2b1240 0%, #4a1d6d 55%, #1a0a29 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8%;
  box-sizing: border-box;
}
.dvd__notitle {
  color: rgba(255, 255, 255, 0.92);
  text-align: center;
  line-height: 1.25;
  font-size: calc(var(--w) * 0.105);
  overflow-wrap: anywhere;
}

/* The clear sleeve. One broad highlight that slides across as the case turns:
   --sheen runs about -1 to 1 under the cursor and is 0 at rest, so the light
   tracks the angle instead of sitting still the way the old photograph did. */
.dvd__gloss {
  position: absolute;
  inset: 0;
  border-radius: var(--r);
  pointer-events: none;
  background:
    linear-gradient(78deg,
      rgba(255, 255, 255, 0) 30%,
      rgba(255, 255, 255, 0.16) 43%,
      rgba(255, 255, 255, 0.05) 50%,
      rgba(255, 255, 255, 0) 60%),
    linear-gradient(160deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0) 42%);
  background-size: 260% 100%, 100% 100%;
  background-position: calc(50% + var(--sheen, 0) * 40%) 0, 0 0;
  transition: background-position 110ms linear;
}

/* --------------------------------------------------------------- the spine */

/*
 * Laid out the way a game's spine is, and for the same reason: every part in
 * the same place on every case, so a shelf of them scans as a set. Top to
 * bottom -- the publisher's band, the title, the product's code, the
 * publisher's logo -- with two moulded hinge caps at the very ends.
 *
 * --label is the colour the label is PRINTED, taken from the cover art
 * (NGProducts.SpineColour). It falls back to the plastic, which is what every
 * case looked like before there was a column for it. --ink-on-label is black or
 * white, whichever can be read on that colour; see onLabel() in DvdBox.tsx.
 */
.dvd__spine {
  --label: var(--plastic);
  --ink-on-label: #ffffff;

  transform: translate(-50%, -50%) rotateY(-90deg) translateZ(calc(var(--w) / 2));
  background:
    /* The moulded caps. SMALL: on a real case these are a few millimetres of
       hinge, and the printed label runs almost the whole height between them. */
    linear-gradient(to bottom,
      hsl(var(--ph) 28% 10%) 0 3.5%,
      transparent 3.5% 96.5%,
      hsl(var(--ph) 28% 10%) 96.5%),
    /* The light across the printed card. TO LEFT, not to right: a rotated
       face's local axes are not the screen's, and lit-first put the highlight
       on the far edge -- which read as a black gap between the spine and the
       cover rather than as a corner. */
    linear-gradient(to left,
      rgba(255, 255, 255, 0.14), rgba(0, 0, 0, 0) 45%, rgba(0, 0, 0, 0.45)),
    var(--label);
  border-radius: calc(var(--r) * 0.5);
  overflow: hidden;
}

/*
 * The publisher's band at the head of the spine, where a console's would be:
 * "ezmuze" in the wordmark's own typeface on a dark plate, identical on every
 * case whatever the label underneath is printed in.
 */
.dvd__brand {
  position: absolute;
  top: 3.5%;
  left: 0;
  right: 0;
  height: 15%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.72);
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-family: Comfortaa, system-ui, sans-serif;
  font-weight: 700;
  font-size: calc(var(--d) * 0.34);
  letter-spacing: 0.12em;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
}

/*
 * The title, reading top to bottom the way a spine does on a shelf.
 *
 * writing-mode rather than a rotate(90deg): it gives the element real vertical
 * metrics, so an ellipsis on a name too long for the case works, where a
 * rotated box would have been measured horizontally and clipped at the wrong
 * end. The component leaves this out below about 180px wide, where the spine is
 * under 19px and the type would be a smear rather than a word.
 */
.dvd__spinetext {
  position: absolute;
  top: 20%;
  bottom: 26%;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  writing-mode: vertical-rl;
  text-orientation: mixed;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: calc(var(--d) * 0.4);
  letter-spacing: 0.02em;
  color: var(--ink-on-label);
  opacity: 0.94;
}

/* The four-letter code, sitting just above the logo the way a catalogue number
   does on a real spine. Small, and set apart from the title. */
.dvd__code {
  position: absolute;
  bottom: 18%;
  left: 0;
  right: 0;
  height: 8%;
  display: flex;
  align-items: center;
  justify-content: center;
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-size: calc(var(--d) * 0.28);
  letter-spacing: 0.14em;
  color: var(--ink-on-label);
  opacity: 0.72;
  white-space: nowrap;
  overflow: hidden;
}

/*
 * The publisher's logo at the foot.
 *
 * mad ninja skillz, and the two marks are not interchangeable: everything in
 * the catalogue was published under the classic one, and ezmuze studio -- the
 * only thing made since -- carries the current one. Both are drawn on their own
 * black ground, so they sit on a plate rather than being knocked out of the
 * label, which is also what keeps them legible on ninja dnb's yellow.
 */
.dvd__pub {
  position: absolute;
  bottom: 5%;
  left: 50%;
  transform: translateX(-50%);
  width: 72%;
  aspect-ratio: 1;
  border-radius: 16%;
  background: center / cover no-repeat;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18);
}
.dvd__pub--classic { background-image: url('/img/madninja-classic.png'); }
.dvd__pub--new     { background-image: url('/img/madninja.png'); }
.dvd__pub--xbox    { background-image: url('/img/xbox360.png'); }

/* The opening edge: the two halves of the case meet here, so it is a pair of
   lips with a dark seam between them rather than one flat strip. */
.dvd__lip {
  transform: translate(-50%, -50%) rotateY(90deg) translateZ(calc(var(--w) / 2));
  background: linear-gradient(to right,
    var(--plastic-dark), var(--plastic) 24%,
    hsl(var(--ph) 60% 4%) 48% 52%,
    var(--plastic) 76%, var(--plastic-dark));
  border-radius: calc(var(--r) * 0.5);
}

.dvd__back {
  transform: translate(-50%, -50%) translateZ(calc(var(--d) / -2)) rotateY(180deg);
  background: linear-gradient(200deg, var(--plastic), var(--plastic-dark));
  border-radius: var(--r);
}

/* The two short edges. */
.dvd__top {
  transform: translate(-50%, -50%) rotateX(90deg) translateZ(calc(var(--h) / 2));
  background: linear-gradient(to bottom, var(--plastic-lit), var(--plastic) 45%, var(--plastic-dark));
}
.dvd__bottom {
  transform: translate(-50%, -50%) rotateX(-90deg) translateZ(calc(var(--h) / 2));
  background: linear-gradient(to top, var(--plastic-dark), var(--plastic) 60%);
}

/* Grounds the box. Slides the opposite way to the tilt, so the case looks like
   it is being turned over a surface rather than painted onto one. */
.dvd__shadow {
  position: absolute;
  left: 6%;
  right: 6%;
  bottom: calc(var(--w) * -0.045);
  height: calc(var(--w) * 0.09);
  border-radius: 50%;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0) 70%);
  filter: blur(calc(var(--w) * 0.012));
  transform: translateX(calc(var(--sheen, 0) * var(--w) * -0.05));
  transition: transform 460ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 460ms ease;
  pointer-events: none;
}
.dvd:hover .dvd__shadow {
  opacity: 0.7;
  transition: transform 110ms linear, opacity 110ms ease;
}

/* Somebody who has asked for less movement still gets the box, the colour and
   the spine -- just a still one: the geometry is the point, the turning and the
   zoom are the flourish. */
@media (prefers-reduced-motion: reduce) {
  .dvd__box, .dvd:hover .dvd__box, .dvd__gloss,
  .dvd__shadow, .dvd:hover .dvd__shadow { transition: none; }
  .dvd:hover .dvd__box { --z: 1; }
  /* Square-on from the first frame: the settle is the flourish. */
  .dvd__box.is-settling { animation: none; }
}

/* ==========================================================================
   /comingsoon — the landing page ezmuze.studio points at until launch.

   One job: the name, the box, and the date. Anyone here followed a link for
   the application itself, so the page owes them the date and nothing to wade
   through. It borrows the hero's two-column shape rather than inventing a
   second one, so the box sits exactly where the box sits on the home page.
   ========================================================================== */
.coming {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: min(72vh, 720px);
  padding: calc(var(--u) * 3) 0;
}

.coming__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: center;
  max-width: var(--page);
  width: 100%;
  margin: 0 auto;
}

.coming__title {
  font-size: clamp(34px, 5vw, 58px);
  margin: 0 0 var(--u);
}

.coming__lead {
  font-size: clamp(16px, 1.6vw, 19px);
  color: var(--text-dim);
}

.coming__when {
  font-size: clamp(18px, 1.8vw, 22px);
  color: var(--brand-soft);
  margin-top: calc(var(--u) * 1.5);
  margin-bottom: var(--u);
}

.coming__live {
  font-size: clamp(20px, 2.2vw, 26px);
  color: var(--brand-soft);
  margin-top: calc(var(--u) * 1.5);
}

.coming__case { display: flex; justify-content: center; }

/* The four units read as one clock: a shared baseline, tabular figures so the
   seconds do not shove their neighbours about as they tick, and the label
   under the number rather than beside it so a narrow screen keeps all four in
   a row instead of wrapping mid-count. */
.countdown {
  display: flex;
  gap: clamp(10px, 2vw, 22px);
  flex-wrap: nowrap;
}

.countdown__unit {
  min-width: 62px;
  padding: calc(var(--u) * 0.75) var(--u);
  border-radius: 10px;
  background: var(--panel-a);
  text-align: center;
}

.countdown__value {
  font-size: clamp(24px, 3.4vw, 40px);
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

.countdown__label {
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-top: 4px;
}

/* One column, and the box goes first: on a phone the artwork is the thing
   worth leading with, and the countdown still lands above the fold under it. */
@media (max-width: 900px) {
  .coming__inner { grid-template-columns: minmax(0, 1fr); }
  .coming__case  { order: -1; }
  .coming        { min-height: 0; }
}
