/* ═══════════════════════════════════════════════════════════════════
   Air Drift — implementation of the Air Drift design system.
   Token names, values, type scale, spacing, radii, shadows and sizes
   are taken from the system; see design-system.html for the reference.
   ═══════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: "Archivo";
  src: url("../fonts/Archivo-Variable.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/IBMPlexMono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/IBMPlexMono-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/IBMPlexMono-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ─── tokens: Night (the system's primary theme) ───────────────────── */

:root {
  color-scheme: dark;

  --surface-000: #08090a;
  --surface-100: #0f1113;
  --surface-200: #16191c;
  --surface-300: #1e2226;
  --surface-400: #2a2f34;

  --line-100: #242a2f;
  --line-200: #3b4248;
  --line-300: #7c868f;

  --ink-100: #eef2f5;
  --ink-200: #b3bcc4;
  --ink-300: #8b97a2;

  --green-300: #a7f0c8;
  --green-500: #46d98a;
  --green-700: #1f9c5e;
  --on-green:  #04150c;

  --danger-500: #ff7d95;
  --focus-ring: #9ad8ff;
  --scrim: rgba(5, 7, 9, 0.74);

  --shadow-raised:  0 1px 2px rgba(0, 0, 0, 0.6);
  --shadow-overlay: 0 16px 40px rgba(0, 0, 0, 0.55);
  --shadow-player:  0 -8px 24px rgba(0, 0, 0, 0.5);

  /* type */
  --sans: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, monospace;

  /* 4px grid */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-pill: 999px;

  --control-sm: 28px;
  --control-md: 36px;
  --control-lg: 44px;
  --row-md: 44px;
  --row-lg: 56px;
  --art-sm: 40px;
  --art-md: 48px;
  --art-lg: 96px;
  --sidebar-w: 232px;
  --topbar-h: 56px;
  --player-h: 76px;
  --tile-w: 200px;

  /* motion: 120ms for hover/focus, 200ms for a panel */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur-control: 120ms;
  --dur-panel: 200ms;

  /* layout: content is held to a container, chrome runs full bleed */
  --container: 1200px;
  --gutter: var(--space-6);
}

/* ─── tokens: High contrast (the system's second theme) ────────────── */

:root[data-theme="hc"] {
  color-scheme: dark;

  --surface-000: #000000;
  --surface-100: #000000;
  --surface-200: #0a0c0d;
  --surface-300: #14171a;
  --surface-400: #22272b;

  --line-100: #333a3f;
  --line-200: #555f66;
  --line-300: #96a1aa;

  --ink-100: #ffffff;
  --ink-200: #e4eaee;
  --ink-300: #b6c0c8;

  --green-300: #c9fadf;
  --green-500: #6ff0a8;
  --green-700: #2fb96f;
  --on-green:  #000000;

  --danger-500: #ffa8b6;
  --focus-ring: #ffffff;
  --scrim: rgba(0, 0, 0, 0.88);

  /* Shade alone must never separate, so these become borders. */
  --shadow-raised:  0 0 0 1px #555f66;
  --shadow-overlay: 0 0 0 2px #96a1aa;
  --shadow-player:  0 -1px 0 0 #555f66;
}

/* ─── tokens: Daylight ────────────────────────────────────────────────
   NOT part of the published design system, which defines Night and High
   contrast only. Added because a light option was asked for; values are
   derived to the system's rules — cool neutrals, one green, ink-300 the
   dimmest text at 4.5:1 — rather than taken from the source.
   ──────────────────────────────────────────────────────────────────── */

:root[data-theme="light"] {
  color-scheme: light;

  --surface-000: #dce2e6;
  --surface-100: #eceff1;
  --surface-200: #ffffff;
  --surface-300: #e0e6e9;
  --surface-400: #cfd7dc;

  --line-100: #d2dade;
  --line-200: #b2bcc3;
  --line-300: #6b757c;

  --ink-100: #11161a;
  --ink-200: #48525a;
  --ink-300: #545e67;

  --green-300: #0d6b3c;
  --green-500: #117a47;
  --green-700: #0b5c34;
  --on-green:  #ffffff;

  --danger-500: #b4243c;
  --focus-ring: #0b64a8;
  --scrim: rgba(20, 24, 28, 0.5);

  /* The globe sits heavier here: on a pale page a faint wash landed within
     a few points of a control's own fill and swallowed it. */
  --globe-ambient: .35;

  --shadow-raised:  0 1px 2px rgba(16, 24, 32, 0.12);
  --shadow-overlay: 0 16px 40px rgba(16, 24, 32, 0.18);
  --shadow-player:  0 -8px 24px rgba(16, 24, 32, 0.10);
}

/* ─── base ─────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--surface-100);
  color: var(--ink-100);
  /* body: 15/22, 400 */
  font-family: var(--sans);
  font-size: 15px;
  line-height: 22px;
  font-weight: 400;
  /* The system asks for no -webkit-font-smoothing: Archivo's hinting is better. */
  text-rendering: optimizeLegibility;
  overscroll-behavior-y: none;
}

button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; }

a { color: var(--green-500); text-decoration: none; }
a:hover { color: var(--green-300); text-decoration: underline; }

/* Focus is a 2px ring with a 2px offset, drawn outside the control so it
   never lands inside a green fill. It is never removed. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

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

/* ─── type scale ───────────────────────────────────────────────────── */

.t-display-xl { font: 700 44px/44px var(--sans); letter-spacing: -0.02em; }
.t-display-lg { font: 700 32px/36px var(--sans); letter-spacing: -0.018em; }
.t-display-md { font: 600 24px/28px var(--sans); letter-spacing: -0.012em; }
.t-title      { font: 600 17px/24px var(--sans); letter-spacing: -0.006em; }
.t-body       { font: 400 15px/22px var(--sans); }
.t-body-strong{ font: 600 15px/22px var(--sans); }
.t-label      { font: 500 13px/18px var(--sans); }
.t-label-strong { font: 600 13px/18px var(--sans); }
.t-caption    { font: 400 12px/16px var(--sans); }
.t-data-md    { font: 500 13px/18px var(--mono); letter-spacing: 0.01em; }
.t-data-sm    { font: 500 12px/16px var(--mono); letter-spacing: 0.02em; }
.t-eyebrow    { font: 600 11px/14px var(--mono); letter-spacing: 0.1em; }

/* ═══ shell ═════════════════════════════════════════════════════════
   Fixed: sidebar-w on the left, topbar-h across the content, player-h
   pinned to the bottom. Only the content column scrolls, ending with
   space-16 so the last row clears the player.
   ═════════════════════════════════════════════════════════════════ */

/* No bottom padding here: a sticky child can only stay pinned within its
   containing block's content box, so padding on the grid would unstick the
   sidebar for the last stretch of the page and leave a gap beneath it. The
   clearance for the player bar lives on the content column instead. */
.app {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100%;
}

/* ─── sidebar ──────────────────────────────────────────────────────── */

.rail {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100dvh;
  padding: var(--space-5) var(--space-5) calc(var(--player-h) + var(--space-4));
  background: var(--surface-200);
  border-right: 1px solid var(--line-200);
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-bottom: var(--space-1);
  font: 700 17px/24px var(--sans);
  letter-spacing: -0.02em;
  color: var(--ink-100);
}

/* The logo links home, but it is a logo: no underline or link colour on
   hover, which the global a:hover would otherwise add. */
.brand:hover { color: var(--ink-100); text-decoration: none; }

/* The logo: five signal bars on a green tile, then the name as one
   lowercase word, "air" in ink and "drift" in green. The lockup is sized
   from --brand, so the landing page scales it up with one number. Tracking
   stays near neutral: tighter and the lowercase letters start to touch. */
.brand { --brand: 32px; gap: var(--space-3); }

.brand-word {
  font-size: calc(var(--brand) * 0.72);
  line-height: 1;
  letter-spacing: -0.01em;
}

.brand b { color: var(--green-500); font-weight: 700; }

.brand-tile {
  display: grid;
  flex: none;
  place-items: center;
  width: var(--brand);
  height: var(--brand);
  border-radius: calc(var(--brand) * 0.28);
  background: var(--green-500);
  color: var(--on-green);
}

.brand-tile svg {
  width: 72%;
  height: 72%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.8;
  stroke-linecap: round;
}

/* While a station plays, the bars move like a level meter. */
.brand-tile path {
  transform-box: fill-box;
  transform-origin: center;
}

body:has(.player[data-state="playing"]) .brand-tile path {
  animation: brand-level 0.9s ease-in-out infinite alternate;
}

body:has(.player[data-state="playing"]) .brand-tile path:nth-child(2) { animation-delay: -0.45s; animation-duration: 0.7s; }
body:has(.player[data-state="playing"]) .brand-tile path:nth-child(3) { animation-delay: -0.2s; animation-duration: 1.1s; }
body:has(.player[data-state="playing"]) .brand-tile path:nth-child(4) { animation-delay: -0.6s; animation-duration: 0.8s; }
body:has(.player[data-state="playing"]) .brand-tile path:nth-child(5) { animation-delay: -0.3s; animation-duration: 1s; }

@keyframes brand-level {
  from { transform: scaleY(1); }
  to { transform: scaleY(0.45); }
}

@media (prefers-reduced-motion: reduce) {
  body:has(.player[data-state="playing"]) .brand-tile path { animation: none; }
}

.brand-tag {
  padding-top: var(--space-2);
  padding-bottom: var(--space-6);
  font: 400 12px/16px var(--sans);
  color: var(--ink-300);
}

.rail-group { margin-bottom: var(--space-6); }

.rail-label {
  padding: 0 var(--space-3) var(--space-2);
  font: 600 11px/14px var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-300);
}

/* NavItem: control-md floor, space-3 padding. */
.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--control-md);
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  font: 500 13px/18px var(--sans);
  color: var(--ink-200);
  text-align: left;
  transition: background var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}

.nav-item svg { flex: none; width: 18px; height: 18px; }

/* Hover changes the surface, never the text colour — green means "here". */
.nav-item:hover { background: var(--surface-300); color: var(--ink-100); }
.nav-item:hover { text-decoration: none; }

/* Active carries three signals: fill, green label at 600, and a leading rule. */
.nav-item[aria-current="true"] {
  background: var(--surface-300);
  color: var(--green-500);
  font-weight: 600;
}

.nav-item[aria-current="true"]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 20px;
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  background: var(--green-500);
}

.nav-item .count {
  margin-left: auto;
  font: 500 13px/18px var(--mono);
  letter-spacing: 0.01em;
  color: var(--ink-300);
}

/* ─── main + top bar ───────────────────────────────────────────────── */

.main { min-width: 0; }

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--topbar-h);
  padding-block: var(--space-2);
  /* One content line runs down the page: the top bar, the view and the player
     bar all start and end on the same pixel. .view is the reference — a
     centred container whose max-width includes its gutters — so everything
     that spans a wider box has to inset by half the slack *plus* that gutter.
     Centring on the container alone left the search field a gutter wider than
     the table under it. */
  padding-inline: max(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
  background: var(--surface-200);
  border-bottom: 1px solid var(--line-200);
}

/* SearchField: control-lg, filters as you type, no submit. */
.search {
  position: relative;
  flex: 1;
  max-width: 520px;
}

.search > svg {
  position: absolute;
  left: var(--space-3);
  top: 50%;
  width: 18px;
  height: 18px;
  transform: translateY(-50%);
  color: var(--ink-300);
  pointer-events: none;
}

.search input {
  width: 100%;
  height: var(--control-lg);
  padding: 0 var(--space-12) 0 var(--space-10);
  background: var(--surface-300);
  border: 1px solid var(--line-300);
  border-radius: var(--radius-md);
  font: 400 15px/22px var(--sans);
  color: var(--ink-100);
}

.search input::placeholder { color: var(--ink-300); }

/* The one control in the system with a custom focus treatment: a permanent
   fixture, so an offset ring would sit against the top bar all day. */
.search input:focus-visible {
  outline: none;
  border-color: var(--green-500);
  box-shadow: inset 0 0 0 1px var(--green-500);
}

.search kbd {
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  min-width: 20px;
  padding: 2px var(--space-1);
  border: 1px solid var(--line-300);
  border-radius: var(--radius-xs);
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--ink-300);
}

.search input:not(:placeholder-shown) ~ kbd { display: none; }

.theme-btn { margin-left: auto; flex: none; }

/* The player's favourite toggle is always visible — unlike the one in a row,
   which appears on hover — because the bar is the only place the current
   station can be saved from. */
.fav-btn svg { width: 18px; height: 18px; }
.fav-btn[aria-pressed="true"] { color: var(--green-500); }

/* ─── view + section header ────────────────────────────────────────── */

.view {
  max-width: var(--container);
  margin-inline: auto;
  padding: var(--space-8) var(--gutter) calc(var(--player-h) + var(--space-16));
}

.view-head {
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--line-100);
}

/* One display-xl per page, and nothing else at that size. */
.view-head h1 {
  margin: 0;
  font: 700 44px/44px var(--sans);
  letter-spacing: -0.02em;
  color: var(--ink-100);
}

.view-head p {
  margin: var(--space-2) 0 0;
  font: 500 13px/18px var(--mono);
  letter-spacing: 0.01em;
  color: var(--ink-200);
}

/* ═══ station rows ══════════════════════════════════════════════════
   Name is body-strong and never smaller. Metadata beneath is data-sm
   in ink-200 — a different family, size and colour, so the two lines
   are never confused. The playing row keeps its place.
   ═════════════════════════════════════════════════════════════════ */

.table-wrap {
  border: 1px solid var(--line-100);
  border-radius: var(--radius-lg);
  background: var(--surface-200);
}

table.directory {
  table-layout: fixed;
  width: 100%;
  border-collapse: collapse;
}

.directory thead th {
  position: sticky;
  top: var(--topbar-h);
  z-index: 5;
  padding: var(--space-2) var(--space-4);
  background: var(--surface-200);
  border-bottom: 1px solid var(--line-100);
  font: 600 11px/14px var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-300);
  text-align: left;
  white-space: nowrap;
}

.directory thead th:first-child { border-top-left-radius: var(--radius-lg); }
.directory thead th:last-child { border-top-right-radius: var(--radius-lg); }

.directory tbody tr {
  border-bottom: 1px solid var(--line-100);
  transition: background var(--dur-control) var(--ease);
}

.directory tbody tr:last-child { border-bottom: 0; }
.directory tbody tr:hover { background: var(--surface-300); }

/* Playing: surface-300, the name green, the index swapped for bars. */
.directory tr[data-active="true"] { background: var(--surface-300); }

.directory td {
  height: var(--row-lg);
  padding: 0 var(--space-4);
  vertical-align: middle;
}

.c-rank { width: 56px; }

.rank {
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-300);
}

tr[data-active="true"] .rank { color: var(--green-500); }

.row-name {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  padding: 0;
  text-align: left;
}

.row-text { min-width: 0; }

.row-title {
  display: block;
  overflow: hidden;
  font: 600 15px/22px var(--sans);
  color: var(--ink-100);
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The one green per screen: the station that is sounding. */
tr[data-active="true"] .row-title { color: var(--green-500); }

.row-sub {
  display: block;
  overflow: hidden;
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.c-genre { max-width: 190px; }

.genre-text {
  display: block;
  overflow: hidden;
  font: 500 13px/18px var(--sans);
  color: var(--ink-200);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.c-plays {
  width: 80px;
  font: 500 13px/18px var(--mono);
  letter-spacing: 0.01em;
  color: var(--ink-200);
  text-align: right;
}

.c-format { width: 116px; }
.c-actions { width: 150px; }

.cell-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* ─── artwork ──────────────────────────────────────────────────────
   Directory artwork is untrusted: a surface-300 tile, object-fit cover,
   falling back to two initials in ink-200 — never a generic glyph.
   ─────────────────────────────────────────────────────────────────── */

.art {
  position: relative;
  width: var(--art-sm);
  height: var(--art-sm);
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--surface-300);
  overflow: hidden;
  font: 600 13px/18px var(--mono);
  color: var(--ink-200);
}

.art img,
.now-art img,
.hifi-art img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.art img[hidden],
.now-art img[hidden],
.hifi-art img[hidden] { display: none; }

/* ─── badges ───────────────────────────────────────────────────────
   One fact each, mono-set, never an action. Three in a row is the max.
   ─────────────────────────────────────────────────────────────────── */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--control-sm);
  padding: 0 var(--space-2);
  border: 1px solid var(--line-300);
  border-radius: var(--radius-xs);
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
  white-space: nowrap;
}

.badge-none { border-color: transparent; color: var(--ink-300); }

/* ─── favourite + tune in ──────────────────────────────────────────── */

.fav {
  width: var(--control-md);
  height: var(--control-md);
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  color: var(--ink-200);
  opacity: 0;
  transition: opacity var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}

/* A saved station reads as saved without hovering. */
.station:hover .fav,
.directory tr:hover .fav,
.fav[aria-pressed="true"],
.fav:focus-visible { opacity: 1; }

.fav:hover { background: var(--surface-400); color: var(--ink-100); }
.fav[aria-pressed="true"] { color: var(--green-500); }
.fav svg { width: 18px; height: 18px; }

.tunein {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--control-md);
  padding: 0 var(--space-3);
  border: 1px solid var(--line-300);
  border-radius: var(--radius-md);
  font: 600 13px/18px var(--sans);
  color: var(--ink-100);
  white-space: nowrap;
  transition: background var(--dur-control) var(--ease),
              border-color var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}

.tunein:hover {
  background: var(--surface-400);
  border-color: var(--line-300);
  color: var(--ink-100);
}

.tunein-on { display: none; }
.tunein-on svg { width: 14px; height: 14px; }

/* The playing row's button states it in a word, with green beside it. */
tr[data-active="true"] .tunein,
.station[data-active="true"] .tunein {
  border-color: var(--green-500);
  color: var(--green-500);
}

tr[data-active="true"] .tunein-on,
.station[data-active="true"] .tunein-on { display: inline-flex; }

/* ─── cards view ───────────────────────────────────────────────────── */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-w), 1fr));
  gap: var(--space-3);
}

.station {
  display: grid;
  grid-template-columns: var(--art-sm) 1fr auto;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--row-lg);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-300);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  text-align: left;
  transition: background var(--dur-control) var(--ease);
}

.station:hover { background: var(--surface-400); }

.station[data-active="true"] {
  background: var(--surface-400);
  box-shadow: inset 0 0 0 1px var(--green-500);
}

.station-body { min-width: 0; }

.station-name {
  overflow: hidden;
  font: 600 15px/22px var(--sans);
  color: var(--ink-100);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.station[data-active="true"] .station-name { color: var(--green-500); }

.station-meta {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  overflow: hidden;
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
  white-space: nowrap;
}

.station-meta > * { flex: none; }
.station-meta .flag { font-family: var(--sans); font-size: 13px; }
.station-meta .dot { color: var(--ink-300); }

.station-meta .tag {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ─── country tiles + genre chips ──────────────────────────────────── */

.chips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tile-w), 1fr));
  gap: var(--space-3);
}

/* CountryTile: name outranks the count; never below control-md. */
.chip {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-md);
  padding: 0 var(--space-3);
  background: var(--surface-300);
  border: 1px solid var(--line-100);
  border-radius: var(--radius-sm);
  font: 600 13px/18px var(--sans);
  color: var(--ink-100);
  transition: background var(--dur-control) var(--ease);
}

.chip:hover {
  background: var(--surface-400);
  border-color: var(--line-200);
  text-decoration: none;
}

:root[data-theme="light"] .chip { background: var(--surface-200); }
:root[data-theme="light"] .chip:hover { background: var(--surface-300); }
:root[data-theme="light"] .station { background: var(--surface-200); }
:root[data-theme="light"] .station:hover { background: var(--surface-300); }

.chip[aria-pressed="true"] {
  background: var(--surface-400);
  box-shadow: inset 0 0 0 1px var(--green-500);
}

.chip > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The count is supporting data, trailing, so the figures form a column. */
.chip .n {
  margin-left: auto;
  flex: none;
  font: 500 13px/18px var(--mono);
  letter-spacing: 0.01em;
  color: var(--ink-200);
}

/* Genre chips size to their label rather than a fixed column. */
.genre-group .chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.genre-group .chip { min-width: 0; }

.genre-group { margin-bottom: var(--space-8); }

.genre-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  font: 600 11px/14px var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-300);
}

.genre-head::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line-100);
}

.genre-count { color: var(--ink-300); }

/* ─── in-page filter ───────────────────────────────────────────────── */

/* Sits in the toolbar row beside the sort and view controls, at the same
   height as them, rather than taking a band of its own. */
.list-filter {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 180px;
  max-width: 300px;
}

.list-filter > svg {
  position: absolute;
  left: var(--space-2);
  width: 16px;
  height: 16px;
  color: var(--ink-300);
  pointer-events: none;
}

.list-filter input {
  width: 100%;
  height: var(--control-md);
  padding: 0 var(--space-10) 0 var(--space-8);
  background: var(--surface-200);
  border: 1px solid var(--line-300);
  border-radius: var(--radius-md);
  font: 500 13px/18px var(--sans);
  color: var(--ink-100);
}

.list-filter input::placeholder { color: var(--ink-300); }

/* The browser draws its own clear button inside a search field, mid-way
   across it and on top of whatever else is there. Ours is positioned. */
.list-filter input::-webkit-search-cancel-button,
.list-filter input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.list-filter input::-ms-clear { display: none; }

.list-filter__clear {
  position: absolute;
  right: var(--space-1);
  display: grid;
  place-items: center;
  width: var(--control-sm);
  height: var(--control-sm);
  border-radius: var(--radius-xs);
  color: var(--ink-200);
  transition: background var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}

.list-filter__clear:hover { background: var(--surface-400); color: var(--ink-100); }
.list-filter__clear svg { width: 14px; height: 14px; }

/* Leave room for whichever sits in the trailing slot. */
.list-filter[data-filled="true"] input { padding-right: var(--space-8); }

/* Matches the top bar's field: an inset green border rather than the
   standard ring, since this control is a fixture of the page. */
.list-filter input:focus-visible {
  outline: none;
  border-color: var(--green-500);
  box-shadow: inset 0 0 0 1px var(--green-500);
}

.list-filter__count {
  position: absolute;
  right: var(--space-2);
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
  pointer-events: none;
}

/* ─── pager ────────────────────────────────────────────────────────── */

.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-6) 0 var(--space-2);
}

.pager-range {
  font: 500 13px/18px var(--mono);
  letter-spacing: 0.01em;
  color: var(--ink-200);
}

/* The page numbers are provisional until the count settles, so they are
   dimmed and inert rather than inviting a jump that may not exist. */
.pager[data-counting="true"] .pager-range { color: var(--ink-300); }

/* Tabular figures so the scrambling total holds its width instead of
   jittering the line as the digits change. */
.pager-total {
  font-variant-numeric: tabular-nums;
  color: var(--green-500);
}

.pager[data-counting="true"] .pager-controls {
  opacity: .5;
  pointer-events: none;
}

.pager-controls { display: flex; align-items: center; gap: var(--space-1); }

.page-btn {
  min-width: var(--control-md);
  height: var(--control-md);
  padding: 0 var(--space-2);
  background: var(--surface-300);
  border: 1px solid var(--line-100);
  border-radius: var(--radius-sm);
  font: 500 13px/18px var(--mono);
  letter-spacing: 0.01em;
  color: var(--ink-200);
  transition: background var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}

.page-btn:hover:not(:disabled) { background: var(--surface-400); color: var(--ink-100); }

.page-btn:disabled {
  color: var(--ink-300);
  cursor: default;
  opacity: .55;
}

/* The page you are on, marked by fill and weight rather than colour alone. */
.page-btn[aria-current="page"] {
  background: var(--surface-400);
  border-color: var(--green-500);
  color: var(--ink-100);
  font-weight: 600;
}

.page-btn[data-busy="true"] { color: var(--ink-300); }

.pager-gap {
  padding: 0 var(--space-1);
  font: 500 13px/18px var(--mono);
  color: var(--ink-300);
}

@media (max-width: 860px) {
  .pager { justify-content: center; }
  .pager-range { width: 100%; text-align: center; }
}

/* ─── toolbar ──────────────────────────────────────────────────────── */

.toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.toolbar-label {
  font: 600 11px/14px var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-300);
}

.seg {
  display: inline-flex;
  border: 1px solid var(--line-300);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.seg button {
  height: var(--control-md);
  padding: 0 var(--space-3);
  border-right: 1px solid var(--line-300);
  font: 500 13px/18px var(--sans);
  color: var(--ink-200);
  transition: background var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}

.seg button:last-child { border-right: 0; }
.seg button:hover { background: var(--surface-400); color: var(--ink-100); }

.seg button[aria-pressed="true"] {
  background: var(--surface-400);
  color: var(--ink-100);
  font-weight: 600;
  box-shadow: inset 0 0 0 1px var(--green-500);
}

.toolbar-spacer { flex: 1; }

/* ═══ globe ═════════════════════════════════════════════════════════
   A wireframe locator: green-500 at low opacity with land paths in
   line-300. Kept as a full-bleed backdrop at the product owner's
   direction; the system's own rule caps it at 220px (see
   design-system.html, "Where this implementation differs").
   ═════════════════════════════════════════════════════════════════ */

.globe-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-panel) var(--ease);
  contain: strict;
}

.globe-layer[data-mode="ambient"] { opacity: var(--globe-ambient, .25); }

.globe-layer[data-mode="focus"] {
  opacity: 1;
  pointer-events: auto;
}

.globe-spacer {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Kept in step with the focus fallback in js/app.js. The band grows with
     the globe so the grid still starts underneath it, not behind it. */
  height: min(70vh, 660px);
  pointer-events: none;
}

/* The globe layer sits below .app, so the structural containers go
   transparent in focus mode and the interactive parts opt back in. */
body[data-globe="focus"] .app,
body[data-globe="focus"] .main,
body[data-globe="focus"] .view { pointer-events: none; }

body[data-globe="focus"] .rail,
body[data-globe="focus"] .topbar,
body[data-globe="focus"] .view > *:not(.globe-spacer) { pointer-events: auto; }

.globe-canvas { display: block; width: 100%; height: 100%; }
.globe-layer[data-mode="focus"] .globe-canvas { touch-action: none; }

.globe-hint {
  padding-bottom: var(--space-1);
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-300);
  pointer-events: none;
}

.globe-tip {
  position: absolute;
  z-index: 5;
  transform: translate(-50%, calc(-100% - var(--space-3)));
  padding: var(--space-1) var(--space-2);
  background: var(--surface-300);
  border: 1px solid var(--line-300);
  border-radius: var(--radius-xs);
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-100);
  white-space: nowrap;
  pointer-events: none;
}

/* ═══ player bar ════════════════════════════════════════════════════
   Three zones: what, control, how loud. The name is title, the
   metadata data-sm, and the state is always a word.
   ═════════════════════════════════════════════════════════════════ */

.player {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 40;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-4);
  height: var(--player-h);
  /* Fixed to the window, so unlike the top bar it spans the rail too and has
     to step over it by hand. Without the rail's width in the left inset the
     whole bar sat a sidebar to the left of everything above it. */
  padding-left: calc(var(--sidebar-w) +
    max(var(--gutter), calc((100% - var(--sidebar-w) - var(--container)) / 2 + var(--gutter))));
  padding-right:
    max(var(--gutter), calc((100% - var(--sidebar-w) - var(--container)) / 2 + var(--gutter)));
  background: var(--surface-200);
  border-top: 1px solid var(--line-200);
  box-shadow: var(--shadow-player);
}

.player[data-idle="true"] .now-art,
.player[data-idle="true"] .player-right { visibility: hidden; }

.now { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }

.now-art {
  position: relative;
  width: var(--art-md);
  height: var(--art-md);
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--surface-300);
  overflow: hidden;
  font: 600 15px/22px var(--mono);
  color: var(--ink-200);
}

.now-body { min-width: 0; }

.now-title {
  overflow: hidden;
  font: 600 17px/24px var(--sans);
  letter-spacing: -0.006em;
  color: var(--ink-100);
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Playing turns the name green; paused leaves it ink-100. */
.player[data-state="playing"] .now-title { color: var(--green-500); }

.now-sub {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: 2px;
  overflow: hidden;
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
  white-space: nowrap;
}

/* The state word leads the metadata line and carries its own colour. */
.state-word { font-weight: 600; }
.player[data-state="playing"] .state-word { color: var(--green-500); }
.player[data-state="error"] .state-word { color: var(--danger-500); }

/* Equaliser: four bars where a scrubber would be. Still while buffering. */
.bars {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
}

.bars i {
  display: block;
  width: 3px;
  height: 35%;
  border-radius: 1px;
  background: var(--green-500);
}

.player[data-state="playing"] .bars i { animation: eq .9s ease-in-out infinite; }
.bars i:nth-child(2) { animation-delay: .18s; }
.bars i:nth-child(3) { animation-delay: .36s; }
.bars i:nth-child(4) { animation-delay: .1s; }

@keyframes eq {
  0%, 100% { height: 25%; }
  50% { height: 100%; }
}

.transport { display: flex; align-items: center; gap: var(--space-2); }

.t-btn {
  width: var(--control-md);
  height: var(--control-md);
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  color: var(--ink-200);
  transition: background var(--dur-control) var(--ease),
              color var(--dur-control) var(--ease);
}

.t-btn:hover:not(:disabled) { background: var(--surface-300); color: var(--ink-100); }
.t-btn:disabled { color: var(--ink-300); cursor: default; }
.t-btn svg { width: 18px; height: 18px; }

/* The largest target in the product; nothing else on the bar matches it. */
.t-play {
  width: var(--control-lg);
  height: var(--control-lg);
  border-radius: var(--radius-pill);
  background: var(--green-500);
  color: var(--on-green);
}

.t-play:hover:not(:disabled) { background: var(--green-500); color: var(--on-green); }
.t-play:active:not(:disabled) { background: var(--green-700); }
.t-play:disabled { background: var(--surface-400); color: var(--ink-300); }
.t-play svg { width: 20px; height: 20px; }
.t-play[data-busy="true"] svg { animation: spin .9s linear infinite; }

@keyframes spin { to { transform: rotate(360deg); } }

.player-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}

.vol { display: flex; align-items: center; gap: var(--space-2); }

.vol input[type="range"] {
  width: 96px;
  height: 4px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--surface-400);
  border-radius: var(--radius-xs);
}

.vol input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 14px;
  border-radius: var(--radius-pill);
  background: var(--ink-100);
  cursor: pointer;
}

.vol input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--ink-100);
  cursor: pointer;
}

/* ═══ expanded sheet ════════════════════════════════════════════════ */

.hifi {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: var(--space-5);
}

.hifi[hidden] { display: none; }

.hifi-backdrop {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  animation: fade var(--dur-panel) var(--ease);
}

@keyframes fade { from { opacity: 0; } }

.hifi-panel {
  position: relative;
  width: min(880px, 94vw);
  padding: var(--space-5);
  background: var(--surface-200);
  border: 1px solid var(--line-100);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-overlay);
  animation: rise var(--dur-panel) var(--ease);
  isolation: isolate;
}

@keyframes rise { from { opacity: 0; transform: translateY(12px); } }

.hifi-head {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.hifi-art {
  position: relative;
  width: var(--art-lg);
  height: var(--art-lg);
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-lg);
  background: var(--surface-300);
  overflow: hidden;
  font: 600 24px/28px var(--mono);
  color: var(--ink-200);
}

.hifi-id { flex: 1; min-width: 0; }

.hifi-name {
  overflow: hidden;
  font: 700 32px/36px var(--sans);
  letter-spacing: -0.018em;
  color: var(--ink-100);
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* ─── now playing ──────────────────────────────────────────────────
   What the station says is on air. Green, because it is the one live
   fact on a panel otherwise full of static ones.
   ─────────────────────────────────────────────────────────────────── */

.hifi-track {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-2);
  overflow: hidden;
  font: 600 15px/20px var(--sans);
  color: var(--green-500);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.hifi-track[hidden] { display: none; }

.track-artist { flex: none; color: var(--ink-200); font-weight: 500; }
.track-sep { flex: none; color: var(--ink-300); }
.track-title { overflow: hidden; text-overflow: ellipsis; }

/* In the bar the line is mono like the facts it replaces, so the row's
   rhythm does not change when a track arrives. */
.now-track {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
  overflow: hidden;
  color: var(--green-500);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.now-track .track-artist,
.now-track .track-sep { color: var(--ink-200); }

.hifi-meta {
  margin-top: var(--space-1);
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
}

.hifi-deck {
  padding: var(--space-4);
  background: var(--surface-100);
  border: 1px solid var(--line-100);
  border-radius: var(--radius-lg);
}

.viz-full {
  display: block;
  width: 100%;
  height: 208px;
  contain: strict;
}

.viz-compact {
  display: block;
  flex: none;
  width: 104px;
  height: 28px;
  contain: strict;
}

/* ─── country mini map ─────────────────────────────────────────────
   Sits beside the artwork in the player's "what" zone: the bar already
   says which station and which stream, this says where it is.
   ─────────────────────────────────────────────────────────────────── */

/* No tile behind it: the outline carries the shape on its own, and a filled
   box beside the artwork read as a second, competing thumbnail. Sized to the
   bar's full content height. */
.cmap {
  width: 60px;
  height: 52px;
  flex: none;
  display: grid;
  place-items: center;
}

.cmap[hidden] { display: none; }

.cmap-shape { display: block; }

.cmap-shape path {
  fill: var(--ink-300);
  transition: fill var(--dur-control) var(--ease);
}

/* The one green: the country you are actually listening to. */
.player[data-state="playing"] .cmap-shape path { fill: var(--green-500); }
.player[data-state="error"] .cmap-shape path { fill: var(--danger-500); }

.player[data-idle="true"] .viz-compact { visibility: hidden; }

/* ─── where it broadcasts from ─────────────────────────────────────
   The panel's one piece of reference: the country as a shape, the
   station's own coordinates pinned on it, and a way out to its site.
   ─────────────────────────────────────────────────────────────────── */

.hifi-where {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--space-5);
  margin-top: var(--space-4);
  padding: var(--space-4);
  background: var(--surface-100);
  border: 1px solid var(--line-100);
  border-radius: var(--radius-lg);
}

.hifi-where[hidden] { display: none; }

/* A handful of countries have no outline at this resolution (city states,
   mostly). Without a map to sit beside, the facts take the whole width. */
.hifi-where:not(:has(.hifi-map)) { grid-template-columns: 1fr; }

.hifi-map {
  width: 240px;
  height: 168px;
  flex: none;
  display: grid;
  place-items: center;
}

/* Reuse the bar's outline treatment, a step down in weight: the pin is what
   the eye should land on, so the country behind it recedes. */
/* The SVG carries pixel width and height for the player bar's fixed tile.
   Here the box owns the size, so let the viewBox scale into it. */
.hifi-map .cmap-shape { width: 100%; height: auto; }

.hifi-map .cmap-shape path { fill: var(--line-300); }

.hifi-map[data-pinned="true"] .cmap-shape path { fill: var(--surface-400); }

.cmap-halo {
  fill: var(--green-500);
  opacity: 0.22;
}

.cmap-dot {
  fill: var(--green-500);
  stroke: var(--surface-100);
  stroke-width: 1.2;
}

/* The pin only pulses while sound is coming out of the transmitter. */
.hifi[data-state="playing"] .cmap-halo {
  animation: pin-pulse 2.4s var(--ease) infinite;
}

@keyframes pin-pulse {
  0%, 100% { opacity: 0.18; transform: scale(0.88); }
  50% { opacity: 0.4; transform: scale(1.12); }
}

.cmap-halo { transform-box: fill-box; transform-origin: center; }

@media (prefers-reduced-motion: reduce) {
  .hifi[data-state="playing"] .cmap-halo { animation: none; }
}

.hifi-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  min-width: 0;
  margin: 0;
}

.hifi-facts dt {
  font: 500 11px/16px var(--mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-300);
  white-space: nowrap;
}

.hifi-facts dd {
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font: 500 14px/20px var(--sans);
  color: var(--ink-100);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.hifi-facts .hifi-coords {
  font: 500 13px/20px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
}

.hifi-home {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  overflow: hidden;
  color: var(--green-500);
  text-decoration: none;
  white-space: nowrap;
  text-overflow: ellipsis;
  transition: color var(--dur-control) var(--ease);
}

.hifi-home:hover { color: var(--ink-100); text-decoration: underline; }
.hifi-home svg { width: 14px; height: 14px; flex: none; }

.hifi-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.hifi-transport { display: flex; align-items: center; gap: var(--space-2); }

.signal {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 132px;
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
  cursor: help;
}

.signal-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--ink-300);
  transition: background var(--dur-control) var(--ease);
}

.signal[data-mode="live"] { color: var(--green-500); }
.signal[data-mode="live"] .signal-dot { background: var(--green-500); }
.signal[data-mode="sim"] .signal-dot { background: var(--ink-200); }

.hifi-rate {
  min-width: 132px;
  font: 500 12px/16px var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-200);
  text-align: right;
}

/* ═══ states ════════════════════════════════════════════════════════ */

.empty {
  padding: var(--space-12) var(--space-5);
  text-align: center;
  color: var(--ink-200);
}

.empty svg { width: 32px; height: 32px; margin-bottom: var(--space-3); color: var(--ink-300); }

.empty h2 {
  margin: 0 0 var(--space-2);
  font: 600 17px/24px var(--sans);
  letter-spacing: -0.006em;
  color: var(--ink-100);
}

.empty p { margin: 0 auto; max-width: 46ch; font: 400 15px/22px var(--sans); }

/* The station list fades in when it is replaced, so the snapshot-to-live
   swap reads as an update rather than a jump. The header and toolbar sit
   outside this and stay put. */
.list-body { animation: list-in var(--dur-panel) var(--ease); }

@keyframes list-in {
  from { opacity: 0; transform: translateY(4px); }
}

.skeleton {
  height: var(--row-lg);
  border-radius: var(--radius-lg);
  background: var(--surface-300);
}

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--player-h) + var(--space-4));
  z-index: 60;
  transform: translate(-50%, 8px);
  max-width: min(92vw, 420px);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-300);
  border: 1px solid var(--line-200);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  font: 400 15px/22px var(--sans);
  color: var(--ink-100);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-panel) var(--ease), transform var(--dur-panel) var(--ease);
}

.toast[data-show="true"] { opacity: 1; transform: translate(-50%, 0); }
.toast[data-kind="error"] { border-color: var(--danger-500); }

/* ═══ small screens ═════════════════════════════════════════════════ */

.tabbar { display: none; }

@media (max-width: 860px) {
  /* The rail is gone here, so the player bar has nothing to step over. */
  :root { --gutter: var(--space-4); --sidebar-w: 0px; }

  .app { grid-template-columns: 1fr; }
  .rail { display: none; }

  .view {
    padding-top: var(--space-6);
    padding-bottom: calc(var(--player-h) + 58px + var(--space-8));
  }
  .view-head h1 { font: 700 32px/36px var(--sans); letter-spacing: -0.018em; }

  .grid, .chips { grid-template-columns: 1fr; }
  /* The rail is hidden, so the top bar carries the way home: the logo's
     tile alone, at the search field's height, leaving the field its room. */
  .topbar .topbar-brand { display: flex; flex: none; padding-bottom: 0; --brand: 40px; }

  /* No globe on Countries on a phone (see globeModeFor in js/app.js). */
  .globe-spacer { display: none; }
  .viz-compact { display: none; }

  .directory thead { display: none; }
  .directory td { height: var(--row-lg); padding: 0 var(--space-2); }
  .c-rank, .c-genre, .c-plays, .c-format { display: none; }

  /* The table is table-layout: fixed, so a 1% actions column reserved no
     space and the button overflowed its cell onto the station name. Give it
     a real width and let the name take what is left. */
  .c-actions { width: 118px; }
  .tunein { padding: 0 var(--space-2); }

  /* No hover on touch, so both actions are always visible. The on-air bars
     still belong to the playing row alone. */
  .directory .fav { opacity: 1; }
  .directory .fav { width: 32px; height: 32px; }
  .cell-actions { gap: var(--space-1); }

  /* A 240px map beside two columns of labels does not fit a phone: stack it,
     centred, and give the facts the full width. */
  .hifi-where {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: var(--space-4);
  }
  .hifi-map { width: 180px; height: 126px; }

  /* Labels beside values leave a phone about 200px for the value, which cuts
     "United States" in half. Stack each label over what it labels. */
  .hifi-facts {
    width: 100%;
    grid-template-columns: 1fr;
    gap: 0;
  }
  .hifi-facts dt { margin-top: var(--space-3); }
  .hifi-facts dt:first-of-type { margin-top: 0; }

  /* Never drop the state word, and never shrink the play button. The inset is
     the gutter here too: the content line should hold on a phone as well. */
  .player {
    bottom: 58px;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-2);
    padding-inline: var(--gutter);
  }

  .player-right { display: none; }
  .transport .t-btn:not(.t-play) { display: none; }

  .toolbar-label { display: none; }

  .hifi { padding: 0; place-items: end stretch; }
  .hifi-panel {
    width: 100%;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    padding: var(--space-4) var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom));
  }
  .hifi-name { font: 700 24px/28px var(--sans); letter-spacing: -0.012em; }
  .hifi-art { width: 64px; height: 64px; font-size: 18px; }
  .viz-full { height: 150px; }
  .hifi-foot { flex-wrap: wrap; justify-content: center; }
  .signal, .hifi-rate { min-width: 0; flex: 1; text-align: center; }
  .hifi-transport { order: -1; width: 100%; justify-content: center; }

  .tabbar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 45;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    height: calc(58px + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--surface-200);
    border-top: 1px solid var(--line-200);
  }

  .tab {
    display: grid;
    place-items: center;
    gap: 2px;
    font: 600 11px/14px var(--mono);
    letter-spacing: 0.06em;
    color: var(--ink-300);
  }

  .tab svg { width: 20px; height: 20px; }
  .tab[aria-current="true"] { color: var(--green-500); }
  .tab:hover { text-decoration: none; }
}

/* The equaliser becomes static bars; panels cut rather than slide. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
  .bars i { height: 60% !important; }
}

/* ═══ landing ═══════════════════════════════════════════════════════
   The page at #/. It drops the rail and top bar for its own header,
   puts the globe beside the headline, and slides an opaque sheet of
   explanatory copy up over the globe as the reader scrolls.

   The headline is the one display type on the page, set larger than
   display-xl: the landing is the single place the system allows that,
   because it introduces the product rather than a list of stations.
   ═════════════════════════════════════════════════════════════════ */

/* Until app.js marks the body, the entry flag set in <head> stands in. */
:is(body[data-view="home"], :root[data-entry="home"] body:not([data-view])) .app {
  grid-template-columns: 1fr;
}

:is(body[data-view="home"], :root[data-entry="home"] body:not([data-view])) :is(.rail, .topbar) {
  display: none;
}

:is(body[data-view="home"], :root[data-entry="home"] body:not([data-view])) .view {
  max-width: none;
  padding: 0 0 calc(var(--player-h) + var(--space-8));
}

:root[data-entry="deep"] body:not([data-view]) [data-home] { display: none; }

.home-bar,
.home-hero,
.home-section {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.home-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: 72px;
}

.home-bar .brand { padding-bottom: 0; }

/* Only on a phone: the rail carries the logo everywhere else. */
.topbar-brand { display: none; }

/* The landing page is where the name introduces itself: a larger lockup. */
.brand-lg { --brand: 44px; }
.brand-lg .brand-word { font-size: 32px; letter-spacing: -0.015em; }

.home-links { display: flex; gap: var(--space-6); }

.home-links a {
  font: 500 14px/20px var(--sans);
  color: var(--ink-200);
}

.home-links a:hover { color: var(--ink-100); text-decoration: none; }

/* ─── hero ─────────────────────────────────────────────────────────── */

.home-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
  /* A screen's worth, but no taller: on a very tall window the hero would
     float in the middle of a mostly empty page. */
  min-height: min(calc(100dvh - 72px - var(--player-h)), 900px);
  padding-block: var(--space-6) var(--space-16);
}

.home-copy {
  /* A grid item's minimum width is its content by default, so one long
     unbroken station name — or scrambled text mid-drift — widened the
     column past the screen and the page scrolled sideways for a moment.
     Held to the track, long lines wrap or truncate instead. */
  min-width: 0;
}

.home-globe {
  align-self: stretch;
  min-height: min(72vh, 680px);
}

.home-hero h1 {
  max-width: 11ch;
  margin: 0;
  font: 700 clamp(44px, 6.2vw, 84px)/0.98 var(--sans);
  letter-spacing: -0.035em;
  color: var(--ink-100);
  text-wrap: balance;
}

.home-lede {
  max-width: 44ch;
  margin: var(--space-5) 0 0;
  font: 400 18px/28px var(--sans);
  color: var(--ink-200);
  text-wrap: pretty;
}

.home-lede [data-live],
.home-prose [data-live] {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-100);
}

.home-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-8);
}

/* The Drift button starts playback, so it wears the play button's green. */
.home-drift {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 52px;
  padding: 0 var(--space-6) 0 var(--space-5);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--green-500);
  color: var(--on-green);
  font: 700 16px/1 var(--sans);
  letter-spacing: -0.005em;
  cursor: pointer;
}

.home-drift svg { width: 20px; height: 20px; flex: none; }
.home-drift:active { background: var(--green-700); }
.home-drift[aria-busy="true"] { cursor: progress; }
.home-drift[aria-busy="true"] svg { animation: spin .9s linear infinite; }

.home-search { flex: 1 1 240px; max-width: 320px; }

.home-search input {
  width: 100%;
  height: 52px;
  padding: 0 var(--space-5);
  background: var(--surface-300);
  border: 1px solid var(--line-200);
  border-radius: var(--radius-pill);
  font: 400 15px/22px var(--sans);
  color: var(--ink-100);
}

.home-search input::placeholder { color: var(--ink-300); }
.home-search input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* ─── tuner ────────────────────────────────────────────────────────── */

.tuner {
  max-width: 560px;
  min-width: 0;
  margin-top: var(--space-6);
  padding: var(--space-4) var(--space-5);
  background: var(--surface-200);
  border: 1px solid var(--line-200);
  border-radius: var(--radius-lg);
}

.tuner-top {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.tuner-signal {
  flex: none;
  width: 22px;
  height: 22px;
  fill: none;
  stroke: var(--ink-300);
  stroke-width: 2;
  stroke-linecap: round;
}

/* Green only once a station is actually live: it is the live indicator. */
.tuner[data-state="tuned"] .tuner-signal { stroke: var(--green-500); }
.tuner[data-state="tuning"] .tuner-signal { stroke: var(--ink-100); }

.tuner-place {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 600 24px/30px var(--sans);
  letter-spacing: -0.012em;
  color: var(--ink-100);
}

.tuner-coords {
  flex: none;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font: 500 13px/18px var(--mono);
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  white-space: pre;
  color: var(--ink-200);
}

.tuner-station {
  margin: var(--space-2) 0 0;
  overflow-wrap: anywhere;
  font: 400 15px/22px var(--sans);
  color: var(--ink-200);
}

.tuner[data-state="tuned"] .tuner-station {
  font-weight: 600;
  color: var(--ink-100);
}

.tuner-more {
  margin: var(--space-1) 0 0;
  overflow-wrap: anywhere;
  font: 400 13px/18px var(--sans);
  color: var(--ink-300);
}

.tuner-more a { color: var(--ink-200); text-decoration: underline; text-underline-offset: 3px; }
.tuner-more a:hover { color: var(--ink-100); }

.moods { margin-top: var(--space-6); }

.moods-label {
  display: block;
  margin-bottom: var(--space-2);
  font: 500 13px/18px var(--sans);
  color: var(--ink-300);
}

.moods-list { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.moods .chip { cursor: pointer; }
.moods .chip[aria-busy="true"] { box-shadow: inset 0 0 0 1px var(--ink-200); }

/* ─── the globe beside the hero ────────────────────────────────────── */

/* --hero-intro fades it in on first load; --hero-globe fades it as the
   hero scrolls away, so it never sits behind the reading copy. */
.globe-layer[data-mode="hero"] {
  opacity: calc(var(--hero-intro, 1) * var(--hero-globe, 1));
  pointer-events: auto;
  transition: none;
}

.globe-layer[data-mode="hero"] .globe-canvas { touch-action: pan-y; }

body[data-globe="hero"] .app,
body[data-globe="hero"] .main,
body[data-globe="hero"] .view,
body[data-globe="hero"] .home,
body[data-globe="hero"] .home-hero { pointer-events: none; }

body[data-globe="hero"] .home-bar,
body[data-globe="hero"] .home-copy,
body[data-globe="hero"] .home-sheet { pointer-events: auto; }

/* ─── the sheet ────────────────────────────────────────────────────── */

.home-sheet {
  position: relative;
  background: var(--surface-100);
  border-top: 1px solid var(--line-200);
}

.home-section {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--space-8);
  padding-block: var(--space-16);
  border-bottom: 1px solid var(--line-100);
}

.home-section h2 {
  max-width: 14ch;
  margin: 0;
  font: 700 32px/36px var(--sans);
  letter-spacing: -0.018em;
  color: var(--ink-100);
  text-wrap: balance;
}

.home-prose p {
  max-width: 62ch;
  margin: 0 0 var(--space-4);
  font: 400 17px/28px var(--sans);
  color: var(--ink-200);
}

.home-features {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-8) var(--space-10);
  margin: 0;
}

.home-features dt {
  font: 600 17px/24px var(--sans);
  color: var(--ink-100);
}

.home-features dt a {
  color: var(--ink-100);
  text-decoration: underline;
  text-decoration-color: var(--line-300);
  text-underline-offset: 4px;
}

.home-features dt a:hover { text-decoration-color: var(--ink-100); }

.home-features dd {
  margin: var(--space-2) 0 0;
  font: 400 15px/24px var(--sans);
  color: var(--ink-200);
}

.home-faq details { border-top: 1px solid var(--line-100); }
.home-faq details:last-child { border-bottom: 1px solid var(--line-100); }

.home-faq summary {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) 0;
  list-style: none;
  font: 600 17px/24px var(--sans);
  color: var(--ink-100);
  cursor: pointer;
}

.home-faq summary::-webkit-details-marker { display: none; }

.home-faq summary::after {
  content: "+";
  flex: none;
  font: 500 17px/24px var(--mono);
  color: var(--ink-300);
}

.home-faq details[open] summary::after { content: "\2212"; }

.home-faq details p {
  max-width: 64ch;
  margin: 0 0 var(--space-5);
  font: 400 15px/24px var(--sans);
  color: var(--ink-200);
}

.home-faq a { color: var(--ink-100); text-decoration: underline; text-underline-offset: 3px; }

.home-end {
  grid-template-columns: 1fr;
  justify-items: start;
  border-bottom: 0;
}

.home-end h2 { max-width: 18ch; font-size: clamp(32px, 4vw, 48px); line-height: 1.05; }

.home-end-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
}

.home-browse {
  font: 600 15px/22px var(--sans);
  color: var(--ink-100);
  text-decoration: underline;
  text-decoration-color: var(--line-300);
  text-underline-offset: 4px;
}

.home-browse:hover { text-decoration-color: var(--ink-100); }

/* The contact line under the closing call to action. */
.home-credit {
  margin: var(--space-4) 0 0;
  font: 400 13px/18px var(--sans);
  color: var(--ink-300);
}

/* Held back until js/home.js animates them in; revealed regardless if
   the animation never arrives. */
[data-intro="pending"] [data-intro-item] {
  visibility: hidden;
  animation: intro-fallback 0s 2.5s forwards;
}

@keyframes intro-fallback { to { visibility: visible; } }

@media (max-width: 860px) {
  /* Same selector as the rule above, or its :is() specificity wins here. */
  :is(body[data-view="home"], :root[data-entry="home"] body:not([data-view])) .view {
    padding-bottom: calc(var(--player-h) + 58px + var(--space-8));
  }

  .home-links { display: none; }

  .home-hero {
    grid-template-columns: 1fr;
    gap: 0;
    min-height: 0;
    padding-block: 0 var(--space-12);
  }

  /* No globe on a phone (see globeModeFor in js/app.js): the headline leads. */
  .home-globe { display: none; }
  .home-hero { padding-top: var(--space-6); }
  .home-hero h1 { font-size: 40px; }
  .home-lede { margin-top: var(--space-3); }
  .home-actions { margin-top: var(--space-5); }

  .home-lede { font-size: 16px; line-height: 26px; }
  .home-search { max-width: none; flex-basis: 100%; }
  .tuner-top { flex-wrap: wrap; row-gap: 0; }
  .tuner-coords { flex-basis: 100%; padding-left: calc(22px + var(--space-3)); }

  .home-section {
    grid-template-columns: 1fr;
    gap: var(--space-5);
    padding-block: var(--space-12);
  }

  .home-features { grid-template-columns: 1fr; gap: var(--space-6); }
}
