/* Worldly — the component kit.
   ---------------------------------------------------------------------------
   Everything reusable across screens: the app bar and its navigation, buttons,
   cards, chips, tabs, forms, meters, toasts, and the interactive map widget.
   Per-screen composition lives in screens.css; tokens live in styles.css.

   Two conventions run through the whole file:

   1. Meters are scale bars. Progress, XP, timers and stat bars all carry the
      same tick pattern as the section rules, because they are all measuring
      something along a line. The fill is set by JS via CSSOM (`el.style.width`)
      — never an inline style attribute, which the CSP would drop.

   2. Width, not just weight, marks hierarchy. Titles widen, data labels
      compress. Anything numeric also takes tabular figures so columns line up. */

/* ---- app bar --------------------------------------------------------------- */
.topbar {
  display: flex;
  align-items: center;
  gap: var(--s5);
  padding: var(--s3) var(--s5);
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}

/* A <button>, so the UA button defaults (background, border, system font)
   have to be cleared for it to read as the wordmark it is. */
.brand {
  display: flex;
  align-items: center;
  gap: var(--s2);
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  color: var(--text);
  font-family: inherit;
  font-size: 1.16rem;
  font-weight: 800;
  font-stretch: var(--wd-display);
  letter-spacing: -0.015em;
  flex: 0 0 auto;
}
/* The mark is a drawn globe with its own graticule (js/icons.js), so the brand
   states the site's structural motif before any content does. */
.brand-globe {
  display: block;
  width: 26px;
  height: 26px;
  color: var(--brass);
}
.brand-globe svg { display: block; width: 100%; height: 100%; }
.brand-name { color: var(--text); }
.brand:hover .brand-globe { color: var(--text); }

/* ---- primary navigation ----------------------------------------------------
   Deliberately NOT class `.tab`: wireTabs() in main.js claims every .tab inside
   the screen it wires and drives it from data-tab, so a nav link wearing that
   class would be swept into whatever tab group the current screen has. */
.navrail {
  display: none;
  align-items: center;
  gap: var(--s1);
  margin-left: var(--s3);
}
.navlink {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 13px;
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: var(--t-sm);
  font-weight: 650;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--transition), background var(--transition);
}
.navlink svg { width: 17px; height: 17px; flex: 0 0 auto; }
.navlink:hover { color: var(--text); background: var(--surface); }
.navlink[aria-current="page"] { color: var(--brass); background: var(--brass-soft); }

/* ---- HUD — the instrument cluster ------------------------------------------
   Level, XP, streak and accuracy read as one gauge rather than four equal
   chips: the level number sits inside its own XP arc, and the rest hang off it
   as small readings. */
.hud {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin-left: auto;
  min-width: 0;
}
.hud .chip {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-sm);
  font-stretch: var(--wd-data);
  color: var(--muted);
  white-space: nowrap;
}
.hud .chip strong {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.hud .chip svg { width: 15px; height: 15px; color: var(--muted); }
.hud .chip.chip-name {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-stretch: var(--wd-data);
  cursor: pointer;
  max-width: 13ch;
  overflow: hidden;
}
.hud .chip.chip-name strong { overflow: hidden; text-overflow: ellipsis; }
.hud .chip.chip-name:hover strong,
.hud .chip.chip-name:focus-visible strong { color: var(--brass); }

/* The level gauge: a brass ring whose arc is the XP into this level. The arc is
   a conic-gradient driven by --arc, which renderHUD() sets via CSSOM. */
.gauge {
  position: relative;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: conic-gradient(var(--brass) calc(var(--arc, 0) * 1%), var(--surface-2) 0);
}
.gauge::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--bg);
}
.gauge-n {
  position: relative;
  z-index: 1;
  font-size: 0.84rem;
  font-weight: 800;
  font-stretch: var(--wd-data);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* The XP bar still exists for narrow layouts and for anything that wants a
   linear read of the same number. */
.xpbar {
  width: 96px;
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  overflow: hidden;
  border: 1px solid var(--border);
}
.xpbar > span {
  display: block;
  height: 100%;
  background: var(--brass);
  transition: width 400ms var(--ease-out);
}

.icon-btn {
  display: grid;
  place-items: center;
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  width: 40px;
  height: 40px;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
  font-size: 1.05rem;
}
.icon-btn svg { width: 19px; height: 19px; }
.icon-btn:hover { background: var(--surface); color: var(--text); border-color: var(--border); }

/* ---- bottom tab bar (small screens) ----------------------------------------
   The mobile "native feel" ask: the four primary destinations always within
   thumb reach, rather than a home button in the far corner of the header. */
.tabbar {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  padding: 6px var(--s2) calc(6px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg) 93%, transparent);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
}
.tabbar-link {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 5px 2px;
  border-radius: var(--r-sm);
  color: var(--muted);
  font-size: var(--t-micro);
  font-weight: 650;
  font-stretch: var(--wd-data);
  text-decoration: none;
}
.tabbar-link svg { width: 21px; height: 21px; }
.tabbar-link[aria-current="page"] { color: var(--brass); }

/* ---- buttons ---------------------------------------------------------------
   display/text-decoration are no-ops for <button> but let an <a class="btn">
   (the 404 and error screens) render identically instead of as an underlined
   inline link. */
.btn {
  font-family: inherit;
  font-size: var(--t-sm);
  font-weight: 700;
  font-stretch: var(--wd-text);
  letter-spacing: 0.005em;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 11px 17px;
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), color var(--transition), filter var(--transition);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  text-decoration: none;
  text-align: center;
}
.btn svg { width: 16px; height: 16px; flex: 0 0 auto; }
.btn:hover { background: var(--surface-2); border-color: var(--muted); }
.btn.primary {
  background: var(--brass);
  color: var(--primary-ink);
  border-color: transparent;
  font-weight: 750;
}
.btn.primary:hover { background: var(--brass); filter: brightness(1.09); border-color: transparent; }
.btn.ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn.ghost:hover { background: var(--surface); color: var(--text); border-color: var(--border); }
.btn.danger { border-color: var(--bad); color: var(--bad); background: transparent; }
.btn.danger:hover { background: var(--bad-bg); border-color: var(--bad); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
.btn-row { display: flex; gap: var(--s3); flex-wrap: wrap; }

/* ---- cards -----------------------------------------------------------------
   A card is a plate on a chart: square-ish top-left corner where the icon sits,
   soft elsewhere, with a hairline that warms to brass on hover. The icon gets a
   tinted plate of its own so the emoji reads as a placed symbol rather than a
   character that happens to be there. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: var(--s3);
}
.card {
  position: relative;
  background-color: var(--surface);
  /* The cards are <button> elements: without these, browsers apply the UA
     button defaults (near-black `buttontext` color + a system font), which
     made the card titles unreadable on the dark surface in dark mode. */
  color: var(--text);
  font: inherit;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--s4) var(--s4) var(--s5);
  cursor: pointer;
  transition: border-color var(--transition), transform var(--transition), background var(--transition);
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  text-align: left;
  overflow: hidden;
}
.card:hover { transform: translateY(-2px); border-color: var(--brass); background-color: var(--surface-2); }
.card .emoji {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: 0 0 auto;
  color: var(--brass);
  border-radius: var(--r-plate);
  background: var(--surface-2);
  border: 1px solid var(--border);
  transition: background var(--transition), border-color var(--transition);
}
.card .emoji svg { width: 23px; height: 23px; }
.card:hover .emoji { border-color: var(--brass); background: var(--brass-soft); }
/* Flag image used in place of an emoji on cards (Windows lacks flag emoji). */
/* Fixed box + contain, rather than height:auto, so the card reserves its space
   before the image loads. Flags range from 1:1 (Switzerland) to 28:11 (Qatar),
   so a single aspect-ratio would distort some; contain letterboxes instead. */
.card .emoji-flag {
  width: 46px;
  height: 31px;
  object-fit: contain;
  border-radius: 3px;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}
.card .card-title {
  font-weight: 700;
  font-size: 1.02rem;
  line-height: 1.25;
  color: var(--text);
}
.card .card-desc {
  color: var(--muted);
  font-size: var(--t-sm);
  line-height: 1.45;
}

/* The Flag Key is a reference, not a menu — its cards do not act, so they must
   not offer the affordances of one.

   content-visibility lets the browser skip layout and paint for the cards that
   are off screen. It matters here more than anywhere else in the app: the live
   search toggles `.hidden` on up to 251 cards per keystroke, and without this
   every one of them is re-laid-out on every character. contain-intrinsic-size
   is the placeholder box a skipped card reserves, so the scrollbar does not
   jump as you scroll. */
.flagkey-card {
  cursor: default;
  content-visibility: auto;
  contain-intrinsic-size: auto 124px;
}
/* Same reasoning for the 198-card country index: it filters the same way. */
.card[data-slug] {
  content-visibility: auto;
  contain-intrinsic-size: auto 124px;
}
.flagkey-card:hover { transform: none; border-color: var(--border); background-color: var(--surface); }
.flagkey-card:hover .emoji-flag { border-color: var(--border); }

/* ---- chips and pills -------------------------------------------------------- */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 6px 13px;
  font-size: var(--t-xs);
  font-weight: 700;
  font-stretch: var(--wd-data);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--muted);
}
.pill svg { width: 14px; height: 14px; flex: 0 0 auto; }
.pill strong { color: var(--text); }
.pill .accent { color: var(--brass); }
/* `fire` is a second class on .pill, not a descendant -- this was `.pill .fire`,
   which never matched, so the streak pill was never actually colored. */
.pill.fire { color: var(--brass); border-color: var(--brass); background: var(--brass-soft); }
.pill.ok { color: var(--good); border-color: var(--good); background: var(--good-bg); }

/* A region badge, colored from the continent key so the same continent is the
   same color everywhere it appears. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: var(--t-micro);
  font-weight: 700;
  font-stretch: var(--wd-data);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--con, var(--muted));
  border: 1px solid color-mix(in srgb, var(--con, var(--border)) 45%, transparent);
  background: color-mix(in srgb, var(--con, var(--surface)) 12%, transparent);
}
.con-africa { --con: var(--con-africa); }
.con-asia { --con: var(--con-asia); }
.con-europe { --con: var(--con-europe); }
.con-north-america { --con: var(--con-namerica); }
.con-south-america { --con: var(--con-samerica); }
.con-oceania { --con: var(--con-oceania); }

/* ---- meters ----------------------------------------------------------------
   The shared scale-bar treatment: a track, a fill, and ticks printed over both
   so the bar reads as a measured distance rather than a decorative gradient. */
.progress, .bar-track, .timer, .ach .mini {
  position: relative;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  overflow: hidden;
  border: 1px solid var(--border);
}
.progress::after, .bar-track::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(90deg, var(--bg) 0 1px, transparent 1px 10%);
  opacity: 0.55;
}
.progress { flex: 1; min-width: 140px; height: 10px; }
.progress > span {
  display: block;
  height: 100%;
  background: var(--brass);
  transition: width 320ms var(--ease-out);
}
.bar-track { height: 9px; }
.bar-track > span { display: block; height: 100%; background: var(--brass); }
.timer { height: 6px; margin-bottom: var(--s4); }
/* width is the countdown's starting state; startTimer() then drives it via
   CSSOM (an inline style, which always wins over this rule). */
.timer > span { display: block; width: 100%; height: 100%; background: var(--good); transition: width 120ms linear; }
.timer.low > span { background: var(--bad); }

.bar-row {
  display: grid;
  grid-template-columns: 148px 1fr 52px;
  align-items: center;
  gap: var(--s3);
  margin: 7px 0;
}
.bar-row .name { font-size: var(--t-sm); }
/* A region bar takes its continent's color, so the bar on the dashboard and
   the badge on that country's page are always the same hue. */
.bar-row.tinted .bar-track > span { background: var(--con, var(--brass)); }
.bar-row .pct {
  text-align: right;
  color: var(--muted);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
  font-stretch: var(--wd-data);
}

/* ---- stat tiles ------------------------------------------------------------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(158px, 100%), 1fr));
  gap: var(--s3);
  margin-bottom: var(--s5);
}
.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--s4);
  min-width: 0;
}
.stat .big {
  display: block;
  overflow-wrap: break-word;
  font-size: clamp(1.25rem, 0.86rem + 1.9vw, 1.9rem);
  font-weight: 800;
  font-stretch: var(--wd-wide);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  letter-spacing: -0.02em;
}
.stat .lbl {
  display: block;
  color: var(--muted);
  font-size: var(--t-xs);
  font-stretch: var(--wd-data);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 5px;
}

/* ---- lists ------------------------------------------------------------------ */
.weak-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 7px; }
.weak-list li {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 11px var(--s4);
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  align-items: baseline;
  font-size: var(--t-sm);
}
.weak-list .ans { color: var(--good); font-weight: 700; white-space: nowrap; }

/* ---- forms ------------------------------------------------------------------ */
.form-block {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--s5);
  margin-bottom: var(--s4);
}
/* These are h2 (they sit directly under the screen's h1); sized down to keep
   the previous visual weight now that the level, not the size, carries the
   document structure. */
.form-block h2 {
  margin: 0 0 var(--s3);
  font-size: var(--t-h3);
  font-stretch: var(--wd-wide);
}
/* Search + region filter above a grid. It is a control strip, not a panel, so
   it drops the card's border and padding and lets the search field take the
   room the grid below it has. */
.form-block.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  align-items: center;
  background: none;
  border: 0;
  padding: 0;
  margin-bottom: var(--s4);
}
.filter-bar .type-input { flex: 1 1 240px; max-width: 420px; }
.filter-bar .select { flex: 0 1 220px; }
/* A form block's heading row, when the block has All/None shortcuts beside the
   title. The h2 keeps its own margin so a block without a head is unaffected. */
.block-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}
.block-head h2 { margin-bottom: var(--s3); }
.block-actions { display: flex; gap: var(--s3); }
.link-btn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: var(--t-xs);
  font-weight: 650;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.link-btn:hover { color: var(--brass); }

/* ---- nested checklist --------------------------------------------------------
   A continent with its subregions folded underneath. The continent's own box is
   a roll-up of its children (main.js keeps it in sync, including the
   indeterminate state), so the two rows never claim different things.

   The children are laid out as their own wrapping row rather than a list: they
   are the same .check pills as everywhere else, just indented and rule-marked so
   it reads as "inside" the continent above them. */
.tree { display: flex; flex-direction: column; gap: var(--s2); }
.tree-head { display: flex; align-items: center; gap: var(--s2); }
.tree-head .check { flex: 1 1 auto; min-width: 0; }
.tree-count {
  font-size: var(--t-xs);
  font-stretch: var(--wd-data);
  color: var(--muted);
  flex: 0 0 auto;
}
.tree-toggle {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--muted);
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition);
}
.tree-toggle svg { width: 16px; height: 16px; transition: transform var(--transition); }
.tree-toggle:hover { color: var(--text); border-color: var(--muted); }
.tree-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }
.tree-kids {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s2) 0 var(--s3) var(--s4);
  padding-left: var(--s4);
  border-left: 2px solid var(--border);
}
.tree-kids .check { font-size: var(--t-xs); padding: 5px 12px; min-height: 36px; }
/* `display: flex` above outranks the UA's `[hidden] { display: none }`, so the
   collapsed state has to be restated here or every group renders open. */
.tree-kids[hidden] { display: none; }

.checks { display: flex; flex-wrap: wrap; gap: var(--s2); }
.check {
  display: flex;
  align-items: center;
  gap: var(--s2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 7px 15px;
  cursor: pointer;
  font-size: var(--t-sm);
  user-select: none;
  transition: border-color var(--transition), background var(--transition);
}
.check:hover { border-color: var(--brass); }
/* A checkbox on its own is still a pill, not a full-width bar: inside .checks
   the row packs them, but a lone one in a form block would otherwise stretch. */
.form-block > .check { width: fit-content; max-width: 100%; }
.check input { accent-color: var(--brass); }
.seg {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  overflow: hidden;
  background: var(--surface-2);
}
.seg button {
  background: transparent;
  border: none;
  color: var(--muted);
  padding: 8px 17px;
  cursor: pointer;
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 650;
  transition: background var(--transition), color var(--transition);
}
.seg button:hover { color: var(--text); }
.seg button.active { background: var(--brass); color: var(--primary-ink); }

.select {
  appearance: none;
  width: 100%;
  max-width: 360px;
  padding: 11px 38px 11px 15px;
  background: var(--surface-2);
  color: inherit;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
  /* The chevron is drawn, not an emoji or a background image file: it has to
     recolour with the theme, and img-src has no room for decoration. */
  background-image:
    linear-gradient(45deg, transparent 50%, currentcolor 50%),
    linear-gradient(135deg, currentcolor 50%, transparent 50%);
  background-position: calc(100% - 19px) center, calc(100% - 13px) center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
.select:hover { border-color: var(--muted); }

.type-form { display: flex; gap: var(--s2); margin-top: var(--s2); flex-wrap: wrap; }
.type-input {
  flex: 1 1 220px;
  padding: 13px 15px;
  background: var(--surface-2);
  color: inherit;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font: inherit;
}
.type-input::placeholder { color: var(--muted); }
.type-input.correct { border-color: var(--good); background: var(--good-bg); }
.type-input.wrong { border-color: var(--bad); background: var(--bad-bg); }

/* ---- tabs -------------------------------------------------------------------
   wireTabs() owns every element carrying .tab within the screen it wires. */
.tabs {
  display: flex;
  gap: var(--s1);
  margin: 0 0 var(--s5);
  flex-wrap: wrap;
  padding: var(--s1);
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--border);
  width: fit-content;
  max-width: 100%;
}
.tab {
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 650;
  color: var(--muted);
  background: transparent;
  border: none;
  border-radius: var(--r-pill);
  padding: 8px 17px;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
  white-space: nowrap;
}
.tab:hover { color: var(--text); background: var(--surface-2); }
.tab.active { background: var(--brass); color: var(--primary-ink); }
.tab-panel { display: none; }
.tab-panel.active { display: block; animation: tab-fade 180ms var(--ease-out); }

/* ---- in-screen back rail ----------------------------------------------------
   With real navigation in the header and the bottom bar, this row no longer
   needs to shout. It is a quiet way back, not a second menu. */
.top-nav {
  display: flex;
  gap: var(--s1);
  margin-bottom: var(--s4);
  flex-wrap: wrap;
  align-items: center;
}
.top-nav .btn { font-size: var(--t-xs); padding: 7px 12px; }

/* ---- callouts ---------------------------------------------------------------
   A field note pinned to the page: brass rule down the left edge, no box
   shouting for attention. */
.callout {
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--surface);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s4);
  line-height: 1.6;
  font-size: var(--t-sm);
}
.callout-warn { border-left-color: var(--bad); }
.about-list { margin: 0; padding-left: var(--s5); color: var(--muted); line-height: 1.75; font-size: var(--t-sm); }
.about-list a { color: var(--brass); }

/* ---- toasts ------------------------------------------------------------------ */
.toasts {
  position: fixed;
  right: var(--s4);
  bottom: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  z-index: 50;
}
.toast {
  background: var(--surface);
  border: 1px solid var(--brass);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  padding: var(--s3) var(--s4);
  box-shadow: var(--shadow);
  display: flex;
  gap: var(--s3);
  align-items: center;
  animation: slidein 260ms var(--ease-out);
  max-width: 340px;
  transition: opacity 260ms ease;
}
.toast .ic { display: grid; place-items: center; color: var(--brass); }
.toast .ic svg { width: 22px; height: 22px; }
.toast .t-title { font-weight: 750; font-stretch: var(--wd-wide); }
.toast .t-sub { color: var(--muted); font-size: var(--t-xs); }

/* ---- skeletons ---------------------------------------------------------------
   Shown while the leaderboard and the big map SVGs are in flight, so a slow
   network shows the shape of what is coming instead of the word "Loading". */
.skel {
  border-radius: var(--r-sm);
  background:
    linear-gradient(90deg, transparent 0%, var(--surface-3) 45%, transparent 90%) 0 0 / 200% 100% no-repeat,
    var(--surface-2);
  animation: shimmer 1500ms linear infinite;
}
.skel-line { height: 13px; margin: 10px 0; }
.skel-line.w-40 { width: 40%; }
.skel-line.w-60 { width: 60%; }
.skel-line.w-80 { width: 80%; }
.skel-block { height: 62px; margin-bottom: var(--s2); }

/* ---- speaker / pronunciation button --------------------------------------- */
/* Appearance only. The 44px touch-target sizing is intentionally kept in the
   WCAG 2.5.8 block in styles.css, so that block remains the one place the
   minimum target size is declared and audited. */
.spk {
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.95em;
  padding: 0 2px;
  line-height: 1;
  opacity: 0.8;
  transition: opacity 0.12s ease, transform 0.12s ease, color 0.12s ease;
}
.spk:hover { opacity: 1; color: var(--brass); transform: scale(1.12); }

/* ---- interactive maps (click-the-map modes) ------------------------------ */
.map-mount { margin: var(--s2) 0 var(--s1); }
.map-wrap { position: relative; }
.map-holder {
  touch-action: none; /* wrangle pinch/pan ourselves (pointer events) */
  position: relative;
  overflow: hidden;
  width: 100%;
  /* Fallback for the rare case mapview.js cannot read a viewBox; normally it
     replaces this with the map's own aspect ratio so no frame area is wasted. */
  height: clamp(300px, 52vh, 560px);
  /* Keeps a tall map (Canada) from running off a short viewport. */
  max-height: min(72vh, 620px);
  background: var(--ocean);
  border: 1px solid var(--border);
  border-radius: var(--r-plate);
  cursor: grab;
}
/* The ocean is ruled with a graticule, so the map sits on a chart rather than
   floating on a flat panel. Behind the SVG, and inert to pointers. */
.map-holder::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.45;
  background:
    repeating-linear-gradient(90deg, var(--border-soft) 0 1px, transparent 1px 7.5%),
    repeating-linear-gradient(0deg, var(--border-soft) 0 1px, transparent 1px 12%);
}
.map-holder:active { cursor: grabbing; }
.map-svg {
  position: relative;
  width: 100%;
  height: 100%;
  transform-origin: 0 0;
  display: block;
}
.map-svg path {
  fill: var(--land);
  stroke: var(--border);
  stroke-width: 0.4;
  vector-effect: non-scaling-stroke;
  cursor: pointer;
  transition: fill 0.12s ease;
}
.map-svg path:hover { fill: var(--accent); }
.map-svg path:focus-visible { outline: 3px solid var(--focus); outline-offset: 1px; }
.map-svg.map-answered path { cursor: default; pointer-events: none; }
.map-svg path.region-correct { fill: var(--good); stroke: var(--good); }
.map-svg path.region-wrong { fill: var(--bad); stroke: var(--bad); }
/* Reverse "name the region" mode: the map is display-only and the target glows. */
.map-svg.map-static path { cursor: default; }
.map-svg.map-static path:hover { fill: var(--land); }
.map-svg path.region-highlight,
.map-svg.map-static path.region-highlight { fill: var(--accent); stroke: var(--accent); }

.map-controls {
  position: absolute;
  top: var(--s2);
  right: var(--s2);
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 3px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border);
}
.map-btn {
  width: 34px;
  height: 34px;
  font-size: 1rem;
  line-height: 1;
  display: grid;
  place-items: center;
  background: transparent;
  color: var(--muted);
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.map-btn:hover { background: var(--brass); color: var(--primary-ink); }

.map-hint {
  margin: var(--s2) 0 0;
  color: var(--muted);
  font-size: var(--t-xs);
  font-stretch: var(--wd-data);
  line-height: 1.5;
}

/* ---- language choropleth ---------------------------------------------------
   These .lf-* / .lg-* rules MUST stay below `.map-svg path:hover` above: they
   have equal specificity, so the later rule wins and a painted fill survives
   hover. Hover feedback is then given back by .map-choropleth, which brightens
   and outlines instead of recolouring.

   Color is never the only encoding here. The validator flags several slots
   below 3:1 against the page, which obliges relief: the legend carries a count
   per bucket, clicking a legend entry isolates that bucket, and clicking any
   country names its language outright. */
.map-svg.map-choropleth path { transition: none; }

/* A picker map has no prompt telling you where to aim, so unlike a quiz map it
   cannot afford to be surface-on-surface — at light-theme defaults the whole
   world rendered as white on white. Tint the land and firm up the borders. */
.map-svg.map-picker path { fill: var(--surface-2); stroke: var(--muted); stroke-width: 0.6; }
.map-svg.map-picker path:hover { fill: var(--accent); }
.map-svg.map-picker path.region-highlight { fill: var(--brass); stroke: var(--brass); }

.map-svg.map-choropleth path:hover {
  fill: inherit;
  filter: brightness(1.25) saturate(1.1);
  stroke: var(--text);
  stroke-width: 1.2;
}
.map-svg path.lf-romance { fill: var(--cat-1); stroke: var(--cat-1); }
.map-svg path.lf-germanic { fill: var(--cat-2); stroke: var(--cat-2); }
.map-svg path.lf-slavic { fill: var(--cat-3); stroke: var(--cat-3); }
.map-svg path.lf-indo-iranian { fill: var(--cat-4); stroke: var(--cat-4); }
.map-svg path.lf-afro-asiatic { fill: var(--cat-5); stroke: var(--cat-5); }
.map-svg path.lf-niger-congo { fill: var(--cat-6); stroke: var(--cat-6); }
.map-svg path.lf-turkic { fill: var(--cat-7); stroke: var(--cat-7); }
.map-svg path.lf-east-asian { fill: var(--cat-8); stroke: var(--cat-8); }
.map-svg path.lf-austronesian { fill: var(--cat-9); stroke: var(--cat-9); }
.map-svg path.lf-creole { fill: var(--cat-10); stroke: var(--cat-10); }
.map-svg path.lf-other { fill: var(--cat-11); stroke: var(--cat-11); }
.map-svg path.lg-1 { fill: var(--cat-1); stroke: var(--cat-1); }
.map-svg path.lg-2 { fill: var(--cat-2); stroke: var(--cat-2); }
.map-svg path.lg-3 { fill: var(--cat-3); stroke: var(--cat-3); }
.map-svg path.lg-4 { fill: var(--cat-4); stroke: var(--cat-4); }
.map-svg path.lg-5 { fill: var(--cat-5); stroke: var(--cat-5); }
.map-svg path.lg-6 { fill: var(--cat-6); stroke: var(--cat-6); }
.map-svg path.lg-7 { fill: var(--cat-7); stroke: var(--cat-7); }
.map-svg path.lg-8 { fill: var(--cat-8); stroke: var(--cat-8); }
.map-svg path.lg-9 { fill: var(--cat-9); stroke: var(--cat-9); }
.map-svg path.lg-10 { fill: var(--cat-10); stroke: var(--cat-10); }
.map-svg path.lf-nodata,
.map-svg path.lg-other { fill: var(--cat-none); stroke: var(--cat-none); }

/* Isolate mode: one bucket keeps its color, everything else drops back to the
   plain map fill, so no one has to tell eleven hues apart at once. */
.map-svg.map-choropleth path.region-dim {
  fill: var(--land);
  stroke: var(--border);
  opacity: 0.5;
}

.map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: var(--s3) 0 0;
  padding: 0;
  list-style: none;
}
.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 32px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-size: var(--t-xs);
  font-stretch: var(--wd-data);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition), color var(--transition);
}
.legend-item:hover { border-color: var(--brass); color: var(--text); }
.legend-item[aria-pressed="true"] {
  border-color: var(--brass);
  background: var(--brass-soft);
  color: var(--text);
  font-weight: 700;
}
.legend-swatch {
  width: 12px;
  height: 12px;
  border-radius: 2px;
  flex: 0 0 auto;
  background: var(--cat-none);
}
.legend-swatch.lf-nodata,
.legend-swatch.lg-other { background: var(--cat-none); }
.legend-swatch.lf-romance { background: var(--cat-1); }
.legend-swatch.lf-germanic { background: var(--cat-2); }
.legend-swatch.lf-slavic { background: var(--cat-3); }
.legend-swatch.lf-indo-iranian { background: var(--cat-4); }
.legend-swatch.lf-afro-asiatic { background: var(--cat-5); }
.legend-swatch.lf-niger-congo { background: var(--cat-6); }
.legend-swatch.lf-turkic { background: var(--cat-7); }
.legend-swatch.lf-east-asian { background: var(--cat-8); }
.legend-swatch.lf-austronesian { background: var(--cat-9); }
.legend-swatch.lf-creole { background: var(--cat-10); }
.legend-swatch.lf-other { background: var(--cat-11); }
.legend-swatch.lg-1 { background: var(--cat-1); }
.legend-swatch.lg-2 { background: var(--cat-2); }
.legend-swatch.lg-3 { background: var(--cat-3); }
.legend-swatch.lg-4 { background: var(--cat-4); }
.legend-swatch.lg-5 { background: var(--cat-5); }
.legend-swatch.lg-6 { background: var(--cat-6); }
.legend-swatch.lg-7 { background: var(--cat-7); }
.legend-swatch.lg-8 { background: var(--cat-8); }
.legend-swatch.lg-9 { background: var(--cat-9); }
.legend-swatch.lg-10 { background: var(--cat-10); }
.legend-count { color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---- responsive -------------------------------------------------------------
   Three breakpoints where there used to be one: 560 (phone), 900 (where the
   header nav has room to appear and the bottom bar retires), 1200 (two-column
   compositions in screens.css). */
@media (min-width: 900px) {
  .navrail { display: flex; }
}

@media (max-width: 899px) {
  .tabbar { display: flex; }
  /* Clear the fixed bar, plus the home indicator on iOS. */
  .app { padding-bottom: calc(84px + env(safe-area-inset-bottom)); }
  .footer { padding-bottom: calc(var(--s5) + 68px + env(safe-area-inset-bottom)); }
  .toasts { bottom: calc(74px + env(safe-area-inset-bottom)); }
}

@media (max-width: 560px) {
  /* At 390px the bar spent ~100px on padding and gaps alone before the icon
     buttons, which cannot shrink — they are the WCAG 2.5.8 touch-target floor.
     Reclaiming spacing and dropping the linear XP bar (the gauge already says
     the same thing) clears it down to 320px without hiding a control or
     shrinking a target. */
  .topbar { gap: var(--s2); padding: 10px var(--s3); }
  .app { padding-left: var(--s4); padding-right: var(--s4); }
  .xpbar { display: none; }
  .hud { gap: 10px; }
  .hud .chip.hide-sm { display: none; }
  .grid { grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; }
  .card { padding: var(--s3) var(--s3) var(--s4); }
  .card .emoji { width: 38px; height: 38px; font-size: 1.2rem; }
  .card .card-title { font-size: 0.95rem; }
  .card .card-desc { font-size: var(--t-xs); }
  .bar-row { grid-template-columns: 100px 1fr 42px; gap: var(--s2); }
  .tabs { width: 100%; overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
  .tab { padding: 8px 14px; }
  .form-block { padding: var(--s4); }
  /* Search takes its own row; the two dropdowns share the next one rather than
     stacking into three rows of chrome above the grid they filter. */
  .filter-bar .type-input { flex: 1 1 100%; max-width: none; }
  .filter-bar .select { flex: 1 1 0; min-width: 0; }
  .toasts { left: var(--s3); right: var(--s3); }
  .toast { max-width: none; }
}
