/* mines — phone first, because that is the only place the small grid is hard.
 *
 * TWO THEMES, AND LIGHT IS THE BASE, matching the rest of the house. The
 * palette below is the light one on bare :root; dark is a token swap in two
 * guarded blocks — the media query for "system", the attribute for a choice
 * the user made. Nothing outside those blocks names a literal colour, so a
 * rule written once is right in both. The duplication between the two dark
 * blocks is unavoidable: a media query cannot be OR-ed with an attribute
 * selector, and defining a colour in only one of them is how a theme ends up
 * half-applied.
 *
 * THE LOOK IS conquest's, SINCE 2026-09-12: a tinted ground rather than the
 * iOS grouped grey, and every card, control and overlay the same GLASS —
 * translucent, blurred, a hairline edge, a soft shadow. One recipe, on one
 * selector list, so a new panel cannot be almost-right. The glyphs are
 * game-icons.net paths (icons.js) drawn in currentColor, so they take the
 * theme, the number colours and the gold like any text would.
 *
 * GOLD MEANS YOU. wizard's rule, conquest's too: one accent, reserved for the
 * current person — your row on a board, your best on a card, the one button
 * that is the thing to do next. Safe Mode's tint stays BLUE on purpose: it is
 * a mode, not a person, and the whole board wears it.
 *
 * Insets go on PADDING, never on layout, and every one carries a 0px fallback —
 * `calc(6px + env(...))` with no fallback is an invalid length and drops the
 * whole declaration on every desktop browser.
 */

:root {
  /* A cool slate ground — the field the tiles sit on. Lighter than
     conquest's ocean, because a white tile has to stand up off it and a
     number has to read on the tile, not on the ground. */
  --bg:       #dfe5ec;
  --panel:    #ffffff;
  --panel-2:  #f6f6f9;
  --glass:    rgba(255, 255, 255, .78);
  --glass-2:  rgba(255, 255, 255, .55);
  --line:     rgba(60, 60, 67, .16);
  --line-2:   rgba(60, 60, 67, .08);
  --ink:      #000000;
  --ink-2:    #3c3c43;
  --dim:      #8a8a8e;
  --dim-2:    #b0b0b6;
  --pill:     #efeff0;
  --good:     #1a9e4b;
  --warn:     #b8730a;
  --bad:      #e5322b;
  --link:     #007aff;
  --gold:     #d9a520;
  --gold-lit: #ffd45c;
  --gold-ink: #1a1200;
  --shadow:   0 1px 2px rgba(0,0,0,.04), 0 6px 20px rgba(0,0,0,.05);

  /* The board. A closed cell is a raised tile; an open one is a hole in it —
     and the hole has to be DARKER THAN THE GROUND, not merely darker than the
     tile: the grid sits straight on --bg, and the first version of this
     ground put the open cells within three points of it, so a cleared region
     was invisible in a screenshot until the numbers gave it away. */
  --cell:      #ffffff;
  --cell-edge: rgba(60,60,67,.14);
  --cell-open: #bfc8d2;
  --cell-press:#aeb8c3;
  --cell-flag: #fff3f2;

  /* THE NUMBER COLOURS ARE THE ONE PART OF THIS APP THE TILT PALETTE MAY NOT
     TOUCH. A "3" has to be the same red at every angle or the board stops being
     readable, which would be a very expensive way to have a nice background.
     Classic Minesweeper hues, darkened enough to pass on white. */
  --n1: #1a56c4; --n2: #17803d; --n3: #cc2b1f; --n4: #1a2f7a;
  --n5: #8c2415; --n6: #0f7a80; --n7: #2b2b2f; --n8: #6b6b72;

  --r-card: 18px;
  --r-mid:  14px;
  --r-sm:   10px;
  --r-cell: 7px;

  --font: -apple-system, BlinkMacSystemFont, system-ui, "SF Pro Text",
          "Helvetica Neue", sans-serif;
  /* The timer and the mine count only. A proportional font makes a running
     clock jitter sideways on every tick, which is distracting in exactly the
     moment the clock matters. */
  --font-num: ui-rounded, -apple-system, "SF Pro Rounded", system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* Set by tilt.js while the tilt palette is running, and REMOVED when it stops.
     --tilt-a is the master alpha: at 0 the gradient below is completely
     invisible and the page is exactly --bg, which is the state every device
     that cannot or will not do motion stays in forever. */
  --tilt-a: 0;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:       #0f1519;
    --panel:    #1c1c1e;
    --panel-2:  #2c2c2e;
    --glass:    rgba(28, 28, 30, .86);
    --glass-2:  rgba(44, 44, 46, .75);
    --line:     rgba(84, 84, 88, .5);
    --line-2:   rgba(84, 84, 88, .28);
    --ink:      #ffffff;
    --ink-2:    #ebebf5;
    --dim:      #8e8e93;
    --dim-2:    #636366;
    --pill:     #2c2c2e;
    --good:     #30d158;
    --warn:     #ffd60a;
    --bad:      #ff453a;
    --link:     #0a84ff;
    --gold:     #e8c05a;
    --gold-lit: #ffd77a;
    --shadow:   0 1px 2px rgba(0,0,0,.5), 0 6px 20px rgba(0,0,0,.4);

    --cell:      #2c2c2e;
    --cell-edge: rgba(255,255,255,.10);
    --cell-open: #141618;
    --cell-press:#48484a;
    --cell-flag: #3a2523;

    --n1: #5aa9ff; --n2: #4ddb7f; --n3: #ff6b5e; --n4: #9db4ff;
    --n5: #ff9d8a; --n6: #4fd8de; --n7: #e6e6ea; --n8: #9a9aa2;
  }
}
:root[data-theme="dark"] {
  --bg:       #0f1519;
  --panel:    #1c1c1e;
  --panel-2:  #2c2c2e;
  --glass:    rgba(28, 28, 30, .86);
  --glass-2:  rgba(44, 44, 46, .75);
  --line:     rgba(84, 84, 88, .5);
  --line-2:   rgba(84, 84, 88, .28);
  --ink:      #ffffff;
  --ink-2:    #ebebf5;
  --dim:      #8e8e93;
  --dim-2:    #636366;
  --pill:     #2c2c2e;
  --good:     #30d158;
  --warn:     #ffd60a;
  --bad:      #ff453a;
  --link:     #0a84ff;
  --gold:     #e8c05a;
  --gold-lit: #ffd77a;
  --shadow:   0 1px 2px rgba(0,0,0,.5), 0 6px 20px rgba(0,0,0,.4);

  --cell:      #2c2c2e;
  --cell-edge: rgba(255,255,255,.10);
  --cell-open: #141618;
  --cell-press:#48484a;
  --cell-flag: #3a2523;

  --n1: #5aa9ff; --n2: #4ddb7f; --n3: #ff6b5e; --n4: #9db4ff;
  --n5: #ff9d8a; --n6: #4fd8de; --n7: #e6e6ea; --n8: #9a9aa2;
}

* { box-sizing: border-box; }

/* `[hidden]` MUST WIN, AND BY DEFAULT IT DOES NOT.
 *
 * The browser's own `[hidden] { display: none }` lives in the UA stylesheet, so
 * ANY author rule that sets `display` on the same element beats it — no
 * `!important`, no specificity war, just author-over-UA. `.controls
 * { display: flex }` and `.btn-wide { display: block }` both did, which meant
 * setting `.hidden` from JavaScript did precisely nothing and the element sat
 * there in plain sight. Two of those in one day, in one afternoon's work, and
 * the second one shipped: the "Look at the board" button was drawn on the WIN
 * card too, where it is meant to be hidden.
 *
 * The property was `true` the whole time, so anything that asserts on
 * `el.hidden` — as bin/check_boot.js did — agrees with the code and not with
 * the screen. This ends the class for the whole stylesheet; checks now read the
 * computed display instead. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;              /* the page never scrolls; a view does (see "the shell") */
  font-family: var(--font);
  color: var(--ink);
  background-color: var(--bg);
  -webkit-font-smoothing: antialiased;
}

/* THE TILT GROUND. One gradient over the flat background, entirely transparent
   until tilt.js sets --tilt-a. Nothing else on the page reads these variables,
   so the palette can never affect a number, a cell or a piece of text. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    var(--tilt-angle, 160deg),
    hsl(var(--tilt-hue, 215) var(--tilt-sat, 0%) 55% / calc(var(--tilt-a) * .55)) 0%,
    transparent calc(38% + var(--tilt-spread, 0%))
  );
  transition: background .35s linear;
}

/* The whole app refuses text selection and the iOS long-press callout: both
   interrupt a hold, which is the flag gesture. Inputs opt back in below. */
body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
input, textarea { -webkit-user-select: text; user-select: text; }

/* ── the glass at the top of the screen ────────────────────────────────────

   iOS 26 blurs the top edge of a full-screen web app against the status bar:
   content does not stop under the bar, it goes SOFT beneath it, and anything in
   that band is unreadable rather than hidden.

   `env(safe-area-inset-top)` does not describe it. That inset is the status
   bar's own height — it is what keeps content out from UNDER the bar, and it is
   already applied everywhere below. The blur reaches past it, so the two are
   different measurements and both are needed. Adding them is the same fix
   `budget` made, and 20px is the same number: about one line of small text,
   which is about what the effect eats.

   It costs a browser tab nothing — 20px of background at the top of a scrolling
   page reads as margin — so it is not gated on display-mode. A media query that
   silently fails to match is worse than a margin nobody notices, and this house
   has been bitten by exactly that shape of thing before. */
:root { --glass-t: 20px; }

/* ── THE GLASS ─────────────────────────────────────────────────────────────
   One recipe for every panel in the app. Add a new surface to this list
   rather than restating three of the four properties on it. */
.card, .signin-card, .size-card, .safe, .overlay-card, .banner, .icon, .btn, .board-list {
  background: var(--glass);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  border: .5px solid var(--line);
  box-shadow: var(--shadow);
}

/* ── the shell ─────────────────────────────────────────────────────────────

   THE PAGE DOES NOT SCROLL; A VIEW DOES. Since 2026-09-15 the app is the
   height of the screen and each view is its own scroller, so the two Play
   screens — pick a size, and the board — can be locked to the viewport
   outright, the way a real app's game screen is: nothing under the cards,
   nothing under the controls, no rubber-banding a board you are trying not to
   mis-tap. Boards and Settings live in the menu sheet, which scrolls inside
   itself. There is no tab bar (removed 2026-09-15, conquest's and gems'
   arrangement): the bottom padding is the home indicator's inset and
   nothing more, which is 72px more board.

   100dvh, not 100vh: on iOS the two differ by the browser chrome, and in a
   home-screen app they are the same number — a board sized against the wrong
   one is a board that runs under the tab bar. */

main { height: 100dvh; }

.view {
  height: 100%;
  box-sizing: border-box;
  padding: calc(8px + var(--glass-t) + env(safe-area-inset-top, 0px)) 16px calc(12px + env(safe-area-inset-bottom, 0px));
  max-width: 560px;
  margin: 0 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/* The display rules below would beat the UA's [hidden] on specificity; a
   hidden view must stay gone. */
.view[hidden] { display: none !important; }

/* THE TWO PLAY SCREENS ARE FIXED TO THE VIEWPORT. Flex columns: the cards
   (or the board) take whatever is left between the header and the bottom of
   the screen. The cards' box may still scroll INSIDE itself on a screen too
   short to hold three of them — an iPhone SE — because clipping a board
   someone cannot reach is worse than a scroll nobody on a bigger phone will
   ever see. */
#view-home, #view-game { display: flex; flex-direction: column; overflow: hidden; }
#view-home .size-cards {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  /* Three equal rows that share the whole height — the cards grow to the
     phone rather than sitting on a field of nothing. `1fr` never shrinks a
     row below its content, so on a short screen they keep their size and
     the box scrolls instead. */
  grid-auto-rows: 1fr;
}
#view-home .size-card-body { display: flex; flex-direction: column; justify-content: center; }
#view-game .grid-wrap {
  flex: 1; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  /* THE BOARD BLEEDS INTO THE VIEW'S SIDE PADDING, down to a 6px gutter: on
     a 12-wide Deep board the 16px margins were a whole cell's worth of
     width. Taps resolve to the nearest centre, so the gutter's size is not a
     hit-target question. layoutGrid() knows the same number. */
  margin-inline: -10px;
}

.view-head { padding: 12px 4px 18px; }
/* THE BAR — conquest's lobby header: the title on the left, one round glass
   menu button on the right, and nothing else on the screen to navigate by. */
.view-head.bar { display: flex; align-items: center; gap: 10px; }
.bar-title { flex: 1; min-width: 0; }
.view-head h1 { margin: 0; font-size: 34px; font-weight: 700; letter-spacing: -.6px; }
.view-sub { margin: 4px 0 0; color: var(--dim); font-size: 15px; }

.card {
  border-radius: var(--r-card);
  padding: 16px;
  margin: 0 0 14px;
}
.card h3 { margin: 0 0 12px; font-size: 16px; font-weight: 650; }
.card.rows { padding: 0 16px; }
.card.rows h3 { padding-top: 14px; margin-bottom: 4px; }

.fine {
  margin: 12px 6px 0;
  color: var(--dim);
  font-size: 12.5px;
  line-height: 1.5;
}
.fine strong { color: var(--ink-2); font-weight: 600; }
.fine a { color: var(--link); }
.fine.in-card { margin-left: 0; margin-right: 0; }
.fine.version { font-family: var(--font-num); }
#version-home { text-align: center; margin-top: 18px; opacity: .7; }
.card.rows .fine.in-card { padding-bottom: 14px; }

/* The icons, wherever they sit: a box the size of the text beside them. */
.glyph { line-height: 0; display: inline-block; }
.glyph svg { width: 1em; height: 1em; display: block; }

/* ── buttons ───────────────────────────────────────────────────────────── */

.btn {
  font: inherit; font-size: 16px; font-weight: 600;
  padding: 12px 18px;
  border-radius: var(--r-mid);
  color: var(--ink);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.btn:active { opacity: .7; }
/* GOLD MEANS YOU, and the one thing to do next is yours. */
.btn-primary { background: var(--gold); color: var(--gold-ink); border-color: transparent; }
.btn-danger  { background: transparent; color: var(--bad); box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; border-color: transparent; }
.btn-quiet   { background: transparent; color: var(--link); box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; border-color: transparent; }
.btn-wide    { display: flex; width: 100%; margin-top: 18px; }
.btn-small   { padding: 7px 14px; font-size: 13px; }
.btn-glyph .glyph { font-size: 20px; }
.btn:disabled { opacity: .4; cursor: default; }

/* Round glass buttons in the game header — conquest's `.icon`. */
.icon {
  font: inherit; font-size: 20px; line-height: 0;
  width: 40px; height: 40px; border-radius: 20px;
  display: grid; place-items: center;
  color: var(--ink); cursor: pointer; flex: none; padding: 0;
}
.icon:active { opacity: .7; }

/* ── the gate ──────────────────────────────────────────────────────────── */

.gate { min-height: 100dvh; display: grid; place-items: center; padding: 24px; }
.signin-card {
  max-width: 340px; width: 100%; text-align: center;
  padding: 26px 22px 22px; border-radius: var(--r-card);
}
.signin-card h1 { margin: 14px 0 6px; font-size: 30px; letter-spacing: -.03em; }
.signin-card .lede { margin: 0 0 22px; color: var(--ink-2); font-size: 15px; line-height: 1.45; }
.signin-card .btn-wide { margin-top: 0; }
/* The brand: nine tiles, drawn with the board's own colours. */
.brand {
  display: grid; grid-template-columns: repeat(3, 26px); gap: 3px;
  justify-content: center; margin: 0 auto;
}
.brand .t {
  width: 26px; height: 26px; border-radius: 6px;
  display: grid; place-items: center;
  background: var(--cell-open); font-family: var(--font-num); font-weight: 700; font-size: 15px;
}
.brand .t.n1 { color: var(--n1); }
.brand .t.flag { background: var(--cell); box-shadow: inset 0 0 0 1px var(--cell-edge); color: var(--gold); font-size: 16px; }

/* ── size cards ────────────────────────────────────────────────────────── */

.size-cards { display: grid; gap: 12px; }

.size-card {
  display: flex; gap: 14px; align-items: stretch; width: 100%; text-align: left;
  font: inherit; color: inherit; cursor: pointer;
  padding: 16px;
  border-radius: var(--r-card);
}
.size-card:active { background: var(--glass-2); }
.size-card-body { flex: 1; min-width: 0; }
.size-card-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.size-card h3 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: -.3px; }
.size-card .shape { color: var(--dim); font-size: 12.5px; font-family: var(--font-num); white-space: nowrap; }
.size-card .blurb { margin: 5px 0 0; color: var(--ink-2); font-size: 14px; line-height: 1.45; }

/* THE SHAPE, DRAWN: a thumbnail of the board at its real proportions, so the
   three cards read as three boards and not three sentences. The tiny cells
   are rects in one svg, a mine dotted in at the real density. */
.size-thumb {
  flex: none; width: 54px; height: 72px; align-self: center;
  display: grid; place-items: center;
}
.size-thumb svg { width: 100%; height: 100%; display: block; }
.size-thumb rect { fill: var(--cell); stroke: var(--cell-edge); stroke-width: .25; }
.size-thumb rect.m { fill: var(--dim); stroke: none; }

.size-card-stats {
  display: flex; gap: 18px; margin-top: 12px;
  padding-top: 11px; border-top: .5px solid var(--line-2);
}
.size-card-stats div { display: flex; flex-direction: column; gap: 1px; }
.size-card-stats .v { font-family: var(--font-num); font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }
.size-card-stats .k { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .4px; }
.size-card-stats .v.none { color: var(--dim-2); }
/* Your best is yours. */
.size-card-stats .v.you { color: var(--gold); }

/* ── the game ──────────────────────────────────────────────────────────── */

/* NO SAFE-AREA INSET AND NO GLASS OFFSET HERE — both are on `.view`, which
   wraps this. They were on both, so a notched phone was pushed down by the
   status bar TWICE: 8 + inset from the section and 4 + inset again from this
   header, which on an iPhone is about 120px of nothing above the title. Adding
   the glass offset to both would have doubled that too, which is how the
   duplicate got noticed at all. One screen, one measurement, in the outer
   element that every screen shares. */
.game-head {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0 8px;
}
.game-stat { display: flex; flex-direction: column; align-items: center; line-height: 1.05; }
.game-stat-time { margin-left: auto; }
.game-stat-v {
  font-family: var(--font-num); font-size: 26px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.game-stat-k { font-size: 10.5px; color: var(--dim); text-transform: uppercase; letter-spacing: .5px; }
.game-head .game-stat:first-of-type { margin-left: 8px; }
.game-head .game-stat-time { margin-right: 8px; }

.grid-wrap { position: relative; }

.grid {
  display: grid;
  gap: var(--gap, 4px);
  grid-template-columns: repeat(var(--cols, 8), var(--cell-size, 40px));
  /* THE ELEMENT IS THE BOARD, exactly — no free space inside it to centre the
     cells in. It used to be a full-width box with `justify-content: center`,
     which draws the same picture but leaves the element's left edge up to 90px
     away from the first cell on a wide screen. touch.js measures the cells
     themselves now and no longer cares, but a box that lies about where its
     contents are is a trap for the next thing that reads this rect. Centring is
     the margin's job. */
  width: max-content;
  max-width: 100%;
  margin-inline: auto;
  /* The grid owns every gesture inside it. Without this a vertical drag to
     correct a mis-tap is stolen by the page scroller and the tap is lost. */
  touch-action: none;
}

/* A CELL IS A TILE WITH A GLYPH IN IT AND, ON iOS, A SWITCH OVER IT.
   `position: relative` is for the switch (haptics.js lays one over every cell,
   `inset: 0`); the glyph is a child span rather than the cell's own text so
   that painting a cell never has to assign textContent — which would delete
   the switch along with the old glyph. */
.cell {
  position: relative;
  width: var(--cell-size, 40px);
  height: var(--cell-size, 40px);
  border-radius: var(--r-cell);
  background: var(--cell);
  box-shadow: inset 0 0 0 1px var(--cell-edge);
  display: grid; place-items: center;
  font-family: var(--font-num);
  font-weight: 700;
  font-size: calc(var(--cell-size, 40px) * .52);
  line-height: 1;
  color: transparent;
  transition: background .09s ease, transform .09s ease;
}
.cell .g { line-height: 1; pointer-events: none; }
.cell .g svg { width: calc(var(--cell-size, 40px) * .6); height: calc(var(--cell-size, 40px) * .6); display: block; }

.cell.open {
  background: var(--cell-open);
  box-shadow: inset 0 1px 2px rgba(0,0,0,.10);     /* a hole has a lip */
}
.cell.n1 { color: var(--n1); } .cell.n2 { color: var(--n2); }
.cell.n3 { color: var(--n3); } .cell.n4 { color: var(--n4); }
.cell.n5 { color: var(--n5); } .cell.n6 { color: var(--n6); }
.cell.n7 { color: var(--n7); } .cell.n8 { color: var(--n8); }

/* The press preview. It follows the finger across the grid, and it is the
   whole reason a 30pt cell is usable: you can see which cell you are about to
   commit to while there is still time to move. */
.cell.press { background: var(--cell-press); transform: scale(.92); }

.cell.flag, .cell.q {
  color: var(--bad);
  background: var(--cell-flag);
}
.cell.q { color: var(--warn); }
.cell.flag .g svg { width: calc(var(--cell-size, 40px) * .62); height: calc(var(--cell-size, 40px) * .62); }

.cell.mine   { background: var(--cell-open); color: var(--ink-2); }
.cell.boom   { background: var(--bad); color: #fff; }
.cell.boom .g svg { width: calc(var(--cell-size, 40px) * .74); height: calc(var(--cell-size, 40px) * .74); }
/* A wrong flag: the flag, faded, with a stroke through it — the same mark
   the legend under the board uses. */
.cell.wrong  { color: var(--dim); }
.cell.wrong .g { opacity: .55; }
.cell.wrong::after, .review-key.wrong::after {
  content: ""; position: absolute; left: 22%; right: 22%; top: 50%; height: 2px;
  margin-top: -1px; background: var(--bad); transform: rotate(-35deg); border-radius: 1px;
}

.cell.hint {
  animation: hint 1.1s ease-out 3;
  box-shadow: inset 0 0 0 2px var(--gold);
}
@keyframes hint {
  0%, 100% { box-shadow: inset 0 0 0 2px var(--gold); }
  50%      { box-shadow: inset 0 0 0 5px var(--gold); }
}
@media (prefers-reduced-motion: reduce) {
  .cell, .cell.hint, .grid.review .cell.boom { transition: none; animation: none; }
}

/* SAFE MODE. When it is on the whole board is tinted, because a mode you can
   forget you are in is a mode that will lose you a game — the tint is on the
   grid itself rather than only on the switch, so it is impossible to be in
   Safe Mode while looking at the cells and not know it.

   THE CLASS IS `safe-mode`, NOT `safe`, AND THAT IS NOT A STYLE CHOICE. The
   toggle button below is `.safe`, and the first version put the same word on
   the grid: turning Safe Mode on therefore matched `.safe { display: flex }`
   and collapsed a 12x20 board into a single flex row inside a rounded panel.
   Nothing threw, no test failed, and every unit the board is made of was
   correct — it was only visible in a screenshot. Two elements, two meanings,
   one word. */
.grid.safe-mode .cell:not(.open) {
  background: color-mix(in srgb, var(--link) 10%, var(--cell));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--link) 30%, transparent);
}
.grid.safe-mode .cell.press { background: color-mix(in srgb, var(--link) 26%, var(--cell)); }

/* PAUSED. Every cell is drawn blank and identical — app.js has already stripped
   their glyphs and state classes, so this only has to stop the grid LOOKING
   like information. The board underneath is genuinely gone from the page, not
   covered up. */
.grid.paused .cell {
  background: var(--cell);
  box-shadow: inset 0 0 0 1px var(--cell-edge);
  color: transparent;
}

/* ── controls ──────────────────────────────────────────────────────────── */

.controls { display: flex; gap: 8px; margin-top: 12px; align-items: stretch; }
/* Hint and Pause are compact so Safe Mode keeps the room: it is the control
   that gets used mid-thought, the other two are deliberate. */
.controls .btn { padding-left: 14px; padding-right: 14px; flex: none; }
#pause-btn { padding-left: 12px; padding-right: 12px; }

.safe {
  flex: 1;
  display: flex; align-items: center; gap: 12px;
  font: inherit; text-align: left; cursor: pointer;
  padding: 10px 14px;
  border-radius: var(--r-mid);
  color: var(--ink);
}
.safe-dot {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--pill); color: var(--dim);
  box-shadow: inset 0 0 0 1px var(--line);
  display: grid; place-items: center; font-size: 16px;
  flex: none;
  transition: background .15s ease, box-shadow .15s ease, color .15s ease;
}
.safe[aria-pressed="true"] { background: var(--link); color: #fff; border-color: transparent; }
.safe[aria-pressed="true"] .safe-dot { background: #fff; color: var(--link); box-shadow: none; }
.safe[aria-pressed="true"] small { color: rgba(255,255,255,.82); }
.safe-text { display: flex; flex-direction: column; gap: 1px; }
.safe-text strong { font-size: 15px; font-weight: 600; }
.safe-text small { font-size: 12px; color: var(--dim); }

/* ── reviewing a lost board ────────────────────────────────────────────── */

.review-bar { margin-top: 18px; }
.review-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px;
  margin: 0 2px 10px;
  color: var(--dim); font-size: 12px; line-height: 1.4;
}
/* Each key travels with the words it explains. Without this the row wraps
   between a swatch and its label and the legend reads as three unrelated
   fragments — which is what it did on a phone the first time. */
.review-item { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
/* The legend is the same three glyphs the board is using, drawn the same way,
   because "the red one" is not a thing you can say to somebody who is
   colour-blind and not a thing you can find by scanning either. */
.review-key {
  position: relative;
  display: inline-grid; place-items: center;
  width: 20px; height: 20px; flex: none;
  border-radius: 5px; font-size: 12px;
  background: var(--cell-open); color: var(--ink-2);
}
.review-key.boom  { background: var(--bad); color: #fff; font-size: 14px; }
.review-key.wrong { background: var(--cell); color: var(--dim); box-shadow: inset 0 0 0 1px var(--cell-edge); }
.review-key.wrong .glyph { opacity: .55; }
.review-btns { display: flex; gap: 8px; }
.review-btns .btn { flex: 1; }

/* THE MINE THAT WENT OFF, FOUND WITHOUT HUNTING. Three pulses, then still —
   long enough to catch the eye on a board that has just been uncovered, short
   enough that it is not blinking at somebody trying to read the position. */
.grid.review .cell.boom { animation: boom-pulse 1.2s ease-out 3; }
@keyframes boom-pulse {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  40%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--bad) 45%, transparent); }
}

.game-note {
  position: absolute; left: 8px; right: 8px; bottom: 6px; z-index: 2;
  margin: 0; padding: 8px 12px; border-radius: 12px;
  background: var(--glass); -webkit-backdrop-filter: blur(12px) saturate(140%); backdrop-filter: blur(12px) saturate(140%);
  border: .5px solid var(--line); box-shadow: var(--shadow);
  color: var(--ink-2); font-size: 12.5px; line-height: 1.45; text-align: center;
  pointer-events: none;
  opacity: 0; transition: opacity .2s;
}
.game-note.show { opacity: 1; }
.game-note.at-top { bottom: auto; top: 6px; }
.game-note.warn { color: var(--warn); }

/* ── the end-of-game overlay ───────────────────────────────────────────── */

/* THE WHOLE SCREEN, NOT THE GRID'S BOX. It was absolute inside .grid-wrap,
   which on Pocket is 350px tall against a 460px card: the card overflowed
   the board top and bottom and lay across the controls. A scrim over
   everything, panel centred, is conquest's treatment and it is right here
   too — every control under it is dead while a card is up, the menu button
   included; the card's own buttons (Leaderboard, Look at the board) are the
   way on, and the menu sheet (z 30) opens above it. */
.overlay {
  position: fixed; inset: 0; z-index: 5;
  display: grid; place-items: center;
  padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  background: color-mix(in srgb, var(--bg) 60%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.overlay-card {
  width: min(320px, 90%);
  padding: 24px 20px;
  text-align: center;
  border-radius: var(--r-card);
}
.overlay-mark { font-size: 46px; line-height: 0; display: flex; justify-content: center; color: var(--ink-2); }
.overlay-mark.won { color: var(--gold); }
.overlay-mark.lost { color: var(--bad); }
.overlay-card h2 { margin: 12px 0 4px; font-size: 24px; letter-spacing: -.4px; }
.overlay-line { margin: 0; color: var(--dim); font-size: 14px; line-height: 1.5; }
.overlay-stats {
  display: flex; justify-content: center; gap: 22px;
  margin: 18px 0 0; padding: 14px 0 0;
  border-top: .5px solid var(--line-2);
}
.overlay-stats div { display: flex; flex-direction: column; gap: 2px; }
.overlay-stats dt { font-size: 10.5px; color: var(--dim); text-transform: uppercase; letter-spacing: .4px; order: 2; }
.overlay-stats dd { margin: 0; font-family: var(--font-num); font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums; order: 1; }
.overlay-submit { margin: 14px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--dim); min-height: 17px; }
.overlay-submit.bad { color: var(--warn); }
.overlay-submit.good { color: var(--good); }
.overlay-btns { display: flex; gap: 10px; margin-top: 18px; }
.overlay-btns .btn { flex: 1; padding-left: 10px; padding-right: 10px; }

/* ── segmented control ─────────────────────────────────────────────────── */

.segmented {
  display: flex; gap: 3px; padding: 3px;
  background: var(--glass-2); border-radius: var(--r-sm);
  border: .5px solid var(--line-2);
}
.segmented button {
  flex: 1; font: inherit; font-size: 14px; font-weight: 600;
  padding: 8px 6px; border: 0; border-radius: 8px;
  background: transparent; color: var(--ink); cursor: pointer;
}
.segmented button[aria-selected="true"], .segmented button.is-on {
  background: var(--gold); color: var(--gold-ink);
  box-shadow: var(--shadow);
}

/* ── the boards ────────────────────────────────────────────────────────── */

.board-list { margin-top: 12px; padding: 0; overflow: hidden; }
.board-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px;
  border-bottom: .5px solid var(--line-2);
}
.board-row:last-child { border-bottom: 0; }
.board-row.you { background: color-mix(in srgb, var(--gold) 14%, transparent); }
.board-rank {
  width: 26px; flex: none; text-align: right;
  font-family: var(--font-num); font-weight: 700; font-size: 15px;
  font-variant-numeric: tabular-nums; color: var(--dim);
}
.board-row.you .board-rank { color: var(--gold); }
.board-who { flex: 1; min-width: 0; }
.board-name { font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.board-row.you .board-name { font-weight: 700; }
.board-meta { font-size: 11.5px; color: var(--dim); margin-top: 1px; }
.board-time {
  font-family: var(--font-num); font-size: 18px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.board-empty { padding: 30px 20px; text-align: center; color: var(--dim); font-size: 14px; line-height: 1.5; }

/* ── settings rows ─────────────────────────────────────────────────────── */

.rows > .row, .rows > div > .row {
  padding: 13px 0;
  border-bottom: .5px solid var(--line-2);
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
}
.rows > .row:last-child, .rows > div > .row:last-child { border-bottom: 0; }
/* `.rows > .row` above is more specific than a bare `.row-stack`, so its
   `align-items: center` won and every stacked row's header shrank to its
   content and sat in the middle of the card instead of spanning it — label
   left, switch right. Matched at the same depth so the override actually
   overrides. */
.rows > .row.row-stack { flex-direction: column; align-items: stretch; gap: 8px; }
.row-head { display: flex; align-items: center; justify-content: space-between; }
.row-tools { display: flex; align-items: center; gap: 10px; }
.row-k { font-size: 15px; font-weight: 500; }
.row-v { font-family: var(--font-num); font-weight: 600; font-variant-numeric: tabular-nums; }
.row-note { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--dim); }
/* Settings › Name (README "Supporters"): a row you tap. A <button> so the
 * haptic switch lands on it like every other control; the value sits at the
 * right like a settings row anywhere, the chevron says it opens. */
.rows > button.row-tap {
  appearance: none; -webkit-appearance: none; width: 100%; background: none; border: 0;
  border-bottom: 1px solid var(--line-2); font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.rows > button.row-tap:active { background: var(--pill); }
.row-tap .row-v { margin-left: auto; font-family: inherit; font-weight: 500; color: var(--dim); display: inline-flex; align-items: center; gap: 6px; }
.row-tap .row-v.own { color: var(--ink); }
.row-tap .row-v .mark { margin-left: 0; }
.row-tap .chev { color: var(--dim); font-size: 22px; line-height: 1; margin-top: -2px; transition: transform .15s; }
.row-tap[aria-expanded="true"] .chev { transform: rotate(90deg); }
.name-actions { display: flex; gap: 8px; align-items: center; }
.name-actions .btn { padding: 8px 14px; font-size: 14px; }
/* 16px: below that iOS zooms the page to the field, and the sheet is a
 * scroll away from where it was. */
.name-input {
  width: 100%; box-sizing: border-box; font: inherit; font-size: 16px; padding: 10px 12px;
  color: var(--ink); background: var(--pill); border: 1px solid var(--line); border-radius: 10px;
}
.name-input:focus { outline: 2px solid var(--gold, #d9a520); outline-offset: 0; border-color: transparent; }

.switch { position: relative; display: inline-block; width: 51px; height: 31px; flex: none; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; z-index: 2; }
.switch span {
  position: absolute; inset: 0; border-radius: 999px;
  background: var(--pill); transition: background .2s ease;
}
.switch span::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 27px; height: 27px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25);
  transition: transform .2s ease;
}
.switch input:checked + span { background: var(--good); }
.switch input:checked + span::after { transform: translateX(20px); }
.switch input:disabled + span { opacity: .4; }

.prose p { margin: 0 0 12px; font-size: 14px; line-height: 1.6; color: var(--ink-2); }
.prose p:last-child { margin-bottom: 0; }
.prose .btn-wide { margin-top: 12px; }
.prose-meta { font-size: 12px !important; color: var(--dim) !important; font-family: var(--font-mono); }

/* Not dim like the fineprint it sits under: this one is the only line in the
   app that is asking for something to be done. */
.update-note { color: var(--warn) !important; }

/* ── the menu sheet ────────────────────────────────────────────────────── */

.sheet {
  position: fixed; inset: 0; z-index: 30; background: var(--bg);
  display: flex; flex-direction: column;
}
.sheet[hidden] { display: none; }
.sheet-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(12px + var(--glass-t) + env(safe-area-inset-top, 0px)) calc(16px + env(safe-area-inset-right, 0px)) 10px calc(16px + env(safe-area-inset-left, 0px));
  border-bottom: .5px solid var(--line-2);
}
.sheet-head h2 { margin: 0; font-size: 18px; }
.sheet-body {
  flex: 1; overflow: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  padding: 4px calc(16px + env(safe-area-inset-right, 0px)) calc(24px + env(safe-area-inset-bottom, 0px)) calc(16px + env(safe-area-inset-left, 0px));
  max-width: 560px; width: 100%; margin: 0 auto; box-sizing: border-box;
}
h2.sec { margin: 26px 4px 10px; font-size: 24px; font-weight: 700; letter-spacing: -.4px; }
.sheet-body h2.sec:first-child { margin-top: 14px; }
.fine.top { margin: 0 4px 12px; }

/* ── the supporter's mark, the toast, the daily card ─────────────────── */

/* The flag, in the games' gold — the same colour the other games' marks use. */
.mark { display: inline-block; color: #c48a12; margin-left: 5px; vertical-align: -2px; cursor: pointer; }
.mark svg { width: .95em; height: .95em; display: inline-block; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .mark { color: #f2b93a; } }
:root[data-theme="dark"] .mark { color: #f2b93a; }
#support-card .btn-wide { margin-top: 0; }
.support-thanks { margin: 0 0 10px; }
.support-note { margin-top: 10px; }

.toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(24px + env(safe-area-inset-bottom, 0px)); z-index: 40;
  max-width: min(92vw, 420px); padding: 10px 14px; border-radius: 14px;
  background: var(--glass); -webkit-backdrop-filter: blur(12px) saturate(140%); backdrop-filter: blur(12px) saturate(140%);
  border: .5px solid var(--line); box-shadow: var(--shadow);
  color: var(--ink); font-size: 14px; line-height: 1.4; text-align: center;
}
.toast-link { color: var(--link); font-weight: 600; }

/* Today's card: a gold hairline down its edge, so the four cards read as
   three sizes and one day. */
.size-card.daily { box-shadow: var(--shadow), inset 3px 0 0 var(--gold); }
.size-card.daily:disabled { opacity: .55; }

/* ── the banner ────────────────────────────────────────────────────────── */

.banner {
  position: fixed; left: 12px; right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  border-radius: var(--r-mid);
  font-size: 13.5px; line-height: 1.45;
  z-index: 20;
}

/* The banner's Retry. An installed home-screen app has no address bar and no
   reload button, so a message about a failed read used to be the end of the
   conversation. */
.banner .banner-retry {
  margin-left: auto;
  padding: 6px 13px;
  font: inherit; font-size: 12.5px; font-weight: 600; line-height: 1.2;
  border: 0; border-radius: 999px;
  background: var(--gold); color: var(--gold-ink); cursor: pointer;
}
.banner .banner-retry:disabled { opacity: .45; }
.banner .banner-retry + button { margin-left: 4px; }

/* Why the sign-in screen is showing, when there is a reason worth giving. */
.gate-msg {
  margin: 0 0 18px;
  padding: 11px 13px;
  border-radius: var(--r-mid);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  color: var(--warn);
  font-size: 13px; line-height: 1.45; text-align: left;
}
.banner button {
  margin-left: auto; flex: none;
  font: inherit; font-size: 20px; line-height: 1;
  border: 0; background: transparent; color: inherit; cursor: pointer;
}
.banner #banner-x { padding: 0 4px; }

/* The troubleshooting list. Numbered because the order is the advice: try the
   cheap thing first, and clearing site data is genuinely last. */
.tshoot { margin: 0; padding-left: 20px; font-size: 14px; line-height: 1.6; color: var(--ink-2); }
.tshoot li { margin-bottom: 10px; }
.tshoot li:last-child { margin-bottom: 0; }
.tshoot strong { font-weight: 600; }
/* The measured shell line is a different kind of statement from the advice
   above it — a fact about this device, not a step to take — so it gets a rule
   above it rather than reading as a fifth instruction. */
.tshoot + .prose-meta {
  margin-top: 14px; padding-top: 12px;
  border-top: .5px solid var(--line-2, var(--line));
}
/* The tap check sits under the shell line as a second fact about this device.
   `.prose-meta` sets its colour with !important, so saying something is wrong
   has to shout back. */
.prose-meta + .prose-meta { margin-top: 8px; }
.prose-meta.bad { color: var(--warn) !important; }

/* ── motion ─────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
