/* Memedle — a sunlit paper tabletop where the memecoin world is built out of
   lime-green toy blocks. Read DESIGN.md before editing: a literal hex or px in
   a component rule is a bug. Hand-rolled — no framework, no build step. */

:root {
  /* ── the neutral ramp, Ink -> Paper ── */
  --ink: #16161A;    /* Ink        — every die line, every hard shadow, body text */
  --ink-2: #4A4A52;  /* Slate      — secondary text */
  --ink-3: #84848F;  /* Fog        — placeholder, meta, footer */
  --ash: #C9C8C2;    /* Ash        — inert fills only, never text */
  --line: #E4E2D9;   /* Rule       — the hairline on a calm card */
  --card-2: #F2F0E8; /* Shelf      — a recessed surface inside a card */
  --card: #FFFFFF;   /* Card       — the default card face */
  --paper: #FEFDF7;  /* Paper      — the page itself */
  --paper-2: #EAE7DC;/* Paper Deep — the band under the fold */

  /* ── the accent. Exactly one, spent once per view. ── */
  --lime: #BCF23F;
  --lime-2: #A6DE23;
  --lime-3: #EDFBC4;

  /* ── status. Tiles, flags, verdicts. Never a button fill. ── */
  --hit: #8BD418;      --hit-ink: #16161A;
  --near: #FFC53D;     --near-ink: #3A2A00;
  --miss: #FB6F84;     --miss-ink: #3A0C14;

  /* ── scenery ──
     The landscape is now the supplied plate (img/bg.webp), not drawn in code,
     so there is no scenery ramp to name. --paper carries the plate's own field
     colour and is written by tools/use-bg.mjs — see the world section. The one
     green the UI still needs is --lime, and that is an accent, not scenery. */

  /* ── two families. That is the whole list. ── */
  /* Figtree carries every digit: it has real tabular figures, so a counter
     does not jiggle as it ticks. Baloo 2's digits are proportional and are
     allowed only where the number never changes in place. */
  --font-display: "Baloo 2", "Segoe UI", system-ui, sans-serif;
  --font-ui: "Figtree", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* ── three radii. That is the whole list. ── */
  --r-1: 12px;     /* controls: buttons, inputs, tiles, chips, logo plates */
  --r-2: 20px;     /* cards: panels, modals, the tray */
  --r-pill: 999px;

  /* ── two die widths ── */
  --cut: 2px;      /* the outline on a pressable object */
  --cut-in: 1.5px; /* a line printed inside a card */

  /* ── one elevation philosophy, two tiers, and the tier is the affordance.
        Calm rests on the paper. Live is a token you can pick up. Both have
        zero blur: a soft shadow anywhere in the UI is a bug. ── */
  --lift: 4px;
  --rise: 2px;
  --shadow: 0 var(--lift) 0 var(--ink);
  --shadow-soft: 0 2px 0 var(--line);

  /* ── the curve vocabulary. Three, never hand-rolled, never ease-in. ── */
  --ease-out: cubic-bezier(.23, 1, .32, 1);
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);
  --ease-pop: cubic-bezier(.2, .9, .3, 1.35);

  /* ── durations. Down is faster than up; leaving is faster than arriving. ── */
  --dur-press: 90ms;
  --dur-tint: 130ms;
  --dur-close: 160ms;
  --dur-pop: 220ms;
  --dur-flip: 420ms;
  --hold-flash: 1800ms;

  /* the press contract: down flat and fast, back with energy. box-shadow rides
     --ease-out so the printed shadow never renders deeper than --lift while
     the body springs past it. */
  --press:
    transform var(--dur-pop) var(--ease-pop),
    box-shadow var(--dur-pop) var(--ease-out),
    background-color var(--dur-tint) ease,
    border-color var(--dur-tint) ease,
    color var(--dur-tint) ease;
  --press-in:
    transform var(--dur-press) var(--ease-out),
    box-shadow var(--dur-press) var(--ease-out),
    background-color var(--dur-tint) ease,
    border-color var(--dur-tint) ease,
    color var(--dur-tint) ease;

  /* Layout numbers, all fluid. Fixed rails look right at exactly one width:
     on a 2560px screen a 1340px page is an island, and at 1200px the same
     rails crush the board. clamp() lets one set of numbers cover both. */
  /* The CENTRE column is the budget; the rails and the plate get what is left.
     An earlier version sized the page at 70vw so the cards would clear the
     plate's characters, and it starved the board: at 1280px, 20 of 23 clue
     tiles were truncated to 29px ("Eth…", "20…"). A readable board beats
     uncovered background art — the cards are opaque and sit ON the plate,
     which is how a tabletop reads anyway. The rails are narrower for the same
     reason: they hold reference material, the board holds the game. */
  --page: clamp(960px, 84vw, 1480px);
  --rail: clamp(214px, 16.5vw, 300px);
  --rail-2: clamp(228px, 17.5vw, 318px);
  --gap: clamp(8px, 0.8vw, 14px);
  --pad: clamp(14px, 1.2vw, 20px);
  --section: clamp(18px, 1.9vw, 32px);
}

/* colourblind play swaps only the two status hues; Lime and Near are untouched */
body.cb {
  --hit: #3B7FD4; --hit-ink: #FFFFFF;
  --miss: #E07B18; --miss-ink: #2A1400;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-ui);
  /* Fluid, not fixed: 15px on a 2560px monitor is a different reading distance
     from 15px on a laptop, and a fixed body size is what makes a big screen
     look like a small screen with margins. */
  font-size: clamp(14px, 0.26vw + 12.6px, 18px);
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  overflow-x: hidden;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

button, input { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
.hidden { display: none !important; }
:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; border-radius: 4px; }

/* ═══════════════════ the world ═══════════════════ */
/* The supplied plate (img/bg.webp), used as the artwork rather than redrawn.
   Fixed and behind everything: it is the table the cards lie on, and a table
   that slides away is parallax, which this system does not have.

   ONE sizing rule at every viewport: full width, anchored to the bottom.
   `cover` was tried and is wrong here — above the plate's 16:9 it scales to
   fill the height and crops the SIDES, and the sides are the two character
   clusters, so it throws away the part of the picture worth having. At
   `100% auto` nothing is ever cropped horizontally, the plate's proportions
   are identical at every width, and the cream gap the cards sit in stays a
   fixed percentage of the viewport — which is what makes --page's clamp
   predictable instead of a guess.

   When the viewport is taller than the plate, the bare area above it is
   --paper, which tools/use-bg.mjs set to the plate's OWN field colour
   (#FEFDF7, sampled, not eyeballed). Hand-editing --paper puts a seam across
   the full width wherever the image stops. */
.world {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: url("img/bg.webp") no-repeat center bottom / 100% auto;
}

#confetti {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 60;
}


/* ═══════════════════ shell ═══════════════════ */
/* Vertically centred, SAFELY. The block used to start 22px from the top at
   every size, which left 440px+ of dead paper under it on a 2560x1440 screen,
   and a 320-420px bottom padding (sized for mascots that no longer exist) made
   every viewport scroll even when the content fitted.

   Centred with AUTO MARGINS on the first and last child, not with
   justify-content: center. On a column taller than the viewport, centre
   overflows equally in both directions and pushes the top of the page above
   the scroll origin, where it can never be scrolled back into view. An auto
   margin just collapses to zero, so a short screen starts at the top and
   scrolls normally, and a tall one centres.

   The bottom padding is deliberately larger than the top: the lower third of
   the viewport is the plate's green and its cast, so the geometric centre
   reads as low. This puts the block at the optical centre instead. */
.app {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--page);
  margin: 0 auto;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding:
    clamp(24px, 5vh, 64px)
    clamp(14px, 3vw, 28px)
    clamp(36px, 9vh, 128px);
}
.app > .topbar { margin-top: auto; }
.app > .more { margin-bottom: auto; }

/* ═══════════════════ topbar + wordmark ═══════════════════ */
.topbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--gap);
  min-height: 56px;
}
.topbar-side { display: flex; align-items: center; gap: 8px; }
.topbar-side.right { justify-content: flex-end; }

.ico-btn {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  background: var(--card);
  border: var(--cut) solid var(--ink);
  border-radius: var(--r-1);
  box-shadow: var(--shadow);
  transition: var(--press);
}
.ico-btn .ico { font-size: 18px; font-weight: 800; line-height: 1; }
.ico-btn[aria-pressed="true"] { background: var(--lime); }

/* the wordmark: Ink type over a Lime Deep copy offset behind it. The offset is
   a real element, not a text-shadow, so motion can move it on its own. */
/* The supplied lockup: the character grid beside a plain black wordmark.
   The Lime offset shadow and the sparkle were this site's own invention before
   there was a real logo; the brand has neither, so they are gone.

   Proportion is taken from the banner, then tightened for a header: there the
   grid stands ~2.3x the height of the word's ascenders, which in a top bar
   would push the whole centred layout down a full row. 1.55em keeps it
   unmistakably the same mark at a header's size. */
.brand {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .16em;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(40px, 5.4vw, 66px);
  line-height: 1;
  letter-spacing: -.035em;
  text-transform: lowercase;
  white-space: nowrap;
  color: var(--ink);
  user-select: none;
}
.brand .b-mark {
  display: block;
  height: 1.55em;
  width: auto;
  flex: none;
  cursor: pointer;
  will-change: transform;
}
/* Baloo 2 sits high in its own line box; this lands the word's optical centre
   on the grid's centre, which is how the banner aligns them. */
.brand .b-face { position: relative; top: .06em; }

.tagline {
  text-align: center;
  color: var(--ink-2);
  font-size: 15px;
  margin: 8px 0 var(--section);
}

.streak-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 13px;
  border-radius: var(--r-pill);
  background: var(--lime);
  border: var(--cut) solid var(--ink);
  font-weight: 800;
  font-size: 13px;
  box-shadow: var(--shadow);
}

/* ═══════════════════ 3-column layout ═══════════════════ */
.layout {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr) var(--rail-2);
  gap: var(--section);
  align-items: start;
}
.col { display: flex; flex-direction: column; gap: var(--gap); min-width: 0; }

/* ═══════════════════ the panel system ═══════════════════ */
/* Calm tier: a white card resting on the paper. Hairline border, a zero-blur
   shadow barely darker than the border. It does not invite a click. */
.panel {
  background: var(--card);
  border: var(--cut-in) solid var(--line);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-soft);
  overflow: hidden;
}
.panel-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  padding: 14px var(--pad);
  border-bottom: var(--cut-in) solid var(--line);
}
.panel-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink);
}
.panel-badge {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .06em;
  color: var(--ink-3);
}
.panel-body { padding: var(--pad); display: flex; flex-direction: column; gap: 14px; }

/* ═══════════════════ mode rail ═══════════════════ */
.mode-list {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  /* The blurb's fit depends on the CARD's width, not the viewport's: the rail
     is 214px at both 1100 (two columns) and 1280 (three), and a viewport media
     query cannot tell those apart. A container query answers the real question. */
  container-type: inline-size;
}
/* Below ~236px the text column is under 95px and even two lines cannot hold
   "One line, name blacked out." A subtitle cut off mid-word reads as a bug;
   the mode name and its logo already say what the mode is. */
@container (max-width: 235px) {
  /* two classes, not one: .mode-blurb sets display:-webkit-box LATER in this
     file at equal specificity, so a single-class rule here silently lost the
     cascade and the query looked like it did nothing */
  .mode-card .mode-blurb { display: none; }
}

/* Calm when idle. Live and Lime-filled when active — the one place the rail
   spends the accent. */
.mode-card {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  padding: 14px;
  background: var(--card);
  border: var(--cut-in) solid var(--line);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-soft);
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  transition: var(--press);
}
.mode-card.on {
  background: var(--lime);
  border: var(--cut) solid var(--ink);
  box-shadow: var(--shadow);
}
.mode-ico {
  width: 46px;
  height: 46px;
  border-radius: var(--r-1);
  border: var(--cut) solid var(--ink);
  object-fit: cover;
  background: var(--card);
  flex: none;
}
.mode-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mode-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 17px;
  line-height: 1.1;
  text-transform: uppercase;
}
.mode-blurb {
  font-size: 13px;
  line-height: 1.3;
  color: var(--ink-2);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.mode-card.on .mode-blurb { color: var(--ink); opacity: .72; }

.mode-flag {
  flex: none;
  min-width: 42px;
  text-align: center;
  padding: 4px 9px;
  border-radius: var(--r-pill);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  font-size: 12px;
  font-weight: 800;
}
.mode-card.on .mode-flag { background: var(--card); border-color: var(--ink); }
.mode-flag.win { background: var(--hit); color: var(--hit-ink); border-color: var(--ink); }
.mode-flag.lost { background: var(--miss); color: var(--miss-ink); border-color: var(--ink); }

/* the progress rail runs along the bottom edge of the card, inside the radius */
.mode-prog {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  background: var(--card-2);
}
.mode-card.on .mode-prog { background: rgba(22, 22, 26, .14); }
.mode-prog i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--ink);
  transition: transform var(--dur-pop) var(--ease-out);
}

/* ═══════════════════ stage ═══════════════════ */
.stage {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 190px;
  padding: 18px;
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  border-radius: var(--r-1);
  overflow: hidden;
}
/* the burst behind the mystery coin — a lime sunburst, drawn as a conic ramp */
.stage.burst::before {
  content: "";
  position: absolute;
  inset: -40%;
  background: repeating-conic-gradient(var(--lime-3) 0 7deg, transparent 7deg 14deg);
  opacity: .9;
}
.stage > * { position: relative; }
.mystery {
  width: 108px;
  height: 108px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--card);
  border: 5px solid var(--ink);
  font-family: var(--font-display);
  font-size: 46px;
  font-weight: 800;
  color: var(--ink);
}
.stage-cap {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.blur-frame {
  width: 150px;
  height: 150px;
  border-radius: var(--r-1);
  border: var(--cut) solid var(--ink);
  background: var(--card);
  overflow: hidden;
}
.blur-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: filter var(--dur-flip) var(--ease-out), transform var(--dur-flip) var(--ease-out);
}
.blur-note {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.lore-card {
  position: relative;
  width: 100%;
  padding: 6px 10px 6px 34px;
}
.lore-mark {
  position: absolute;
  left: 0; top: -12px;
  font-family: var(--font-display);
  font-size: 54px;
  font-weight: 800;
  line-height: 1;
  color: var(--ash);
}
.lore-quote { font-size: 16px; line-height: 1.55; }
.redacted {
  background: var(--ink);
  color: transparent;
  border-radius: 3px;
  padding: 0 2px;
  user-select: none;
}

.game-meta { font-size: 13px; color: var(--ink-3); text-align: center; }

/* ═══════════════════ input ═══════════════════ */
.input-wrap { position: relative; display: flex; gap: 10px; }
#guess-input {
  flex: 1;
  min-width: 0;
  height: 52px;
  padding: 0 20px;
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  border-radius: var(--r-pill);
  font-size: 15px;
  transition: border-color var(--dur-tint) ease, background-color var(--dur-tint) ease;
}
#guess-input::placeholder { color: var(--ink-3); }
#guess-input:focus { outline: 0; background: var(--card); border-color: var(--ink); }

/* the one Live key in the centre column */
.go-btn {
  flex: none;
  width: 58px;
  height: 52px;
  display: grid;
  place-items: center;
  background: var(--lime);
  border: var(--cut) solid var(--ink);
  border-radius: var(--r-1);
  box-shadow: var(--shadow);
  font-size: 22px;
  font-weight: 800;
  transition: var(--press);
}
.go-btn:disabled { opacity: .45; box-shadow: none; }

.ac-list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 8px);
  left: 0; right: 0;
  max-height: 268px;
  overflow-y: auto;
  background: var(--card);
  border: var(--cut) solid var(--ink);
  border-radius: var(--r-1);
  box-shadow: var(--shadow);
  padding: 6px;
}
.ac-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border-radius: 8px;
  text-align: left;
  transition: background-color var(--dur-tint) ease;
}
.ac-item[aria-selected="true"], .ac-item:hover { background: var(--lime-3); }
.ac-logo {
  width: 30px; height: 30px;
  border-radius: 7px;
  border: var(--cut-in) solid var(--line);
  object-fit: cover;
  flex: none;
}
.ac-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ac-ticker { margin-left: auto; font-size: 13px; font-weight: 700; color: var(--ink-3); }

/* ═══════════════════ pips / hint / clues ═══════════════════ */
.pips { display: flex; gap: 7px; justify-content: center; }
.pip {
  width: 30px;
  height: 7px;
  border-radius: var(--r-pill);
  background: var(--ash);
}
.pip.used { background: var(--ink); opacity: 1; }

.hint-area { display: flex; justify-content: center; }
.hint-btn {
  padding: 8px 16px;
  border-radius: var(--r-pill);
  border: var(--cut-in) solid var(--line);
  background: var(--card-2);
  font-size: 13px;
  font-weight: 700;
  transition: var(--press);
}
.hint-chip {
  padding: 8px 16px;
  border-radius: var(--r-pill);
  background: var(--lime-3);
  border: var(--cut-in) solid var(--lime-2);
  font-size: 13px;
  font-weight: 700;
}
.clue-strip { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.clue-chip {
  padding: 7px 13px;
  border-radius: var(--r-pill);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  font-size: 13px;
}
.clue-chip strong { font-weight: 800; }

/* ═══════════════════ board ═══════════════════ */
.col-head, .guess-row {
  display: grid;
  /* The label is context, the five tiles are the answer. 1.5fr gave the label
     a quarter of the row and left the tiles too narrow to print a chain name. */
  grid-template-columns: minmax(92px, 1.15fr) repeat(5, minmax(0, 1fr));
  gap: 6px;
  align-items: stretch;
}
.col-head { padding-bottom: 2px; }
.col-name {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: center;
}
.board { display: flex; flex-direction: column; gap: 6px; }

.coin-label { display: flex; align-items: center; gap: 8px; min-width: 0; }
.coin-logo {
  width: 32px; height: 32px;
  border-radius: 8px;
  border: var(--cut-in) solid var(--line);
  object-fit: cover;
  flex: none;
}
.coin-label-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.15; }
.coin-ticker {
  font-weight: 800;
  font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.coin-name {
  font-size: 11px;
  color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* one clue cell. The flip is the reveal, so it gets its own perspective. */
.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-height: 54px;
  padding: 6px 4px;
  border-radius: var(--r-1);
  border: var(--cut-in) solid transparent;
  background: var(--card-2);
  font-size: 13px;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  backface-visibility: hidden;
}
/* Each status names its own colours as variables so ONE reveal keyframe can
   land whichever of the three it is — the keyframe does not need to know. */
.tile.s-g { --tile-bg: var(--hit);  --tile-ink: var(--hit-ink); }
.tile.s-y { --tile-bg: var(--near); --tile-ink: var(--near-ink); }
.tile.s-x { --tile-bg: var(--miss); --tile-ink: var(--miss-ink); }
.tile.s-g, .tile.s-y, .tile.s-x {
  background: var(--tile-bg);
  color: var(--tile-ink);
  border-color: var(--ink);
}
.tile-val { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.tile-dir { font-size: 11px; opacity: .8; }
.tile .v-short { display: none; }
.tile.demo { min-height: 46px; }

.empty-note {
  padding: 22px 12px;
  text-align: center;
  color: var(--ink-3);
  font-size: 13px;
}

.miss-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.miss-name { font-weight: 600; font-size: 14px; }
.miss-x { margin-left: auto; color: var(--miss); font-weight: 800; }

/* ═══════════════════ right rail ═══════════════════ */
.yday { display: flex; align-items: center; gap: 12px; }
.yday img {
  width: 44px; height: 44px;
  border-radius: var(--r-1);
  border: var(--cut-in) solid var(--line);
  object-fit: cover;
  flex: none;
}
.yday-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.yday-label { font-size: 12px; color: var(--ink-3); }
.yday-name { font-family: var(--font-display); font-weight: 800; font-size: 19px; }
.yday-flag {
  margin-left: auto;
  width: 34px; height: 34px;
  display: grid;
  place-items: center;
  border-radius: var(--r-1);
  border: var(--cut-in) solid var(--line);
  background: var(--card-2);
  font-weight: 800;
  flex: none;
}
.yday-flag.win { background: var(--hit); color: var(--hit-ink); border-color: var(--ink); }
.yday-flag.lost { background: var(--miss); color: var(--miss-ink); border-color: var(--ink); }

/* the numbered how-to-play list: Lime numerals, one line of copy each */
.rule-list { list-style: none; display: flex; flex-direction: column; gap: 11px; }
/* Not a grid. An <li> holding a <b> plus a bare text node makes TWO grid
   items, and the text node lands in the 24px numeral column and renders one
   letter wide. A positioned marker takes any inline content. */
.rule-list li {
  position: relative;
  display: block;
  min-height: 24px;
  padding: 2px 0 2px 35px;
  line-height: 1.45;
  font-size: 13.5px;
  counter-increment: rule;
}
.rule-list li b { font-weight: 800; margin-right: .3em; }
.rule-list { counter-reset: rule; }
.rule-list li::before {
  content: counter(rule);
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--lime);
  font-size: 12px;
  font-weight: 800;
  color: var(--ink);
}

/* ═══════════════════ buttons ═══════════════════ */
.btn {
  padding: 12px 20px;
  border-radius: var(--r-1);
  border: var(--cut) solid var(--ink);
  background: var(--card);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: .02em;
  text-transform: uppercase;
  box-shadow: var(--shadow);
  transition: var(--press);
}
.btn-block { display: block; width: 100%; text-align: center; }
.btn-primary { background: var(--lime); }
.btn-danger { background: var(--miss); color: var(--miss-ink); }

/* ═══════════════════ the tray of side quests ═══════════════════ */
.dock {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 8px;
  /* width:100% is load-bearing. .app is a flex column, and there a horizontal
     auto margin means "shrink to content, then centre" — without this the
     720px tray silently collapsed to ~440px on every screen. */
  width: 100%;
  max-width: 720px;
  margin: var(--section) auto 0;
  padding: 12px;
  background: var(--card);
  border: var(--cut-in) solid var(--line);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-soft);
}
.dock-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 10px 4px 8px;
  border-radius: var(--r-1);
  text-decoration: none;
  color: inherit;
  transition: background-color var(--dur-tint) ease;
}
.dock-plate {
  width: 54px; height: 54px;
  display: grid;
  place-items: center;
  border-radius: var(--r-1);
  background: var(--lime-3);
  border: var(--cut) solid var(--ink);
  box-shadow: var(--shadow);
  transition: var(--press);
}
.dock-plate svg { width: 26px; height: 26px; display: block; }
.dock-label {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .05em;
  line-height: 1.15;
  text-transform: uppercase;
  text-align: center;
  color: var(--ink-2);
}
.dock-badge {
  position: absolute;
  top: 4px;
  right: calc(50% - 34px);
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  background: var(--miss);
  border: var(--cut-in) solid var(--ink);
  color: var(--miss-ink);
  font-size: 11px;
  font-weight: 800;
  z-index: 1;
}

/* ═══════════════════ footer ═══════════════════ */
.more { margin-top: var(--section); text-align: center; }
.text-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  align-items: center;
  font-size: 13px;
  color: var(--ink-3);
}
.text-links button, .text-links a {
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  border-bottom: var(--cut-in) solid var(--line);
  transition: color var(--dur-tint) ease, border-color var(--dur-tint) ease;
}
.text-links span { color: var(--ash); }
.deck-count { color: var(--ink-3); border: 0; }

.social-row { display: flex; gap: 10px; justify-content: center; margin-top: 14px; }
.social-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: var(--cut) solid var(--ink);
  box-shadow: var(--shadow);
  font-weight: 700;
  font-size: 13px;
  text-decoration: none;
  color: inherit;
  transition: var(--press);
}
.social-btn svg { width: 17px; height: 17px; }
.social-btn.soon { opacity: .5; box-shadow: none; border-color: var(--line); cursor: default; }
.soon-tag {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--card-2);
}
.fineprint { margin-top: 16px; font-size: 12px; color: var(--ink-3); }
.more-title {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 12px;
}
.pill-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.pill {
  padding: 9px 16px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: var(--cut) solid var(--ink);
  box-shadow: var(--shadow);
  font-weight: 700;
  font-size: 13px;
  transition: var(--press);
}

/* ═══════════════════ modals ═══════════════════ */
/* The scrim is the only blur in the interface, alongside Blur mode's coin. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(22, 22, 26, .42);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  overflow-y: auto;
}
.modal {
  position: relative;
  width: min(560px, 100%);
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  padding: 28px;
  background: var(--card);
  border: var(--cut) solid var(--ink);
  border-radius: var(--r-2);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.modal h2 {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -.01em;
}
.modal h3 {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: 6px;
}
.modal p { font-size: 14.5px; }
.modal-close {
  position: absolute;
  top: 16px; right: 16px;
  width: 34px; height: 34px;
  display: grid;
  place-items: center;
  border-radius: var(--r-1);
  border: var(--cut-in) solid var(--line);
  background: var(--card-2);
  font-weight: 800;
  transition: var(--press);
}
.fine { font-size: 12.5px; color: var(--ink-3); }

.help-modes { display: flex; flex-direction: column; gap: 8px; }
.help-mode {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  padding: 11px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.help-mode-ico {
  width: 38px; height: 38px;
  border-radius: 9px;
  border: var(--cut-in) solid var(--line);
  object-fit: cover;
}
.help-mode-name { font-family: var(--font-display); font-weight: 800; font-size: 15px; }
.help-mode-desc { font-size: 13px; color: var(--ink-2); }

/* The Classic legend is keyed to the board: each term is set exactly like the
   column header it explains (CHAIN, TYPE, YEAR...), so the modal reads as a
   key to the thing on screen rather than a list of bold-labelled claims. Two
   children per row on purpose: a bare text node in a grid becomes its own
   item, which is how a sentence ends up squeezed into the label column. */
.help-legend { list-style: none; display: flex; flex-direction: column; gap: 10px; font-size: 14px; }
.help-legend li {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
}
.help-term {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.help-list { list-style: none; display: flex; flex-direction: column; gap: 9px; font-size: 14px; }
.help-list li { padding-left: 14px; position: relative; }
.help-list li::before {
  content: "";
  position: absolute;
  left: 0; top: .62em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--lime-2);
}
.chip {
  display: inline-block;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  font-size: 12px;
  font-weight: 700;
}
.chip.cy { background: var(--near); color: var(--near-ink); }
.help-demo { display: flex; gap: 8px; }
.help-demo .tile { flex: 1; }
.demo-caption {
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: center;
}
.cb-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  cursor: pointer;
  padding: 10px 12px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.cb-row input { width: 17px; height: 17px; accent-color: var(--ink); cursor: pointer; }

/* ── stats ── */
.stat-tabs { display: flex; gap: 6px; }
.stat-tab {
  flex: 1;
  padding: 9px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  transition: var(--press);
}
.stat-tab.on { background: var(--lime); border-color: var(--ink); }
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 14px 6px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.stat-num { font-family: var(--font-display); font-weight: 800; font-size: 26px; line-height: 1; }
.stat-label { font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); }
.dist { display: flex; flex-direction: column; gap: 6px; }
.dist-row { display: grid; grid-template-columns: 16px minmax(0, 1fr); align-items: center; gap: 9px; }
.dist-n { font-size: 12px; font-weight: 800; color: var(--ink-3); }
.dist-bar, .dd-bar { height: 24px; border-radius: var(--r-pill); background: var(--card-2); overflow: hidden; }
.dist-count, .dd-fill {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  min-width: 28px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
  font-size: 12px;
  font-weight: 800;
}
.dist-count.on, .dd-fill.on { background: var(--hit); color: var(--hit-ink); }

/* ── coin list ── */
.coins-search {
  width: 100%;
  height: 46px;
  padding: 0 16px;
  border-radius: var(--r-pill);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.coins-search:focus { outline: 0; background: var(--card); border-color: var(--ink); }
.coins-body { display: flex; flex-direction: column; gap: 6px; max-height: 46vh; overflow-y: auto; }
.coin-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  padding: 9px 11px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.coin-row-logo {
  width: 34px; height: 34px;
  border-radius: 9px;
  border: var(--cut-in) solid var(--line);
  object-fit: cover;
}
.coin-row-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.coin-row-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coin-row-ticker { font-size: 12px; font-weight: 800; color: var(--ink-3); }
.coin-row-meta { font-size: 11.5px; color: var(--ink-3); text-align: right; }
.coins-empty { padding: 20px; text-align: center; color: var(--ink-3); font-size: 13px; }
.coins-note { margin-top: 4px; }

/* ── archive ── */
.archive-body { display: flex; flex-direction: column; gap: 6px; max-height: 50vh; overflow-y: auto; }
.arch-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 11px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  text-align: left;
  transition: var(--press);
}
.arch-day { font-family: var(--font-display); font-weight: 800; font-size: 15px; }
.arch-mode { font-size: 12px; color: var(--ink-3); letter-spacing: .06em; text-transform: uppercase; }
.arch-state { font-size: 12px; font-weight: 800; color: var(--ink-3); }

/* ── reveal ── */
.reveal-verdict {
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 800;
  line-height: 1.05;
  text-align: center;
}
.reveal-sub { text-align: center; color: var(--ink-2); font-size: 14px; }
.reveal-pot {
  padding: 13px 16px;
  border-radius: var(--r-1);
  background: var(--ink);
  color: var(--paper);
  font-size: 13.5px;
  text-align: center;
}
.coin-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.coin-card-title { display: flex; align-items: center; gap: 13px; }
.coin-card-logo {
  width: 62px; height: 62px;
  border-radius: var(--r-1);
  border: var(--cut) solid var(--ink);
  object-fit: cover;
  flex: none;
}
.coin-card-title-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.15; }
.coin-card-name { font-family: var(--font-display); font-weight: 800; font-size: 22px; }
.coin-card-ticker { font-size: 13px; font-weight: 700; color: var(--ink-3); }
.coin-card-facts { display: flex; flex-wrap: wrap; gap: 7px; }
.fact-chip {
  padding: 5px 12px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: var(--cut-in) solid var(--line);
  font-size: 12.5px;
  font-weight: 600;
}
.coin-card-lore { font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }
.btn-row { display: flex; gap: 9px; flex-wrap: wrap; }
.btn-row .btn { flex: 1; min-width: 132px; }
.countdown {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 12px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.countdown b { font-family: var(--font-display); font-size: 24px; font-weight: 800; }
.countdown-label { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
.wiki-link { font-size: 13px; color: var(--ink-2); text-align: center; }

/* ═══════════════════ handle, wallet + board ═══════════════════ */
/* #gate-body is a bare <div> in the markup, so it laid its children out as
   blocks and every align-self on them was inert — which is why the skip button
   would not stretch. Making it a column is what lets the dialog control its
   own rhythm. */
.profile-body, .gate-form, #gate-body { display: flex; flex-direction: column; gap: 11px; }
.profile-row { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.profile-name { font-family: var(--font-display); font-weight: 800; font-size: 19px; }
.profile-x { font-size: 13px; color: var(--ink-3); }
/* One shell, not three parts in a row. The @ used to be a flex SIBLING of the
   input, so it floated in the gap beside a pill-shaped box and read as a
   stranded character rather than as part of the field. */
.field {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 52px;
  padding: 0 16px;
  border-radius: var(--r-pill);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  transition: border-color var(--dur-tint) ease, background-color var(--dur-tint) ease;
}
.field:focus-within { background: var(--card); border-color: var(--ink); }
.field.is-ok { border-color: var(--hit); }
.field.is-bad { border-color: var(--miss); }
.field-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  background: none;
  border: 0;
}
.field-input:focus { outline: 0; }
.field-at { display: flex; align-items: center; color: var(--ink-3); font-weight: 700; flex: none; }
/* The 3-to-16 rule was prose the player had to remember. Counting in place is
   the same information at the moment it is needed. */
.field-count {
  flex: none;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.field-count.short { color: var(--miss); }

.gate-sub { font-size: 14px; color: var(--ink-2); }

/* Reserved height and a state dot. Without the min-height the line appearing
   shoved the skip button down on every keystroke pause; without the dot,
   "Free." and "Taken." were the same grey text and said nothing at a glance. */
.gate-note {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 20px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-3);
}
.gate-note::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: transparent;
}
.gate-note.ok { color: var(--ink); }
.gate-note.ok::before { background: var(--hit); }
.gate-note.bad { color: var(--miss-ink); }
.gate-note.bad::before { background: var(--miss); }
.gate-note.wait::before { background: var(--ash); }

/* A taken name used to be a dead end. Three one-tap alternatives turn it into
   a choice; they are unchecked on purpose, and tapping one runs the normal
   debounced lookup. */
.gate-suggest { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; }
.gate-suggest-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.gate-chip {
  padding: 6px 12px;
  border-radius: var(--r-pill);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
  font-size: 13px;
  font-weight: 700;
  transition: var(--press);
}
@media (hover: hover) and (pointer: fine) {
  .gate-chip:hover { background: var(--lime-3); border-color: var(--lime-2); }
}
.gate-chip:active { transform: translateY(1px); }
/* A ghost, not a bordered button. When the primary is disabled — which is
   the gate's resting state — a bordered secondary sits at the same visual
   weight and the two read as a choice between equals. It is not: one claims
   your name, the other declines it. */
.gate-skip {
  align-self: stretch;
  padding: 11px 18px;
  border-radius: var(--r-1);
  border: 0;
  background: none;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-3);
  transition: background-color var(--dur-tint) ease, color var(--dur-tint) ease;
}
@media (hover: hover) and (pointer: fine) {
  .gate-skip:hover { background: var(--card-2); color: var(--ink); }
}
.gate-skip:active { transform: translateY(1px); }

/* A disabled primary is the gate's main state before anything is typed, so it
   has to look deliberately inert rather than broken: the lift goes, the fill
   drains to a neutral, and the cursor says so. */
.btn:disabled {
  background: var(--card-2);
  color: var(--ink-3);
  border-color: var(--line);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

.lb-list { display: flex; flex-direction: column; gap: 5px; max-height: 44vh; overflow-y: auto; }
.lb-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  padding: 9px 11px;
  border-radius: var(--r-1);
  background: var(--card-2);
  border: var(--cut-in) solid var(--line);
}
.lb-row.me { background: var(--lime-3); border-color: var(--lime-2); }
.lb-rank { font-family: var(--font-display); font-weight: 800; font-size: 15px; color: var(--ink-3); }
.lb-player { display: flex; align-items: center; gap: 7px; min-width: 0; }
.lb-name { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-x { color: var(--ink-3); font-size: 12px; }
.lb-score { font-weight: 800; font-size: 13px; }
.lb-res { font-size: 12px; color: var(--ink-3); }
.lb-time { font-size: 11.5px; color: var(--ink-3); }
.lb-tag {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: var(--cut-in) solid var(--line);
  color: var(--ink-3);
}
.lb-empty { padding: 20px; text-align: center; color: var(--ink-3); font-size: 13px; }
.lb-note { font-size: 12px; color: var(--ink-3); }
.lb-pot {
  padding: 12px 14px;
  border-radius: var(--r-1);
  background: var(--ink);
  color: var(--paper);
  font-size: 13px;
}
.lb-callout {
  padding: 12px 14px;
  border-radius: var(--r-1);
  background: var(--lime-3);
  border: var(--cut-in) solid var(--lime-2);
  font-size: 13px;
}

/* ═══════════════════ flash ═══════════════════ */
.flash-layer {
  position: fixed;
  left: 50%;
  top: 18px;
  transform: translateX(-50%);
  z-index: 90;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;
}
.flash {
  padding: 11px 20px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
  font-weight: 700;
  font-size: 14px;
  box-shadow: var(--shadow-soft);
  will-change: transform, opacity;
}

/* ═══════════════════ press ═══════════════════ */
/* The arithmetic must close: translateY down by --lift, shadow off by --lift.
   Any other pairing reads as the object changing size. */
@media (hover: hover) and (pointer: fine) {
  .ico-btn:hover, .go-btn:hover, .btn:hover, .pill:hover,
  .social-btn:not(.soon):hover, .stat-tab:hover, .arch-row:hover,
  .modal-close:hover, .hint-btn:hover, .dock-item:hover .dock-plate {
    transform: translateY(calc(var(--rise) * -1));
    box-shadow: 0 calc(var(--lift) + var(--rise)) 0 var(--ink);
  }
  .mode-card:hover { transform: translateY(calc(var(--rise) * -1)); }
  .mode-card.on:hover { box-shadow: 0 calc(var(--lift) + var(--rise)) 0 var(--ink); }
  .dock-item:hover { background: var(--card-2); }
  .text-links button:hover, .text-links a:hover { color: var(--ink); border-color: var(--ink); }
  .modal-close:hover { background: var(--miss); color: var(--miss-ink); border-color: var(--ink); }
}
.ico-btn:active, .go-btn:active, .btn:active, .pill:active,
.social-btn:not(.soon):active, .stat-tab:active, .arch-row:active,
.modal-close:active, .hint-btn:active,
.dock-item:active .dock-plate, .mode-card:active {
  transform: translateY(var(--lift));
  box-shadow: 0 0 0 var(--ink);
  transition: var(--press-in);
}
.mode-card:not(.on):active { box-shadow: none; }

/* ═══════════════════ entrance + reveal ═══════════════════ */
/* Never scale(0): nothing in a physical world appears out of nothing. */
@keyframes rise-in {
  from { opacity: 0; transform: translateY(14px) scale(.94); }
  to   { opacity: 1; transform: none; }
}
/* The reveal. A tile used to arrive already coloured and then flip, which
   gave the answer away before the motion that is supposed to deliver it. Now
   it waits blank, turns over, and its colour lands at the exact frame it is
   edge-on — the moment it is invisible — so the eye reads the colour as the
   thing the flip revealed. The 49.9%/50% pair is a snap, not a fade. */
@keyframes flip-reveal {
  0% {
    transform: perspective(420px) rotateX(0deg);
    background-color: var(--card-2); border-color: var(--line); color: transparent;
  }
  49.9% {
    transform: perspective(420px) rotateX(-90deg);
    background-color: var(--card-2); border-color: var(--line); color: transparent;
  }
  50% {
    transform: perspective(420px) rotateX(-90deg);
    background-color: var(--tile-bg); border-color: var(--ink); color: var(--tile-ink);
  }
  100% {
    transform: perspective(420px) rotateX(0deg);
    background-color: var(--tile-bg); border-color: var(--ink); color: var(--tile-ink);
  }
}
/* an exact match lands with a small overshoot, the only tile that does */
@keyframes tile-hit {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.09); }
  100% { transform: scale(1); }
}
/* the winning row: each tile hops in turn after the whole row has landed */
@keyframes tile-hop {
  0%, 100% { transform: translateY(0); }
  40%      { transform: translateY(-14px); }
  70%      { transform: translateY(2px); }
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}
@keyframes pip-land {
  0%   { transform: scaleX(.3); }
  100% { transform: scaleX(1); }
}

/* --d is set inline per tile. A variable rather than animation-delay, because
   the exact-match pop has to start exactly when its own flip ends, and only a
   variable lets both animations read the same number. */
.tile.flip { animation: flip-reveal var(--dur-flip) var(--ease-in-out) var(--d, 0s) both; }
.tile.flip.s-g {
  animation:
    flip-reveal var(--dur-flip) var(--ease-in-out) var(--d, 0s) both,
    tile-hit 240ms var(--ease-pop) calc(var(--d, 0s) + var(--dur-flip)) both;
}
.tile.hop { animation: tile-hop 420ms var(--ease-out) var(--d, 0s) both; }

/* the row arrives before its tiles turn: its label slides in first */
.guess-row.fresh .coin-label { animation: rise-in var(--dur-pop) var(--ease-out) both; }

/* the streak pill peels off the sheet and slaps back down */
@keyframes streak-bump {
  0%   { transform: scale(1) rotate(0deg); }
  30%  { transform: scale(1.18) rotate(-4deg); }
  60%  { transform: scale(.96) rotate(2deg); }
  100% { transform: scale(1) rotate(0deg); }
}
.streak-pill.bump { animation: streak-bump 460ms var(--ease-pop); }

/* a spent hint turns over into view */
.hint-chip.fresh { animation: rise-in var(--dur-pop) var(--ease-pop) both; }

/* the losing board: one hard shake, then still */
.board.lost { animation: shake 420ms var(--ease-out); }
.pip.fresh { animation: pip-land var(--dur-pop) var(--ease-pop) both; }
.input-wrap.refuse { animation: shake 260ms var(--ease-out); }

.modal-backdrop { animation: fade-in var(--dur-pop) var(--ease-out); }
.modal-backdrop .modal { animation: rise-in var(--dur-pop) var(--ease-out) both; }

/* ═══════════════════ responsive ═══════════════════ */
@media (max-width: 1440px) {
}
@media (max-width: 1240px) {
  .layout { grid-template-columns: var(--rail) minmax(0, 1fr); }
  .col-right {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--gap);
    align-items: start;
  }
}
@media (max-width: 860px) {
  .layout { grid-template-columns: minmax(0, 1fr); }
  .col-left { order: -1; }
  .mode-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .mode-blurb, .mode-prog { display: none; }
  /* Keep the counter inline. Spanning it to a second row made every card a
     name over a full-width pill, which reads as a progress bar, not a score. */
  .mode-card { grid-template-columns: auto minmax(0, 1fr) auto; padding: 11px; gap: 9px; }
  .mode-flag { min-width: 38px; padding: 4px 7px; }
  .dock { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* on narrow screens the plate is a band exactly 56.25vw tall (full width,
     16:9): clear it by that much so the footer scrolls up off the green */
  .app { padding-bottom: calc(56.25vw + 24px); }
}
@media (max-width: 620px) {
  body { font-size: 14px; }
  .app { padding-top: 18px; }
  .col-right { grid-template-columns: minmax(0, 1fr); }
  /* On a phone the label shrinks to the logo and ticker; the five tiles are
     the answer and get what is left. */
  .col-head, .guess-row { grid-template-columns: minmax(52px, .8fr) repeat(5, minmax(0, 1fr)); gap: 4px; }
  .tile .v-full { display: none; }
  .tile .v-short { display: inline; }
  .tile { padding: 6px 2px; }
  .coin-name { display: none; }
  .coin-logo { width: 26px; height: 26px; }
  .tile { min-height: 46px; font-size: 11px; }
  .col-name { font-size: 9px; letter-spacing: .04em; }
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
  .modal { padding: 20px; }
  .flag { width: 128px; left: 0; }
  .flag-cloth { font-size: 14px; padding: 10px 11px 12px; }
  .flag-pole { height: 190px; }
  .bubble { width: 120px; font-size: 12px; }
}

/* ═══════════════════ reduced motion ═══════════════════ */
/* Gentler, not zero: travel and transforms go, opacity and colour stay, because
   those are what make a state change legible. Never a blanket
   "* { transition-duration: .01ms }" — it makes descendants transition
   visibility and quietly breaks focus into a newly opened dialog. */
@media (prefers-reduced-motion: reduce) {
  .world { animation: none !important; transform: none !important; }
  /* colour still lands on each tile's turn — that is the information — but
     nothing rotates, hops, or shakes */
  .tile.flip, .tile.flip.s-g { animation: fade-in var(--dur-flip) var(--ease-out) var(--d, 0s) both; }
  .tile.hop, .board.lost, .streak-pill.bump, .hint-chip.fresh,
  .guess-row.fresh .coin-label { animation: none; }
  .brand .b-l, .mystery { transform: none !important; }
  .pip.fresh { animation: none; }
  .input-wrap.refuse { animation: none; }
  .modal-backdrop .modal { animation: fade-in var(--dur-pop) var(--ease-out) both; }
  .ico-btn, .go-btn, .btn, .pill, .social-btn, .stat-tab,
  .arch-row, .modal-close, .hint-btn, .mode-card, .dock-plate {
    transition: background-color var(--dur-tint) ease, border-color var(--dur-tint) ease, color var(--dur-tint) ease;
  }
  .ico-btn:hover, .go-btn:hover, .btn:hover, .pill:hover, .social-btn:hover,
  .stat-tab:hover, .arch-row:hover, .modal-close:hover, .hint-btn:hover,
  .mode-card:hover, .dock-item:hover .dock-plate,
  .ico-btn:active, .go-btn:active, .btn:active, .pill:active, .social-btn:active,
  .stat-tab:active, .arch-row:active, .modal-close:active, .hint-btn:active,
  .mode-card:active, .dock-item:active .dock-plate {
    transform: none;
  }
}

/* ═══════════════════ the states game.js toggles ═══════════════════ */

/* the flash: in from the top, out the same way, faster leaving than arriving */
@keyframes flash-in {
  from { opacity: 0; transform: translateY(-12px) scale(.94); }
  to   { opacity: 1; transform: none; }
}
@keyframes flash-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-10px); }
}
.flash {
  animation:
    flash-in var(--dur-pop) var(--ease-pop) both,
    flash-out var(--dur-close) var(--ease-out) var(--hold-flash) both;
}

/* leaving a dialog: down into the sheet, and faster than arriving */
@keyframes sink-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(8px) scale(.97); }
}
.modal-backdrop.closing { animation: fade-in var(--dur-close) var(--ease-out) reverse both; }
.modal-backdrop.closing .modal { animation: sink-out var(--dur-close) var(--ease-out) both; }

/* the interface refusing a guess it cannot match */
.input-wrap.reject { animation: shake 320ms var(--ease-out); }
.input-wrap.reject #guess-input { border-color: var(--miss); }

/* Enter and the autocomplete row both commit without the key ever entering
   :active, so the press is fired by hand and needs its own animation. */
@keyframes stamp-down {
  0%   { transform: translateY(0); box-shadow: var(--shadow); }
  32%  { transform: translateY(var(--lift)); box-shadow: 0 0 0 var(--ink); }
  100% { transform: translateY(0); box-shadow: var(--shadow); }
}
.go-btn.sent { animation: stamp-down 320ms var(--ease-pop); }

@media (prefers-reduced-motion: reduce) {
  .flash {
    animation:
      fade-in var(--dur-pop) var(--ease-out) both,
      flash-out var(--dur-close) var(--ease-out) var(--hold-flash) both;
  }
  .modal-backdrop.closing .modal { animation: fade-in var(--dur-close) var(--ease-out) reverse both; }
  .input-wrap.reject { animation: none; }
  .go-btn.sent { animation: none; }
}

/* ═══════════════════ corrections after the first render ═══════════════════ */

/* the sound key is drawn, not typed: the crossed-out speaker emoji renders as
   a tofu box on Windows and reads as a missing asset */
.ico-sfx { display: block; width: 19px; height: 19px; }
.ico-sfx svg { width: 100%; height: 100%; display: block; }

/* The footer sits in the valley between the two hills, so it is on paper —
   but the valley floor is shallow at small heights. Give the line its own
   weight rather than trusting the gap. */
/* The footer is the lowest-priority block and the one most likely to land on
   the plate's green — how low it sits depends on the viewport, because the
   block above it is centred. Rather than fight the centring for every screen
   height, it rests on a plaque of the plate's own paper, so the fine print is
   legible over blocks as well as over cream. Calm tier: no die line, no lift. */
.more {
  position: relative;
  align-self: center;
  padding: 8px clamp(16px, 2.4vw, 32px) 6px;
  border-radius: var(--r-2);
  background: color-mix(in srgb, var(--paper) 90%, transparent);
}
.text-links { font-weight: 600; }
.deck-count { color: var(--ink-2); font-weight: 700; }
.fineprint { color: var(--ink-2); opacity: .72; }

/* the stage was sized for a hero image it no longer holds */
.stage { min-height: 168px; }
.stage.burst::before { opacity: .75; }

/* Yesterday's link key is a button, not a dead box: it opens that archive run,
   so it is drawn as one and carries the Live tier. */
.yday-flag.go {
  background: var(--card);
  border: var(--cut) solid var(--ink);
  box-shadow: var(--shadow);
  font-size: 15px;
  transition: var(--press);
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .yday-flag.go:hover {
    background: var(--lime);
    transform: translateY(calc(var(--rise) * -1));
    box-shadow: 0 calc(var(--lift) + var(--rise)) 0 var(--ink);
  }
}
.yday-flag.go:active {
  transform: translateY(var(--lift));
  box-shadow: 0 0 0 var(--ink);
  transition: var(--press-in);
}

/* ═══════════════════ corrections after the play render ═══════════════════ */

/* `.text-links span` (0,1,1) was beating `.deck-count` (0,1,0), so the count
   rendered in Ash — which is an inert-fill token and is not legible as text,
   least of all over the green. Match the element as well as the class. */
.text-links span.deck-count { color: var(--ink-2); font-weight: 700; }
.text-links span.sep { color: var(--ash); }

/* Once the grid has rows in it, the mystery coin is a 168px band of nothing
   sitting above the thing the player is actually reading. Shrink it, and the
   whole page comes back up off the landscape. */
.stage.tight {
  flex-direction: row;
  gap: 14px;
  min-height: 0;
  padding: 12px 16px;
}
.stage.tight .mystery {
  width: 56px;
  height: 56px;
  border-width: 3px;
  font-size: 26px;
}
.stage.tight .stage-cap { margin: 0; }

/* ═══════════════════ corrections after the modal + mobile renders ═══════════════════ */

/* Three children in a two-column grid: the blurb wrapped onto its own row
   under the icon instead of sitting beneath the name. Place all three. */
.help-mode { grid-template-rows: auto auto; row-gap: 2px; }
.help-mode-ico { grid-row: 1 / 3; }
.help-mode-name { grid-column: 2; align-self: end; text-align: left; }
.help-mode-desc { grid-column: 2; align-self: start; text-align: left; }

@media (max-width: 860px) {
  /* The wordmark and three keys do not fit one row at phone width — the brand
     ran straight under the buttons. Give the brand its own line. */
  .topbar {
    grid-template-columns: 1fr auto;
    grid-template-areas: "left right" "brand brand";
    /* line-height .9 on a 54px wordmark leaves almost no leading above the
       caps, so a 6px gap put the ascenders inside the button row */
    row-gap: 14px;
  }
  .topbar-side.left { grid-area: left; }
  .topbar-side.right { grid-area: right; }
  .brand { grid-area: brand; text-align: center; }
  .tagline { margin-bottom: 20px; }

  /* The plate is one flat image, so the cast cannot be hidden separately the
     way the drawn version could. The phone rule in the world section scales it
     by height instead, which keeps the corners legible and pushes the middle
     (empty field) off the sides. */

  .mode-card { flex-wrap: wrap; }
  .mode-name { font-size: 15px; }
}

@media (max-width: 620px) {
  /* stacked, not side by side: at three across the name clipped mid-word */
  .mode-list { grid-template-columns: minmax(0, 1fr); }
  .mode-card {
    grid-template-columns: auto minmax(0, 1fr) auto;
    padding: 10px 12px;
  }
  .mode-flag { grid-column: auto; }
  .mode-ico { width: 38px; height: 38px; }
  .dock { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; padding: 10px; }
  .dock-plate { width: 46px; height: 46px; }
  .dock-plate svg { width: 22px; height: 22px; }
  .dock-badge { right: calc(50% - 30px); }
  .brand { font-size: clamp(38px, 12vw, 54px); }
}

/* ═══════════════════ the toys ═══════════════════ */
/* Two things on the page do nothing useful and are there to be poked: the
   wordmark and the mystery coin. They are the delight budget spent where it is
   cheapest — both are seen rarely enough to stay charming, and neither sits on
   a path the player has to take to play. */

/* Each letter is its own box so it can jump on its own. The grid mark beside
   it hops first when the word is clicked, as if it set the wave off. */
.brand .b-face { display: inline-flex; }
.brand .b-l {
  display: inline-block;
  cursor: pointer;
  will-change: transform;
}

/* the coin of the day, spun by hand */
.mystery {
  cursor: pointer;
  transform-style: preserve-3d;
  will-change: transform;
  transition: box-shadow var(--dur-tint) ease;
}
@media (hover: hover) and (pointer: fine) {
  .mystery:hover { box-shadow: 0 var(--lift) 0 var(--ink); }
  /* the tray icons tilt toward the pointer instead of just lifting */
  .dock-item:hover .dock-plate svg {
    transform: rotate(-8deg) scale(1.08);
    transition: transform var(--dur-pop) var(--ease-pop);
  }
}
.dock-plate svg { transition: transform var(--dur-pop) var(--ease-out); }
