/* Relay marketing — full-bleed row homepage.
 *
 * Each row has its own visual treatment so the page reads as a sequence of
 * moments rather than a template. Scoped to `.home-row*`; the legacy
 * `styles.css` (terms / usage / technically-speaking) is untouched.
 */

:root {
  --home-row-pad-y: clamp(96px, 14vw, 168px);
  --home-row-pad-x: clamp(24px, 6vw, 80px);
  --home-row-max: 1180px;
  --home-row-gap: clamp(40px, 7vw, 96px);
}

/* Cream extends edge-to-edge under the floating nav, instead of the
 * global dark `--bg` peeking through above the hero. */
body.site--home-rows {
  background-color: #f5efe6;
}

/* The default relay-glass header is built for a dark page. On the cream
 * home page, flip the pill to a light glass with black text so it reads
 * naturally over the wash. Scoped to the home page only. */
body.site--home-rows .site-header .relay-glass__face,
body.site--home-rows .mobile-nav .relay-glass__face {
  background: linear-gradient(
    145deg,
    rgba(255, 255, 255, 0.55),
    rgba(255, 255, 255, 0.35)
  );
  border: 0.5px solid rgba(22, 17, 11, 0.1);
}
body.site--home-rows .site-header .relay-glass__emboss,
body.site--home-rows .mobile-nav .relay-glass__emboss {
  box-shadow:
    inset 0 0.5px 0 rgba(255, 255, 255, 0.6),
    inset 0.5px 0 0 rgba(255, 255, 255, 0.3),
    inset 0 -0.5px 0 rgba(22, 17, 11, 0.12),
    inset -0.5px 0 0 rgba(22, 17, 11, 0.06),
    0 1px 2.25px -1.5px rgba(22, 17, 11, 0.12);
}
body.site--home-rows .site-header .relay-glass__flare,
body.site--home-rows .mobile-nav .relay-glass__flare {
  display: none;
}

body.site--home-rows .logo-text,
body.site--home-rows .nav-links a,
body.site--home-rows .mobile-nav__inner a {
  color: #16110b;
  font-weight: 450;
}
body.site--home-rows .nav-links a:hover,
body.site--home-rows .nav-links a:focus-visible,
body.site--home-rows .mobile-nav__inner a:hover {
  color: #000;
}
body.site--home-rows .nav-toggle span {
  background: #16110b;
}

/* Tighter desktop nav with pill-shaped hit areas. The 9-item home page  */
/* nav was way too spread out at the default --space-lg (40px) gap; the  */
/* per-link padding here also doubles as the surface that the active    */
/* scroll-spy state paints onto (see .nav-links a.is-active below).     */
body.site--home-rows .nav-links {
  gap: 2px;
}
body.site--home-rows .nav-links a:not(.nav-pill) {
  padding: 7px 12px;
  border-radius: 999px;
  line-height: 1;
  transition: background-color 180ms ease, color 180ms ease;
}
body.site--home-rows .nav-links a:not(.nav-pill):hover,
body.site--home-rows .nav-links a:not(.nav-pill):focus-visible {
  background-color: rgba(22, 17, 11, 0.06);
}
body.site--home-rows .nav-links a.is-active {
  /* "You are here" pill — Relay-red semi-transparent fill (same        */
  /* family as the download row hover and the FAQ open accent), so red  */
  /* consistently reads as "the active / live thing" across the page.   */
  background-color: rgba(229, 29, 60, 0.14);
  color: #c11636;
}

/* Mobile nav active state — borrow the same fill but use the wider    */
/* row-style hit area the mobile nav already gives each link.          */
body.site--home-rows .mobile-nav__inner a:not(.nav-pill) {
  border-radius: 10px;
  padding-left: 12px;
  padding-right: 12px;
  transition: background-color 180ms ease, color 180ms ease;
}
body.site--home-rows .mobile-nav__inner a.is-active {
  background-color: rgba(229, 29, 60, 0.12);
  color: #c11636;
  font-weight: 550;
}

/* Tablet-range fallback to the hamburger drawer. The marketing home   */
/* nav has 10 items (9 section anchors + Log in / Dashboard / Log out) */
/* — that's roughly twice what the global 768px breakpoint was         */
/* designed for, so anywhere between 768px and 1023px the inline nav   */
/* either truncates the Log in pill or starts wrapping onto a second   */
/* row. Below 1024px we instead show the hamburger toggle and let the  */
/* existing mobile-nav drawer take over (scroll-spy is wired into both */
/* containers, so the active-section highlight follows you in either   */
/* layout). Other pages (Terms, Usage) only have 2-3 nav links, fit    */
/* comfortably in the inline nav at 768px+, and aren't affected.       */
@media (min-width: 768px) and (max-width: 1023px) {
  body.site--home-rows .nav-links {
    display: none;
  }
  body.site--home-rows .nav-toggle {
    display: flex;
  }
  /* Undo styles.css's `.mobile-nav { display: none !important; }` at  */
  /* this width for home-rows only, so the drawer can open. Both rules */
  /* need !important to win over that global rule; the explicit        */
  /* `[hidden]` rule then keeps the drawer collapsed until script.js   */
  /* removes the attribute on toggle. (revert !important is unreliable */
  /* for picking up the user-agent `[hidden] { display: none }` rule.) */
  body.site--home-rows .mobile-nav {
    display: block !important;
  }
  body.site--home-rows .mobile-nav[hidden] {
    display: none !important;
  }
}

/* Nav CTA pill — applied to the data-relay-account login link. The
 * relay-accounts.js script copies this className onto the generated
 * Dashboard <a> and Log out <button>, so all three states share it.
 * Anchor + button reset bundled in so the <button> matches the <a>. */
body.site--home-rows .nav-pill,
body.site--home-rows .mobile-nav__inner .nav-pill {
  appearance: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  padding: 7px 16px;
  border-radius: 999px;
  background: #ff3c5a;
  color: #fff !important;
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.15s ease, filter 0.15s ease, box-shadow 0.15s ease;
  box-shadow:
    0 4px 12px rgba(255, 60, 90, 0.32),
    inset 0 0 0 1px rgba(255, 255, 255, 0.14);
}
body.site--home-rows .nav-pill:hover,
body.site--home-rows .nav-pill:focus-visible {
  color: #fff !important;
  text-decoration: none;
  transform: translateY(-1px);
  filter: brightness(1.05);
  box-shadow:
    0 6px 16px rgba(255, 60, 90, 0.42),
    inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}
body.site--home-rows .nav-pill:active {
  transform: translateY(0);
}

/* Footer reverts to dark so cream-warm CTA → dark footer mirrors the
 * existing hero → dark-everything-else rhythm. Text colors already use
 * --text-muted / --text from styles.css, which are tuned for dark bg. */
body.site--home-rows .site-footer {
  background-color: #0a0a0b;
  color: #f2f0ea;
  border-top: 0;
}

/* Base row container -------------------------------------------------- */
.home-row {
  position: relative;
  isolation: isolate;
  padding: var(--home-row-pad-y) var(--home-row-pad-x);
  background: var(--row-bg, #111);
  color: var(--row-fg, #fff);
  overflow: hidden;
}

.home-row__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--home-row-gap);
  width: 100%;
  max-width: var(--home-row-max);
  margin: 0 auto;
}

.home-row--reverse .home-row__inner {
  direction: rtl;
}
.home-row--reverse .home-row__inner > * {
  direction: ltr;
}

@media (max-width: 880px) {
  /* When the 2-column rows collapse to a single stacked column, the
   * copy left-aligns to match the About section (which is the only row
   * that already used a left-aligned narrow layout). Visual blocks —
   * coverflow, phone mocks, the search bar, etc. — stay horizontally
   * centered in their cell so they read as "the artwork for this
   * section" instead of being shoved to one edge.
   *
   * Hero, Radio, CTA, and Beta are intentionally centered single-column
   * moments (hero brand mark, radio ticker, download closer, beta
   * signup form) rather than copy + media pairs, so they opt back into
   * a centered layout below. */
  .home-row__inner {
    grid-template-columns: 1fr;
    text-align: left;
    gap: 56px;
  }
  .home-row--reverse .home-row__inner {
    direction: ltr;
  }
  /* Visual blocks stay centered in their stacked cell. No override
   * needed here — .home-row__art is already a flex container with
   * `justify-content: center` (see base rule further down), so its
   * children center themselves while .home-row__art itself stretches
   * to fill the grid cell. Earlier versions of this rule used
   * `justify-self: center` to center the art block; that collapsed
   * the wrapper to its intrinsic width (0) and broke any child whose
   * width was declared as `100%` — notably the Library coverflow,
   * whose JS measures `offsetWidth` to lay out the 3D strip. */
  /* Eyebrow icons follow the copy alignment — anchor them to the start
   * instead of centering. Centered-by-design rows re-center their
   * icons via the override block below. */
  .home-row__eyebrow::before,
  .home-row__eyebrow-svg {
    margin-left: 0;
    margin-right: 0;
  }

  /* ── Centered-by-design rows ──────────────────────────────────── */
  /* Hero (brand mark), the Download closer (typographic playlist of
   * platforms + download pills), and the FAQ "More details" closer
   * keep their fully centered layout — they're the page's centered
   * moments (brand opener / cross-platform pitch / Q&A wrap-up).
   * Beta + Radio used to live here too but now follow the standard
   * left-aligned treatment so the whole stack reads off one edge. */
  .home-row--hero .home-row__inner,
  .home-row--cta .home-row__inner,
  .home-row--faq .home-row__inner {
    text-align: center;
  }
  .home-row--hero .home-row__eyebrow::before,
  .home-row--hero .home-row__eyebrow-svg,
  .home-row--cta .home-row__eyebrow::before,
  .home-row--cta .home-row__eyebrow-svg,
  .home-row--faq .home-row__eyebrow-svg,
  .home-row--about .home-row__eyebrow-svg {
    margin-left: auto;
    margin-right: auto;
  }
  /* The FAQ list itself stays left-aligned even when the section
   * header is centered — questions read better flush-left, and the
   * chevron column on the right needs to anchor to a consistent edge. */
  .home-row--faq .home-faq {
    text-align: left;
  }

  /* ── Radio: undo the desktop auto-margins on title/body so they  */
  /*    flush-left when stacked. The ticker is full-width and       */
  /*    unaffected. The antenna icon and "RADIO" eyebrow inherit    */
  /*    the new left default from .home-row__inner.                 */
  /*    The `body.site--home-rows` prefix is needed on rules that   */
  /*    override centering set further down in the source (same     */
  /*    specificity would otherwise lose to source order).          */
  body.site--home-rows .home-row--radio .home-row__inner {
    justify-items: start;
    text-align: left;
  }
  body.site--home-rows .home-row--radio .home-row__title,
  body.site--home-rows .home-row--radio .home-row__body,
  body.site--home-rows .home-row--radio .home-row__eyebrow-svg {
    margin-left: 0;
    margin-right: 0;
  }

  /* ── Beta: same idea — push the body, form, and hint to the left */
  /*    edge. The form pill itself still stretches to fill the      */
  /*    column width (.home-beta-form max-width: 520px), so the     */
  /*    email field + "Request beta access" button keep their       */
  /*    horizontal layout; only their container anchor changes.     */
  /*    Drop the desktop 640px inner cap so the section shares its  */
  /*    left edge with the wider rows (Library, About, etc.) when   */
  /*    stacked — otherwise Beta would sit indented in the middle   */
  /*    of the viewport while everything around it is flush-left.   */
  body.site--home-rows .home-row--beta .home-row__inner {
    justify-items: start;
    text-align: left;
    max-width: var(--home-row-max);
  }
  body.site--home-rows .home-row--beta .home-row__body,
  body.site--home-rows .home-row--beta .home-beta-form,
  body.site--home-rows .home-row--beta .home-row__eyebrow-svg {
    margin-left: 0;
    margin-right: 0;
  }
  body.site--home-rows .home-row--beta .home-beta-form__hint {
    text-align: left;
  }
}

/* Typography ---------------------------------------------------------- */
/* `.home-row__copy` wraps eyebrow + title + body in every row so the
 * internal rhythm is driven by margins (consistent everywhere) instead
 * of the outer grid's `gap` (which varies per row). */
.home-row__copy {
  display: block;
}

.home-row__eyebrow {
  margin: 0 0 14px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.78;
}
/*
 * Section icons above each eyebrow — section-specific Iconoir glyphs
 * (https://iconoir.com) saved locally under /public/icons. We use
 * mask-image instead of background-image so each icon inherits the
 * eyebrow's currentColor and tracks the row's --row-fg automatically
 * (white on dark sections, dark on light ones) — no per-section color
 * overrides needed.
 *
 * About is excluded below because it already shows the Plex + Relay
 * lockup directly above its eyebrow.
 */
.home-row__eyebrow::before {
  content: "";
  display: block;
  width: 22px;
  height: 22px;
  margin: 0 0 10px;
  background-color: currentColor;
  -webkit-mask-image: var(--eyebrow-icon, none);
          mask-image: var(--eyebrow-icon, none);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  /* Eyebrow text uses opacity:0.78 — match it so icon weight reads the
   * same as the label below. */
  opacity: 0.88;
}
/* Per-section icon assignments. Each section's --eyebrow-icon var feeds
 * the mask-image above so the icon inherits the current text color. */
.home-row--library     { --eyebrow-icon: url("/icons/music-double-note.svg"); }
.home-row--albums      { --eyebrow-icon: url("/icons/album-open.svg"); }
.home-row--playlists   { --eyebrow-icon: url("/icons/playlist.svg"); }
.home-row--radio       { --eyebrow-icon: url("/icons/antenna.svg"); }
.home-row--search      { --eyebrow-icon: url("/icons/search.svg"); }
.home-row--downloads   { --eyebrow-icon: url("/icons/wifi-off.svg"); }
.home-row--cta         { --eyebrow-icon: url("/icons/multi-mac-os-window.svg"); }
/* Centered layouts → centered icon. Radio + CTA both use a 1-column
 * centered grid, so the eyebrow text is centered but a block-level
 * ::before defaults to left-aligned — these overrides re-center it. */
.home-row--cta .home-row__eyebrow::before,
.home-row--radio .home-row__eyebrow::before {
  margin-left: auto;
  margin-right: auto;
}

/*
 * Inline-SVG eyebrow variant. Used by sections that need a per-path
 * animation (e.g. Radio's pulsing antenna signal) — the default
 * mask-image approach can only animate the icon as a whole.
 *
 * Sections opting into this drop the ::before mask icon and render
 * an inline SVG before the label text, sized + colored to match.
 */
.home-row__eyebrow--inline-icon::before {
  content: none;
}
.home-row__eyebrow-svg {
  /* Drop-in replacement for the ::before mask icon — same 22px block
   * sitting above the eyebrow text, inheriting the section's text
   * alignment from the parent .home-row__eyebrow. Centered sections
   * (Radio, CTA) re-center it via the override below. */
  display: block;
  width: 22px;
  height: 22px;
  margin: 0 0 10px;
  opacity: 0.88;
}
.home-row--cta .home-row__eyebrow-svg,
.home-row--radio .home-row__eyebrow-svg,
.home-row--beta .home-row__eyebrow-svg,
.home-row--faq .home-row__eyebrow-svg,
.home-row--about .home-row__eyebrow-svg {
  margin-left: auto;
  margin-right: auto;
}

/*
 * Radio antenna — signal waves pulse outward on a deliberately low,
 * step-based frame rate (~2.5 fps) so the motion reads as a tiny
 * blinking idle indicator instead of a smooth animation.
 *
 * Implementation: both side-waves share one keyframe track; each
 * keyframe range is held flat (no interpolation between them) so the
 * intermediate states snap rather than tween. The right wave starts a
 * beat behind the left so the two sides feel like a back-and-forth
 * exchange rather than firing in lockstep.
 *
 * Each wave fades from invisible -> partial -> full -> hold -> off,
 * then a brief gap before repeating. Scale + translate together push
 * the curve outward from the antenna mast so the wave reads as
 * propagating away from the source.
 */
.antenna__wave {
  /* transform-box: fill-box + transform-origin keeps the scale
   * pinned to the wave's own bounding box rather than the SVG's
   * viewBox origin (which would slide it across the icon). */
  transform-box: fill-box;
  transform-origin: center center;
  opacity: 0;
}
.antenna__wave--left {
  /* Anchor scale-out toward the left of the icon, and push the
   * pulse outward to the LEFT (negative X). */
  transform-origin: right center;
  --wave-dir: -1;
  animation: antenna-wave-pulse 1.6s steps(1, end) infinite;
}
.antenna__wave--right {
  /* Anchor scale-out toward the right and push outward to the RIGHT.
   * Shares the left wave's timing exactly so the two sides fire as
   * a single mirrored pulse. */
  transform-origin: left center;
  --wave-dir: 1;
  animation: antenna-wave-pulse 1.6s steps(1, end) infinite;
}

@keyframes antenna-wave-pulse {
  /* Each block below covers ~20% of the 1.6s cycle and uses two
   * keyframes (start, end-just-before-next) to hold the value flat
   * — combined with `steps(1, end)` above this gives a hard snap
   * between four discrete states per cycle (~2.5 fps feel).
   * --wave-dir flips the outward direction per-wave (-1 left, +1 right). */
  0%,   19.99% { opacity: 0;    transform: scale(0.35) translateX(0); }
  20%,  39.99% { opacity: 0.55; transform: scale(0.7)  translateX(calc(0.5px * var(--wave-dir, 1))); }
  40%,  59.99% { opacity: 1;    transform: scale(1)    translateX(calc(1px   * var(--wave-dir, 1))); }
  60%,  79.99% { opacity: 0.4;  transform: scale(1.1)  translateX(calc(2px   * var(--wave-dir, 1))); }
  80%, 100%    { opacity: 0;    transform: scale(0.35) translateX(0); }
}

/*
 * ─── Library: bouncing notes ─────────────────────────────────────────
 * Both note heads bob in mirror, slightly out of phase with the music
 * playing-vibe. Staff line stays anchored as the visual baseline.
 */
.notes__head {
  transform-box: fill-box;
  transform-origin: center center;
  animation: notes-bounce 1.4s steps(1, end) infinite;
}
.notes__head--left {
  /* Half-cycle offset so the two notes alternate up/down. */
  animation-delay: 0.7s;
}
@keyframes notes-bounce {
  0%,   24.99% { transform: translateY(0);     }
  25%,  49.99% { transform: translateY(-1.2px); }
  50%,  74.99% { transform: translateY(-2px);   }
  75%, 100%    { transform: translateY(-0.8px); }
}

/*
 * ─── Albums: note bounce on the album-list icon ──────────────────────
 * The icon is Iconoir's `album-list` — a stack of two album covers
 * with a tiny vinyl-and-tone-arm mark on the front cover. The vinyl
 * + arm are grouped as `.album-list__note` (circle = note head, arm =
 * stem) and bounce side to side in stepped frames. `step-end` timing
 * snaps between each position so the motion reads as a chunky
 * low-frame-rate sway — same low-fps animation grammar as the
 * envelope ping in the beta section.
 */
.album-list__note {
  transform-box: fill-box;
  transform-origin: center;
  animation: album-list-note-bounce 2.4s step-end infinite;
}
@keyframes album-list-note-bounce {
  /* Palindromic chevron — the note hops between only three positions
   * (lean L, apex up, lean R) so its trajectory traces an upward
   * chevron `^` each half-cycle. No bottom-centre rest frame, so
   * there's never a moment where it drops back to the baseline
   * between the leans and the apex. Plays identically forward or in
   * reverse. */
  0%   { transform: translate(-1.6px, 0); }   /* lean L */
  25%  { transform: translate(0, -1.4px); }   /* apex */
  50%  { transform: translate(1.6px, 0); }    /* lean R */
  75%  { transform: translate(0, -1.4px); }   /* apex */
  100% { transform: translate(-1.6px, 0); }   /* lean L (loops) */
}

/*
 * ─── Playlists: rolling line wave ────────────────────────────────────
 * Each of the three list lines pulses in sequence, like rows being
 * scrolled through. The staggered delays produce a slow wave from top
 * to bottom that cycles back. The repeat-mode note glyph stays solid
 * as the "currently playing" anchor.
 */
.playlist__line {
  animation: playlist-line-wave 1.8s steps(1, end) infinite;
}
.playlist__line--2 { animation-delay: 0.3s; }
.playlist__line--3 { animation-delay: 0.6s; }
@keyframes playlist-line-wave {
  0%,   24.99% { opacity: 0.55; }
  25%,  49.99% { opacity: 1;    }
  50%,  74.99% { opacity: 0.85; }
  75%, 100%    { opacity: 0.55; }
}

/*
 * ─── Search: magnifier scan ──────────────────────────────────────────
 * Subtle scale + opacity pulse on the loupe ring, like it's actively
 * sweeping. Origin pinned to the circle's centre in viewBox coords.
 */
.search__circle {
  transform-box: fill-box;
  transform-origin: center center;
  animation: search-scan 2s steps(1, end) infinite;
}
@keyframes search-scan {
  0%,   24.99% { transform: scale(1);    opacity: 1;    }
  25%,  49.99% { transform: scale(1.06); opacity: 0.8;  }
  50%,  74.99% { transform: scale(1.1);  opacity: 0.55; }
  75%, 100%    { transform: scale(1.04); opacity: 0.85; }
}

/*
 * ─── Offline: flickering wifi arcs ───────────────────────────────────
 * The three connection arcs flicker independently, suggesting a
 * dropped signal that won't quite reconnect. Slash + dot stay solid.
 */
.wifi__arc {
  animation: wifi-flicker 2s steps(1, end) infinite;
}
.wifi__arc--middle { animation-delay: 0.4s; }
.wifi__arc--inner  { animation-delay: 0.8s; }
@keyframes wifi-flicker {
  0%,   24.99% { opacity: 0.15; }
  25%,  49.99% { opacity: 0.75; }
  50%,  74.99% { opacity: 0.3;  }
  75%, 100%    { opacity: 0.1;  }
}

/*
 * ─── CTA: download arrow drop ────────────────────────────────────────
 * The tray stays static (it's the receiving device). The arrow appears
 * above it, drops in, fades out as it lands, and the cycle repeats —
 * reads as "downloading to your device" the way a real progress
 * affordance does. `transform-box: fill-box` keeps the translate origin
 * tied to the arrow's own bounding box instead of the SVG viewport.
 */
.download__arrow {
  transform-box: fill-box;
  transform-origin: center;
  /* `steps(12)` snaps the animation into 12 discrete positions over
   * the 2.4s cycle (~200ms per frame, ~5fps) so the arrow reads as
   * stop-motion rather than smoothly interpolated. Bump higher for
   * a smoother drop, lower for a chunkier one. */
  animation: download-arrow-drop 2.4s steps(12) infinite;
}
@keyframes download-arrow-drop {
  0%   { transform: translateY(-4px); opacity: 0; }
  15%  { transform: translateY(-4px); opacity: 1; }
  55%  { transform: translateY(2px);  opacity: 1; }
  70%  { transform: translateY(2px);  opacity: 0; }
  100% { transform: translateY(-4px); opacity: 0; }
}

/*
 * Beta envelope — "new mail" ping dot in the top-right of the envelope.
 * Fades and scales in once per ~2.6s cycle so it reads as a notification
 * arriving rather than a constant blink. The body + flap stay still so
 * the icon doesn't feel jittery.
 */
.envelope__ping {
  transform-box: fill-box;
  transform-origin: center;
  /* `step-end` timing makes each keyframe hold its value until the
   * next stop and then snap — no easing in between. Reads as a
   * chunky, low-frame-rate notification ping (think classic UI
   * sprites) instead of a smooth modern tween. */
  animation: envelope-ping 2.6s step-end infinite;
}
@keyframes envelope-ping {
  /* Each stop is a single discrete "frame". With step-end the dot
   * holds at each scale until the next stop and then jumps — the
   * grow-in reads as four punchy frames rather than a smooth pop. */
  0%, 30%  { opacity: 0; transform: scale(0);    }
  35%      { opacity: 1; transform: scale(0.4);  }
  42%      { opacity: 1; transform: scale(0.75); }
  48%      { opacity: 1; transform: scale(1.15); }
  55%      { opacity: 1; transform: scale(1);    }
  85%      { opacity: 0; transform: scale(1);    }
  100%     { opacity: 0; transform: scale(1);    }
}
/*
 * ─── FAQ: help-circle dot pulse ─────────────────────────────────────
 * The outer ring and the question-mark curve stay still — only the
 * dot beneath the "?" animates. A slow ease-in-out heartbeat that
 * brightens + scales the dot reads as the icon "thinking" or
 * inviting a question, matching the section's role as the page's
 * Q&A closer. Slower and more organic than the wifi/CTA dot ticks
 * because this is the only animation in a section that's otherwise
 * very still typography; a snappy step animation would feel out of
 * place against the smooth ::details-content accordion ease.
 */
.help__dot {
  transform-box: fill-box;
  transform-origin: center;
  animation: help-dot-pulse 2.6s ease-in-out infinite;
}
@keyframes help-dot-pulse {
  0%, 100% { opacity: 0.6; transform: scale(1);    }
  50%      { opacity: 1;   transform: scale(1.45); }
}

/*
 * ─── About: compass needle hunting for north ───────────────────────
 * The face (outer ring) stays still as the steady reference. Only
 * the diamond needle moves, bouncing between four discrete rotations
 * — overshoot left, correct right, overshoot right, correct left —
 * like a real compass needle searching for magnetic north and not
 * quite settling. The asymmetric angles (bigger overshoots, smaller
 * corrections) keep the motion from feeling like a mechanical
 * pendulum and read as "instrument hunting" instead.
 *
 * `steps(1, end)` snaps between the four positions with no in-between
 * interpolation, giving the same low-frame-rate discrete-tick feel
 * the antenna pulse and the Library notes bob use — they all share
 * a "real-world instrument, not a CSS animation" cadence. 2.4s
 * cycle (~1.7 fps) is slow enough to feel deliberate without losing
 * the sense of motion.
 */
.compass__needle {
  transform-box: fill-box;
  transform-origin: center;
  animation: about-compass-hunt 2.4s steps(1, end) infinite;
}
@keyframes about-compass-hunt {
  0%,   24.99% { transform: rotate(-24deg); }
  25%,  49.99% { transform: rotate(8deg);   }
  50%,  74.99% { transform: rotate(22deg);  }
  75%, 100%    { transform: rotate(-8deg);  }
}

@media (prefers-reduced-motion: reduce) {
  .antenna__wave,
  .notes__head,
  .album-list__note,
  .playlist__line,
  .search__circle,
  .wifi__arc,
  .download__arrow,
  .envelope__ping,
  .help__dot,
  .compass__needle {
    animation: none !important;
    opacity: 0.88;
    transform: none;
  }
}

.home-row__title {
  margin: 0 0 22px;
  font-family: "DM Sans", system-ui, -apple-system, sans-serif;
  font-size: clamp(40px, 5.4vw, 60px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  font-weight: 600;
}

.home-row__body {
  margin: 0 0 16px;
  max-width: 520px;
  font-size: clamp(16px, 1.4vw, 18px);
  line-height: 1.55;
  opacity: 0.85;
}
.home-row__copy > .home-row__body:last-child {
  margin-bottom: 0;
}

.home-row__art {
  display: flex;
  justify-content: center;
  align-items: center;
}

/* Phone frame (used in hero only now) --------------------------------- */
.home-row__phone {
  position: relative;
  display: block;
  width: min(300px, 70vw);
  aspect-ratio: 390 / 844;
  border-radius: 36px;
  overflow: hidden;
  background: #050507;
  box-shadow:
    0 30px 90px rgba(0, 0, 0, 0.45),
    0 8px 24px rgba(0, 0, 0, 0.35),
    inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
.home-row__phone img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ─────────────────────────────────────────────────────────────────────
 * Row 1: Hero — cream wash, type-driven, beta signup as CTA.
 * Compact: brand mark → wordmark → tagline → one-line lede → beta form
 *          → small "coming to App Store / Google Play" badge row.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--hero {
  --row-bg: linear-gradient(180deg, #f5efe6 0%, #efe6d8 100%);
  --row-fg: #16110b;
  /* Fill the viewport. svh is the small-viewport height — accounts for
   * Safari's collapsing address bar so the hero doesn't visibly resize
   * when the toolbar appears/disappears. vh is the fallback for browsers
   * without svh support. */
  min-height: 100vh;
  min-height: 100svh;
  /* Vertically center the inner content within the section. Flex is
   * scoped to the section level; the inner block keeps its own grid
   * layout. The asymmetric vertical padding (more on top) gives the
   * content a small optical lift to compensate for the visual weight of
   * the nav above it. */
  display: flex;
  align-items: center;
  justify-content: center;
  padding-top: clamp(96px, 11vw, 132px);
  padding-bottom: clamp(72px, 9vw, 112px);
}
.home-row--hero .home-row__inner {
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
  gap: 28px;
  max-width: 680px;
}
.home-row--hero .home-row__title {
  margin: 0;
  font-size: clamp(48px, 7vw, 80px);
  line-height: 0.98;
  font-weight: 700;
}
.home-row--hero .home-row__title small {
  display: block;
  margin-top: 10px;
  font-size: clamp(18px, 2.2vw, 24px);
  font-weight: 500;
  letter-spacing: -0.015em;
  color: rgba(22, 17, 11, 0.7);
}
.home-row--hero .home-row__logo {
  width: 64px;
  height: 64px;
  margin: 0 auto;
  display: block;
  border-radius: 16px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}

/* Quiet beta status badge — small outlined pill that links down to
 * the real signup form in the CTA section. Sits above the app icon
 * as the first thing in the hero stack so visitors see "beta opening
 * soon" before the wordmark. Reads as a status indicator, not a CTA. */
.home-row__hero-badge {
  display: inline-flex;
  align-items: center;
  margin: 0 0 18px;
  padding: 7px 14px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(22, 17, 11, 0.72);
  text-decoration: none;
  background: rgba(255, 251, 244, 0.4);
  border: 1px solid rgba(22, 17, 11, 0.14);
  border-radius: 999px;
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  transition:
    background 200ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 200ms cubic-bezier(0.22, 1, 0.36, 1),
    color 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.home-row__hero-badge:hover {
  /* Override the global `a:hover { text-decoration: underline }`
   * — only the surface treatment shifts on hover. */
  text-decoration: none;
  color: rgba(22, 17, 11, 0.92);
  background: rgba(255, 251, 244, 0.7);
  border-color: rgba(22, 17, 11, 0.24);
}
.home-row__hero-badge-label {
  /* Pull the wide letter-spacing's trailing space back so the label
   * sits optically centred inside the pill. */
  margin-right: -0.14em;
}

/* Hero ambient aurora -------------------------------------------------
 *
 * Five large blurred warm-tone blobs drift slowly behind the wordmark
 * to give the hero a soft, premium atmosphere — no literal imagery,
 * just colour. Each blob has its own slow translate animation off
 * a different timing so the mesh never repeats in a way the eye can
 * track. Blur on each blob (instead of on the wrapper) keeps the
 * GPU cost flat — every blob renders into its own layer once and is
 * then just translated.
 *
 * The wrapper is inset slightly negatively so blur halos don't get
 * cut at the hero edges. overflow: hidden clips anything that drifts
 * past the section so the aurora can't bleed into the next row.
 */
.home-row__hero-aurora {
  position: absolute;
  inset: -40px;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
  z-index: 0;
}
.home-row__hero-aurora-blob {
  position: absolute;
  display: block;
  border-radius: 50%;
  /* Heavy per-blob blur produces a soft mesh when the blobs overlap.
   * 60px is a sweet spot for performance vs. how mushy the colour
   * field feels — much higher and the GPU starts working hard, much
   * lower and the blobs read as discrete circles. */
  filter: blur(60px);
  opacity: 0.55;
  will-change: transform;
}
/* Blob palette chosen to harmonise with the hero's cream-to-cream
 * base (#f5efe6 → #efe6d8). Warm peach + rose + amber stay in the
 * same tonal family so the aurora reads as "the cream subtly
 * glowing" rather than five coloured stains. */
.home-row__hero-aurora-blob--1 {
  width: 560px; height: 560px;
  top: -12%; left: -8%;
  background: #ffd4b5; /* peach */
  animation: aurora-drift-1 38s ease-in-out infinite alternate;
}
.home-row__hero-aurora-blob--3 {
  width: 620px; height: 620px;
  bottom: -18%; left: 18%;
  background: #f4d2a0; /* warm amber */
  animation: aurora-drift-3 52s ease-in-out infinite alternate;
  opacity: 0.45;
}
.home-row__hero-aurora-blob--5 {
  width: 520px; height: 520px;
  top: 30%; left: 32%;
  background: #fae0c0; /* warm cream */
  animation: aurora-drift-5 58s ease-in-out infinite alternate;
  opacity: 0.5;
}

/* Each drift keyframe has a unique direction + magnitude so the
 * blobs never lock into a synchronised pattern. ease-in-out
 * alternate gives a smooth back-and-forth without sudden snaps. */
@keyframes aurora-drift-1 {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(60px, 40px, 0) scale(1.08); }
}
@keyframes aurora-drift-3 {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(80px, -60px, 0) scale(0.95); }
}
@keyframes aurora-drift-5 {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(40px, 60px, 0) scale(1.12); }
}

@media (prefers-reduced-motion: reduce) {
  .home-row__hero-aurora-blob {
    animation: none;
  }
}

/* Hero inner content sits above the aurora (z 0) so the wordmark +
 * CTA always read crisply against the soft colour field. */
.home-row--hero .home-row__inner {
  position: relative;
  z-index: 2;
}

/* ─── macOS app-shell preview in the hero ──────────────────────────────
 * Two floating glass panels flank the hero: the Library sidebar on the
 * left, the Up Next queue on the right. Both rebuilt from the same
 * .relay-glass primitives the macOS app uses (face / emboss / flare are
 * real DOM layers — see /relay-glass.css), in the LIGHT variant so they
 * read as warm translucent cream against the hero aurora.
 *
 * Purely decorative:
 *   - aria-hidden + pointer-events: none keeps them out of tab order
 *     and never blocks the CTA / wordmark in the centre.
 *   - sit at z-index 1 (above aurora @ 0, below hero copy @ 2).
 *   - hugged to viewport edges (left/right: 12px) so they read as real
 *     app chrome attached to the window.
 *   - hidden below 1180px so phones / tablets keep the clean hero.
 * ─────────────────────────────────────────────────────────────────── */

.home-row__hero-shell {
  position: absolute;
  /* Lock the four sides onto the same gutters the floating nav header
   * uses (see styles.css :root). Top sits `--site-header-float-inset`
   * (30px) below the nav, bottom mirrors that same inset, and the
   * left/right rules below pin the panels' outer edges to the nav's
   * outer edges — so the whole hero reads as a single piece of chrome.*/
  top: calc(var(--site-header-stack-height) + var(--site-header-float-inset));
  bottom: var(--site-header-float-inset);
  z-index: 1;
  pointer-events: none;
  color: #1a1814;
  font-family: inherit;
  /* Subtle drop / pop animation on mount so the panels feel like they
   * sail in alongside the wordmark instead of just snapping into view. */
  opacity: 0;
  transform: translateY(8px);
  animation: hero-shell-rise 900ms cubic-bezier(0.22, 1, 0.36, 1) 180ms forwards;
}

.home-row__hero-shell--sidebar {
  left: max(var(--site-header-float-inset), env(safe-area-inset-left));
  width: 260px;
}

.home-row__hero-shell--upnext {
  right: max(var(--site-header-float-inset), env(safe-area-inset-right));
  width: 312px;
  animation-delay: 280ms;
}

@keyframes hero-shell-rise {
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .home-row__hero-shell {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 1180px) {
  .home-row__hero-shell { display: none; }
}

/* ── Sidebar ────────────────────────────────────────────────────────── */
.hero-sidebar {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 0 8px 16px;
  height: 100%;
  overflow: hidden;
  /* Soft fade at the bottom so the list reads as scrollable chrome
   * even though it isn't actually scrolling. */
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 32px), transparent 100%);
  mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 32px), transparent 100%);
}

.hero-sidebar__titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 6px 10px;
}

.hero-sidebar__logo {
  width: 22px;
  height: 22px;
  display: block;
  border-radius: 5px;
}

.hero-sidebar__settings {
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  color: rgba(26, 24, 20, 0.55);
}

.hero-sidebar__search {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 10px;
  margin: 0 4px 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.55);
  border: 0.5px solid rgba(26, 24, 20, 0.08);
  box-shadow:
    inset 0 0.5px 0 rgba(255, 255, 255, 0.7),
    0 1px 1.5px rgba(26, 24, 20, 0.04);
  color: rgba(26, 24, 20, 0.45);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: -0.005em;
}

.hero-sidebar__section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 10px 4px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: rgba(26, 24, 20, 0.55);
}

.hero-sidebar__section-sort {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 11px;
  font-weight: 500;
  color: rgba(26, 24, 20, 0.45);
}

.hero-sidebar__nav-tile {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 10px;
  margin: 0 4px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: #1a1814;
  /* Truncate long playlist names with an ellipsis instead of wrapping. */
  white-space: nowrap;
  overflow: hidden;
}

.hero-sidebar__nav-tile svg {
  flex-shrink: 0;
  color: rgba(26, 24, 20, 0.78);
}

.hero-sidebar__nav-tile span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hero-sidebar__nav-tile--selected,
.hero-sidebar__nav-tile--selected svg {
  color: #ee2a4d;
}

.hero-sidebar__nav-tile--add {
  color: rgba(26, 24, 20, 0.78);
  font-weight: 500;
  margin-top: 4px;
}

.hero-sidebar__nav-tile--add svg {
  color: rgba(26, 24, 20, 0.6);
}

/* ── Up Next ────────────────────────────────────────────────────────── */
.hero-upnext {
  display: flex;
  flex-direction: column;
  padding: 0 0 10px;
  height: 100%;
  overflow: hidden;
}

.hero-upnext__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 14px 14px 10px;
  border-bottom: 0.5px solid rgba(26, 24, 20, 0.10);
}

.hero-upnext__title {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: #1a1814;
}

.hero-upnext__count {
  margin-top: 2px;
  font-size: 12px;
  font-weight: 500;
  color: rgba(26, 24, 20, 0.55);
}

.hero-upnext__dismiss {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  color: rgba(26, 24, 20, 0.7);
}

.hero-upnext__list {
  list-style: none;
  margin: 0;
  padding: 8px 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  /* Bottom mask fades the visible queue into the Clear / Save footer
   * so the list reads as scrollable chrome. */
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 24px), transparent 100%);
  mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 24px), transparent 100%);
}

/* All rows share the same DOM (relay-glass base + face/emboss/flare
 * layers). The brand-red treatment is faded in via opacity when the
 * row carries the `--now` class — JS just toggles that class on
 * advance / skip, no DOM swap, so the transition is smooth. */
.hero-upnext__row {
  position: relative;
  border-radius: 10px;
  margin: 1px 0;
  transition: box-shadow 220ms ease;
}

.hero-upnext__row-inner {
  display: grid;
  grid-template-columns: 14px 18px 32px 1fr;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  color: inherit;
}

/* Hide the relay-red glass layers on non-active rows so the row reads
 * as a plain transparent list item; .hero-upnext__row--now fades them
 * back in. The backdrop layer stays subtle and out of the way. */
.hero-upnext__row .relay-glass__backdrop {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.hero-upnext__row .relay-glass__face,
.hero-upnext__row .relay-glass__emboss,
.hero-upnext__row .relay-glass__flare {
  opacity: 0;
  transition: opacity 220ms ease;
}

/* Now-playing row: brand-red glass treatment matching the macOS app
 * (relay-glass.css :: __face--relay-red). White text inside the
 * .__content surface and a soft red floating shadow. */
.hero-upnext__row--now .relay-glass__face,
.hero-upnext__row--now .relay-glass__emboss,
.hero-upnext__row--now .relay-glass__flare {
  opacity: 1;
}

.hero-upnext__row--now {
  box-shadow:
    0 6px 18px rgba(255, 60, 90, 0.32),
    0 2px 6px rgba(255, 60, 90, 0.18);
}

.hero-upnext__row--now .hero-upnext__row-inner { color: #fff; }
.hero-upnext__row--now .hero-upnext__drag::before,
.hero-upnext__row--now .hero-upnext__drag::after {
  background: rgba(255, 255, 255, 0.55);
}
.hero-upnext__row--now .hero-upnext__index { color: rgba(255, 255, 255, 0.85); }
.hero-upnext__row--now .hero-upnext__meta,
.hero-upnext__row--now .hero-upnext__meta strong { color: #fff; }

/* Stacked-line drag-handle glyph (two hairline rules), built without an
 * SVG so it stays crisp at 14px and inherits the row's text colour. */
.hero-upnext__drag {
  position: relative;
  width: 14px;
  height: 14px;
  opacity: 0.55;
}

.hero-upnext__drag::before,
.hero-upnext__drag::after {
  content: "";
  position: absolute;
  left: 1px;
  right: 1px;
  height: 1.5px;
  border-radius: 999px;
  background: rgba(26, 24, 20, 0.78);
}

.hero-upnext__drag::before { top: 4px; }
.hero-upnext__drag::after  { top: 8.5px; }

.hero-upnext__index {
  font-size: 11.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: rgba(26, 24, 20, 0.5);
}

.hero-upnext__art {
  width: 32px;
  height: 32px;
  border-radius: 5px;
  display: block;
  /* Cover sized in the inline `style="background-image:url(...)"` on
   * each row — JS in home-rows.js reads `data-art` on the row and
   * keeps both this thumb and the mini-player below in sync. */
  background-size: cover;
  background-position: center;
  background-color: rgba(26, 24, 20, 0.08);
  box-shadow:
    inset 0 0 0 0.5px rgba(0, 0, 0, 0.08),
    0 1px 1.5px rgba(0, 0, 0, 0.12);
}

.hero-upnext__meta {
  min-width: 0;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: rgba(26, 24, 20, 0.78);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hero-upnext__meta strong {
  font-weight: 600;
  color: #1a1814;
}

.hero-upnext__footer {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 8px;
  padding: 10px 12px 14px;
  border-top: 0.5px solid rgba(26, 24, 20, 0.10);
}

.hero-upnext__btn {
  appearance: none;
  border: none;
  height: 30px;
  padding: 0 14px;
  border-radius: 999px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  cursor: default;
}

.hero-upnext__btn--outline {
  background: rgba(255, 255, 255, 0.55);
  border: 0.5px solid rgba(26, 24, 20, 0.18);
  color: #1a1814;
  box-shadow:
    inset 0 0.5px 0 rgba(255, 255, 255, 0.7),
    0 1px 1.5px rgba(26, 24, 20, 0.04);
}

.hero-upnext__btn--solid {
  background: linear-gradient(180deg, #ff4e6c 0%, #ee2a4d 100%);
  color: #fff;
  box-shadow:
    inset 0 0.5px 0 rgba(255, 255, 255, 0.42),
    0 1px 2.5px rgba(238, 42, 77, 0.32);
}

/* Now Playing widget ---------------------------------------------------
 *
 * PEELABLE LAYER — to remove this widget entirely:
 *   1. Delete the .home-row__hero-now-playing element from index.html
 *   2. Delete the initHeroNowPlaying() call + function in home-rows.js
 *   3. Delete this CSS block
 * The aurora background above will stand on its own.
 *
 * Sits at the bottom of the hero copy stack as a Liquid Glass mini
 * player — modeled on macOS Tahoe's translucent player chrome.
 * Cycles through real album/track/artist tuples every ~10s with a
 * crossfade, and exposes prev/play/next/queue affordances so the
 * widget reads as the actual product (not just a marketing badge).
 *
 * The "Liquid Glass" effect is the combination of a high-blur,
 * saturated, slightly brightened backdrop-filter with a soft top
 * highlight (inset top border) + dropped underglow — picks up colour
 * from the aurora behind it instead of sitting on the page as a
 * hard rectangle.
 */
/* Register four aura colours as animatable custom properties. Plain
 * CSS variables don't interpolate between keyframes — they snap at
 * each stop — but @property-registered variables tween smoothly
 * because the browser knows their type. Four slots (A/B/C/D) drive
 * four box-shadow layers anchored at top/right/bottom/left around the
 * pill. Each holds one of four Apple-Intelligence-style hues
 * (blue/purple/pink/orange), and the keyframes rotate the hues
 * clockwise from slot to slot, so all four colours are always visible
 * around the pill while the rainbow slowly spins. Browsers without
 * @property support (pre-Safari 16.4 etc.) still get the animation;
 * it just steps between colours instead of easing through them. */
@property --hero-aura-a {
  syntax: "<color>";
  inherits: false;
  initial-value: rgba( 80, 200, 120, 0.30);
}
@property --hero-aura-b {
  syntax: "<color>";
  inherits: false;
  initial-value: rgba( 95, 150, 255, 0.30);
}
@property --hero-aura-c {
  syntax: "<color>";
  inherits: false;
  initial-value: rgba(255, 220,  90, 0.30);
}
@property --hero-aura-d {
  syntax: "<color>";
  inherits: false;
  initial-value: rgba(255, 110, 170, 0.30);
}

.home-row__hero-now-playing {
  /* Floats at the bottom edge of the hero (matches the macOS app, where
   * the mini-player chrome lives just above the window's bottom border)
   * rather than tagged onto the end of the wordmark stack. Sits above
   * the flanking sidebar / Up Next panels (z 1) so when those reach to
   * the same bottom inset they read as background chrome and the player
   * stays the foreground element. */
  position: absolute;
  left: 50%;
  bottom: var(--site-header-float-inset);
  transform: translateX(-50%);
  margin: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 14px;
  /* Extra left padding inside the pill so the album art sits inset
   * from the curve with breathing room (not pressed against the
   * left edge). The right side stays tighter — the last control
   * button has its own internal padding and reads as visually
   * balanced when the leading edge is bumped in. */
  padding: 8px 14px 8px 18px;
  /* Static width per breakpoint (overridden in the media queries
   * below) — doesn't fluidly resize with the container, just snaps
   * between sizes. max-width protects against viewports narrower
   * than the smallest tier. */
  width: 580px;
  max-width: 100%;
  text-align: left;
  /* Full pill — radius larger than half the card's height so the
   * left/right edges form perfect semicircles regardless of how the
   * card's content reflows. */
  border-radius: 999px;
  /* Liquid Glass surface: a slight top-to-bottom brightness gradient
   * on top of a heavy frosted blur so the pill reads as a polished,
   * lit piece of glass rather than a flat translucent rectangle. */
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.55) 0%,
    rgba(255, 255, 255, 0.32) 100%
  );
  border: 1px solid rgba(255, 255, 255, 0.55);
  /* Starting aura colours — picked up by the four box-shadow layers
   * below (top/right/bottom/left around the pill) and rotated by
   * hero-aura-rotate through a six-hue palette (green, blue, yellow,
   * pink, orange, red) so every primary except indigo eventually
   * passes through every slot. */
  --hero-aura-a: rgba( 80, 200, 120, 0.30);
  --hero-aura-b: rgba( 95, 150, 255, 0.30);
  --hero-aura-c: rgba(255, 220,  90, 0.30);
  --hero-aura-d: rgba(255, 110, 170, 0.30);
  box-shadow:
    /* AURA — four coloured glows anchored at top / right / bottom /
     * left around the pill. At any moment four of the six hues are
     * showing in the four slots; the keyframes shift them one slot
     * clockwise per stop so over a full cycle every colour visits
     * every position and the eye reads a continuous rainbow slowly
     * orbiting the pill. Lower alpha + slightly tighter spread than
     * before so the halo is felt more than seen — the goal is a
     * faint shimmer, not a coloured ring. */
    0 -22px 56px -10px var(--hero-aura-a),
    22px 0  56px -10px var(--hero-aura-b),
    0  22px 56px -10px var(--hero-aura-c),
    -22px 0 56px -10px var(--hero-aura-d),
    /* No dark underglow: the warm brown drop-shadow we used to stack
     * here (`0 24px 60px -16px rgba(40,28,12,…)`) muddied the bottom
     * aura colour and visibly cut into the rainbow ring. The aura
     * itself supplies enough off-page-glow to imply floating; the
     * inset highlight below carries the glass curvature. */
    /* Inset top highlight — the specular line that makes glass read
     * as glass; subtle bottom reflection completes the curvature. */
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 -1px 0 rgba(255, 255, 255, 0.35);
  -webkit-backdrop-filter: blur(42px) saturate(180%) brightness(1.04);
          backdrop-filter: blur(42px) saturate(180%) brightness(1.04);
  /* Single rotation animation — colours shift one slot clockwise per
   * keyframe stop. Faster cadence (was 20s) so the rainbow feels
   * alive rather than ambient; linear timing keeps the rotation
   * steady (no breathing pauses). */
  animation: hero-aura-rotate 13s linear infinite;
}

@keyframes hero-aura-rotate {
  /* Six-hue palette — green / blue / yellow / pink / orange / red —
   * shifted one slot clockwise per keyframe stop. Four slots × six
   * colours means each stop drops one colour off the trailing edge
   * and brings the next one in on the leading edge, so over a full
   * cycle every hue spends time in every position and the halo
   * never lands on the same combination twice. The interpolation
   * between stops makes each slot's colour appear to slide into the
   * next hue rather than crossfade in place. */
  0%, 100% {
    --hero-aura-a: rgba( 80, 200, 120, 0.30); /* top    — green  */
    --hero-aura-b: rgba( 95, 150, 255, 0.30); /* right  — blue   */
    --hero-aura-c: rgba(255, 220,  90, 0.30); /* bottom — yellow */
    --hero-aura-d: rgba(255, 110, 170, 0.30); /* left   — pink   */
  }
  16.66% {
    --hero-aura-a: rgba( 95, 150, 255, 0.30); /* top    — blue   */
    --hero-aura-b: rgba(255, 220,  90, 0.30); /* right  — yellow */
    --hero-aura-c: rgba(255, 110, 170, 0.30); /* bottom — pink   */
    --hero-aura-d: rgba(255, 140,  70, 0.30); /* left   — orange */
  }
  33.33% {
    --hero-aura-a: rgba(255, 220,  90, 0.30); /* top    — yellow */
    --hero-aura-b: rgba(255, 110, 170, 0.30); /* right  — pink   */
    --hero-aura-c: rgba(255, 140,  70, 0.30); /* bottom — orange */
    --hero-aura-d: rgba(229,  60,  80, 0.30); /* left   — red    */
  }
  50% {
    --hero-aura-a: rgba(255, 110, 170, 0.30); /* top    — pink   */
    --hero-aura-b: rgba(255, 140,  70, 0.30); /* right  — orange */
    --hero-aura-c: rgba(229,  60,  80, 0.30); /* bottom — red    */
    --hero-aura-d: rgba( 80, 200, 120, 0.30); /* left   — green  */
  }
  66.66% {
    --hero-aura-a: rgba(255, 140,  70, 0.30); /* top    — orange */
    --hero-aura-b: rgba(229,  60,  80, 0.30); /* right  — red    */
    --hero-aura-c: rgba( 80, 200, 120, 0.30); /* bottom — green  */
    --hero-aura-d: rgba( 95, 150, 255, 0.30); /* left   — blue   */
  }
  83.33% {
    --hero-aura-a: rgba(229,  60,  80, 0.30); /* top    — red    */
    --hero-aura-b: rgba( 80, 200, 120, 0.30); /* right  — green  */
    --hero-aura-c: rgba( 95, 150, 255, 0.30); /* bottom — blue   */
    --hero-aura-d: rgba(255, 220,  90, 0.30); /* left   — yellow */
  }
}

@media (prefers-reduced-motion: reduce) {
  .home-row__hero-now-playing {
    animation: none;
    /* Hold a static slice of the six-hue palette so the pill still
     * picks up four colours around its perimeter — just not moving. */
    --hero-aura-a: rgba( 80, 200, 120, 0.24); /* top    — green  */
    --hero-aura-b: rgba( 95, 150, 255, 0.24); /* right  — blue   */
    --hero-aura-c: rgba(255, 220,  90, 0.24); /* bottom — yellow */
    --hero-aura-d: rgba(255, 110, 170, 0.24); /* left   — pink   */
  }
}

.home-row__hero-now-playing-art {
  position: relative;
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  /* Rounded square — keeps the album art reading as a square cover
   * (not a circular avatar) while still feeling soft inside the pill. */
  border-radius: 12px;
  overflow: hidden;
  background-color: #d8cfbf;
  box-shadow:
    0 6px 14px -4px rgba(40, 28, 12, 0.28),
    inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}
.home-row__hero-now-playing-art-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Crossfade managed by toggling a class from JS — the image swap
   * is masked by the opacity transition. */
  transition: opacity 360ms cubic-bezier(0.22, 1, 0.36, 1);
}
.home-row__hero-now-playing-art-img[data-fading] {
  opacity: 0;
}

/* Middle column — meta text + scrubber stacked. */
.home-row__hero-now-playing-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
}
.home-row__hero-now-playing-meta {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.home-row__hero-now-playing-track {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: #16110b;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity 360ms cubic-bezier(0.22, 1, 0.36, 1);
}
.home-row__hero-now-playing-artist {
  font-size: 12px;
  font-weight: 500;
  color: rgba(22, 17, 11, 0.55);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity 360ms cubic-bezier(0.22, 1, 0.36, 1);
}
.home-row__hero-now-playing-track[data-fading],
.home-row__hero-now-playing-artist[data-fading] {
  opacity: 0;
}
/* Scrubber — thin track with a soft white draggable-looking thumb at
 * the leading edge of the filled portion. Purely visual; we don't
 * wire up seeking, but the thumb sells the "real player" idea. The
 * thumb lives on the fill's ::after so it travels with the fill
 * without needing a second JS-positioned element. */
.home-row__hero-now-playing-progress {
  position: relative;
  height: 4px;
  border-radius: 999px;
  background: rgba(22, 17, 11, 0.12);
  /* No overflow:hidden — the thumb needs to sit outside the track
   * vertically and is allowed to peek slightly past the fill's
   * right edge horizontally. */
}
.home-row__hero-now-playing-progress-fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: inherit;
  background: linear-gradient(90deg, #ef3a64 0%, #d61543 100%);
  position: relative;
}
.home-row__hero-now-playing-progress-fill::after {
  content: "";
  position: absolute;
  right: -5px;
  top: 50%;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #ffffff;
  transform: translateY(-50%);
  box-shadow:
    0 1px 3px rgba(40, 28, 12, 0.3),
    0 0 0 1px rgba(40, 28, 12, 0.04);
}

/* Right-side transport controls — small icon buttons in a row. The
 * play/pause button is slightly visually emphasised through icon
 * weight rather than chrome (matches Tahoe's flat-on-glass treatment;
 * no pill background on the active control). */
.home-row__hero-now-playing-controls {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 2px;
}
.home-row__hero-now-playing-control {
  appearance: none;
  background: transparent;
  border: 0;
  margin: 0;
  padding: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: rgba(22, 17, 11, 0.78);
  cursor: pointer;
  transition:
    background 160ms cubic-bezier(0.22, 1, 0.36, 1),
    color 160ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
.home-row__hero-now-playing-control:hover {
  background: rgba(22, 17, 11, 0.06);
  color: #16110b;
}
.home-row__hero-now-playing-control:active {
  transform: scale(0.92);
  background: rgba(22, 17, 11, 0.1);
}
.home-row__hero-now-playing-control:focus-visible {
  outline: 2px solid rgba(239, 58, 100, 0.55);
  outline-offset: 2px;
}
.home-row__hero-now-playing-control-icon {
  width: 16px;
  height: 16px;
  display: block;
}
.home-row__hero-now-playing-control--play .home-row__hero-now-playing-control-icon {
  width: 18px;
  height: 18px;
}
/* Pause/play icon swap — only one is shown at a time. The play
 * variant is hidden by default since cycling starts on load; the JS
 * toggles [data-paused] on the button to flip them. */
.home-row__hero-now-playing-control-icon--play {
  display: none;
}
.home-row__hero-now-playing-control--play[data-paused]
  .home-row__hero-now-playing-control-icon--pause {
  display: none;
}
.home-row__hero-now-playing-control--play[data-paused]
  .home-row__hero-now-playing-control-icon--play {
  display: block;
}

/* Tablet — snap to a narrower static width so the pill doesn't
 * leave the section feeling lopsided when the hero copy column
 * itself is narrower. */
@media (max-width: 900px) {
  .home-row__hero-now-playing {
    width: 500px;
  }
}
/* Narrow phones: smaller static width, tighter gutters so all four
 * controls + the meta still fit comfortably; the meta column
 * truncates with ellipsis if a track/artist name is long. Left
 * padding stays slightly larger than the right so the album art
 * keeps some breathing room from the curve. */
@media (max-width: 600px) {
  .home-row__hero-now-playing {
    width: 380px;
    padding-left: 10px;
    padding-right: 6px;
    gap: 10px;
  }
  .home-row__hero-now-playing-control {
    width: 28px;
    height: 28px;
  }
}

/* Beta perks ----------------------------------------------------------
 * Short bullet list sitting between the body copy and the email-pill
 * form, surfacing the value of joining the beta (early builds, dev
 * access, roadmap influence, lifetime discount). Sized to mirror the
 * form's `max-width: 480px` so the column reads as one CTA card.
 * The Relay-red check icons reuse `currentColor` so the colour is
 * controlled by the wrapper rule and stays consistent if we ever
 * theme the section. */
.home-beta__perks {
  width: 100%;
  /* Matches `.home-row--beta .home-row__inner` so the 2-up grid has
   * enough room for each perk to sit on a single line. The form
   * below stays narrower (520px) on purpose -- the perks visually
   * frame it. */
  max-width: 640px;
  margin: 14px auto 18px;
  padding: 0;
  list-style: none;
  display: grid;
  /* 2x2 above ~480px container, single column on narrower viewports. */
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 10px 24px;
}

.home-beta__perk {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.95rem;
  line-height: 1.45;
  /* Light text matching the body copy on the saturated beta gradient. */
  color: rgba(255, 255, 255, 0.95);
  /* Force each perk onto a single typographic line: the bullet copy
   * is intentionally short, and the 2-up grid loses its rhythm the
   * moment one item wraps to two lines while its neighbour stays at
   * one (the "messed-up" look). If a future copy edit overflows the
   * cell, fix the copy rather than letting it wrap silently. */
  white-space: nowrap;
}

.home-beta__perk-icon {
  flex: 0 0 auto;
  /* Was Relay-red (#ff3c5a) when the section was cream. The red
   * gets lost against the pink end of the new teal->pink gradient,
   * so the check uses the same light token as the bullet text. */
  color: rgba(255, 255, 255, 0.95);
}

/* Beta signup pill ----------------------------------------------------- */
.home-beta-form {
  width: 100%;
  max-width: 480px;
  margin: 8px auto 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}
.home-beta-form__pill {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px;
  border-radius: 999px;
  background: #16110b;
  box-shadow:
    0 12px 32px rgba(22, 17, 11, 0.18),
    inset 0 0 0 1px rgba(255, 255, 255, 0.04);
  transition: box-shadow 0.2s ease;
}
/* `display: flex` above wins over the user-agent `[hidden] { display: none }` */
/* rule, so we have to re-assert it here for the post-submit success state.   */
.home-beta-form__pill[hidden] {
  display: none;
}
.home-beta-form__pill:focus-within {
  box-shadow:
    0 16px 36px rgba(22, 17, 11, 0.24),
    inset 0 0 0 1.5px rgba(255, 60, 90, 0.7);
}
.home-beta-form__pill .hero-notify__input {
  flex: 1;
  min-width: 0;
  appearance: none;
  background: transparent;
  border: 0;
  padding: 12px 18px;
  color: #f5efe6;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.005em;
  outline: 0;
}
.home-beta-form__pill .hero-notify__input::placeholder {
  color: rgba(245, 239, 230, 0.45);
  font-weight: 500;
}
.home-beta-form__pill .hero-notify__btn {
  appearance: none;
  border: 0;
  padding: 12px 20px;
  border-radius: 999px;
  background: linear-gradient(180deg, #ff3c5a 0%, #e51d3c 100%);
  color: #fff;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  white-space: nowrap;
  cursor: pointer;
  transition: transform 0.15s ease, filter 0.15s ease, opacity 0.15s ease;
  box-shadow:
    0 4px 14px rgba(229, 29, 60, 0.36),
    inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
.home-beta-form__pill .hero-notify__btn:hover:not(:disabled) {
  filter: brightness(1.05);
  transform: translateY(-1px);
}
.home-beta-form__pill .hero-notify__btn:active:not(:disabled) {
  transform: translateY(0);
}
.home-beta-form__pill .hero-notify__btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  background: rgba(245, 239, 230, 0.14);
  box-shadow: inset 0 0 0 1px rgba(245, 239, 230, 0.08);
}

/* Post-submit "Sent" state: button stays a solid dark chip (matches the */
/* pill background) so the form reads as completed rather than greyed-out */
/* into mush.                                                            */
.home-beta-form--sent .home-beta-form__pill .hero-notify__btn:disabled {
  opacity: 1;
  cursor: default;
  color: rgba(245, 239, 230, 0.85);
  background: rgba(245, 239, 230, 0.08);
  box-shadow: inset 0 0 0 1px rgba(245, 239, 230, 0.16);
}
.home-beta-form--sent .home-beta-form__pill .hero-notify__input:read-only {
  color: rgba(245, 239, 230, 0.85);
}

.home-beta-form__hint {
  margin: 0;
  font-size: 13px;
  color: rgba(22, 17, 11, 0.55);
  letter-spacing: -0.005em;
  transition: color 180ms ease;
}

/* After a successful submit the hint text doubles as the confirmation    */
/* copy; nudge the color a touch darker so it reads as "this is the new   */
/* status", not just placeholder fine print.                              */
.home-beta-form--sent .home-beta-form__hint {
  color: rgba(22, 17, 11, 0.72);
}

/* Inline status message rendered after a *failed* async submit. Stays  */
/* empty (and invisible) until the script writes an error string.       */
.home-beta-form__feedback {
  margin: 14px auto 0;
  max-width: 460px;
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.45;
  letter-spacing: -0.005em;
  text-align: center;
  border: 1px solid transparent;
  background: transparent;
  color: rgba(22, 17, 11, 0.7);
  opacity: 0;
  transform: translateY(-2px);
  transition:
    opacity 180ms ease,
    transform 220ms ease,
    background-color 180ms ease,
    border-color 180ms ease;
  pointer-events: none;
}

.home-beta-form__feedback[data-shown] {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.home-beta-form__feedback--error {
  background: rgba(214, 21, 67, 0.08);
  border-color: rgba(214, 21, 67, 0.28);
  color: #b3142f;
}
/* Small footnote-style disclaimer that sits between the platform list and */
/* the beta form. Bottom margin gives the form breathing room below it. */
.home-cta-disclaimer {
  margin: 18px auto 60px;
  max-width: 460px;
  font-size: 12px;
  line-height: 1.55;
  color: rgba(22, 17, 11, 0.5);
  letter-spacing: 0;
  text-align: center;
}

/* CTA platform poster — Helvetica-stacked device list, left-aligned
 * inside a centered inline-block so the column rags clean on the left
 * while the block centers in the section.
 *
 * Mouse-follow gradient
 * ---------------------
 * On hover, a Relay-red radial gradient is painted as the element's
 * background and clipped to the text glyphs via background-clip: text.
 * JS (home-rows.js → initPlatformsWave) tracks the pointer and writes
 * the cursor's local coordinates into --mx / --my, so the gradient
 * center moves with the mouse. Outside the radius the gradient fades
 * to the base dark color so the rest of the type stays legible.
 *
 * The fallback --mx / --my of -9999px parks the gradient far outside
 * the element when the cursor isn't over it, so the text reads as
 * normal black without any visible wave until you hover. */
.home-cta-platforms {
  display: inline-block;
  margin: 24px 0 36px;
  font-family: "Helvetica Neue", Helvetica, "DM Sans", Arial, sans-serif;
  font-size: clamp(48px, 9vw, 100px);
  font-weight: 700;
  line-height: 0.96;
  letter-spacing: -0.04em;
  color: #16110b;
  text-align: left;
  /* Cursor stays as the arrow even though we're over text — the wave is
   * decorative, not interactive. */
  cursor: default;
  /*
   * Iridescent "liquid" wave
   * ------------------------
   * Instead of one solid red disc, we layer four radial gradients with
   * different colors and slight chromatic offsets from the cursor. They
   * blend into each other through their soft transparent stops so the
   * type reads like oil-slick / soap-bubble film catching light as the
   * pointer passes through it.
   *
   *   1. Hot near-white core   — small specular highlight at the cursor
   *   2. Cyan ghost            — offset up-left, like a refracted edge
   *   3. Magenta ghost         — offset down-right, the other split
   *   4. Warm halo             — wider pink/red glow that fades to dark
   *   5. Base color            — solid dark fills everything beyond
   *
   * --mx / --my default to -9999px so all five layers park outside the
   * element at rest; the type reads as flat black until you hover.
   */
  /* Solid dark base. background-clip: text clips BOTH color and image
   * to the text shape, so this fills any pixel of the glyphs that the
   * ripples above don't reach — the type reads as flat dark everywhere
   * outside an active ripple. NOTE: bare colors are NOT valid inside
   * `background-image`, so the base must live on background-color. */
  background-color: #16110b;
  /*
   * Six ripple slots, each an independent expanding ring driven by
   * --x-N / --y-N (origin), --r-N (current radius), and --a-N (alpha).
   * JS allocates these slots round-robin as the cursor moves — every
   * spawn pins a new origin and ramps r from 0 → max while a fades
   * 1 → 0, producing the pond-ripple effect of concentric rings
   * propagating outward from each contact point with the text.
   *
   * Each gradient is a wide soft halo (band from ~25% out to 100% of
   * its radius, peak around 80%) — the broader the band, the more
   * adjacent ripples can overlap and bleed into each other instead of
   * reading as discrete rings. The interior 25% stays transparent so
   * the *center* of an old ripple goes back to dark text once the
   * wavefront has passed, which is the visual cue that something is
   * propagating outward rather than just glowing.
   *
   * The 3-stop falloff (soft inner / bright peak / dimmed edge) gives
   * each halo a smooth profile so where two ripples meet they multiply
   * smoothly rather than crossing in a hard seam.
   *
   * Colors cycle red / cyan / gold / magenta / cyan / white across the
   * six slots so successive ripples shift hue, reinforcing the
   * iridescent feel as several blend together.
   */
  /* Per-ripple alphas are deliberately low (peak around 0.32 of the
   * slot's --a). Six wide halos at full alpha would saturate every
   * letter on screen at all times; running each at ~⅓ strength means a
   * single isolated ripple is barely a tint, but several overlapping
   * ripples *additively* build up to a richer color — the blend reads
   * as iridescent shimmer instead of a solid wash.
   *
   * IMPORTANT: each gradient ends with `transparent 100%` AFTER the
   * dim-edge stop. Without it, when JS spawns a ripple with --r-N
   * at 0px and --a-N at 1 (before the first rAF tick can grow the
   * radius), the gradient's ending shape has zero area — and the
   * CSS spec then fills the entire element with the color of the
   * last stop. That produces a one-frame flash of ~12% red across
   * every letter on pointerenter. Capping with transparent ensures
   * beyond the explicit radius is always transparent, regardless of
   * alpha — no bleed, no flash. The dim-edge stop is shifted from
   * 100% → 96% so the ripple still fades softly to its outer edge
   * instead of cutting hard. */
  background-image:
    radial-gradient(
      circle var(--r-1, 0px) at var(--x-1, -9999px) var(--y-1, -9999px),
      transparent 0%,
      transparent 30%,
      rgba(255, 60, 90, calc(var(--a-1, 0) * 0.1)) 55%,
      rgba(255, 60, 90, calc(var(--a-1, 0) * 0.32)) 82%,
      rgba(255, 60, 90, calc(var(--a-1, 0) * 0.12)) 96%,
      transparent 100%
    ),
    radial-gradient(
      circle var(--r-2, 0px) at var(--x-2, -9999px) var(--y-2, -9999px),
      transparent 0%,
      transparent 30%,
      rgba(80, 220, 255, calc(var(--a-2, 0) * 0.1)) 55%,
      rgba(80, 220, 255, calc(var(--a-2, 0) * 0.32)) 82%,
      rgba(80, 220, 255, calc(var(--a-2, 0) * 0.12)) 96%,
      transparent 100%
    ),
    radial-gradient(
      circle var(--r-3, 0px) at var(--x-3, -9999px) var(--y-3, -9999px),
      transparent 0%,
      transparent 30%,
      rgba(255, 200, 90, calc(var(--a-3, 0) * 0.1)) 55%,
      rgba(255, 200, 90, calc(var(--a-3, 0) * 0.32)) 82%,
      rgba(255, 200, 90, calc(var(--a-3, 0) * 0.12)) 96%,
      transparent 100%
    ),
    radial-gradient(
      circle var(--r-4, 0px) at var(--x-4, -9999px) var(--y-4, -9999px),
      transparent 0%,
      transparent 30%,
      rgba(255, 60, 165, calc(var(--a-4, 0) * 0.1)) 55%,
      rgba(255, 60, 165, calc(var(--a-4, 0) * 0.32)) 82%,
      rgba(255, 60, 165, calc(var(--a-4, 0) * 0.12)) 96%,
      transparent 100%
    ),
    radial-gradient(
      circle var(--r-5, 0px) at var(--x-5, -9999px) var(--y-5, -9999px),
      transparent 0%,
      transparent 30%,
      rgba(80, 220, 255, calc(var(--a-5, 0) * 0.1)) 55%,
      rgba(80, 220, 255, calc(var(--a-5, 0) * 0.32)) 82%,
      rgba(80, 220, 255, calc(var(--a-5, 0) * 0.12)) 96%,
      transparent 100%
    ),
    radial-gradient(
      circle var(--r-6, 0px) at var(--x-6, -9999px) var(--y-6, -9999px),
      transparent 0%,
      transparent 30%,
      rgba(255, 235, 245, calc(var(--a-6, 0) * 0.1)) 55%,
      rgba(255, 235, 245, calc(var(--a-6, 0) * 0.32)) 82%,
      rgba(255, 235, 245, calc(var(--a-6, 0) * 0.12)) 96%,
      transparent 100%
    );
  -webkit-background-clip: text;
          background-clip: text;
  /* text-fill-color is inherited, so the child <i> ampersands also show
   * the gradient through their glyphs without any extra rule. */
  -webkit-text-fill-color: transparent;
}
.home-cta-platforms > span {
  display: block;
}
.home-cta-platforms > span > i {
  font-style: normal;
  font-weight: 700;
  /* color stays declared (for a non-Webkit fallback that ignores
   * text-fill-color), but -webkit-text-fill-color inherited from the
   * parent overrides it on every engine that supports background-clip:
   * text — which is all modern browsers. */
  color: rgba(22, 17, 11, 0.55);
  margin-left: 0.02em;
}

/* Closing CTA pill — dark, larger sibling of the nav pill. */
.home-cta-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 26px;
  border-radius: 999px;
  background: #16110b;
  color: #f5efe6;
  text-decoration: none;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1;
  transition: transform 0.15s ease, filter 0.15s ease, box-shadow 0.15s ease;
  box-shadow:
    0 8px 20px rgba(22, 17, 11, 0.22),
    inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.home-cta-btn::after {
  content: "→";
  font-size: 17px;
  line-height: 1;
  transform: translateX(0);
  transition: transform 0.15s ease;
}
.home-cta-btn:hover,
.home-cta-btn:focus-visible {
  color: #fff;
  text-decoration: none;
  transform: translateY(-1px);
  filter: brightness(1.1);
  box-shadow:
    0 12px 26px rgba(22, 17, 11, 0.28),
    inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.home-cta-btn:hover::after,
.home-cta-btn:focus-visible::after {
  transform: translateX(3px);
}

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

/* ─────────────────────────────────────────────────────────────────────
 * Row 1b: About — manifesto / point-of-view.
 *
 * Sits between the cream Beta section and the indigo Library row, so it
 * needs to own its dark editorial moment without competing with either
 * neighbour. The previous version was a two-column lockup-on-left,
 * paragraph-on-right editorial; this version is a single centered
 * header (lockup stamp + eyebrow + oversized thesis title) followed by
 * a four-up grid of numbered POV pillars beneath. Pillars collapse
 * 4 → 2×2 → 1 as the viewport narrows.
 *
 * Visual anatomy:
 *
 *                 ┌────────────────────┐
 *                 │  [Plex + Relay]   │   ← glass "co-sign" stamp
 *                 └────────────────────┘
 *
 *          A SMALL PLAYER WITH A CLEAR POINT OF VIEW
 *
 *               Built for the music you
 *               already own.            ← accent gradient on closing words
 *
 *      ─────────┬──────────┬──────────┬─────────
 *      01       │ 02       │ 03       │ 04          ← red-gradient numerals
 *      Album-   │ Direct   │ No algo- │ Out of      ← pillar titles
 *      first    │ to Plex  │ rithms   │ the way
 *      …copy…   │ …copy…   │ …copy…   │ …copy…
 *      ─────────┴──────────┴──────────┴─────────
 * ──────────────────────────────────────────────────────────────────── */
.home-row--about {
  --row-bg: linear-gradient(180deg, #34343a 0%, #16161a 60%, #0d0d10 100%);
  --row-fg: #f5efe6;
  padding-top: clamp(96px, 12vw, 144px);
  padding-bottom: clamp(96px, 12vw, 144px);
}
.home-row--about .home-row__inner {
  grid-template-columns: minmax(0, 1fr);
  text-align: center;
  max-width: 1080px;
  gap: clamp(56px, 8vw, 96px);
}

/* Header stack ────────────────────────────────────────────────────────
 * Eyebrow → title, vertically centred. Replaces the old
 * .home-row__copy--about-title block, so all of the spacing lives on
 * the wrapper instead of being divided across mixed margin rules.
 *
 * Earlier revisions also rendered a Plex+Relay glass-pill lockup (then
 * a tiny "Plex × Relay" signature) above the eyebrow. Both reads
 * pulled focus from the oversized thesis title without earning their
 * weight; the Plex relationship is already stated explicitly in the
 * "02 Direct to Plex" pillar copy below, so the header is now just
 * the eyebrow + title and the section opens straight on the headline. */
.home-about__header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}
.home-about__eyebrow {
  margin: 0;
  color: rgba(245, 239, 230, 0.55);
}
.home-about__title {
  margin: 0;
  /* A touch larger than the global .home-row__title so the thesis
   * statement carries the centered single-column layout. */
  font-size: clamp(2.6rem, 5.6vw, 4.25rem);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.025em;
  max-width: 18ch;
}
/* Brand-red gradient applied to the last word group of the title. Uses
 * the same red ramp as the beta button so the page's accents read as a
 * single colour family. */
.home-about__title-accent {
  background: linear-gradient(180deg, #ff5577 0%, #ef3a64 55%, #d61543 100%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  /* Safari occasionally drops the gradient text fill mid-scroll; the
   * explicit transparent text color above plus this fallback keep the
   * word readable if the gradient ever fails to render. */
  -webkit-text-fill-color: transparent;
}

/* Pillars grid ────────────────────────────────────────────────────────
 * Four numbered POV statements arranged horizontally. The grid itself
 * has hairline top + bottom borders that frame the strip; vertical
 * dividers between cells are drawn with ::before on each pillar after
 * the first so they don't bleed past the top/bottom borders.
 *
 * Breakpoints:
 *   > 880px : 4 columns, vertical dividers between cells
 *   ≤ 880px : 2×2 grid, vertical dividers only at column boundary,
 *             horizontal divider between rows
 *   ≤ 560px : single column stack, horizontal dividers between cells
 */
.home-about__pillars {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  text-align: left;
  border-top: 1px solid rgba(245, 239, 230, 0.14);
  border-bottom: 1px solid rgba(245, 239, 230, 0.14);
}
.home-about__pillar {
  position: relative;
  padding: 36px 28px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Vertical hairline between adjacent pillars (desktop 4-up). Insets
 * vertically so it floats inside the framed strip instead of butting
 * up against the top/bottom borders. */
.home-about__pillar + .home-about__pillar::before {
  content: "";
  position: absolute;
  left: 0;
  top: 28px;
  bottom: 28px;
  width: 1px;
  background: rgba(245, 239, 230, 0.12);
}
.home-about__pillar-num {
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: linear-gradient(180deg, #ff7088 0%, #ff3c5a 55%, #d61543 100%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
.home-about__pillar-title {
  margin: 0;
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: clamp(1.25rem, 1.6vw, 1.5rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: #f5efe6;
}
.home-about__pillar-body {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.55;
  color: rgba(245, 239, 230, 0.62);
}

@media (max-width: 880px) {
  .home-about__pillars {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  /* Reset the default "vertical divider between every adjacent pillar"
   * rule — on a 2×2 grid only items 2 and 4 sit at a column boundary. */
  .home-about__pillar + .home-about__pillar::before {
    display: none;
  }
  .home-about__pillar:nth-child(2)::before,
  .home-about__pillar:nth-child(4)::before {
    content: "";
    position: absolute;
    left: 0;
    top: 28px;
    bottom: 28px;
    width: 1px;
    background: rgba(245, 239, 230, 0.12);
    display: block;
  }
  /* Horizontal divider between row 1 (items 1-2) and row 2 (items 3-4). */
  .home-about__pillar:nth-child(3),
  .home-about__pillar:nth-child(4) {
    border-top: 1px solid rgba(245, 239, 230, 0.12);
  }
}
@media (max-width: 560px) {
  .home-about__pillars {
    grid-template-columns: 1fr;
  }
  /* Drop every vertical divider; rely on horizontal hairlines between
   * stacked items instead. */
  .home-about__pillar::before,
  .home-about__pillar:nth-child(2)::before,
  .home-about__pillar:nth-child(4)::before {
    display: none;
  }
  .home-about__pillar:nth-child(n + 2) {
    border-top: 1px solid rgba(245, 239, 230, 0.12);
  }
  .home-about__pillar {
    padding: 28px 4px;
  }
}

/* (Previously defined .home-row__lockup{,-content,-plus} primitives
 * lived here. They powered the bright Plex+Relay glass-pill lockup in
 * the About section, which has been replaced by .home-about__signature
 * above. Removed to keep the stylesheet free of dead selectors — if a
 * future row wants the glass-lockup treatment, reach for the
 * .relay-glass + .relay-glass--pill primitives in relay-glass.css and
 * style the row's own wrapper inline; the old shared classes weren't
 * actually shared by anything.) */

/* ─────────────────────────────────────────────────────────────────────
 * Row 2: Library — floating album wall, no phone.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--library {
  --row-bg: linear-gradient(135deg, #fb7185 0%, #d946ef 50%, #4f46e5 100%);
  --row-fg: #fff;
  --row-glow: rgba(79, 70, 229, 0.4);
}
/* Library coverflow ----------------------------------------------------
 *
 * Apple iTunes / iPod CoverFlow-inspired 3D strip: a single album cover
 * faces the camera, with the rest of the library receding to either
 * side at a fixed yaw. The center cover auto-cycles every few seconds
 * (driven by initCoverflow in home-rows.js), giving the row a slow,
 * gallery-like rhythm rather than a chaotic grid of tiny tiles.
 *
 * Layout strategy:
 *   • .home-row__coverflow is a fixed-aspect "stage" with CSS
 *     perspective, so we get true 3D foreshortening.
 *   • .home-row__coverflow-stage is the 3D context (preserve-3d) — all
 *     covers are absolutely positioned at its center and offset via
 *     transforms set inline by JS, never via CSS layout.
 *   • The horizontal mask fades both edges so off-screen covers
 *     dissolve into the gradient instead of clipping into a hard wall.
 *   • -webkit-box-reflect gives the signature CoverFlow mirror floor.
 *     Reflections are non-standard but supported in Chromium + Safari;
 *     Firefox simply shows no reflection (graceful degradation).
 */
.home-row__coverflow {
  position: relative;
  width: 100%;
  max-width: 600px;
  /* Stable aspect-ratio so the section height doesn't reflow as covers
   * animate. Slightly wider than tall to give CoverFlow room to fan. */
  aspect-ratio: 16 / 11;
  perspective: 1100px;
  perspective-origin: 50% 50%;
  /* Soft horizontal fade so the side stacks dissolve. Hard-clip the
   * top/bottom so reflections don't get cut. */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 10%, #000 90%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0%, #000 10%, #000 90%, transparent 100%);
}
.home-row__coverflow-stage {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}
.home-row__coverflow img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(140px, 32%, 210px);
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 6px;
  box-shadow:
    0 14px 32px rgba(0, 0, 0, 0.55),
    0 2px 6px rgba(0, 0, 0, 0.35),
    inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  transform: translate(-50%, -50%) translateZ(-1200px);
  opacity: 0;
  transform-origin: center center;
  backface-visibility: hidden;
  /* All cover transitions are driven by JS-set inline transforms; this
   * transition just gives those changes the cinematic CoverFlow ease.
   * Slightly longer than default so the swap feels deliberate, not
   * twitchy. */
  transition:
    transform 750ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 500ms cubic-bezier(0.22, 1, 0.36, 1),
    filter 500ms cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform, opacity;
  /* Signature CoverFlow mirror floor. Subtle so it works on top of the
   * Library row's bright gradient — too strong a reflection on a
   * non-black background reads as a glitch. */
  -webkit-box-reflect: below 4px linear-gradient(
    transparent 0%,
    transparent 55%,
    rgba(0, 0, 0, 0.32) 100%
  );
}
@media (max-width: 880px) {
  .home-row__coverflow {
    max-width: 420px;
    margin: 0 auto;
    /* Taller aspect on mobile — desktop's 16:11 left no room for the
     * reflection floor underneath the cover at the narrower width, so
     * the bottom of the mirror was clipping into the section padding.
     * 4:3 gives the centered cover + its reflection a comfortable
     * vertical envelope. */
    aspect-ratio: 4 / 3;
  }
  .home-row__coverflow img {
    width: clamp(120px, 38%, 170px);
  }
}

/* ─────────────────────────────────────────────────────────────────────
 * Row 3: Albums — giant rotating vinyl disc behind a small phone.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--albums {
  --row-bg: linear-gradient(135deg, #22d3ee 0%, #22d3ee 40%, #a3e635 90%, #fde047 100%);
  --row-fg: #16110b;
  --row-glow: rgba(163, 230, 53, 0.35);
}
.home-row__record-stack {
  position: relative;
  width: min(480px, 80vw);
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.home-row__record {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background:
    radial-gradient(circle at center, #0a0a0a 18%, transparent 18.5%),
    repeating-radial-gradient(circle, rgba(255, 255, 255, 0.04) 0 2px, transparent 2px 5px),
    radial-gradient(circle at center, #1a1a1a 60%, #050505 100%);
  box-shadow:
    0 30px 80px rgba(0, 0, 0, 0.55),
    inset 0 0 30px rgba(0, 0, 0, 0.7);
  animation: home-record-spin 32s linear infinite;
}
.home-row__record::before {
  content: "";
  position: absolute;
  /* Real vinyl labels are ~30% of the record diameter. */
  inset: 35%;
  border-radius: 50%;
  background-image: var(--record-label, url("/screenshots/album-coldplay-cover.png"));
  background-size: cover;
  background-position: center;
  box-shadow:
    0 0 0 2px rgba(0, 0, 0, 0.6),
    0 4px 12px rgba(0, 0, 0, 0.4);
}
.home-row__record::after {
  content: "";
  position: absolute;
  inset: calc(50% - 6px);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #efe6d8;
  z-index: 1;
}
@keyframes home-record-spin {
  to { transform: rotate(360deg); }
}
.home-row__record-phone {
  position: absolute;
  right: -8%;
  bottom: -4%;
  width: 38%;
  z-index: 2;
  filter: drop-shadow(0 20px 60px rgba(0, 0, 0, 0.5));
}

/* ─────────────────────────────────────────────────────────────────────
 * Row 4: Playlists & mixes — vertical tracklist (mini player) with a
 * "now playing" row highlighted in Relay red, top + bottom mask fade,
 * and animated EQ bars over the playing track's album art.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--playlists {
  --row-bg: linear-gradient(135deg, #0a0a0c 0%, #1f2125 55%, #3a3d44 100%);
  --row-fg: #fff;
  /* Tighter vertical padding than the global default so the section
   * visually wraps around the tracklist instead of leaving a large
   * black band below the last row. */
  --home-row-pad-y: clamp(48px, 7vw, 96px);
}
.home-row__art--playlists {
  display: block;
  width: 100%;
  align-self: stretch;
}
/* Masked viewport — fixed window the rail scrolls inside. The mask
 * creates the spotlight (middle ~36% at full opacity); the wrapper is
 * a real scroll container (overflow-y: hidden permits programmatic
 * scrollTop without exposing a scrollbar or letting the user trap
 * page scroll inside the list). Native scrolling is used here on
 * purpose — applying `transform` to a parent of `backdrop-filter`
 * children breaks the glass sampling in Safari (the backdrop reads
 * the transparent compositor layer instead of the page bg), so we
 * scroll the wrapper rather than translating the rail. */
.home-row__playlists-tracks {
  position: relative;
  /* Horizontal padding leaves room for the red glass pill's drop shadow
   * to breathe instead of being clipped against the list edge. Vertical
   * padding sits inside the masked area, so the fade ramps over empty
   * space before reaching the first / last rows currently in view. */
  padding: 48px 28px;
  margin: 0 auto;
  width: 100%;
  max-width: 560px;
  /* Capped height so the rail can be longer than the visible window.
   * Roughly 9–10 row heights at the upper end so the spotlight always
   * has 3–4 rows above and below the currently playing one for the
   * fade-in / fade-out to feel like a real listening surface instead
   * of a peephole. */
  height: clamp(540px, 72vh, 720px);
  overflow-x: hidden;
  overflow-y: hidden;
  /* Hide the scrollbar on platforms that paint one even for overflow:
   * hidden scroll containers. Programmatic scrollTop still works. */
  scrollbar-width: none;
  /* Long, soft top + bottom fade — ~32% ramp on each side so the first
   * and last rows trail off gradually with no perceptible edge, and the
   * middle ~36% of the list reads at full opacity. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 32%, #000 68%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 32%, #000 68%, transparent 100%);
}
.home-row__playlists-tracks::-webkit-scrollbar {
  display: none;
}

/* The actual list of tracks. Plain flex column — no transform, no
 * will-change, no compositor layer. Keeping the rail in the normal
 * paint flow is what lets each <li>'s relay-glass backdrop-filter
 * sample the section's painted background correctly in Safari. */
.home-row__playlists-tracks-rail {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.home-row__playlists-track {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto auto 22px;
  align-items: center;
  gap: 14px;
  /* Left padding pulls the art tile away from the pill's curved corner
   * so it sits cleanly inside the straight portion of the surface. */
  padding: 8px 20px 8px 18px;
  color: rgba(255, 255, 255, 0.92);
}
/* Promote every direct child that isn't an aria-hidden glass layer above
 * the glass scaffolding (backdrop=0, face=1, emboss=2, flare=3). */
.home-row__playlists-track > :not([aria-hidden="true"]) {
  position: relative;
  z-index: 4;
}
.home-row__playlists-track-art {
  position: relative;
  width: 44px;
  height: 44px;
  /* Rounded square (matches the search section's chip album art) rather
   * than a full circle, so cover art reads clearly inside the tile. */
  border-radius: 6px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
  flex-shrink: 0;
  /* Soft inset border + subtle outer shadow gives the art chip a
   * physical "tile sitting on the pill" quality. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.18),
    0 2px 6px rgba(0, 0, 0, 0.35);
}
.home-row__playlists-track-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Two-line stack: artist label on top, track name below.
 * Lives in the row's flexible (1fr) grid column. */
.home-row__playlists-track-meta {
  display: flex;
  flex-direction: column;
  /* min-width:0 lets the inner text actually shrink + ellipsis instead
   * of forcing the row to overflow when names are long. */
  min-width: 0;
  gap: 1px;
  line-height: 1.2;
}
.home-row__playlists-track-artist {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.home-row__playlists-track-name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.005em;
  /* Truncate long titles to keep the row's 5-column grid stable. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.home-row__playlists-track-format {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 4px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.55);
  /* Tabular for visual alignment across rows with different format codes. */
  font-variant-numeric: tabular-nums;
}
.home-row__playlists-track-duration {
  font-size: 13px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.55);
  font-variant-numeric: tabular-nums;
  min-width: 36px;
  text-align: right;
}
.home-row__playlists-track-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  color: rgba(255, 255, 255, 0.5);
}
.home-row__playlists-track-status--downloaded {
  color: rgba(255, 255, 255, 0.85);
}
/* "Now playing" row — the red relay-glass face/emboss/flare layers handle
 * the surface treatment; here we just invert the typography and swap the
 * downloaded indicator to a white disc so it reads against the red glass. */
.home-row__playlists-track.is-playing {
  color: #fff;
}
.home-row__playlists-track.is-playing .home-row__playlists-track-art {
  /* Slightly brighter inset border so the art tile still has a visible
   * edge against the red glass surface. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.3),
    0 2px 6px rgba(0, 0, 0, 0.2);
}
.home-row__playlists-track.is-playing .home-row__playlists-track-artist {
  color: rgba(255, 255, 255, 0.78);
}
.home-row__playlists-track.is-playing .home-row__playlists-track-name {
  color: #fff;
  font-weight: 700;
}
.home-row__playlists-track.is-playing .home-row__playlists-track-format {
  border-color: rgba(255, 255, 255, 0.7);
  color: #fff;
}
.home-row__playlists-track.is-playing .home-row__playlists-track-duration {
  color: #fff;
}
/* Downloaded indicator becomes a white filled disc with the red check inside —
 * mirrors how a "this is selected and downloaded" affordance reads on iOS. */
.home-row__playlists-track.is-playing .home-row__playlists-track-status--downloaded {
  background: #fff;
  border-radius: 50%;
  color: #ff3c5a;
}
/* EQ overlay — pre-baked into every row's album art at init time and
 * hidden by default; CSS reveals it only on the currently .is-playing
 * row. Doing the visibility toggle in CSS (instead of injecting /
 * removing the DOM each advance) keeps Safari from re-rasterizing the
 * relay-glass backdrop on every track switch, which is what produced
 * the visible stutter and the brief washed-out frame on the playing
 * pill. The container fills the full art tile (as the dark scrim), but
 * the bars are constrained to a small square icon area centered within
 * it, so the EQ reads as a compact icon rather than stretching to the
 * tile's full height. */
.home-row__playlists-track-eq {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  gap: 2.5px;
  background: rgba(0, 0, 0, 0.55);
  border-radius: inherit;
}
.home-row__playlists-track.is-playing .home-row__playlists-track-eq {
  display: flex;
}
.home-row__playlists-track-eq .eq__bar {
  display: block;
  width: 2.5px;
  background: #fff;
  border-radius: 999px;
  transform-origin: center;
  animation: home-playlist-eq 0.9s ease-in-out infinite;
}
/* Bar heights in px keep the icon visually square (~20px tall), regardless
 * of how tall the art tile itself ends up being. */
.home-row__playlists-track-eq .eq__bar--1 { height: 12px; animation-duration: 0.9s;  animation-delay: -0.05s; }
.home-row__playlists-track-eq .eq__bar--2 { height: 20px; animation-duration: 1.2s;  animation-delay: -0.4s;  }
.home-row__playlists-track-eq .eq__bar--3 { height: 16px; animation-duration: 0.75s; animation-delay: -0.2s;  }
.home-row__playlists-track-eq .eq__bar--4 { height: 13px; animation-duration: 1.0s;  animation-delay: -0.55s; }
@keyframes home-playlist-eq {
  0%, 100% { transform: scaleY(0.3); }
  50%      { transform: scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
  .home-row__playlists-track-eq .eq__bar {
    animation: none;
    /* Freeze at a balanced "playing" pose so the indicator still reads. */
    transform: scaleY(0.7);
  }
}
@media (max-width: 880px) {
  .home-row__playlists-tracks {
    padding: 18px 4px;
    max-width: 560px;
  }
}
@media (max-width: 560px) {
  .home-row__playlists-track {
    grid-template-columns: 40px minmax(0, 1fr) auto 18px;
    gap: 12px;
    padding: 8px 10px;
  }
  .home-row__playlists-track-art {
    width: 40px;
    height: 40px;
  }
  /* On narrow phones, hide the format chip to keep the row uncluttered;
   * track name + duration + downloaded state still carry the metaphor. */
  .home-row__playlists-track-format {
    display: none;
  }
}

/* ─────────────────────────────────────────────────────────────────────
 * Row 5: Radio — typographic-first with horizontal station ticker.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--radio {
  --row-bg: linear-gradient(135deg, #9333ea 0%, #e11d48 50%, #f97316 100%);
  --row-fg: #fff;
}
.home-row--radio .home-row__inner {
  grid-template-columns: 1fr;
  text-align: center;
  justify-items: center;
  max-width: 1080px;
  gap: 48px;
}
.home-row--radio .home-row__copy {
  max-width: 720px;
}
.home-row--radio .home-row__title {
  max-width: 14ch;
  margin-left: auto;
  margin-right: auto;
}
.home-row--radio .home-row__body {
  margin: 0 auto;
  max-width: 560px;
}
.home-row__ticker {
  position: relative;
  width: 100%;
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent 0, #000 8%, #000 92%, transparent 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 8%, #000 92%, transparent 100%);
}
.home-row__ticker-track {
  display: inline-flex;
  gap: 18px;
  white-space: nowrap;
  padding: 4px 0;
  animation: home-ticker-scroll 36s linear infinite;
  will-change: transform;
}
.home-row__ticker-pill {
  flex-shrink: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
  /* Keep each pill on its own GPU layer so the glass `backdrop-filter`
   * doesn't have to re-rasterize when the marquee loop snaps back. */
  transform: translateZ(0);
  backface-visibility: hidden;
}
.home-row__ticker-pill > .relay-glass__content {
  display: inline-flex;
  align-items: center;
  padding: 10px 18px;
}
.home-row__ticker-pill > .relay-glass__content::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
  margin-right: 10px;
}
@keyframes home-ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ─────────────────────────────────────────────────────────────────────
 * Row 6: Search — large mock search bar + result chips.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--search {
  --row-bg: linear-gradient(to right, #4E6BFE 30%, #79FFDB 100%);
  /* Light copy — matches the Downloads treatment. The blue-to-mint
   * gradient is saturated enough that white reads cleanly without an
   * additional dark overlay. Eyebrow/body inherit via opacity from
   * the base .home-row__eyebrow / .home-row__body rules. */
  --row-fg: #fff;
}
.home-row--search::after {
  display: none;
}
.home-row__search-mock {
  width: 100%;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.home-row__search-bar {
  color: #16110b;
  font-size: 17px;
  /* Relay-red outline so the active field reads as the focal point of the section. */
  border: 1.5px solid #ff3c5a;
  box-shadow:
    0 0 0 4px rgba(255, 60, 90, 0.08),
    0 3px 9px -0.5px rgba(0, 0, 0, 0.11),
    0 8px 17px rgba(0, 0, 0, 0.065);
}
.home-row__search-bar-inner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 22px;
}
.home-row__search-bar svg {
  flex-shrink: 0;
  opacity: 0.8;
}
.home-row__search-bar-text {
  flex: 1;
  font-weight: 500;
}
.home-row__search-bar-caret {
  display: inline-block;
  width: 1.5px;
  height: 18px;
  background: currentColor;
  margin-left: 2px;
  animation: home-caret-blink 1.1s steps(2) infinite;
  vertical-align: -3px;
}
/* Clear button on the right of the field — only shown once text has been typed. */
.home-row__search-bar-clear {
  flex-shrink: 0;
  margin-left: auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(22, 17, 11, 0.12);
  color: rgba(22, 17, 11, 0.7);
  opacity: 0;
  transform: scale(0.85);
  transition: opacity 0.18s ease, transform 0.18s ease;
  pointer-events: none;
}
.home-row__search-bar--has-text .home-row__search-bar-clear {
  opacity: 1;
  transform: scale(1);
}
@keyframes home-caret-blink {
  50% { opacity: 0; }
}
.home-row__chip-row {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 10px;
  /* Padding gives pill drop-shadows room before overflow:hidden clips them; */
  /* negative margins counter-offset so the visible layout footprint is unchanged. */
  padding: 10px 20px 0;
  margin: -10px -20px 0;
  /* Locked height (not max-height) so the section doesn't shrink as chips */
  /* collapse out of flow during filtering. */
  height: clamp(230px, 35vh, 350px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
}
.home-row__chip {
  color: rgba(22, 17, 11, 0.78);
  font-size: 14px;
  font-weight: 500;
  transition: opacity 0.2s ease, transform 0.2s ease;
  transform-origin: left center;
}
.home-row__chip--out {
  opacity: 0;
  transform: scale(0.88);
  pointer-events: none;
}
.home-row__chip > .relay-glass__content {
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  white-space: nowrap;
}
/* Album-cover chip variant — small inset on the left so the art doesn't kiss the pill edge. */
.home-row__chip--with-art > .relay-glass__content {
  padding: 4px 14px 4px 8px;
  gap: 8px;
}
.home-row__chip-art {
  width: 28px;
  height: 28px;
  border-radius: 5px;
  object-fit: cover;
  flex-shrink: 0;
  background: rgba(22, 17, 11, 0.06);
  /* Visual tweak: nudge the art slightly down + in so it doesn't kiss the pill edges. */
  margin-left: 2px;
  margin-top: 2px;
}

/* ─────────────────────────────────────────────────────────────────────
 * Row 7: Downloads — phone + circular progress ring + count badge.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--downloads {
  --row-bg: linear-gradient(135deg, #0a0a0c 0%, #1f2125 55%, #3a3d44 100%);
  --row-fg: #fff;
}
.home-row__download-viz {
  position: relative;
  width: min(360px, 80vw);
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Audio-style pulses radiating outward from behind the phone. Each pulse is
 * a wavy, sine-modulated ring (14 lobes around the circumference) that
 * starts near the phone's footprint, scales outward past the container, and
 * fades to transparent. Staggered copies give a continuous outbound rhythm. */
.home-row__download-pulses {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Let pulses scale beyond the viewBox without being clipped by the SVG box. */
  overflow: visible;
  /* Soft blur + outer glow applied here (on the <svg>) rather than on each */
  /* <use> child — CSS filter on an SVG <use> can be clipped to the element's */
  /* default filter region (~110% of bbox), so large blurs don't visually */
  /* register. Moving it to the <svg> applies it at the CSS compositing layer */
  /* instead, so the full blur radius renders without clipping. */
  filter:
    blur(2px)
    drop-shadow(0 0 5px rgba(30, 193, 255, 0.7))
    drop-shadow(0 0 14px rgba(30, 193, 255, 0.35));
}
.home-row__download-pulse {
  fill: none;
  /* Electric blue — bright with a touch of cyan so the pulse reads as a */
  /* luminous signal rather than a solid line. */
  stroke: #1ec1ff;
  /* Stronger beats render with a thicker stroke so they hit harder visually, */
  /* like a kick drum cutting through a softer hi-hat. --pulse-energy is set */
  /* per <use> (0 → 1) and scales both the line weight and (below) how far */
  /* the wave travels + how bright it gets at attack. */
  stroke-width: calc(0.55 + 0.55 * var(--pulse-energy, 0.7));
  stroke-linejoin: round;
  /* Keep visual stroke thickness constant as each pulse scales outward, */
  /* like a real wavefront thinning and dissipating into the distance. */
  vector-effect: non-scaling-stroke;
  /* Scale from the SVG viewBox center so the sine pattern radiates evenly. */
  transform-box: view-box;
  transform-origin: 50% 50%;
  transform: scale(0.55);
  opacity: 0;
  /* 2s loop = one full 4/4 measure at 120 BPM. With four pulses spaced 0.5s */
  /* apart (delays 0 / 0.5 / 1 / 1.5s), each pulse fires once per beat in a */
  /* repeating STRONG–soft–MEDIUM–soft pattern — the visual heartbeat of a */
  /* song rather than an even mechanical sweep. */
  animation: home-download-pulse 2s cubic-bezier(0.18, 0.7, 0.3, 1) infinite;
  animation-delay: var(--pulse-delay, 0s);
  will-change: transform, opacity;
}
@keyframes home-download-pulse {
  /* Sharp attack on the downbeat: pulse appears small and snaps to peak */
  /* brightness in the first ~7% of the cycle (≈140ms) so it feels like a */
  /* transient — a drum hit — rather than a gentle fade-in. */
  0% {
    transform: scale(0.5);
    opacity: 0;
  }
  7% {
    /* Peak brightness scales with energy: STRONG (1.0) hits ~0.95 opacity, */
    /* MEDIUM (0.78) ~0.74, soft (0.45) ~0.43. */
    opacity: calc(0.95 * var(--pulse-energy, 0.7));
  }
  100% {
    /* Travel distance also scales with energy — louder beats throw the wave */
    /* further out, quieter ones stay closer to the phone. Base 1.05 keeps */
    /* even soft pulses visible beyond the device's footprint. */
    transform: scale(calc(1.05 + 0.7 * var(--pulse-energy, 0.7)));
    opacity: 0;
  }
}
.home-row__download-phone {
  position: relative;
  width: 56%;
  z-index: 1;
  filter: drop-shadow(0 30px 80px rgba(0, 0, 0, 0.55));
}

/*
 * Live playback overlay
 * --------------------------------------------------------------------------
 * Sits inside .device-frame__screen, positioned to *exactly* eclipse the
 * static playhead/time row baked into downloads-get-lucky.png. The dark
 * backdrop hides the underlying pixels so we can render an animated copy
 * on top — time text, progress fill, and playhead all driven by JS each
 * second. Positions are in % of the screen so they track the device frame
 * at any size.
 */
.home-row__phone-playback {
  position: absolute;
  /* Y-anchor matches where the progress bar row sits in the source image
   * (≈83% of its 1024px height) — tweaked slightly to account for the
   * screen's object-fit cover crop. The new screenshot has no baked-in
   * bar, so this overlay is the only progress UI on screen. */
  top: 81.3%;
  left: 0;
  right: 0;
  height: 4.8%;
  display: flex;
  align-items: center;
  gap: 3.6%;
  padding: 0 5.2%;
  z-index: 3;
  pointer-events: none;
  /* Inherit the bezel's container so cqw units scale with device width. */
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', system-ui, sans-serif;
  font-size: clamp(8px, 2cqw, 13px);
  line-height: 1;
  color: rgba(255, 255, 255, 0.78);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
.home-row__phone-time {
  flex-shrink: 0;
  font-weight: 500;
  /* Fixed minimum width so the track doesn't wobble as digits change
   * (1:00 → 1:09 → 1:10). Tabular-nums above keeps glyphs equal-width. */
  min-width: 7%;
}
.home-row__phone-time--elapsed {
  text-align: left;
}
.home-row__phone-time--total {
  text-align: right;
  color: rgba(255, 255, 255, 0.6);
}
.home-row__phone-track {
  position: relative;
  flex: 1;
  height: clamp(2px, 0.6cqw, 4px);
  background: rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  overflow: visible;
}
.home-row__phone-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: inherit;
  /* Relay red — matches the highlighted track + brand color. */
  background: linear-gradient(90deg, #ff3c5a 0%, #ff5775 100%);
  width: 16.26%; /* 60s / 369s — synced with JS starting state */
  /* 1s linear matches the JS tick interval; the bar creeps continuously
   * rather than snapping each second. */
  transition: width 1s linear;
  box-shadow: 0 0 6px rgba(255, 60, 90, 0.5);
}
.home-row__phone-playhead {
  position: absolute;
  top: 50%;
  left: 16.26%;
  width: clamp(7px, 1.9cqw, 12px);
  height: clamp(7px, 1.9cqw, 12px);
  border-radius: 50%;
  background: #fff;
  transform: translate(-50%, -50%);
  transition: left 1s linear;
  box-shadow:
    0 1px 4px rgba(0, 0, 0, 0.55),
    0 0 0 1px rgba(0, 0, 0, 0.15);
}
.home-row__download-badge {
  position: absolute;
  bottom: 8%;
  right: 6%;
  z-index: 2;
  color: #f5efe6;
  box-shadow:
    0 18px 42px rgba(0, 0, 0, 0.55),
    0 4px 10px rgba(0, 0, 0, 0.28);
}
.home-row__download-badge-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 22px 12px 18px;
}
.home-row__download-badge svg {
  flex-shrink: 0;
  color: #ff3c5a;
  filter: drop-shadow(0 0 6px rgba(255, 60, 90, 0.45));
}
.home-row__download-badge-copy {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: 6px;
}
.home-row__download-badge-copy strong {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.005em;
}
.home-row__download-badge-copy small {
  font-size: 14px;
  font-weight: 500;
  opacity: 0.65;
  letter-spacing: -0.005em;
}
.home-row__download-badge-copy small::before {
  content: "·";
  margin-right: 6px;
  opacity: 0.7;
}

/* ─────────────────────────────────────────────────────────────────────
 * Row 8: CTA — watermark app icon behind dramatic centered headline.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--cta {
  --row-bg: linear-gradient(180deg, #efe6d8 0%, #f5efe6 100%);
  --row-fg: #16110b;
  text-align: center;
}
.home-row--cta .home-row__inner {
  grid-template-columns: 1fr;
  /* `justify-items: stretch` (the default) instead of `center` so the
   * inner grid cell fills the full 880px column. The `.home-row__copy`
   * child can then carry the downloads list at its full width; the
   * narrow centred-on-page look still holds because the eyebrow,
   * headline, body and list each centre themselves inside .home-row__copy
   * via `text-align: center` + `margin: 0 auto`. */
  justify-items: stretch;
  /* Wider than the rest of the page so the stacked download list
   * below the headline has meaningful horizontal room. The headline
   * itself (`.home-cta-platforms`) line-breaks per word and stays
   * centred regardless, so widening the column doesn't change the
   * typographic block — it just gives the list and any body copy
   * more room to breathe. */
  max-width: 880px;
  gap: 32px;
}
.home-row--cta .home-row__copy {
  /* Fill the now-stretched inner cell so the downloads list inside
   * has the full 880px column to spread across. Everything inside
   * stays centred via per-element rules. */
  width: 100%;
}
.home-row--cta .home-row__title {
  font-size: clamp(48px, 6.4vw, 76px);
  font-weight: 700;
  line-height: 1;
}
.home-row--cta .home-row__body {
  margin: 0 auto;
  color: rgba(22, 17, 11, 0.74);
  font-size: clamp(17px, 1.5vw, 19px);
}

/* Per-platform download list below the closing CTA's typography block.
 * Stacked vertical rows (one platform per row) rather than a wrapping
 * grid of tiles — the bold "iPhone & Android & iPad & Mac & Windows &
 * Web." headline is the hero of this section, and the list below now
 * reads as supporting metadata (icon → platform → status) instead of
 * competing call-to-action buttons. A hairline divider between each
 * row holds the rhythm. Only the Web pill is "live"; everything else
 * is a "Coming soon" badge — once individual platforms ship, swap
 * their rows to .home-cta-download--ready / .home-cta-download--web
 * and add real <a href> hooks.                                        */
.home-cta-downloads {
  display: flex;
  flex-direction: column;
  /* Narrower than the bold headline above so the list feels like a
   * settled, supporting register rather than another headline-width
   * block of UI. Sized to give each row meaningful breathing room
   * without sprawling on wide viewports. */
  max-width: 820px;
  margin: 32px auto 0;
  width: 100%;
  /* Top hairline closes the list at the top edge; bottom hairline is
   * provided by the last row's border-bottom (every row carries one). */
  border-top: 1px solid rgba(22, 17, 11, 0.12);
}

/* Each platform row. Icon + name sit on the leading edge; the status
 * label is pushed to the trailing edge by margin-left:auto so the row
 * reads like a settings-style list item ("[icon] iPhone …… Coming
 * soon"). No fixed background by default — the row is a list entry,
 * not a button — but a faint warm tint on hover keeps it feeling
 * interactive in advance of becoming a real <a href> later.            */
.home-cta-download {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 22px 6px;
  border-bottom: 1px solid rgba(22, 17, 11, 0.12);
  text-decoration: none;
  color: rgba(22, 17, 11, 0.82);
  transition:
    background 160ms ease,
    color 160ms ease;
}
.home-cta-download:hover {
  /* Override the global `a:hover { text-decoration: underline }` —
   * the row's tint shift is the hover affordance, not an underline. */
  text-decoration: none;
}

/* Platform mark (Apple / Android / Windows / globe). Lives on the
 * leading edge of the row, sized to align with the platform name's
 * cap height. Inherits the row's text color via currentColor so the
 * mark stays coherent with the label and reacts to row state changes
 * without needing its own colour rule.                                */
.home-cta-download-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  color: rgba(22, 17, 11, 0.78);
  transition: color 160ms ease;
}
.home-cta-download-mark svg {
  display: block;
  width: 100%;
  height: 100%;
}

.home-cta-download-name {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
/* Status badge — pushed to the trailing edge of the row by
 * margin-left:auto. Rendered as a small uppercase pill so it reads
 * as a status indicator (think "BETA" or "COMING SOON" tags in
 * release-notes UIs) rather than another piece of body copy. */
.home-cta-download-sub {
  margin-left: auto;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(22, 17, 11, 0.05);
  color: rgba(22, 17, 11, 0.55);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  line-height: 1.2;
  transition:
    background 160ms ease,
    color 160ms ease;
}

/* Ready / available — for whatever platform ships first (iPhone, iPad,
 * Mac). Keeps the same list-row layout as everything else but tints
 * the status badge in Relay red so the eye lands on the "live" row
 * before the rest. The row's text colour stays the same; only the
 * badge announces availability. When wiring real download links,
 * swap the <span> to an <a href="…"> and the row will pick up the
 * shared hover treatment automatically.                              */
.home-cta-download--ready .home-cta-download-sub {
  background: rgba(229, 29, 60, 0.12);
  color: #c11636;
}

/* Coming-soon row — used for all platforms during the closed beta.
 * Inherits the default row treatment; the cursor explicitly bans
 * interaction because clicking does nothing yet. After the beta,
 * swap individual rows to .home-cta-download--ready / --web with
 * real <a href="…"> destinations and remove the not-allowed cursor. */
.home-cta-download--soon {
  cursor: not-allowed;
}

/* Web row — the only live entry point. The surrounding row is
 * decorative chrome (a <span>, matches every other tile); only the
 * "Listen ↗" badge is interactive. Tinted in Relay red so it
 * announces itself as the actionable element. Opens in a new tab so
 * visitors keep their place on the marketing site. */
.home-cta-download--web .home-cta-download-sub {
  background: rgba(229, 29, 60, 0.12);
  color: #c11636;
  /* Override the global a:hover underline — the bg shift + arrow
   * nudge is the affordance, not an underline. */
  text-decoration: none;
}
.home-cta-download--web .home-cta-download-sub:hover {
  background: rgba(229, 29, 60, 0.20);
  text-decoration: none;
}
/* Keyboard-focus ring — the badge is a small target, so a clear
 * outline is important for tab navigation. */
.home-cta-download--web .home-cta-download-sub:focus-visible {
  outline: 2px solid #c11636;
  outline-offset: 2px;
  background: rgba(229, 29, 60, 0.20);
}
.home-cta-download-arrow {
  display: inline-block;
  margin-left: 6px;
  opacity: 0.9;
  transition: transform 180ms ease;
}
.home-cta-download--web .home-cta-download-sub:hover .home-cta-download-arrow,
.home-cta-download--web .home-cta-download-sub:focus-visible .home-cta-download-arrow {
  transform: translate(2px, -2px);
}

/* Beta — closed-beta signup row. Sits as the second section right after */
/* the hero. Same warm cream backdrop + centered single-column layout as */
/* the CTA closer (so the page reads as a matched pair of bookends), and */
/* the same internal rhythm: eyebrow → title → body uses the default     */
/* .home-row__eyebrow / __title / __body margins, then the form pill     */
/* gets an extra breathing gap below the body so it doesn't collide with */
/* the paragraph above. Don't zero out the default margins here or the   */
/* section will visually compress relative to its neighbors.             */
.home-row--beta {
  /* Hot pink -> warm orange diagonal -- in the same vivid family
   * as library / radio / albums. Two stops only, kept clean so the
   * beta CTA stays focused as a destination. */
  --row-bg: linear-gradient(135deg, #ec4899 0%, #f97316 100%);
  --row-fg: #ffffff;
  text-align: center;
}
.home-row--beta .home-row__inner {
  grid-template-columns: 1fr;
  justify-items: center;
  max-width: 640px;
}
.home-row--beta .home-row__title {
  font-size: clamp(48px, 6.4vw, 76px);
  font-weight: 700;
  line-height: 1;
}
.home-row--beta .home-row__body {
  margin: 0 auto 16px;
  max-width: 480px;
  /* Light text on the saturated teal -> indigo -> pink gradient. */
  color: rgba(255, 255, 255, 0.88);
  font-size: clamp(17px, 1.5vw, 19px);
  line-height: 1.55;
}
/* Form sits below the body copy with a comfortable gap so the pill      */
/* doesn't crowd the paragraph above. The pill brings its own internal   */
/* sizing; the hint below is set apart with a small margin.              */
.home-row--beta .home-beta-form {
  margin: 32px auto 0;
  width: 100%;
  max-width: 520px;
}
.home-row--beta .home-beta-form__pill {
  width: 100%;
}
.home-row--beta .home-beta-form__hint {
  margin-top: 14px;
  text-align: center;
}

/* Soft atmospheric blob ----------------------------------------------- */
.home-row::after {
  content: "";
  position: absolute;
  width: 480px;
  height: 480px;
  border-radius: 50%;
  background: var(--row-glow, rgba(255, 255, 255, 0.18));
  filter: blur(120px);
  z-index: 0;
  top: 50%;
  right: -120px;
  transform: translateY(-50%);
  pointer-events: none;
}
.home-row--reverse::after {
  right: auto;
  left: -120px;
}
.home-row--hero::after,
.home-row--cta::after,
.home-row--radio::after {
  display: none;
}


/* ─────────────────────────────────────────────────────────────────────
 * Row 9: FAQ — "More details" closer with native <details> accordion.
 *
 * Sits after the Download CTA, before the footer. Background picks up
 * where CTA leaves off (#f5efe6) and warms slightly into a deeper
 * cream so the section feels like a continued, quieter chapter rather
 * than a fresh CTA fighting for attention. The accordion uses native
 * <details>/<summary> elements:
 *
 *   - No JS needed for expand/collapse
 *   - Keyboard accessible by default (Enter/Space)
 *   - Degrades gracefully in any browser context
 *
 * The default disclosure triangle is hidden (see ::-webkit-details-
 * marker and list-style: none on .home-faq__question); we render our
 * own pill-chevron on the right that rotates 180° via CSS when the
 * parent <details> has the [open] attribute.
 * ──────────────────────────────────────────────────────────────────── */
.home-row--faq {
  --row-bg: linear-gradient(180deg, #f5efe6 0%, #ebdfca 100%);
  --row-fg: #16110b;
  padding-top: clamp(72px, 9vw, 108px);
  padding-bottom: clamp(96px, 11vw, 132px);
  /* interpolate-size scoped to the FAQ row (it inherits) so transitions
   * between `0` and `auto` work for the accordion's ::details-content
   * animation below. Scoped here rather than globally on :root so we
   * don't accidentally animate every auto-sized box on the page. */
  interpolate-size: allow-keywords;
}
.home-row--faq .home-row__inner {
  grid-template-columns: minmax(0, 1fr);
  text-align: center;
  max-width: 820px;
  gap: clamp(40px, 5vw, 60px);
}
.home-row--faq .home-row__title {
  font-size: clamp(40px, 5vw, 60px);
}
.home-row--faq .home-row__body {
  max-width: 560px;
  margin: 0 auto;
  color: rgba(22, 17, 11, 0.66);
}
.home-row--faq .home-row__body a {
  color: #16110b;
  font-weight: 600;
  text-decoration-color: rgba(22, 17, 11, 0.3);
  text-underline-offset: 3px;
}

/* The header copy block opts back into the centered single-column
 * layout when the section stacks at 880px (the global stacking rule
 * left-aligns by default, but the FAQ is a centered-by-design closer
 * sibling to Hero/CTA). Same pattern as those rows further up. */
.home-faq__header {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* FAQ list ─────────────────────────────────────────────────────────── */
.home-faq {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  text-align: left;
  border-top: 1px solid rgba(22, 17, 11, 0.14);
}
.home-faq__item {
  border-bottom: 1px solid rgba(22, 17, 11, 0.14);
}
.home-faq__details {
  /* Anchor target for ::marker neutralisation in Safari. */
  position: relative;
}
.home-faq__question {
  /* Hide the default disclosure triangle in every engine. */
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 22px 4px;
  font-family: inherit;
  font-size: clamp(17px, 1.55vw, 19px);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: #16110b;
  transition: color 180ms ease;
}
.home-faq__question::-webkit-details-marker {
  display: none;
}
.home-faq__question::marker {
  /* Belt-and-suspenders: Firefox/standards engines use ::marker. */
  display: none;
  content: "";
}
.home-faq__question:hover,
.home-faq__question:focus-visible {
  color: #d61543;
}
.home-faq__question:focus-visible {
  outline: 2px solid rgba(214, 21, 67, 0.45);
  outline-offset: 4px;
  border-radius: 6px;
}
.home-faq__question-text {
  flex: 1;
}

/* Chevron pill ─────────────────────────────────────────────────────── */
.home-faq__chevron {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 999px;
  background: rgba(22, 17, 11, 0.05);
  color: rgba(22, 17, 11, 0.6);
  transition:
    transform 240ms cubic-bezier(0.22, 1, 0.36, 1),
    background 180ms ease,
    color 180ms ease;
}
.home-faq__question:hover .home-faq__chevron,
.home-faq__question:focus-visible .home-faq__chevron {
  background: rgba(214, 21, 67, 0.08);
  color: #d61543;
}
.home-faq__details[open] .home-faq__chevron {
  transform: rotate(180deg);
  background: rgba(22, 17, 11, 0.08);
  color: #16110b;
}

/* Open / close animation ──────────────────────────────────────────────
 *
 * Native <details> normally snaps open / closed. We animate the
 * transition using three modern CSS features so the section stays
 * fully JS-free:
 *
 *   1. ::details-content
 *        Pseudo-element targeting just the rendered content slot for
 *        a <details> (everything that isn't <summary>). We clip it
 *        with `block-size: 0; overflow-y: clip` when closed and let
 *        it expand to its intrinsic height on [open].
 *
 *   2. interpolate-size: allow-keywords (set on .home-row--faq above)
 *        Enables transitions between numeric values and the keyword
 *        `auto`. Without this, `block-size: auto → 0` snaps instead
 *        of tweening.
 *
 *   3. @starting-style
 *        Provides the "from" frame the first time [open] is applied,
 *        so the open direction animates from height 0 / faded out
 *        instead of starting at the destination value and skipping
 *        the transition.
 *
 *   4. transition-behavior: allow-discrete (on content-visibility)
 *        Holds content-visibility on its "visible" frame through the
 *        height transition on close, so the content isn't hidden
 *        instantly the moment [open] is removed.
 *
 * On top of the height change we cross-fade opacity and add a small
 * upward slide so the answer feels like it's "settling in" rather
 * than just a flat reveal. The fade is delayed slightly on open so
 * the height starts moving first, then the copy arrives — feels
 * more polished than firing everything in lockstep.
 *
 * Browsers that don't support ::details-content / interpolate-size
 * yet just get the native snap. No layout breakage. */
.home-faq__details::details-content {
  block-size: 0;
  overflow-y: clip;
  opacity: 0;
  transform: translateY(-4px);
  transition:
    block-size 380ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 240ms 80ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 380ms cubic-bezier(0.22, 1, 0.36, 1),
    content-visibility 380ms allow-discrete;
}
.home-faq__details[open]::details-content {
  block-size: auto;
  opacity: 1;
  transform: translateY(0);
}
@starting-style {
  .home-faq__details[open]::details-content {
    block-size: 0;
    opacity: 0;
    transform: translateY(-4px);
  }
}

/* Answer body ──────────────────────────────────────────────────────── */
.home-faq__answer {
  padding: 0 4px 24px;
  font-size: clamp(15px, 1.35vw, 16.5px);
  line-height: 1.6;
  color: rgba(22, 17, 11, 0.72);
}
.home-faq__answer p {
  margin: 0;
  max-width: 640px;
}
.home-faq__answer p + p {
  margin-top: 12px;
}
.home-faq__answer a {
  color: #d61543;
  font-weight: 500;
  text-decoration-color: rgba(214, 21, 67, 0.35);
  text-underline-offset: 3px;
}
.home-faq__answer a:hover {
  text-decoration-color: rgba(214, 21, 67, 0.8);
}

/* Reduce motion -------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .home-row__record,
  .home-row__ticker-track,
  .home-row__download-pulse,
  .home-row__search-bar-caret {
    animation: none !important;
  }
  /* Coverflow: kill the JS-driven cycle but keep the static 3D pose so
   * the section still reads as the CoverFlow strip. initCoverflow()
   * checks the same media query and skips its setInterval. */
  .home-row__coverflow img {
    transition: none !important;
  }
  .home-row__download-pulse {
    /* Hold a single static ring at the phone's footprint when motion is reduced. */
    opacity: 0.35;
    transform: scale(1);
  }
  /* FAQ accordion: drop the chevron rotation tween + the
   * ::details-content height/opacity/transform animation so the row
   * snaps open/closed instead of easing. Native <details> behaviour
   * with no transition layer on top. */
  .home-faq__chevron,
  .home-faq__details::details-content {
    transition: none !important;
  }
}
