/*
 * Sound Travels design tokens.
 *
 * Colour: one brand hue (the logo's red, sampled at #e70e00) and a neutral
 * ramp tinted toward it. Flat grey next to a saturated red reads as two
 * unrelated systems, so every "grey" here carries a few degrees of the same
 * warm hue — that is what makes the surfaces feel like they belong to the
 * mark. The red is split by job: a lifted step for anything that must stay
 * legible as text on black, the pure brand red for fills.
 *
 * Everything else — space, size, radius, weight — steps on a scale. Ad-hoc
 * values are what make an interface look assembled rather than designed.
 */

/*
 * Archivo, self-hosted: the page's policy allows fonts from this origin only.
 * Cut down to Latin and to the widths (75–125) and weights (600–900) used
 * here, which takes it from 90KB to 49KB. SIL Open Font License, alongside.
 */
@font-face {
  font-family: "Archivo";
  src: url("fonts/archivo.woff2") format("woff2");
  font-weight: 600 900;
  font-stretch: 75% 125%;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light;

  /* Brand */
  --red: #e70e00;           /* the logo red: fills, selected states */
  --red-bright: #b81406;    /* the red that stays legible as text on this ground */
  --red-wash: rgba(231, 14, 0, 0.10);

  /* Neutrals, warm-tinted toward the brand hue */
  --ink-1: #16110f;         /* primary text */
  --ink-2: #57504d;         /* secondary text */
  --ink-3: #6c6562;         /* muted labels — dark enough for AA at 10.5px */
  --surface-0: #fbf9f8;     /* page ground */
  --surface-1: #f4f0ee;     /* panels */
  --surface-2: #ffffff;     /* cards */
  --surface-3: #ece7e4;     /* hover */
  --line-1: rgba(22, 17, 15, 0.10);   /* decorative hairlines */
  --line-2: rgba(22, 17, 15, 0.18);
  /*
   * A control's fill is all but identical to the page, so its border is the
   * only thing that says it is a control. Decorative hairlines may be faint;
   * this one carries meaning and owes 3:1 against both sides of it.
   */
  --line-control: #8a8481;

  /*
   * Map. Concrete, not water: the sea is the whole map panel, edge to edge, so
   * the map has no disc floating on a page and needs no colour of its own to
   * find its edge — the panel is the edge. The base map is deliberately quiet,
   * one warm stone a step darker than the sea, so every colour on it is data.
   * Every fill that means something is darker than the sea; the two that mean
   * "nothing here" sit between the sea and the land, plainly apart from both.
   */
  --ocean: #eeebe5;
  --land: #d3ccc1;
  --land-hover: #c4bcb0;
  --land-dim: #ddd8cf;      /* not charting the song that is playing */
  --land-inert: #dcd7ce;    /* countries YouTube publishes no chart for */
  --grid: rgba(22, 17, 15, 0.07);
  --pin-stroke: rgba(22, 17, 15, 0.45);
  --shadow-lg: 0 16px 40px rgba(22, 17, 15, 0.16);
  --shadow-sm: 0 6px 18px rgba(22, 17, 15, 0.12);

  /*
   * The resting map: how much of a country's chart charts nowhere else. Tide:
   * deep sea where a country plays what the whole world plays — the water
   * music crosses — through jade to sunlit gold where it plays songs charting
   * nowhere else. Every step is a full colour, the middle included: a chart
   * that is half shared and half its own is not "nothing", and a grey middle
   * says it is. Nor is either end empty; the USA, whose songs chart
   * everywhere, is a deep sea, not a blank.
   *
   * Lightness climbs steadily from sea to gold, so the order still reads in
   * greyscale and to red-green colour blindness, where the sweep collapses to
   * blue-to-yellow — the one axis that survives it.
   */
  --local-1: #105e77;       /* shared worldwide */
  --local-2: #198585;
  --local-3: #4ca77b;       /* a mix */
  --local-4: #a3ba55;
  --local-5: #e6c540;       /* only here */

  /*
   * Chart position, once a song is picked. Ordinal, so one hue stepped rather
   * than unrelated colours, and on this light ground more ink means more: #1 is
   * the darkest. It is ink — a warm charcoal with no hue to speak of — so that
   * it can never be read as the tide above: the two answer different
   * questions ("how much of this chart is its own", "where does this song
   * chart"), and a coloured country must not mean one in one moment and the
   * other in the next. Picking a song turns the map from colour to print.
   */
  --rank-1: #3e3631;
  --rank-2: #59514b;
  --rank-3: #756d67;
  --rank-4: #928983;
  --rank-5: #aca29c;

  /* Staggers the map's entrances; each dot and name sets its own. */
  --delay: 0ms;

  /* Space — 4px base */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;

  /* Radius. Square-cornered: printed matter, not app chrome. */
  --r-sm: 2px;
  --r-md: 3px;
  --r-pill: 999px;

  /* Type */
  --font: "Helvetica Neue", Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Headlines, labels and small caps: one variable face doing all three, wide
     and heavy for figures, condensed and tracked for labels. */
  --display: "Archivo", var(--font);
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --t-micro: 10.5px;
  --t-sm: 12px;
  --t-base: 13.5px;
  --t-lg: 16px;

  --control-h: 32px;
  --ease: cubic-bezier(0.2, 0.8, 0.3, 1);
}

* { box-sizing: border-box; }

/* Several of these carry a display rule, which would otherwise beat [hidden]. */
[hidden] { display: none !important; }

/*
 * One screen, measured against the viewport that is actually on screen. On a
 * phone `100%` resolves against the layout viewport, which is the tall one,
 * measured with the browser's toolbars collapsed — so with them showing, the
 * shell is taller than the screen. `100%` stays first as the fallback for
 * anything too old to know the unit.
 */
html, body {
  margin: 0;
  height: 100%;
  height: 100dvh;
}

body {
  display: flex;
  flex-direction: column;
  background: var(--surface-0);
  color: var(--ink-1);
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

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



/* ---------------------------------------------------------------- top bar -- */

.topbar {
  /* Never shrinks: it is the only way off this page, and `main` is flex: 1 on
     a 0% basis, so it absorbs none of a negative free space — every missing
     pixel would come off the bar. Too short for both now costs the map. */
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s3) var(--s5);
  background: var(--surface-0);
  border-bottom: 1px solid var(--line-1);
}

.brand { display: flex; align-items: baseline; gap: var(--s3); min-width: 0; }

.topbar h1 { margin: 0; line-height: 0; }

/* Set as text rather than an image so it themes with everything else — and
   keeps the two-tone split the logo uses. */
.wordmark {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.025em;
  white-space: nowrap;
}

.wordmark-sound { color: var(--ink-1); }
.wordmark-travels { color: var(--red); }

.wordmark-rest {
  font-size: var(--t-sm);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-3);
}


/* Wraps: there are three buttons and a select in here, and on a phone they do
   not fit on one line. Without this the row simply overflows and takes the
   whole page sideways with it. */
.controls { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* A control with nothing to do says so rather than swallowing the click. */
.ghost-button:disabled {
  color: var(--ink-3);
  border-color: var(--line-2);
  background: transparent;
  cursor: default;
  opacity: 0.55;
}

.ghost-button:disabled:hover { background: transparent; }

/* A native select arrow is the loudest "unstyled form" tell, so the chevron is
   drawn here instead — no data: URI, which the page's CSP would refuse. */
.country-picker { position: relative; display: block; }

.country-picker::after {
  content: "";
  position: absolute;
  right: var(--s3);
  top: 50%;
  width: 6px; height: 6px;
  margin-top: -5px;
  border-right: 1.5px solid var(--ink-2);
  border-bottom: 1.5px solid var(--ink-2);
  transform: rotate(45deg);
  pointer-events: none;
}

select, .ghost-button {
  height: var(--control-h);
  white-space: nowrap;
  padding: 0 var(--s3);
  border-radius: var(--r-sm);
  border: 1px solid var(--line-control);
  background: var(--surface-1);
  color: var(--ink-1);
  font: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
  transition: border-color 120ms var(--ease), background 120ms var(--ease);
}

select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--s6);
  min-width: 190px;
}

select:hover, .ghost-button:hover { background: var(--surface-2); }

/* Buttons speak in the labels' voice: condensed, tracked capitals. */
.ghost-button {
  font-family: var(--display);
  font-stretch: 75%;
  font-weight: 650;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

select:focus-visible, .ghost-button:focus-visible,
.video-card:focus-visible, .country.selectable:focus-visible {
  outline: 2px solid var(--red-bright);
  outline-offset: 2px;
}

/* Navigation rather than a map control, so it is a link and not a button —
   it lives in the controls because the brand area is hidden on a phone and a
   page you cannot reach from a phone may as well not exist. */
.meta-link {
  font-size: var(--t-sm);
  color: var(--ink-3);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 1px solid transparent;
}

.meta-link:hover { color: var(--ink-1); border-bottom-color: var(--line-2); }
.meta-link:focus-visible { outline: 2px solid var(--red-bright); outline-offset: 2px; }

/* Instagram: the glyph and a short invitation, at the end of the bar. The
   brand's auto margin packs the controls and this link to the right, and the
   negative margin closes the bar's gap down to the one between the controls.
   On a phone it moves up beside the wordmark, as the glyph alone. */
.brand { margin-right: auto; }
.ig-link { display: inline-flex; align-items: center; gap: 6px; margin-left: calc(var(--s2) - var(--s4)); }
.ig-link svg { width: 15px; height: 15px; flex: none; }

.freshness-short { display: none; }

.freshness {
  font-size: var(--t-sm);
  color: var(--ink-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Only ever a warning now — demo or empty data — so it carries the accent it
   was wearing on every live page for no reason. */
.mode-pill {
  border-color: transparent;
  background: var(--red-wash);
  color: var(--red-bright);
  padding: 0 var(--s2);
  height: 22px;
  display: inline-flex;
  align-items: center;
  border-radius: var(--r-pill);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 1px solid var(--line-2);
}

/* ----------------------------------------------------------------- layout -- */

main {
  flex: 1;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  min-height: 0;
}

.map-panel {
  position: relative;
  min-height: 0;
  overflow: hidden;
  background: var(--ocean);
}

#map { width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; }
#map.dragging { cursor: grabbing; }

/* The map takes focus when clicked (it is tabindex="-1"), and the UA draws a
   ring around the whole panel for it. Drop that for pointer focus only — real
   keyboard focus still shows, and the select remains the keyboard path in. */
#map:focus:not(:focus-visible) { outline: none; }

.sphere { fill: var(--ocean); }
.graticule path { fill: none; stroke: var(--grid); stroke-width: 0.5; vector-effect: non-scaling-stroke; }

/* Borders in the colour of the sea, so the countries read as tiles in one
   mosaic rather than as outlined shapes. */
.country {
  fill: var(--land-inert);
  stroke: var(--ocean);
  stroke-width: 0.5;
  transition: fill 120ms var(--ease);
}

.country.selectable { cursor: pointer; fill: var(--land); }

/* Only plain land brightens on hover — a ranked country must keep its step,
   and while the resting layer is on, every country is carrying a shade that
   flattening to one hover colour would destroy. The stroke below is the hover
   feedback in that state. */
#map:not(.ambient) .country.selectable:not(.ranked):hover { fill: var(--land-hover); }
.country.selectable:hover { stroke: var(--ink-2); stroke-width: 0.9; }
.country.dimmed { fill: var(--land-dim); }

/*
 * The resting map. Every country that charts anything is shaded by how much of
 * its chart is its own, so the map says something before it is clicked — which
 * is the whole argument the site is making.
 */
.country.local-1 { fill: var(--local-1); }
.country.local-2 { fill: var(--local-2); }
.country.local-3 { fill: var(--local-3); }
.country.local-4 { fill: var(--local-4); }
.country.local-5 { fill: var(--local-5); }

.pin.local-1 { fill: var(--local-1); }
.pin.local-2 { fill: var(--local-2); }
.pin.local-3 { fill: var(--local-3); }
.pin.local-4 { fill: var(--local-4); }
.pin.local-5 { fill: var(--local-5); }

/* With no charts at all, "selectable" and "inert" mean the same thing, and a
   map drawn entirely in the recessive fill barely reads as a map. */
#map.no-data .country { fill: var(--land); }

/* A picked country keeps its own shade — a red fill would read as the far end
   of the resting ramp. The keyline, its label and the ring mark it instead. */

.country.rank-1 { fill: var(--rank-1); }
.country.rank-2 { fill: var(--rank-2); }
.country.rank-3 { fill: var(--rank-3); }
.country.rank-4 { fill: var(--rank-4); }
.country.rank-5 { fill: var(--rank-5); }

/*
 * The selected country is outlined by a pair of paths in the #selection group
 * rather than by its own stroke: a stroke on the path itself gets overdrawn by
 * whichever neighbours come later in the document, and at 1px it disappears
 * entirely against a dark rank fill. The halo underneath keeps the line legible
 * on any colour, and non-scaling-stroke holds both widths steady while zooming.
 */
.selection-halo,
.selection-line {
  fill: none;
  pointer-events: none;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}

.selection-halo { stroke: var(--surface-0); stroke-width: 4.5; }
.selection-line { stroke: var(--ink-1); stroke-width: 1.75; }

.pin { fill: none; stroke: var(--pin-stroke); stroke-width: 0.6; pointer-events: none; }
.pin.lit { fill: currentColor; stroke: var(--ink-1); }
.pin-hit { pointer-events: all; cursor: pointer; }

.selection-ring {
  fill: none;
  stroke: var(--ink-1);
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* A dot on every country the song lands in, coloured by where it placed,
   landing with a little overshoot. Radius is set per zoom by app.js so it
   stays the same size on screen. */
.dot {
  stroke: var(--ocean);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  transform-box: fill-box;
  transform-origin: center;
  animation: pop 520ms cubic-bezier(0.34, 1.7, 0.6, 1) backwards;
  animation-delay: var(--delay);
}

.dot.rank-1 { fill: var(--rank-1); }
.dot.rank-2 { fill: var(--rank-2); }
.dot.rank-3 { fill: var(--rank-3); }
.dot.rank-4 { fill: var(--rank-4); }
.dot.rank-5 { fill: var(--rank-5); }
/* The country you picked. */
.dot.home { fill: var(--ink-1); }

@keyframes pop { from { transform: scale(0); } }

/* A ring goes out from the picked country, twice, as it is picked. */
.pulse {
  fill: none;
  stroke: var(--ink-1);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
  animation: pulse 1.5s ease-out 2;
}

@keyframes pulse {
  from { transform: scale(0.3); opacity: 0.9; }
  to { transform: scale(2.4); opacity: 0; }
}

/* Names on the map, haloed in the sea's colour so they read over any fill.
   Size and halo width are set per zoom by app.js, in map units. */
.label {
  font-family: var(--display);
  font-weight: 650;
  fill: var(--ink-2);
  paint-order: stroke;
  stroke: var(--ocean);
  stroke-linejoin: round;
  dominant-baseline: central;
  pointer-events: none;
  animation: appear 400ms ease-out backwards;
  animation-delay: var(--delay);
}

.label.big { font-weight: 800; fill: var(--ink-1); }
.label .label-rank { fill: var(--rank-2); font-weight: 800; }

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

/*
 * The headline: the one number the map is answering with right now, big, over
 * the top-left corner of the map, with a line of small capitals saying what
 * it counts. A poster's figure, not a caption.
 */
.headline {
  position: absolute;
  left: var(--s5);
  top: var(--s4);
  max-width: min(58ch, 60%);
  pointer-events: none;
  z-index: 2;
}

.headline p { margin: 0; }

.headline-figure {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 850;
  font-size: 64px;
  line-height: 0.95;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-1);
}

.headline-kicker {
  margin-top: 6px !important;
  font-family: var(--display);
  font-stretch: 75%;
  font-weight: 650;
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.headline-note {
  margin-top: 2px !important;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.headline-kicker, .headline-note {
  text-shadow: 0 0 3px var(--ocean), 0 0 8px var(--ocean);
}

.tooltip {
  position: absolute;
  z-index: 5;
  padding: var(--s1) var(--s2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--line-1);
  font-size: var(--t-sm);
  pointer-events: none;
  white-space: nowrap;
  transform: translate(-50%, -160%);
  box-shadow: var(--shadow-sm);
}

.tooltip .tooltip-rank { color: var(--red-bright); font-variant-numeric: tabular-nums; }

/* Legend: a quiet row of steps, not a panel competing with the map — on a
   plate of sea, so land passing under it while the map moves stays under it. */
.legend {
  position: absolute;
  left: var(--s4);
  bottom: var(--s3);
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s1) var(--s2);
  background: var(--ocean);
  z-index: 2;
}

.legend-title {
  font-family: var(--display);
  font-stretch: 75%;
  font-weight: 650;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-2);
}

.legend-scale {
  display: flex;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--mono);
  font-size: var(--t-micro);
  color: var(--ink-2);
}

.legend-scale li { display: flex; flex-direction: column; align-items: center; gap: 3px; }

.swatch { width: 28px; height: 6px; display: block; border-radius: 1px; }
.swatch.rank-1 { background: var(--rank-1); }
.swatch.rank-2 { background: var(--rank-2); }
.swatch.rank-3 { background: var(--rank-3); }
.swatch.rank-4 { background: var(--rank-4); }
.swatch.rank-5 { background: var(--rank-5); }
.swatch.local-1 { background: var(--local-1); }
.swatch.local-2 { background: var(--local-2); }
.swatch.local-3 { background: var(--local-3); }
.swatch.local-4 { background: var(--local-4); }
.swatch.local-5 { background: var(--local-5); }

/* The ambient scale runs continuously from "shared" to "only here", so its swatches
   butt together as one bar rather than sitting apart as five categories —
   the rank scale is five discrete positions and keeps its gaps. */
#legend-local .legend-scale { gap: 0; }
#legend-local .swatch { border-radius: 0; }
#legend-local .legend-scale li:first-child .swatch { border-radius: 1px 0 0 1px; }
#legend-local .legend-scale li:last-child .swatch { border-radius: 0 1px 1px 0; }
/* Its labels name the two ends and the middle, and are wider than a swatch:
   pinned to a swatch's width they hang outward at the ends instead of
   pushing the bar apart. */
#legend-local .legend-scale li { width: 36px; white-space: nowrap; }
#legend-local .swatch { width: 36px; }
#legend-local .legend-scale li:first-child { align-items: flex-start; }
#legend-local .legend-scale li:last-child { align-items: flex-end; }

.map-hint {
  position: absolute;
  top: var(--s4);
  right: var(--s4);
  z-index: 3;
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--s1);
  color: var(--ink-3);
  font-size: var(--t-sm);
}

/* Named, boxed and dismissible once a country is picked — so the map itself
   says what is selected, not just the panel below it. */
.map-hint {
  max-width: calc(100% - 2 * var(--s3));
}

/* On the landing the headline says what the sentence used to, so the pill is
   just its button. */
.map-hint:not(.is-selection) #map-hint-text { display: none; }

.map-hint span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.map-hint.is-selection {
  padding: var(--s1) var(--s1) var(--s1) var(--s3);
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  background: var(--surface-2);
  color: var(--ink-1);
  box-shadow: var(--shadow-sm);
}

.map-hint.is-selection strong { font-weight: 600; }

/*
 * The map's own call to action, in the space the passive hint used to fill.
 * It shrinks to a glyph once a country is named, so the pill stays a label
 * rather than growing into a toolbar.
 */
.surprise {
  height: 30px;
  padding: 0 var(--s3);
  background: var(--ink-1);
  border-color: var(--ink-1);
  color: var(--surface-0);
}

.surprise:hover { background: var(--ink-2); border-color: var(--ink-2); }
.map-hint.is-selection .surprise { height: 24px; padding: 0 var(--s2); }
.surprise-short { display: none; }
.map-hint.is-selection .surprise-long { display: none; }
.map-hint.is-selection .surprise-short { display: inline; font-size: var(--t-sm); }

/* The genre beside whatever is playing: a chip in a sentence, so it needs to
   sit on the text baseline rather than float above it. */
.sound-chip { margin-left: var(--s2); vertical-align: baseline; }

/* Sits inside the selection pill, so it is only ever offered once there is
   something worth sharing. Short and quiet: the pill is a label, not a toolbar. */
.copy-link {
  height: 24px;
  padding: 0 var(--s2);
  font-size: var(--t-micro);
  color: var(--ink-2);
  background: var(--surface-1);
  border-radius: var(--r-sm);
}

.copy-link:hover { background: var(--surface-3); color: var(--ink-1); }

.map-hint[hidden] { display: none; }

/* --------------------------------------------------------------- player -- */

/*
 * Docked in the corner of the map rather than over it: the whole point is to
 * watch the video while the countries it is charting in stay lit.
 */
.player {
  /* Lives in the chart panel now, floated above it so it still reads as
     sitting in the corner of the map. On mobile it drops into the flow. */
  position: absolute;
  right: var(--s5);
  bottom: calc(100% + var(--s4));
  width: clamp(240px, 22vw, 320px);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  box-shadow: var(--shadow-lg);
  z-index: 6;
}

.player-stage {
  aspect-ratio: 16 / 9;
  background: #000;  /* letterboxing behind the video is black in both themes */
  display: grid;
  place-items: center;
}

.player-stage.is-note { background: var(--surface-2); }

/* Apple Music's and Deezer's players are a strip of controls and cover art,
   not a picture: a fixed height, the one their embeds are designed for. */
.player-stage.is-audio { aspect-ratio: auto; height: 175px; background: var(--surface-2); }
.player-stage.is-audio.is-note { height: auto; }

.player-stage iframe { width: 100%; height: 100%; border: 0; display: block; }

.player-note {
  padding: var(--s4);
  text-align: center;
  font-size: var(--t-sm);
  color: var(--ink-3);
}

.player-bar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s2) var(--s2) var(--s3);
}

.player-title {
  flex: 1;
  min-width: 0;
  font-size: var(--t-sm);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.icon-button {
  flex: none;
  width: 24px; height: 24px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-3);
  font: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
  transition: background 120ms var(--ease), color 120ms var(--ease);
}

.icon-button:hover { background: var(--surface-3); color: var(--ink-1); }
.icon-button .icon { width: 16px; height: 16px; }

/* Expanding is only meaningful where the player collapses — that is mobile. */
.player-expand { display: none; }
.icon-button:focus-visible { outline: 2px solid var(--red-bright); outline-offset: 2px; }

/* ------------------------------------------------------------ chart panel -- */

.chart-panel {
  position: relative;
  min-width: 0;
  border-top: 1px solid var(--line-1);
  background: var(--surface-1);
  padding: var(--s4) var(--s5) var(--s5);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  min-height: 0;
}

.chart-header { display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap; }

/* The sound picker, when it is holding one. A select gives no pressed state of
   its own, and a filter you have forgotten you set is the one that makes the
   map look broken — so a chosen sound is coloured the way a held switch was. */
#genre-picker.is-set select {
  border-color: var(--ink-1);
  background: var(--ink-1);
  color: var(--surface-0);
  font-weight: 600;
}

#genre-picker.is-set::after { border-color: var(--surface-0); }

.chart-header h2 {
  margin: 0;
  font-family: var(--display);
  font-stretch: 108%;
  font-weight: 800;
  font-size: 18px;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.chart-subtitle { margin: 0; font-size: var(--t-sm); color: var(--ink-3); }

.video-list {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: auto;
  min-height: 0;
  max-height: 42vh;
}

.video-card {
  position: relative;
  display: grid;
  grid-template-columns: 20px 84px minmax(0, 1fr);
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: var(--s2) var(--s3) var(--s2) var(--s2);
  text-align: left;
  color: inherit;
  font: inherit;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  background: var(--surface-2);
  cursor: pointer;
  transition: background 120ms var(--ease), border-color 120ms var(--ease);
}

.video-card:hover { background: var(--surface-3); }

.video-card[aria-pressed='true'] {
  border-color: var(--red);
  background: var(--red-wash);
}

/* Rank as a numeral in its own column — a badge stuck on the artwork is noise. */
.rank-badge {
  font-family: var(--display);
  font-stretch: 100%;
  font-size: 17px;
  font-weight: 800;
  color: var(--ink-3);
  text-align: center;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.video-card[aria-pressed='true'] .rank-badge { color: var(--red-bright); }

.thumb {
  position: relative;
  height: 48px;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--surface-3);
  display: grid;
  place-items: center;
  font-size: var(--t-lg);
  font-weight: 600;
  color: var(--ink-3);
}

.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Album covers are square, so they sit as a square in the artwork column. */
.thumb.is-square { width: 48px; justify-self: center; }

.video-meta { min-width: 0; display: flex; flex-direction: column; gap: 1px; }

.video-title {
  font-size: var(--t-sm);
  font-weight: 500;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.video-channel, .video-stats {
  font-size: var(--t-micro);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.video-stats { display: flex; align-items: center; gap: var(--s2); }
.video-stats .views { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

.reach {
  flex: none;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* The one case worth noticing: this country is the only one charting it. */
.reach.is-local {
  background: var(--red-wash);
  color: var(--red-bright);
  font-weight: 600;
}

/* A selected card is already washed red, so the ordinary chip needs an opaque
   background to stay legible on it. The local chip keeps its own — the wash
   stacking on the card's still clears AA (4.8:1), and losing the accent on the
   one card you selected would be losing it exactly when you are looking. */
.video-card[aria-pressed='true'] .reach:not(.is-local) { background: var(--surface-1); }

/* ------------------------------------------------------- taste neighbours -- */

/* Which countries are listening to the same things — the question the whole
   snapshot can answer and no single chart can. */
.neighbours {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  font-size: var(--t-micro);
}

.neighbours-label {
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  white-space: nowrap;
}

.neighbour {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px var(--s2);
  border-radius: var(--r-pill);
  border: 1px solid var(--line-2);
  background: var(--surface-2);
  color: var(--ink-1);
  font: inherit;
  font-size: var(--t-micro);
  white-space: nowrap;
  cursor: pointer;
  transition: background 120ms var(--ease), border-color 120ms var(--ease);
}

.neighbour:hover { background: var(--surface-3); border-color: var(--line-control); }
.neighbour:focus-visible { outline: 2px solid var(--red-bright); outline-offset: 2px; }

.neighbour-count {
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

.watch-link {
  font-size: var(--t-micro);
  color: var(--ink-3);
  text-decoration: none;
  width: fit-content;
}

.watch-link:hover { color: var(--red-bright); }

.chart-status {
  margin: 0;
  font-size: var(--t-sm);
  color: var(--ink-2);
  min-height: 1.4em;
}

.chart-status strong { color: var(--ink-1); font-weight: 600; }

/* The YouTube credit. Pushed to the foot of the panel: it has to be on the
   page wherever the data is, but it is never what anyone came to read. */
.data-credit {
  margin: auto 0 0;
  font-size: var(--t-micro);
  line-height: 1.4;
  color: var(--ink-3);
}

.data-credit a { color: var(--ink-2); }
.data-credit a:hover { color: var(--ink-1); }
.data-credit a:focus-visible { outline: 2px solid var(--red-bright); outline-offset: 2px; }

/* Temporary, with the readout in app.js: four fixes aimed at four guessed
   mechanisms all missed, so the page reports what it measures instead. Goes
   when a screenshot of the broken state has been read. */
.diag {
  margin: 0;
  font-size: 10px;
  line-height: 1.3;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  word-break: break-all;
}

.placeholder-card {
  border-radius: var(--r-md);
  min-height: 64px;
  background: var(--surface-2);
}

.placeholder-card.loading { animation: pulse 1.2s ease-in-out infinite; }

@keyframes pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.8; } }

/* ------------------------------------------------------------ responsive -- */

/*
 * Wide screens: the chart becomes a column beside the map rather than a strip
 * under it, read top to bottom like a list of arrivals, and the map gets the
 * full height of the window. The player moves in with it, at the top of the
 * column, instead of floating over the map's corner.
 */
@media (min-width: 1101px) {
  main {
    grid-template-columns: minmax(0, 1fr) 360px;
    grid-template-rows: minmax(0, 1fr);
  }

  .chart-panel {
    border-top: 0;
    border-left: 1px solid var(--line-1);
    background: var(--surface-2);
    padding: 0;
    gap: 0;
    overflow-y: auto;
  }

  /* The column scrolls; nothing in it shrinks to fit, or the status line is
     squeezed under its own contents. */
  .chart-panel > * { flex-shrink: 0; }

  .player {
    position: static;
    width: auto;
    border: 0;
    border-bottom: 1px solid var(--line-1);
    border-radius: 0;
    box-shadow: none;
  }

  .chart-header {
    order: 1;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s1);
    padding: var(--s4) var(--s4) var(--s3);
    border-bottom: 1px solid var(--line-1);
  }

  .chart-status { order: 2; min-height: 0; padding: var(--s3) var(--s4) 0; }
  .chart-status:empty { display: none; }
  .neighbours { order: 3; padding: var(--s3) var(--s4) 0; }
  .video-list { order: 4; }
  .diag { order: 5; padding: var(--s3) var(--s4); }
  .data-credit { order: 6; padding: var(--s4); }

  .video-list {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    max-height: none;
    overflow: visible;
    padding-top: var(--s2);
  }

  .video-card {
    border: 0;
    border-left: 3px solid transparent;
    border-bottom: 1px solid var(--line-1);
    border-radius: 0;
    background: transparent;
    padding: var(--s3) var(--s4) var(--s3) var(--s3);
  }

  .video-card:hover { background: var(--surface-1); }

  .video-card[aria-pressed='true'] {
    border-left-color: var(--red);
    background: var(--red-wash);
  }

  .placeholder-card { border-radius: 0; margin: var(--s2) var(--s4); background: var(--surface-1); }
}

@media (max-width: 1100px) {
  .video-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The Instagram invitation shortens to its glyph before the controls wrap:
   with the charts' age showing, the words fit from about 900px up. */
@media (max-width: 999px) {
  .ig-text { display: none; }
}

/* The tagline goes before the controls have to wrap under it. Measured, not
   guessed: with it, the top bar holds one line only from 1310px up. */
@media (max-width: 1309px) {
  .wordmark-rest { display: none; }
}

@media (max-width: 780px) {
  .topbar { padding: var(--s2) var(--s3); flex-wrap: wrap; }
  /* Kept on a phone now, in its short phrasing: it is the only thing up here
     that changes, and how stale the charts are is worth the room. */
  .freshness-long { display: none; }
  .freshness-short { display: inline; }
  /* No height of its own: the wordmark's row has none either (its line-height
     is 0), and anything taller here would push the map down. */
  .ig-link { order: 1; margin-left: 0; height: 0; border-bottom: 0; }
  .controls { order: 2; }
  /*
   * Narrow enough that the three map controls share one row and the three
   * meta items share the next: at its natural 192px the row breaks after two
   * buttons and the top bar takes a third line, which comes straight out of
   * the map. 168px is the width at which the placeholder stops truncating —
   * "Choose a country.." reads as a typo rather than an ellipsis — and the
   * longest country names still ellipsise, which a select does gracefully.
   */
  select { min-width: 0; width: 168px; }

  /*
   * The five cards become a swipeable row rather than a stack. On a phone the
   * vertical budget is the scarce thing, and the map is what needs it.
   */
  .video-list {
    /*
     * min-width/width are load-bearing: a column-flow grid inside a flex column
     * sizes to its content unless it is given a definite width, which blew the
     * page out to 822px on a 390px screen instead of scrolling.
     */
    width: 100%;
    min-width: 0;
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 78%;        /* the next card peeks, so the row reads as scrollable */
    overflow-x: auto;
    overflow-y: hidden;
    max-height: none;
    scroll-snap-type: x mandatory;
    padding-bottom: var(--s1);
  }

  .video-card, .placeholder-card { scroll-snap-align: start; }

  /* "…also in the top 5 in 50 other countries: …" runs to three or four lines
     on a phone, which costs more map than the sentence is worth. */
  .chart-status {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .placeholder-card { min-height: 62px; }

  /* One scrollable row rather than a wrapped block: on a phone the vertical
     budget belongs to the map, and these are a sideline, not the answer. */
  .neighbours {
    flex-wrap: nowrap;
    overflow-x: auto;
    width: 100%;
    min-width: 0;
    padding-bottom: 2px;
  }

  .neighbour { flex: none; }

  /*
   * Collapsed, the player is an ordinary block at the top of the chart panel:
   * no fixed positioning, which on a real phone fights with the browser chrome
   * appearing and disappearing. It costs the map ~65px instead of the 87% the
   * old overlay took.
   */
  .player {
    position: static;
    width: auto;
    margin-bottom: var(--s3);
  }

  /* Expanded, it lifts out of the flow and covers the cards — never the map,
     unless the video is taller than the panel, which is the moment the viewer
     asked for the video anyway. */
  .player.is-expanded {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    z-index: 6;
    border-radius: var(--r-md) var(--r-md) 0 0;
  }

  .player.is-expanded .player-stage { max-height: 42vh; }

  /* Collapsed: a small live player beside the title. Still a visible video —
     an audio-only embed is against YouTube's embed terms. */
  .player.is-mini {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s2);
  }

  /*
   * 160px, not the 84px this started at: YouTube's embed has a practical
   * minimum around 200px and renders a message instead of the video when it is
   * much smaller. Costs ~40px of map and keeps the collapsed player real.
   */
  .player.is-mini .player-stage {
    flex: 0 0 160px;
    width: 160px;
    border-radius: var(--r-sm);
    overflow: hidden;
  }

  .player.is-mini .player-bar { flex: 1; min-width: 0; padding: 0; }
  /* No room for the note in an 84px stage, and an empty box beside the title
     looks like a broken thumbnail — so the stage goes with it. */
  .player.is-mini .player-stage.is-note { display: none; }
  /* A song is heard, not watched, so collapsed it is just the bar. The frame
     stays in the page, hidden, so the music carries on. */
  .player.is-mini .player-stage.is-audio { display: none; }

  /* Expanded: full-width video above the bar, over the cards, never the map. */
  .player.is-expanded .player-stage { width: 100%; }
  .player.is-expanded .player-expand .icon { transform: rotate(180deg); }

  .player-expand { display: grid; }

  /*
   * The legend stays on mobile now. Five shades of one colour cannot be read
   * without it, and the map is visible while a video plays — which is exactly
   * when it is needed.
   */
  /* Top-right, clear of the headline beside it and of the selection pill,
     which sits at the bottom of the map on a phone. */
  .legend {
    left: auto;
    right: var(--s3);
    top: var(--s3);
    bottom: auto;
    gap: var(--s2);
    padding: var(--s1) var(--s2);
    border-radius: var(--r-sm);
    background: var(--surface-0);
  }

  /* The rank scale is numbered and speaks for itself, so it drops its title
     to save room. The ambient scale runs "none" to "all" and says nothing at
     all without one — none of what? — so that one keeps it. */
  .legend-title { display: none; }
  #legend-local .legend-title { display: block; }

  /* The pill cannot hold both the sentence and the button at this width, and
     the sentence is the half that is repeated verbatim in the panel below —
     so on a phone the pill is just the button. */
  /* Wide enough that the rank labels under them ("9–10") do not run together. */
  .swatch { width: 26px; }

  /* The pill moves to the bottom of the map, where a thumb is. */
  .map-hint {
    top: auto;
    right: auto;
    bottom: var(--s3);
    left: 50%;
    transform: translateX(-50%);
  }

  .headline { left: var(--s3); top: var(--s3); max-width: 58%; }
  .headline-figure { font-size: 40px; }
  .headline-kicker { font-size: 11px; letter-spacing: 0.1em; }
  .headline-note { font-size: 11px; }
}

@media (max-width: 520px) {
  .chart-panel { padding: var(--s3); }
  .video-list { grid-auto-columns: 86%; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
