@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500;600;700&display=swap");
@import url("./scrollbars.css");
@import url("./motion.css");

/* Shared chrome for the hub's games.
 *
 * City Limits inlines everything, because a cached page pairing stale markup
 * with fresh code is a bug you only ever see on someone else's machine. That
 * threat is real, but it is a *caching* problem: this server sends
 * `Cache-Control: no-store` on every HTML page and on this file, so the pair
 * can never desynchronise. Fifteen games duplicating this stylesheet would be
 * worse than the risk it avoids. City Limits stays inlined because it is
 * proven and there is nothing to gain by churning it.
 *
 * ---------------------------------------------------------------------------
 * The look is an almanac page, not a game screen.
 *
 * Printed reference works have a grammar the eye already trusts: paper ground,
 * one ink, a rule under the masthead, and figures set in monospace so columns
 * of them line up and read as *record* rather than as decoration. Everything
 * here follows that grammar, and the rule for adding to it is simple:
 *
 *   Inter carries language. IBM Plex Mono carries data.
 *
 * A label, a count, a score, a year, a unit, a coordinate, a countdown, a
 * breadcrumb: mono. A sentence a person reads: Inter. When something could go
 * either way it is data, because that is the half doing the teaching.
 *
 * Green is the only colour with any chroma, and it means "live" or "right".
 * Spending it anywhere else costs it that meaning, so the status colours are
 * held down at atlas saturation and the surfaces are all paper.
 *
 * Square corners throughout, and no shadows. Both are load-bearing rather than
 * taste: a radius and a drop shadow read as a floating control, and every
 * surface here is meant to read as printed onto the page.
 */

:root {
  /* Surfaces. Warm paper, never white: #fff next to these reads as a hole. */
  --bg: #f5f1ec;
  --panel: #f8f5f1;
  --panel-2: #ece7e0;
  --wash: #e5e0da;

  /* Ink. Three weights is all this needs; a fourth always ends up arbitrary. */
  --text: #13110f;
  --muted: #46413e;
  --faint: #6f6862;

  --border: #aaa39d;
  --rule: #13110f;

  /* The one saturated colour. It means live, correct, or yours. */
  --accent: #2e9e5b;        /* green, darkened to pass contrast on paper */
  --accent-bright: #48db7e; /* the signal green, for fills and rules only */
  --accent-ink: #07240f;
  --accent-soft: #e7f7ec;
  --accent-line: #7fc99a;

  /* Status, at atlas saturation so nothing out-shouts the accent. */
  --green: #3f8f5e;
  --lime: #639564;
  --yellow: #a8801f;
  --orange: #bb8e63;
  --red: #ad524d;

  /* The water, and it is the panel's rather than any one game's. Pastel and
     warm-leaning, because it sits under land the colour of paper: a saturated
     blue turns a printed page into a screen, and a grey one is not water. */
  --ocean: #d9e8f1;
  --ocean-swell: #a6c7dc;

  /* Cartographic tints, for anything drawn on a map. */
  --map-blue: #4b81a5;
  --map-green: #639564;
  --map-red: #bb584a;
  --map-tan: #bb8e63;
  --map-yellow: #d3b460;

  --display: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, "Courier New", monospace;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--display);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* No page fade. A tab rendering hidden pauses CSS animations, so anything
 * whose *resting* state is `opacity: 0` renders as a blank page in that
 * harness and reads as a bug for as long as it takes to find. A paper page
 * has nothing to gain from arriving slowly anyway. */

/* ---------- the one-screen shell ----------
 * A round is a thing you read at a glance, so nothing it tells you may sit
 * below the fold. The page is therefore exactly one viewport tall and the
 * board absorbs the slack: prompt, input, tally and buttons keep their natural
 * heights, and the map or pad flexes into whatever is left.
 *
 * `min-height: 0` on every flex ancestor is the load-bearing half. A flex item
 * defaults to `min-height: auto`, which means "never shrink below my content",
 * so without it a tall SVG silently pushes the page open again and the
 * `flex: 1` above it does nothing at all.
 *
 * `main` keeps `overflow: auto` deliberately. It is a safety valve, not a
 * scroller: at any sane window it never triggers, but on a 400px-tall window
 * something has to give, and scrolling is honest where clipping would just
 * hide information and look like a bug. */
#app { display: flex; flex-direction: column; height: 100dvh; overflow: hidden; }

button { font-family: inherit; transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease, opacity 0.18s ease; }
button:focus-visible,
a:focus-visible,
input[type="range"]:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

/* The mono label that runs through everything: section heads, kickers, chips,
 * breadcrumbs, footers. Uppercase and letterspaced so it reads as a caption
 * and never competes with the sentence under it. */
.label,
.prompt .kicker,
#crumbs,
.chip,
.tally {
  font-family: var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ---------- breadcrumb strip ---------- */
#crumbs {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 20px;
  font-size: 10px;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
#crumbs a { color: var(--muted); text-decoration: none; }
#crumbs a, #crumbs .sep { flex-shrink: 0; }
#crumbs .here { min-width: 0; }
#crumbs a:hover { color: var(--text); text-decoration: underline; }
#crumbs .sep { opacity: 0.55; }
#crumbs .here { color: var(--text); }

/* ---------- a link out to the encyclopaedia ---------- */
/* Ink rather than the accent: green means live, correct or yours, and a link
   is none of the three. The rule underneath is drawn in the border colour so
   that a line of them reads as text with somewhere to go rather than as a line
   of emphasis, and it inks up under the pointer. */
a.wiki {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
a.wiki:hover,
a.wiki:focus-visible { text-decoration-color: currentColor; }

/* ---------- a scrollbar the page drew itself ---------- */
/* The same argument as Minefield's set picker: the browser's own bar is a
   rounded overlay in the operating system's colours, sitting on a page whose
   whole premise is that it is printed. A ruled column in the wash with a
   square ink thumb in it, holding its place in the layout rather than fading
   in and out, because on a strip two rows tall the bar is the only thing
   saying there is more.
 
   The order matters and is not a matter of taste. Chrome from 121 onwards
   drops every `::-webkit-scrollbar` rule on an element that also sets
   `scrollbar-width` or `scrollbar-color`, so declaring both plainly is
   declaring the standard one: the first version of this did, and drew the
   default rounded bar in slightly different colours. The standard properties
   are therefore behind a support query that only Firefox, which has no
   `::-webkit-scrollbar` at all, can answer. */
.paper-scroll::-webkit-scrollbar { width: 9px; height: 9px; }
.paper-scroll::-webkit-scrollbar-track,
.paper-scroll::-webkit-scrollbar-corner {
  background: var(--wash);
  border-left: 1px solid var(--border);
}
/* The thumb is drawn in the colour every rule and frame on the page is drawn
   in, and inks up under the pointer -- the same register as the links. */
.paper-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 0; }
.paper-scroll::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.paper-scroll::-webkit-scrollbar-button { display: none; }
@supports not selector(::-webkit-scrollbar) {
  .paper-scroll { scrollbar-width: thin; scrollbar-color: var(--border) var(--wash); }
}

/* ---------- masthead ---------- */
#topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  padding: 22px 20px 10px;
  border-bottom: 2px solid var(--rule);
  flex-wrap: wrap;
}
/* The back arrow lives in the breadcrumb now. This stays for pages that still
 * render one inline, and is styled to disappear into the strip. */
.back {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  text-decoration: none;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.back:hover { color: var(--text); }

#topbar h1 {
  font-family: var(--display);
  font-size: 21px;
  font-weight: 800;
  margin: 0;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.round-chip {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.what.icon-btn, .metric-info.icon-btn, .warp-explain.icon-btn {
  width: 30px; height: 30px; display: inline-flex;
  padding: 0; border: 1px solid var(--border); border-radius: 0;
  background: transparent; color: var(--muted);
  font: 500 13px/1 var(--mono); text-decoration: none;
  align-items: center; justify-content: center;
}
.icon-btn {
  width: 30px; height: 30px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  font-family: var(--mono);
  font-weight: 500;
  font-size: 12px;
  padding: 0;
}
.icon-btn:hover { color: var(--text); border-color: var(--text); }
.icon-btn.muted { opacity: 0.55; }

/* The rules live behind the "?" now, so for the first ten seconds it says so.
   Ink, not green: green means live, correct or yours, and a hint is none of
   the three. The resting look is the whole message and the pulse is only
   decoration, so a throttled or hidden tab shows a button that reads as
   emphasised rather than one that reads as broken. */
.icon-btn.hint { color: var(--text); border-color: var(--text); }
@media (prefers-reduced-motion: no-preference) {
  .icon-btn.hint { animation: hint-ring 1.7s ease-out infinite; }
}
@keyframes hint-ring {
  0%   { box-shadow: 0 0 0 0 rgba(19, 17, 15, 0.28); }
  70%  { box-shadow: 0 0 0 6px rgba(19, 17, 15, 0); }
  100% { box-shadow: 0 0 0 0 rgba(19, 17, 15, 0); }
}

/* ---------- sound popover ---------- */
#settings-ctl { position: relative; }
#settings-pop {
  position: absolute;
  top: 38px; right: 0;
  z-index: 1200;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 0;
  padding: 16px;
  width: 212px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
#settings-pop label {
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
#settings-pop input[type="range"] { width: 100%; accent-color: var(--accent); height: 3px; }
#mute-btn, #wiki-btn {
  padding: 9px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}
#mute-btn:hover, #wiki-btn:hover { color: var(--text); border-color: var(--text); }
/* The switch is not a sound control, so it stands slightly apart from them. */
#wiki-btn { margin-top: 4px; }

/* Wikipedia links, off. The anchors stay where they are and stop being links:
   no colour, no underline, no pointer, and kit.js refuses the click as well,
   because a link is followable from the keyboard too. */
.no-wiki a.wiki {
  color: inherit;
  text-decoration: none;
  pointer-events: none;
  cursor: inherit;
}

/* ---------- layout ---------- */
main {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  padding: 20px 20px 22px;
}
.stage {
  width: 100%;
  max-width: 620px;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* The map games. A world map is a wide thing and 980px was leaving the sides
   of a 1400px window empty, so these get the width and the masthead widens with
   them to stay aligned. */
.stage.wide { max-width: 1320px; }

/* Where the sprints hang their map. The board inside takes the slack. */
#prompt-extra {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 1 auto;
  min-height: 0;
}

/* ---------- the prompt ---------- */
/* Left-aligned, like a page. Centred text reads as a title card, and the
 * whole point is that this looks like something you are reading. */
.prompt {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.prompt .kicker {
  font-size: 10px;
  color: var(--muted);
}
.prompt h2 {
  font-family: var(--display);
  font-size: 34px;
  font-weight: 700;
  margin: 0;
  letter-spacing: -0.025em;
  line-height: 1.15;
}
.prompt .note {
  margin: 2px 0 0;
  font-size: 14px;
  color: var(--muted);
  line-height: 1.45;
}

/* ---------- panels ---------- */
.card-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 20px;
}

/* ---------- inputs ---------- */
.answer-box { position: relative; }
input[type="text"] {
  width: 100%;
  padding: 13px 14px;
  font-size: 16px;   /* >=16px stops iOS zoom-on-focus */
  font-family: var(--display);
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 0;
  outline: none;
}
input[type="text"]:focus { border-color: var(--text); box-shadow: inset 0 -2px 0 var(--accent-bright); }
input[type="text"]::placeholder { color: var(--faint); }

.suggestions {
  position: absolute;
  left: 0; right: 0; top: calc(100% - 1px);
  z-index: 900;
  background: var(--panel);
  border: 1px solid var(--text);
  border-radius: 0;
  max-height: 260px;
  overflow-y: auto;
}
.suggestion {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
  font-size: 14px;
  border-bottom: 1px solid var(--wash);
}
.suggestion:last-child { border-bottom: 0; }
.suggestion .sub { font-family: var(--mono); color: var(--muted); font-size: 11px; }
.suggestion.active, .suggestion:hover { background: var(--wash); }

/* ---------- buttons ---------- */
/* Mono and uppercase, because every button here is an instruction rather than
 * a label. Padding is set for a 44px touch target at the default font size;
 * check it at 375px before trimming it. */
.btn {
  background: var(--accent-soft);
  color: var(--text);
  border: 2px solid var(--accent-bright);
  border-radius: 0;
  padding: 12px 18px;
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}
.btn:hover { background: #d8f2e1; }
.btn.wide { width: 100%; padding: 15px 18px; }
.btn.ghost {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
}
.btn.ghost:hover { background: var(--wash); border-color: var(--text); }
.btn.quiet {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
}
.btn.quiet:hover { color: var(--text); border-color: var(--text); }
.btn:disabled { opacity: 0.38; cursor: default; }
.row { display: flex; gap: 10px; flex-wrap: wrap; }
.row.end { justify-content: flex-end; }
.row.center { justify-content: center; }

/* ---------- what the answer box says back ----------
 * Two states, and they are different facts, so they are different colours.
 * `dupe` is green: you were right, you just already had it. `wrong` is red: a
 * definite miss. Both were being redefined per game, which is how three of them
 * ended up with the green one and none with the red. */
.answer-box input.dupe { border-color: rgba(46, 158, 91, 0.55); }
.answer-box input.wrong { border-color: var(--red); }

/* ---------- answer chips ---------- */
/* The answers you have already given are a log, not the question, so this is
 * the one region allowed to run out of room. It takes what is left over and
 * scrolls inside itself; everything that tells you what to do next stays put
 * above and below it. */
/* Two rows, always, and a scrollbar only once there are more than two rows to
 * scroll. It used to be `flex: 0 1 auto` against a flexing board, which meant
 * it was squeezed to nothing and scrolling from the very first answer: a
 * scrollbar on two chips reads as broken.
 *
 * The height is fixed rather than grown so the board above it does not resize
 * every time a row of answers fills up. */
.chips {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 6px;
  flex: 0 0 auto;
  height: 52px;              /* two 23px rows and the gap between them */
  overflow-y: auto;
  overscroll-behavior: contain;
}
.chip {
  padding: 4px 9px;
  border-radius: 0;
  font-size: 10px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
}
.chip.got {
  color: var(--accent-ink);
  border-color: var(--accent-line);
  background: var(--accent-soft);
}
.chip.missed { color: var(--red); border-color: rgba(173, 82, 77, 0.45); background: transparent; }
/* Transform only, never opacity. A paused animation holds its first keyframe,
 * and a tab that renders hidden pauses every one of them, so `from { opacity: 0 }`
 * means the resting state of this element is invisible. The chip arrives by
 * sliding, and an unplayed slide is still a legible chip. */
@keyframes chip-in { from { transform: translateY(4px); } to { transform: none; } }
.chip.fresh { animation: chip-in 0.28s cubic-bezier(0.22, 0.61, 0.36, 1); }

/* ---------- meters ---------- */
/* `display: block` on the fill is load-bearing, not tidiness. A <span> in a
 * non-flex parent stays inline, `width` does not apply to inline boxes, and
 * the bar renders at 0px while its style attribute cheerfully reads `2%`.
 * That has shipped three times here. Measure it with getBoundingClientRect. */
.meter {
  height: 4px;
  border-radius: 0;
  background: var(--wash);
  overflow: hidden;
}
.meter > span {
  display: block;
  height: 100%;
  border-radius: 0;
  background: var(--text);
  transition: width 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.tally {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 10px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ---------- modal ---------- */
.backdrop {
  position: fixed;
  inset: 0;
  background: rgba(19, 17, 15, 0.28);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  padding: 20px;
  overflow-y: auto;
}
@keyframes modal-in { from { transform: translateY(6px); } to { transform: none; } }
.modal {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 0;
  padding: 26px 28px;
  max-width: 480px;
  width: 100%;
  animation: modal-in 0.28s ease;
  max-height: 88vh;
  overflow-y: auto;
}
.modal h2 {
  margin: 0 0 10px;
  font-family: var(--display);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.025em;
}
.modal p { margin: 6px 0; color: var(--muted); font-size: 14px; line-height: 1.55; }
.modal .headline {
  color: var(--text);
  font-family: var(--mono);
  font-size: 30px;
  font-weight: 600;
  margin: 8px 0 4px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.modal ul { margin: 10px 0 0; padding-left: 18px; color: var(--muted); font-size: 13.5px; line-height: 1.7; }
.modal li { margin-bottom: 4px; }
.modal-actions { margin-top: 22px; display: flex; justify-content: flex-end; gap: 10px; flex-wrap: wrap; }

.scorebits {
  display: flex;
  gap: 22px;
  margin: 16px 0 2px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.scorebit b {
  display: block;
  font-family: var(--mono);
  font-size: 21px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.scorebit span {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---------- the map panel ---------- */
/* Every map in the app is this element, so that they are the same size, the
 * same shape and the same weight on the page. Before it, each game styled its
 * own and no two agreed. */
.map-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 8px;
  overflow: hidden;
  position: relative;
  /* Takes whatever height is left after the fixed rows, which is what keeps a
   * round on one screen. `min-height: 0` is what lets it actually shrink. */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}
/* The slot the panel replaces, shaped like the panel from the first frame.
 * Three games fetch megabytes before they can draw anything -- Population Hunt
 * reads four and a half of them -- and until the panel arrived the page laid
 * itself out around an empty div: the answer box and the score sat up under
 * the heading, and the whole column jumped down when the map appeared. Half a
 * second of a page that is not the page you clicked. */
#map-slot {
  flex: 1 1 auto;
  min-height: 0;
  background: var(--panel);
  border: 1px solid var(--border);
}

.map-panel.can-pan { cursor: grab; touch-action: none; }
.map-panel.can-pan.dragging { cursor: grabbing; }
.map-panel-svg { display: block; width: 100%; height: 100%; min-height: 0; }

/* The swell. Two bands of long shallow curves drifting past each other, built
   once in their own units and placed by one transform per frame (see seaLayer
   in kit.js). A period is 40 of those units however far the camera has come
   in, so the drift is a fixed translate and the rows meet themselves exactly.

   Decoration, and it declares itself as such: the resting state is a correct
   sea and the animation only slides it sideways, so a hidden tab, a throttled
   one and prefers-reduced-motion all leave water on the map. It takes no
   pointer, because two of these maps are clicked and open water is an answer
   there. */
.sea { pointer-events: none; }
/* The water is a shape, not a background: on the world projection it stops at
   the edge of the globe, because paper outside that oval is off the map rather
   than sea. See seaLayer's `outline` in kit.js. */
.sea .ocean { fill: var(--ocean); stroke: none; }
.sea .swell { stroke: var(--ocean-swell); stroke-linecap: round; fill: none; }
.sea .far { opacity: 0.34; animation: sea-drift-far 24s linear infinite; }
.sea .near { opacity: 0.5; animation: sea-drift-near 15s linear infinite; }
@keyframes sea-drift-far { to { transform: translateX(40px); } }
@keyframes sea-drift-near { to { transform: translateX(-40px); } }
@media (prefers-reduced-motion: reduce) { .sea .swell { animation: none; } }

.map-btns {
  position: absolute;
  left: 8px; top: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.map-btns button {
  width: 26px; height: 26px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.map-btns button:hover { border-color: var(--text); }
.map-hint {
  position: absolute;
  right: 8px; bottom: 6px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  pointer-events: none;
  transition: opacity 0.5s ease 0.4s;
}
/* An instruction that has been followed is clutter. It goes once the player has
   zoomed or panned even once, and stays gone for the rest of the round. */
.map-panel.used .map-hint { opacity: 0; }

/* The tooltip a map hands back when you point at something on it. Built by
   K.mapPanel's `hover`, positioned in panel coordinates and clamped to it. */
.map-tip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  background: var(--panel);
  border: 1px solid var(--text);
  padding: 6px 9px;
  /* Position must not affect size. An auto-width absolute box shrinks to the
     space after `left`, even though translate(-50%) centers it afterward.
     That caused edge clamping and text wrapping to feed back into each other. */
  width: max-content;
  max-width: min(240px, calc(100% - 16px));
  white-space: normal;
  overflow-wrap: anywhere;
  transform: translate(-50%, -100%);
  font-size: 13px;
  color: var(--text);
  line-height: 1.35;
}
.map-tip[hidden] { display: none !important; }
.map-tip b { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.map-tip .sub {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.map-tip .num {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
@media (pointer: coarse) {
  .map-btns button { width: 34px; height: 34px; }
  .map-hint { display: none; }
}

/* A live per-continent readout, right-aligned under the board. What is still
 * dark on the map, as numbers, for the games where "how much of the world" is
 * the actual question. */
.continents {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px 12px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  flex: none;
}
.continents span { font-variant-numeric: tabular-nums; }
.continents b { color: var(--text); font-weight: 500; }
.continents .full b { color: var(--accent); }

/* ---------- the briefing ---------- */
/* The rules, opened from the "?". A game no longer opens on this: clicking a
 * game gets you the game, and the "?" glows for ten seconds so the rules are
 * findable without being in the way.
 *
 * It is still a page rather than a dialog. It sits in the flow under the real
 * masthead, so the page you read the rules on is recognisably the same page
 * you play on, and it replaces the round rather than floating over it -- a
 * dialog over a live board is exactly what this site does not do. The game's
 * own `main` is hidden by a class on <body> instead of by anything here,
 * because the chrome is built before the game has created `main` and a rule
 * that waits for it cannot miss.
 *
 * The clock stops while it is open, in both games that own one. That was the
 * original reason for gating the round behind it, and it is handled properly
 * now rather than avoided. */
body.briefing main { display: none !important; }

#briefing {
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  padding: 30px 20px 56px;
}
#briefing .sheet {
  width: 100%;
  max-width: 460px;
  margin: 0 auto;   /* same centred measure the stage uses */
  display: flex;
  flex-direction: column;
  gap: 16px;
}
#briefing .kicker {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
#briefing h2 {
  font-family: var(--display);
  font-size: 36px;
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin: 0;
}
#briefing .body { color: var(--muted); font-size: 14px; line-height: 1.55; }
#briefing .body p { margin: 0 0 12px; }
#briefing .body p:last-child { margin-bottom: 0; }
/* The opening line is the whole game in a sentence, so it is set as a lede. */
#briefing .body > p:first-child {
  font-size: 16.5px;
  line-height: 1.45;
  color: var(--text);
}
#briefing .body strong { color: var(--text); font-weight: 600; }
/* Where the figures came from. A colophon rather than a rule: it is set in
   mono under a rule of its own, so it reads as the imprint at the foot of a
   page and never as an eighth thing the player has to learn. */
#briefing .body .source {
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 10.5px;
  line-height: 1.7;
  letter-spacing: 0.02em;
  color: var(--faint);
}
#briefing .body .source b { color: var(--muted); font-weight: 500; }
#briefing .body kbd {
  font-family: var(--mono);
  font-size: 11px;
  border: 1px solid var(--border);
  padding: 1px 5px;
  color: var(--text);
}

/* The rules, numbered. This is the teaching surface, and numbering it is what
 * turns a list of tips into something that reads as a specification: countable,
 * referable, finite. A bulleted list says "some notes"; 01..07 says "this is
 * all of it", which is the difference the whole design is chasing. */
#briefing .body ul {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: rule;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
#briefing .body li {
  counter-increment: rule;
  position: relative;
  padding-left: 32px;
  font-size: 13.5px;
  line-height: 1.5;
}
#briefing .body li::before {
  content: counter(rule, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 2px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--accent);
}

/* Provenance and shape of the round, as a strip of figures. Optional: a
 * half-filled strip reads worse than none. */
#briefing .meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: 11px 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
#briefing .meta b { color: var(--text); font-weight: 500; }
#briefing .btn { margin-top: 2px; }

@media (max-width: 640px) {
  #crumbs { padding: 7px 14px; }
  #topbar { padding: 16px 14px 9px; gap: 10px; row-gap: 6px; }
  #topbar h1 { font-size: 17px; }
  main { padding: 16px 14px 16px; }
  .prompt h2 { font-size: 26px; }
  #briefing h2 { font-size: 29px; }
  .btn { padding: 13px 16px; }
  .scorebits { gap: 16px; }
}

/* Short windows. A laptop with a browser's chrome on it is routinely under
 * 700px of viewport, and the games whose content is fixed-height rather than a
 * flexing board (Sort It's five rows, Flag Sprint's card) have nothing to give
 * unless the furniture around them gets smaller. This is the tier that keeps
 * them on one screen. */
@media (max-height: 720px) {
  main { padding: 12px 20px 14px; }
  .stage { gap: 10px; }
  .prompt { gap: 3px; }
  .prompt h2 { font-size: 26px; }
  .prompt .note { font-size: 13px; }
  #topbar { padding: 12px 20px 8px; }
  #topbar h1 { font-size: 18px; }
  .btn { padding: 10px 16px; }
}
@media (max-height: 600px) {
  .prompt h2 { font-size: 21px; }
  .stage { gap: 8px; }
  .chips { height: 29px; }   /* one row, on a window with nothing to spare */
}

/* A finger is not a cursor. Everything a round depends on already clears 44px,
   but the chrome did not: the breadcrumb was a 15px-tall hit and the icon
   buttons were 30px square. Measured at 375px, which is the width that finds
   these. */
@media (pointer: coarse) {
  #crumbs { padding: 4px 14px; }
  #crumbs a { padding: 11px 2px; }
  #crumbs .sep, #crumbs .here { padding: 11px 0; }
  .icon-btn { width: 40px; height: 40px; }
  .back { padding: 10px 2px; }
}

@media (prefers-reduced-motion: reduce) {
  .modal, .chip.fresh { animation: none; }
  .meter > span { transition: none; }
}

/* Information occupies the available screen. Long rules use measured pages. */
/* This comes after both the short-window and touch rules so compact phones
   keep their board space without losing keyboard or touch controls. */
@media (max-width: 640px) and (max-height: 740px) {
  #topbar { padding:6px 12px; gap:8px; }
  #crumbs { padding:0 12px; }
  main { padding:8px 12px; }
  .stage { gap:7px; }
  .prompt h2 { font-size:22px; }
  .prompt .note { font-size:12px; }
  .row { gap:6px; }
  .row > .btn { padding:10px; font-size:12px; }
  .tally { font-size:9px; gap:8px; }
}

#briefing { flex: 1; min-height: 0; overflow: hidden; padding: 16px 20px; }
#briefing .sheet { height: 100%; max-width: 720px; gap: 10px; }
#briefing h2 { font-size: 28px; }
#briefing .body.info-content, .info-content {
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  overflow-wrap: anywhere;
}
#briefing .body > p:first-child { font-size: 14px; }
#briefing .body h3 { font-size: 15px; margin: 10px 0 6px; }
#briefing .body ul { margin: 0 0 8px; }
#briefing .body .source { margin-top: 10px; padding-top: 8px; }
#briefing .meta { padding: 5px 0; }
.info-pages { display: flex; align-items: center; justify-content: center; gap: 16px; flex: none; }
.info-pages span { font: 11px var(--mono); color: var(--muted); }
.info-pages button { width: 38px; height: 30px; border: 1px solid var(--border); background: var(--panel); color: var(--text); cursor: pointer; }
.info-pages button:disabled { opacity: .35; cursor: default; }
.info-pages button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.info-modal { display: flex; flex-direction: column; gap: 10px; height: min(600px, calc(100dvh - 32px)); max-height: calc(100dvh - 32px); max-width: 640px; overflow: hidden; padding: 18px 22px; }
.info-modal h2 { font-size: 24px; margin: 0; flex: none; }
.info-modal .modal-actions { margin-top: 0; flex: none; }
@media (max-height: 500px) {
  #briefing { padding: 8px 16px; }
  #briefing .kicker, #briefing .meta { display: none; }
  #briefing h2 { font-size: 21px; }
  .info-modal { padding: 12px 18px; gap: 6px; }
}
