/* ---------- fallback font metrics ----------
 *
 * index.html loads Inter and JetBrains Mono from Google with `display=swap`,
 * so the first paint uses a local fallback and the page re-lays-out when the
 * webfonts land. That swap is a measurable part of this page's Cumulative
 * Layout Shift at phone widths (2026-09-01: the stats row moved twice, 416 ->
 * 462 -> 437px at 390x844, the second move being the swap alone).
 *
 * The cause is metric mismatch, not the swap. Measured in Chromium over a
 * sentence of this page's own copy, Inter sets 6050 units where the fallback
 * `system-ui` sets 6426 — 6% narrower, which is enough to change where a line
 * breaks. So the fallback is re-scaled to Inter's own metrics instead of being
 * hidden or delayed, and the swap stops moving anything:
 *
 *   size-adjust      = webfont advance width / local advance width
 *   ascent/descent   = the webfont's OS/2 sTypoAscender / sTypoDescender
 *                      (both fonts set USE_TYPO_METRICS), divided by the
 *                      size-adjust so the override lands on the adjusted em
 *
 * The `local()` lists are Arial-metric and Courier-metric families: Arial and
 * Courier New ship on Windows and macOS, and fontconfig substitutes Liberation
 * Sans / Liberation Mono on Linux, which are metric-compatible by design. A
 * platform holding none of them matches no face here and falls through to the
 * unchanged tail — no worse than before, which is why the old tail is kept
 * rather than replaced.
 *
 * Re-measure both numbers if either family is ever changed; a stale
 * size-adjust is worse than none, because it moves text the other way.
 */

@font-face {
  font-family: "Inter Fallback";
  src: local("Arial"), local("Liberation Sans"), local("Arimo"),
       local("Helvetica Neue"), local("Helvetica");
  size-adjust: 106.60%;
  ascent-override: 90.87%;
  descent-override: 22.63%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "JetBrains Mono Fallback";
  src: local("Courier New"), local("Liberation Mono"), local("Cousine"),
       local("Nimbus Mono PS"), local("Courier");
  size-adjust: 99.98%;
  ascent-override: 102.02%;
  descent-override: 30.00%;
  line-gap-override: 0%;
}

:root {
  --bg-primary: #080c14;
  --bg-secondary: #0e1420;
  --bg-card: #141c2b;
  --bg-card-hover: #1a2438;
  --border: #334155;
  --border-subtle: #2a3a50;
  --text-primary: #f8fafc;
  --text-secondary: #e2e8f0;
  --text-muted: #c0cad8;
  --accent: #10b981;
  --accent-light: #6ee7b7;
  --accent-glow: rgba(16, 185, 129, 0.18);
  --accent-dim: #064e3b;
  /* droide UI Visual Standards: multi-accent tokens (>=3 visible at rest) */
  --accent-blue: #818cf8;
  --accent-orange: #f59e0b;
  --accent-gold: #daa04a;
  --accent-cyan: #22d3ee;
  --red: #ef4444;
  --red-dim: rgba(239, 68, 68, 0.1);
  --green: #22c55e;
  --green-dim: rgba(34, 197, 94, 0.1);
  --blue: #3b82f6;
  --yellow: #eab308;
  --radius: 12px;
  --radius-sm: 8px;
  --shadow: 0 4px 24px rgba(0,0,0,0.4);
  --shadow-lg: 0 8px 40px rgba(0,0,0,0.5);
  --font: 'Inter', 'Inter Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback', 'Fira Code', 'Cascadia Code', monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html { font-size: 19px; }

body {
  font-family: var(--font);
  background: var(--bg-primary);
  color: var(--text-primary);
  line-height: 1.6;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

/* Layout */
.app { max-width: 1200px; margin: 0 auto; padding: 0 24px; }

/* Header */
header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 0;
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: 32px;
}

.logo {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--text-primary);
}

.logo svg { width: 32px; height: 32px; }

.logo h1 {
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.logo span {
  color: var(--accent);
}

.header-meta {
  display: flex;
  align-items: center;
  gap: 16px;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.header-meta .badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  background: var(--accent-glow);
  color: var(--accent);
  border-radius: 20px;
  font-size: 0.8rem;
  font-weight: 500;
}

.header-meta .version {
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.pubhub-link,
.store-link {
  display: flex;
  align-items: center;
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.15s;
}
.pubhub-link:hover, .store-link:hover { color: var(--accent); }

.header-meta .badge::before {
  content: '';
  width: 6px;
  height: 6px;
  background: var(--accent);
  border-radius: 50%;
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* Stats row */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px;
  margin-bottom: 32px;
}

.stat-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  transition: border-color 0.2s, transform 0.2s;
}

.stat-card:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}

.stat-card .label {
  font-size: 0.8rem;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 8px;
}

.stat-card .value {
  font-size: 1.5rem;
  font-weight: 700;
  font-family: var(--font-mono);
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

.stat-card .value.accent { color: var(--accent); }

/* Two lines, reserved — for this one value only.

   All four values start life as "--" on one line and are replaced from
   /api/status after load. Three of them stay on one line. Last Sync does not:
   "1 Sept, 19:35" is thirteen monospace characters at 1.5rem, and a stat card
   is 176px wide at the .app cap and narrower below it, so that value has always
   wrapped to two lines the moment it arrived. The row grew 120px -> 166px on
   every single load and pushed 46px into the tab bar, the rate grid, the prose
   and the page foot below them.

   Scoped to #statSync rather than to .stat-card .value, which was the first
   attempt: reserving two lines on all four made every card two lines tall, and
   because the phone layout puts them in uneven rows that added 92px of empty
   card to a 390px screen. Reserving space is only correct where the space is
   actually used.

   `lh` is exact whatever the line-height becomes; 3.2em is the same figure at
   the inherited 1.6 for anything that does not know the unit yet. */
#statSync {
  min-height: 3.2em;
  min-height: 2lh;
}
/* Above ~1080px the four cards are wide enough (>=242px) for the longest
   value — "24 Sept, 19:35", fourteen mono characters at 1.5rem — on one line,
   so the reservation would only be 46px of empty card. Below it, keep it. */
@media (min-width: 1080px) {
  #statSync { min-height: 0; }
}

/* Navigation */
nav {
  display: flex;
  gap: 4px;
  margin-bottom: 28px;
  background: var(--bg-secondary);
  padding: 4px;
  border-radius: var(--radius);
  border: 1px solid var(--border-subtle);
}

nav button {
  flex: 1;
  padding: 10px 16px;
  background: transparent;
  color: var(--text-secondary);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 500;
  transition: all 0.2s;
}

nav button:hover { color: var(--text-primary); background: var(--bg-card); }
nav button.active { background: var(--accent-dim); color: var(--accent-light); }

/* Sections */
section { display: none; }
section.active { display: block; }

/* Currency grid */
.currency-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}

.currency-card {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  padding: 16px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  transition: all 0.2s;
}

.currency-card:hover {
  border-color: var(--accent);
  background: var(--bg-card-hover);
  box-shadow: 0 0 0 1px var(--accent-glow);
}

.currency-card .left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.currency-card .flag {
  border-radius: 3px;
  object-fit: cover;
  flex-shrink: 0;
}

.chart-header h2 .flag {
  border-radius: 3px;
  vertical-align: middle;
  margin-right: 2px;
}

.currency-card .code {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent-light);
  background: var(--accent-dim);
  padding: 6px 10px;
  border-radius: 6px;
  min-width: 48px;
  text-align: center;
}

.currency-card .name {
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.currency-card .left { min-width: 0; }

/* The number and its unit are one fixed-width block on the right; the country
   name on the left is what gives way. Before this the flex split let "NOK per
   unit" wrap to three lines in a 3-up grid, which made every third card a
   different height for no reason the eye could find. */
.currency-card .rate {
  font-family: var(--font-mono);
  font-size: 1.1rem;
  font-weight: 600;
  text-align: right;
  flex-shrink: 0;
  white-space: nowrap;
  margin-left: 12px;
}

.currency-card .rate small {
  display: block;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-weight: 400;
}

/* Sparkline canvas inline */
.currency-card .spark {
  width: 80px;
  height: 32px;
  margin-left: 12px;
}

/* Chart pair picker: From / swap / To above the chart. Same field styling as
   the calculator so the two selects on this site look like the same control. */
.pair-picker {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.pair-field { flex: 1 1 260px; min-width: 0; }
.pair-field label {
  display: block;
  font-size: 0.75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}
.pair-field select {
  width: 100%;
  padding: 12px 16px;
  min-height: 44px;
  background: var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  cursor: pointer;
  outline: none;
  transition: border-color 0.15s;
}
.pair-field select:focus { border-color: var(--accent); }
.pair-picker .swap-btn { margin-bottom: 2px; }

/* Chart section */
.chart-container {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  padding: 24px;
  margin-bottom: 24px;
}

.chart-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
  flex-wrap: wrap;
  gap: 12px;
}

.chart-header h2 {
  font-size: 1.2rem;
  font-weight: 600;
}

.chart-header h2 span { color: var(--accent); }

.period-btns {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.period-btns button {
  padding: 6px 12px;
  background: var(--bg-secondary);
  color: var(--text-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 500;
  transition: all 0.15s;
}

.period-btns button:hover { border-color: var(--border); color: var(--text-primary); }
.period-btns button.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }
.period-select {
  padding: 6px 10px;
  background: var(--bg-secondary);
  color: var(--text-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 500;
}
.period-select.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }

.chart-wrap {
  position: relative;
  height: 320px;
}

.back-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  background: var(--bg-secondary);
  color: var(--text-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.85rem;
  margin-bottom: 16px;
  transition: all 0.15s;
}

.back-btn:hover { border-color: var(--border); color: var(--text-primary); }

/* Calculator */
.calc-container {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  padding: 32px;
  max-width: 680px;
  margin: 0 auto;
}

.calc-container h2 {
  font-size: 1.2rem;
  font-weight: 600;
  margin-bottom: 24px;
  text-align: center;
}

.calc-row {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  margin-bottom: 20px;
}

.calc-field {
  flex: 1;
}

.calc-field label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}

.calc-field label .flag {
  border-radius: 2px;
  vertical-align: middle;
}

.calc-field input, .calc-field select {
  width: 100%;
  padding: 12px 14px;
  background: var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 1rem;
  font-family: var(--font-mono);
  outline: none;
  transition: border-color 0.15s;
}

.calc-field input:focus, .calc-field select:focus {
  border-color: var(--accent);
}

.calc-field select { cursor: pointer; }
.calc-field select option { background: var(--bg-secondary); }

.swap-btn {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 50%;
  cursor: pointer;
  font-size: 1.1rem;
  flex-shrink: 0;
  transition: all 0.15s;
  margin-bottom: 2px;
}

.swap-btn:hover { background: var(--accent); color: #fff; }

.calc-result {
  text-align: center;
  padding: 20px;
  background: var(--accent-glow);
  border: 1px solid var(--accent-dim);
  border-radius: var(--radius);
  margin-top: 8px;
}

.calc-result .amount {
  font-size: 2rem;
  font-weight: 700;
  font-family: var(--font-mono);
  color: var(--accent-light);
  letter-spacing: -0.02em;
}

.calc-result .rate-info {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 4px;
}

/* Search */
.search-box {
  margin-bottom: 20px;
}

.search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}
.search-row .search-box { flex: 1; margin-bottom: 0; }

.search-box input {
  width: 100%;
  padding: 12px 16px;
  padding-left: 40px;
  background: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  font-size: 0.9rem;
  outline: none;
  transition: border-color 0.15s;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85zm-5.242.156a5 5 0 1 1 0-10 5 5 0 0 1 0 10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 14px center;
}

.search-box input:focus { border-color: var(--accent); }

/* Skeleton rate cards.

   The grid is painted from /api after load, and until 2026-09-01 the space it
   would need was not reserved: a 198px spinner became a 1629px grid at
   1440x900, which pushed the prose block, the FAQ and the footer down 1476px
   and was, on its own, most of a 0.38 CLS. A card-shaped placeholder per
   currency reserves it at every width, because it is laid out by the same
   auto-fill grid the real cards are — no breakpoint table to keep in step with
   `minmax(280px, 1fr)`.

   The height is the real card's, stated rather than measured: two 16px
   paddings and two 1px borders around the tallest child, which is .rate — one
   1.1rem line for the number above ONE 0.8rem line of "NOK per unit" (it no
   longer wraps), all at the body's 1.6 line-height. A two-line currency name
   (2 x 0.85rem x 1.6 = 2.72rem) is still shorter than that, so this is the
   exact height of every card and the grid does not move when the rates land.

   They are server-rendered from exchange.AllCurrencies, so the count cannot
   drift from the number of cards that actually arrive, and they are replaced
   wholesale by renderCurrencyGrid()'s innerHTML — nothing has to remove them. */
.currency-card.is-skeleton {
  min-height: calc(3.04rem + 34px);
  cursor: default;
  border-style: dashed;
  animation: pulse 2s infinite;
}

.currency-card.is-skeleton:hover {
  border-color: var(--border-subtle);
  background: var(--bg-card);
  box-shadow: none;
}

/* Loading */
.loading {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-muted);
}

.loading .spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  margin: 0 auto 16px;
}

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

/* Empty state */
.empty {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-muted);
}

/* Footer */
/* The site's OWN footer strip, scoped to its class.

   These were bare `footer` selectors, which also matched the Publifye
   constellation footer beneath — so that footer inherited a second border-top,
   another 48px of margin and this block's centring, none of which it asked for.
   anav styles itself; this styles ours. */
.site-footer {
  text-align: center;
  padding: 32px 0;
  margin-top: 48px;
  border-top: 1px solid var(--border-subtle);
  color: var(--text-muted);
  font-size: 0.8rem;
}

.site-footer a { color: var(--accent); text-decoration: none; }
.site-footer a:hover { text-decoration: underline; }

/* Responsive */
@media (max-width: 640px) {
  .app { padding: 0 16px; }
  header { flex-direction: column; gap: 12px; align-items: flex-start; }
  .stats { grid-template-columns: repeat(2, 1fr); }
  .currency-grid { grid-template-columns: 1fr; }
  /* Stacked rows stretch: with the desktop flex-end kept, the date field
     shrank to its intrinsic width and sat off-centre under a full-width
     amount box. */
  .calc-row { flex-direction: column; align-items: stretch; }
  .swap-btn { align-self: center; }
  /* Stacked, the 260px flex-basis would become a HEIGHT — each field a
     260px block with the select at the top of it. Reset it. */
  .pair-picker { flex-direction: column; align-items: stretch; }
  .pair-field { flex: 0 0 auto; }
  .pair-picker .swap-btn { align-self: center; margin-bottom: 0; }
  .chart-header { flex-direction: column; }
  .currency-card .spark { display: none; }
}

.site-footer {
  margin-top: 40px;
  padding: 22px 0 10px;
  border-top: 1px solid var(--border, #1e293b);
  color: var(--text-muted, #64748b);
  font-size: 0.85rem;
  line-height: 1.9;
  text-align: center;
}
.site-footer a { color: var(--accent-blue); text-decoration: none; }
.site-footer a:hover { color: var(--accent-light); }
.site-footer .fineprint { opacity: 0.7; }

/* 50+ accessibility: visible keyboard focus everywhere */
:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 2px;
}

/* Touch targets: 44px minimum for interactive controls */
nav button { min-height: 44px; }
.period-btns button, .period-select { min-height: 40px; }

.hidden { display: none !important; }


/* ---------------------------------------------------------------------------
   The prose block: the tagline under the wordmark and the always-visible
   reference/FAQ block above the footer.

   WHY IT EXISTS AT ALL. Everything else on this page is painted by app.js from
   /api after load, so the served HTML carried no sentence saying what these
   rates are, who publishes them, or how often they move. A crawler — and an
   answer engine quoting the page — saw a shell. This is that missing text, and
   it is real UI: a first-time visitor had the same question.
   --------------------------------------------------------------------------- */

.tagline {
  margin: 0 0 22px;
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1.6;
  max-width: 68ch;
}
.tagline strong { color: var(--text, #e2e8f0); font-weight: 600; }

.about {
  margin-top: 44px;
  padding-top: 28px;
  border-top: 1px solid var(--border-subtle, #2a3a50);
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1.7;
  max-width: 72ch;
}
.about h2 {
  font-size: 1.15rem;
  margin: 0 0 14px;
  color: var(--text, #e2e8f0);
  letter-spacing: -0.01em;
}
.about h3 {
  font-size: 1rem;
  margin: 30px 0 10px;
  color: var(--text, #e2e8f0);
}
.about p { margin: 0 0 14px; }
.about a { color: var(--accent-blue); text-decoration: none; }
.about a:hover { text-decoration: underline; }
.about .about-org { margin-top: 22px; font-size: 0.9rem; opacity: 0.9; }

/* Blog teasers (ablog). The component ships no palette of its own — every
   colour it draws routes through an --ablog-* custom property whose fallback is
   `inherit` — so these four declarations are the whole of this site's theming
   of it, and they sit on the containers rather than on ablog's own classes so
   nothing here depends on the library's internal class names.
   .about carries them for the front-page box, .news-page for the /news list.
   Class-scoped throughout: a bare element selector would reach the
   constellation footer, which is the mistake LANDMARK-020 pins. */
.about, .news-page {
  --ablog-accent: var(--accent-blue);
  --ablog-head-fg: var(--text-secondary, #e2e8f0);
  --ablog-fg: inherit;
}
.about .ablog-box { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border-subtle, #2a3a50); }

/* /news — the teaser list page. Reading measure, not the 1200px grid width the
   rates table needs. */
.news-page {
  max-width: 72ch;
  margin: 0 auto;
  padding: 44px 0 8px;
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1.7;
}
.news-page h1 {
  font-size: 1.6rem;
  margin: 0 0 18px;
  color: var(--text, #e2e8f0);
  letter-spacing: -0.01em;
}

/* The FAQ. <details> rather than JavaScript accordions: it is keyboard- and
   screen-reader-native, it prints, and — the reason it matters here — the
   answer text is in the DOM whether or not the reader opens it, so what a
   crawler reads and what the FAQPage JSON-LD claims are the same bytes. */
.about details {
  border-top: 1px solid var(--border-subtle, #2a3a50);
  padding: 12px 0;
}
.about details:last-of-type { border-bottom: 1px solid var(--border-subtle, #2a3a50); }
.about summary {
  cursor: pointer;
  color: var(--text, #e2e8f0);
  font-weight: 600;
  list-style: none;
  min-height: 28px;
  padding-right: 24px;
  position: relative;
}
.about summary::-webkit-details-marker { display: none; }
.about summary::after {
  content: "+";
  position: absolute;
  right: 4px;
  top: 0;
  color: var(--accent-blue);
  font-weight: 400;
}
.about details[open] summary::after { content: "\2212"; }
.about details p { margin: 10px 0 4px; }

@media (max-width: 640px) {
  .about { margin-top: 32px; }
}


/* ---------------------------------------------------------------------------
   Buying power — one chart, ~200 countries, one argument.

   The chart is a single inline <svg> built in app.js. Everything here is the
   chrome around it: the meta row, the controls, the legend, the scroll shell
   and the tooltip. The bars themselves are styled by class from inside the SVG
   (.bp-bar, .bp-ghost, .bp-row-hit) so hover and reduced-motion live in CSS
   rather than in the render loop.

   Sequential colour along the factor axis is computed per bar in JS (one
   continuous violet -> cyan -> emerald ramp) and never carries meaning alone:
   the bar LENGTH is the value, colour only reinforces it, and the two states
   that matter — floored, capped — are marked by shape (a hatched cap, a dashed
   ghost past the ceiling) as well as by hue.
   --------------------------------------------------------------------------- */

.bp-lede {
  margin: 0 0 22px;
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1.6;
  max-width: 72ch;
}
.bp-lede strong { color: var(--text-secondary); font-weight: 600; }

/* --- meta row --- */

.bp-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin-bottom: 18px;
}

.bp-meta-card {
  background: var(--bg-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  padding: 12px 14px;
  min-height: 84px;
}
.bp-meta-card .label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.bp-meta-card .value {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.25;
}
.bp-meta-card .value.accent { color: var(--accent-light); }
.bp-meta-card .sub {
  margin-top: 3px;
  font-size: 0.68rem;
  color: var(--text-muted);
}

/* The fallback banner. Designed, not blank: serving the table baked into the
   binary is a normal state (cold boot, World Bank outage), and it should read
   as a deliberate answer rather than as an error. */
.bp-banner {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 16px;
  margin-bottom: 18px;
  border-radius: var(--radius);
  border: 1px solid rgba(245, 158, 11, 0.35);
  background: rgba(245, 158, 11, 0.08);
  color: var(--text-secondary);
  font-size: 0.82rem;
  line-height: 1.55;
}
.bp-banner b { color: var(--accent-orange); font-weight: 600; }

/* --- controls --- */

.bp-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 14px;
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--bg-primary);
  padding: 10px 0;
}

.bp-search { position: relative; flex: 1 1 260px; min-width: 200px; }
.bp-search input {
  width: 100%;
  padding: 11px 34px 11px 38px;
  background: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  font-size: 0.88rem;
  font-family: var(--font);
  outline: none;
  transition: border-color 0.15s;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85zm-5.242.156a5 5 0 1 1 0-10 5 5 0 0 1 0 10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 13px center;
}
.bp-search input:focus { border-color: var(--accent); }
.bp-clear {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.bp-clear:hover { color: var(--text-primary); background: var(--bg-card-hover); }

.bp-toggle {
  display: flex;
  gap: 3px;
  padding: 3px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
}
.bp-toggle button {
  padding: 8px 14px;
  min-height: 38px;
  background: transparent;
  color: var(--text-secondary);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 500;
  font-family: var(--font);
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.bp-toggle button:hover { color: var(--text-primary); background: var(--bg-card); }
.bp-toggle button.active { background: var(--accent-dim); color: var(--accent-light); }
.bp-sort button { padding: 8px 12px; }

/* --- legend --- */

.bp-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin-bottom: 10px;
  font-size: 0.72rem;
  color: var(--text-muted);
}
.bp-legend-ramp {
  width: 128px;
  height: 8px;
  border-radius: 4px;
  /* Same three stops the per-bar ramp interpolates between. */
  background: linear-gradient(90deg, #a78bfa 0%, #22d3ee 50%, #34d399 100%);
}
.bp-legend-right { margin-right: 6px; }
.bp-legend-key { display: inline-flex; align-items: center; gap: 6px; }
.bp-legend-key i {
  width: 20px;
  height: 9px;
  border-radius: 2px;
  display: inline-block;
}
.bp-legend-key i.k-floor {
  background: #a78bfa;
  background-image: repeating-linear-gradient(135deg, rgba(8,12,20,0.55) 0 2px, transparent 2px 5px);
}
.bp-legend-key i.k-cap {
  background: linear-gradient(90deg, #34d399 0 55%, transparent 55%);
  border-right: 2px dashed rgba(52, 211, 153, 0.75);
}

/* --- chart shell --- */

.bp-chart-shell {
  /* A shallow top-down gradient and a soft drop give the plate a little depth,
     so a 200-row chart reads as one surface rather than as a bare rectangle of
     rows. Both are barely-there on purpose: the bars are the loud part. */
  background: linear-gradient(180deg, var(--bg-card) 0%, var(--bg-secondary) 100%);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 18px 44px -28px rgba(0, 0, 0, 0.9);
}

/* The axis is a sibling of the scroller, not a child of it, so the tick labels
   and the ceiling marker stay put while ~200 rows scroll underneath. */
.bp-axis {
  position: relative;
  height: 26px;
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-secondary);
}
.bp-axis span {
  position: absolute;
  top: 6px;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.65rem;
  color: var(--text-muted);
  white-space: nowrap;
}
.bp-axis i.bp-axis-split {
  position: absolute;
  top: 4px;
  bottom: 3px;
  width: 1px;
  background: var(--border-subtle);
  opacity: 0.7;
}
.bp-axis span.tick-ceiling { color: var(--accent-light); font-weight: 600; }
.bp-axis span.tick-floor { color: #a78bfa; font-weight: 600; }

.bp-scroll {
  /* The chart is meant to be READ, not paged through. It takes as much of the
     viewport as it is given; the wide-screen block at the bottom of this
     section pairs that with the multi-column split in app.js, so ~200
     countries land in about one screen on a desk instead of four. */
  max-height: min(74vh, 760px);
  overflow-y: auto;
  overflow-x: hidden;
  outline: none;
  scrollbar-width: thin;
  scrollbar-color: var(--border-subtle) transparent;
}
.bp-scroll::-webkit-scrollbar { width: 10px; }
.bp-scroll::-webkit-scrollbar-thumb {
  background: var(--border-subtle);
  border-radius: 6px;
  border: 3px solid transparent;
  background-clip: content-box;
}
.bp-scroll::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); background-clip: content-box; }
.bp-scroll:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: -2px; }
#bpChart { display: block; width: 100%; }

/* --- bars --- */

.bp-row-hit { fill: transparent; cursor: pointer; }
/* Banding, not borders: at 20px rows the eye needs a rail to carry a country
   name across to its bar, and a rule per row would out-shout the bars. */
.bp-row.alt .bp-row-hit { fill: rgba(255, 255, 255, 0.022); }
.bp-col-split { stroke: var(--border-subtle); stroke-width: 1; opacity: 0.7; }
.bp-row:hover .bp-row-hit,
.bp-row.is-active .bp-row-hit { fill: rgba(255, 255, 255, 0.06); }

.bp-bar { transition: opacity 0.15s, filter 0.15s; }
.bp-row:hover .bp-bar { opacity: 1; filter: brightness(1.18) saturate(1.05); }

.bp-ghost { fill: none; stroke-width: 1.5; stroke-dasharray: 3 3; opacity: 0.85; }
.bp-ghost-cap { stroke-width: 1.5; opacity: 0.9; }

.bp-label { fill: var(--text-secondary); font-size: 11px; font-family: var(--font); }
.bp-code { fill: var(--text-muted); font-size: 9.5px; font-family: var(--font-mono); }
.bp-value { fill: var(--text-muted); font-size: 10px; font-family: var(--font-mono); }
.bp-row:hover .bp-label { fill: var(--text-primary); }
.bp-row:hover .bp-value { fill: var(--text-primary); }

.bp-grid { stroke: var(--border-subtle); stroke-width: 1; }
.bp-rule-ceiling { stroke: var(--accent); stroke-width: 1.5; opacity: 0.75; }
.bp-rule-floor { stroke: #a78bfa; stroke-width: 1.25; stroke-dasharray: 4 4; opacity: 0.6; }

/* --- empty state --- */

.bp-empty {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  text-align: center;
  padding: 44px 20px;
  margin-top: 12px;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 0.85rem;
}
.bp-empty strong { color: var(--text-secondary); font-size: 0.95rem; font-weight: 600; }
.bp-empty em { color: var(--accent-light); font-style: normal; font-family: var(--font-mono); }

/* --- tooltip --- */

.bp-tip {
  position: fixed;
  z-index: 60;
  pointer-events: none;
  max-width: 290px;
  padding: 11px 13px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--text-secondary);
}
.bp-tip .tip-name {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 7px;
  display: flex;
  gap: 7px;
  align-items: baseline;
}
.bp-tip .tip-name code {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--text-muted);
}
.bp-tip dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 3px 12px;
  margin: 0;
}
.bp-tip dt { color: var(--text-muted); }
.bp-tip dd { margin: 0; font-family: var(--font-mono); text-align: right; color: var(--text-primary); }
.bp-tip dd.hero { color: var(--accent-light); font-weight: 600; }
.bp-tip .tip-flag {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border-subtle);
  color: var(--text-muted);
  font-size: 0.72rem;
}
.bp-tip .tip-flag b { color: var(--accent-orange); font-weight: 600; }

.bp-foot {
  margin-top: 16px;
  font-size: 0.76rem;
  line-height: 1.65;
  color: var(--text-muted);
  max-width: 76ch;
}
.bp-foot code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--text-secondary);
}
.bp-foot a { color: var(--accent-blue); text-decoration: none; }
.bp-foot a:hover { color: var(--accent-light); }

/* --- phone ---
   375px has to be genuinely good, not merely unbroken. The label column
   narrows, the per-row value is dropped (the tooltip carries it on tap), the
   rows tighten, and the controls stay pinned so a filter is always one tap
   away however far down the list you are. */
@media (max-width: 720px) {
  nav { flex-wrap: wrap; }
  /* Five tabs at 40% each wrapped 2-2-1 and left Settings alone on a row of
     its own. 30% gives 3+2, and "Buying Power" still fits at 390px. */
  nav button { flex: 1 1 30%; padding: 10px 8px; }
}

@media (max-width: 640px) {
  .bp-meta { grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: 8px; }
  .bp-meta-card { padding: 10px 11px; min-height: 74px; }
  .bp-meta-card .value { font-size: 0.92rem; }
  .bp-controls { gap: 8px; padding: 8px 0; }
  .bp-toggle { flex: 1 1 100%; }
  .bp-toggle button { flex: 1; padding: 8px 8px; font-size: 0.72rem; }
  .bp-legend { font-size: 0.68rem; gap: 5px 10px; }
  .bp-legend-ramp { width: 92px; }
  .bp-scroll { max-height: 68vh; }
  .bp-label { font-size: 10px; }
  .bp-tip { max-width: min(88vw, 290px); }
}

/* An animation preference is a real setting, not a nicety. */
@media (prefers-reduced-motion: reduce) {
  .bp-bar, .bp-toggle button, .bp-search input { transition: none; }
  .bp-row:hover .bp-bar { filter: none; opacity: 0.86; }
  .bp-scroll { scroll-behavior: auto; }
}

/* --- the desk ---------------------------------------------------------------
   Prose belongs in a 1200px column; a 200-row chart does not. Above 1280px the
   compare view breaks out of .app and takes the width of the screen, and
   app.js answers that width with 2 (and past ~1700px, 3) side-by-side columns.
   Nothing else on the page moves — the lede, the meta cards and the trend view
   stay in the reading column, so the break-out reads as the chart being given
   the desk rather than as the layout coming apart.

   Vertical is the other half: the cap climbs with the viewport, because on a
   tall display the limit should be the screen, not a number typed in 2026. */
@media (min-width: 1280px) {
  /* The whole section steps out of the reading column together — cards, filter
     and chart on one edge — so it reads as a widescreen view of the data and
     not as one runaway element. Everything made of SENTENCES is pulled back to
     a readable measure inside it. */
  #sec-buying {
    --bp-wide: min(94vw, 1800px);
    width: var(--bp-wide);
    margin-left: calc(50% - var(--bp-wide) / 2);
  }
  #sec-buying .bp-lede { max-width: 78ch; }
  #sec-buying .bp-foot { max-width: 96ch; }
  /* A segmented control has a natural size; stretching it to 1800px would make
     two buttons look like a banner. */
  #sec-buying .bp-views { max-width: 460px; }
  /* One line across a desk is a flat line. In the trend view the whole section
     steps back into the reading column — the toggle it was switched from stays
     in the same place on screen, because both views are left-aligned. */
  #sec-buying.bp-view-trend {
    width: auto;
    max-width: 1180px;
    margin-left: 0;
  }
  .bp-legend { gap: 8px 22px; font-size: 0.75rem; }

  /* Every pixel above the chart is a row of the chart you cannot see, so the
     chrome tightens as the chart widens: the cards lose their reserved second
     line, and the gaps close by a third. Worth ~3 more countries on screen. */
  .bp-meta { gap: 12px; margin-bottom: 14px; }
  .bp-meta-card { min-height: 0; padding: 11px 14px; }
  #sec-buying .bp-lede { margin-bottom: 16px; }
  #sec-buying .bp-views { margin-bottom: 14px; }
  .bp-controls { padding: 8px 0; margin-bottom: 10px; }
}

/* A 4K-ish desk gets a fourth column and the last of the scrolling goes away:
   202 countries over four columns is ~51 rows, which fits under the tall cap
   below. This is the top of the ladder — a fifth column would push a column
   under MIN_COL and start truncating country names. */
@media (min-width: 2000px) {
  #sec-buying { --bp-wide: min(96vw, 2440px); }
}

@media (min-height: 900px) {
  .bp-scroll { max-height: min(80vh, 1120px); }
}
@media (min-height: 1200px) {
  .bp-scroll { max-height: min(84vh, 1560px); }
}


/* --- buying power: the two views, and the per-country trend chart --- */

.bp-views {
  display: flex;
  gap: 3px;
  padding: 3px;
  margin-bottom: 18px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
}
.bp-views button {
  flex: 1;
  min-height: 42px;
  padding: 9px 14px;
  background: transparent;
  color: var(--text-secondary);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: var(--font);
  font-size: 0.82rem;
  font-weight: 500;
  transition: background 0.15s, color 0.15s;
}
.bp-views button:hover { color: var(--text-primary); background: var(--bg-card); }
.bp-views button.active { background: var(--accent-dim); color: var(--accent-light); }

.bp-meta-card .value.warn { color: var(--accent-orange); }

.bp-trend-pick { display: flex; flex-direction: column; gap: 5px; flex: 1 1 240px; min-width: 190px; }
.bp-trend-pick label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
}
.bp-trend-pick select {
  width: 100%;
  padding: 10px 14px;
  min-height: 42px;
  background: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  font-family: var(--font);
  font-size: 0.85rem;
  cursor: pointer;
}
.bp-trend-pick select:focus { border-color: var(--accent); outline: none; }

.bp-trend-head { margin-bottom: 10px; }
.bp-trend-title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-primary);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
}
.bp-trend-title code {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
}
.bp-chip {
  font-size: 0.64rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 3px 8px;
  border-radius: 999px;
}
.bp-chip.cap { color: var(--accent-light); background: var(--accent-dim); }
.bp-chip.flr { color: #c4b5fd; background: rgba(167, 139, 250, 0.16); }

.bp-trend-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 5px 16px;
  margin-top: 7px;
  font-size: 0.74rem;
  color: var(--text-muted);
}
.bp-trend-keys span { display: inline-flex; align-items: center; gap: 6px; }
.bp-trend-keys span::before {
  content: "";
  width: 16px;
  height: 3px;
  border-radius: 2px;
  background: currentColor;
}
.bp-trend-keys .k-country { color: var(--accent-light); }
.bp-trend-keys .k-base { color: var(--accent-blue); }
.bp-trend-keys .k-fact { color: var(--text-muted); }
.bp-trend-keys .k-fact::before { display: none; }

.bp-trend-shell { padding: 8px 4px 0; }
#bpTrend { display: block; width: 100%; }

.bp-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.bp-line-country { stroke: var(--accent-light); }
.bp-line-base { stroke: var(--accent-blue); stroke-width: 1.75; opacity: 0.85; }
.bp-dot-country { fill: var(--accent-light); }
.bp-dot-base { fill: var(--accent-blue); }
.bp-parity { fill: var(--accent); font-size: 9.5px; font-family: var(--font-mono); }

.bp-trend-note {
  margin-top: 12px;
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--text-muted);
}
.bp-trend-note b { color: var(--accent-orange); font-weight: 600; }
.bp-trend-warn { color: var(--text-secondary); }

@media (max-width: 640px) {
  .bp-views button { font-size: 0.75rem; padding: 9px 8px; }
  .bp-trend-title { font-size: 0.95rem; }
  .bp-trend-pick { flex: 1 1 100%; }
}

.bp-area { pointer-events: none; }
.bp-cross { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.55; }
.bp-cross-dot { fill: var(--accent-light); stroke: var(--bg-card); stroke-width: 2; }
#bpTrend .hidden { display: none; }

/* Long/short control labels. Five controls have to share one 375px row, and
   "Recommended factor" alone is wider than a third of it — so the phone gets
   the short word and the desktop keeps the explicit one. Swapped in CSS rather
   than in JS so it survives a resize with no re-render. */
.bp-toggle .sm, .bp-views .sm { display: none; }
@media (max-width: 640px) {
  .bp-toggle .lg, .bp-views .lg { display: none; }
  .bp-toggle .sm, .bp-views .sm { display: inline; }
  .bp-controls > .bp-toggle { flex: 1 1 auto; }
  .bp-toggle button { padding: 8px 6px; font-size: 0.7rem; }
  .bp-views button { font-size: 0.78rem; }
}
