/* bubble — portrait first, because a hex grid eleven rows deep plus a launcher
 * is the shape of a phone held normally, and nothing else.
 *
 * 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.
 *
 * THE BUBBLES ARE THE EXCEPTION and they are not in this file at all. They live
 * in bubbles.js and do not change with the theme, exactly as wizard's card faces
 * and conquest's player colours do not: in a versus match two people are
 * describing one board to each other, and "the red one" has to mean the same
 * thing on two phones. The BACKGROUND changes; the bubbles do not.
 *
 * 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 the whole
 * declaration is dropped.
 */

:root {
  --bg:       #eef1f5;
  --panel:    #ffffff;
  --panel-2:  #f6f6f9;
  --glass:    rgba(255, 255, 255, .82);
  --glass-2:  rgba(255, 255, 255, .55);
  --dim-2:    #b0b0b6;
  --line:     rgba(60, 60, 67, .16);
  --line-2:   rgba(60, 60, 67, .08);
  --ink:      #000000;
  --ink-2:    #3c3c43;
  --dim:      #8a8a8e;
  --pill:     #efeff0;
  --good:     #1a9e4b;
  --warn:     #b8730a;
  --bad:      #e5322b;
  --link:     #007aff;
  --shadow:   0 1px 2px rgba(0,0,0,.04), 0 6px 20px rgba(0,0,0,.06);

  /* The board's own ground, and the launcher's cradle. */
  --field:    #e3e8ef;
  --launcher: rgba(60, 60, 67, .12);

  /* ── THE SEA ──────────────────────────────────────────────────────────
   * The room the app is in — a lagoon by day, deep water in the dark
   * theme. The fish and the weed are Kenney's (ATTRIBUTION.md) and drawn
   * by app.js's buildSea() from web/sea.svg; these tokens are the water,
   * the light and how much of the scenery shows through. The TANK is the
   * translucent panel the board sits in, so ninety bubbles keep one
   * contrast against a background that moves. */
  --sea-top:    #bfe9ff;
  --sea-mid:    #7cc6ee;
  --sea-deep:   #3d8fc9;
  --sea-ray:    rgba(255, 255, 255, .34);
  --sea-fish:   .85;
  --sea-weed:   .8;
  --sea-floor:  1;
  --tank:       rgba(255, 255, 255, .22);
  --tank-edge:  rgba(255, 255, 255, .55);
  --wall:       rgba(255, 255, 255, .16);
  --wall-edge:  rgba(255, 255, 255, .6);

  /* ── GOLD MEANS YOU ──────────────────────────────────────────────────
   * wizard's rule, kept: one accent, reserved for the thing the player is
   * doing. Here it is the primary button. (It was also the guide line, which
   * is gone — the pointer is brass, drawn on the canvas, and does not follow
   * the theme.) */
  --gold:     #d9a520;
  --guide:    #d9a520;

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

  --sat: env(safe-area-inset-top, 0px);
  --sab: env(safe-area-inset-bottom, 0px);
  --sal: env(safe-area-inset-left, 0px);
  --sar: env(safe-area-inset-right, 0px);

  /* ── iOS 26's TOP-EDGE GLASS ──────────────────────────────────────────
   * The status-bar blur reaches PAST the safe-area inset, so a control
   * placed exactly at `env(safe-area-inset-top)` sits under it and is
   * unreadable. ~20px more, once, in one variable rather than sprinkled
   * through the rules that use it. */
  /* iOS 26 BLURS THE TOP OF THE PAGE under the status bar, past the safe
   * area — the siblings' --glass-t, the same 20px, and both are needed:
   * the inset is where the bar is, the allowance is how far the blur
   * reaches. A browser tab reads it as margin. */
  --glass-t: 20px;
  --top: calc(var(--sat) + var(--glass-t));

  --font: -apple-system, BlinkMacSystemFont, system-ui, "SF Pro Text",
          "Helvetica Neue", sans-serif;
  --font-num: ui-rounded, -apple-system, "SF Pro Rounded", system-ui, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:       #12151a;
    --panel:    #1c1c1e;
    --panel-2:  #2c2c2e;
    --glass:    rgba(28, 28, 30, .88);
    --glass-2:  rgba(44, 44, 46, .75);
    --dim-2:    #636366;
    --line:     rgba(84, 84, 88, .5);
    --line-2:   rgba(84, 84, 88, .28);
    --ink:      #ffffff;
    --ink-2:    #ebebf5;
    --dim:      #8e8e93;
    --pill:     #2c2c2e;
    --good:     #30d158;
    --warn:     #ffd60a;
    --bad:      #ff453a;
    --link:     #0a84ff;
    --shadow:   0 1px 2px rgba(0,0,0,.5), 0 6px 20px rgba(0,0,0,.4);
    --field:    #171b22;
    --launcher: rgba(235, 235, 245, .14);
    --gold:     #e8c05a;
    --guide:    #e8c05a;
    --sea-top:    #10395c;
    --sea-mid:    #0a2743;
    --sea-deep:   #05121f;
    --sea-ray:    rgba(180, 220, 255, .10);
    --sea-fish:   .5;
    --sea-weed:   .45;
    --sea-floor:  .55;
    --tank:       rgba(0, 0, 0, .28);
    --tank-edge:  rgba(255, 255, 255, .14);
    --wall:       rgba(255, 255, 255, .06);
    --wall-edge:  rgba(255, 255, 255, .3);
  }
}
:root[data-theme="dark"] {
  --bg:       #12151a;
  --panel:    #1c1c1e;
  --panel-2:  #2c2c2e;
  --glass:    rgba(28, 28, 30, .88);
  --glass-2:  rgba(44, 44, 46, .75);
  --dim-2:    #636366;
  --line:     rgba(84, 84, 88, .5);
  --line-2:   rgba(84, 84, 88, .28);
  --ink:      #ffffff;
  --ink-2:    #ebebf5;
  --dim:      #8e8e93;
  --pill:     #2c2c2e;
  --good:     #30d158;
  --warn:     #ffd60a;
  --bad:      #ff453a;
  --link:     #0a84ff;
  --shadow:   0 1px 2px rgba(0,0,0,.5), 0 6px 20px rgba(0,0,0,.4);
  --field:    #171b22;
  --launcher: rgba(235, 235, 245, .14);
  --gold:     #e8c05a;
  --guide:    #e8c05a;
  --sea-top:    #10395c;
  --sea-mid:    #0a2743;
  --sea-deep:   #05121f;
  --sea-ray:    rgba(180, 220, 255, .10);
  --sea-fish:   .5;
  --sea-weed:   .45;
  --sea-floor:  .55;
  --tank:       rgba(0, 0, 0, .28);
  --tank-edge:  rgba(255, 255, 255, .14);
  --wall:       rgba(255, 255, 255, .06);
  --wall-edge:  rgba(255, 255, 255, .3);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  /* 100dvh, not 100vh. On iOS Safari `vh` is the LARGE viewport — the one that
   * assumes the address bar is hidden — so a board sized to it is taller than
   * the screen, and the bottom row of bubbles and the launcher sit under the
   * chrome until you scroll, which this app does not do. */
  height: 100dvh;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.45 var(--font);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

body {
  /* NOTHING SCROLLS AND NOTHING RUBBER-BANDS. A drag that starts on the board
   * is an aim; if the page can move under it, a shot becomes a scroll and the
   * bubble never fires. eval/board_probe.js asserts this at three sizes. */
  overflow: hidden;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.screen { height: 100%; display: flex; flex-direction: column; position: relative; z-index: 1; }
.screen[hidden], [hidden] { display: none !important; }

/* ── chrome ───────────────────────────────────────────────────────────── */

/* THE BAR — conquest's lobby header, gems' and minesweeper's too: the title
   on the left with a line under it, one round glass menu button on the
   right, and nothing else on the screen to navigate by. It scrolls with the
   page; there is no fixed chrome and no tab bar. */
.view-head { padding: 12px 4px 18px; }
.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; }
/* Ink, not dim grey, for the line under the title: it sits on open water. */
.view-sub { margin: 4px 0 0; color: var(--ink-2); opacity: .8; font-size: 15px; }

.wrap {
  flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: calc(8px + var(--top)) calc(16px + var(--sar)) calc(24px + var(--sab)) calc(16px + var(--sal));
}

h2.sec {
  margin: 22px 4px 10px;
  font-size: 13px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .06em; color: var(--dim);
}
.wrap > h2.sec { color: var(--ink); opacity: .7; }   /* on open water, ink */

/* THE GLASS. One recipe for every panel in the app — translucent, blurred,
   a hairline edge, a soft shadow — conquest's, verbatim, so the games read
   as one house. */
.card, .mode-card, .signin-card, .overlay-card, .icon-btn, .seg, .entry, .note-badge {
  background: var(--glass); backdrop-filter: blur(12px) saturate(140%); -webkit-backdrop-filter: blur(12px) saturate(140%);
  border: .5px solid var(--line); box-shadow: var(--shadow);
}
.icon-btn {
  appearance: none; font: inherit; font-size: 20px; line-height: 1;
  width: 40px; height: 40px; padding: 0; flex: none;
  display: grid; place-items: center;
  border-radius: 20px; color: var(--ink);
  cursor: pointer;
}
.icon-btn:active { opacity: .7; }
.icon-btn svg { display: block; }
.icon-btn.close { font-size: 18px; }

button {
  font: inherit; color: var(--ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-mid);
  padding: 10px 14px; cursor: pointer; position: relative;
}
button:active { opacity: .7; }
button:disabled { opacity: .4; cursor: default; }
button.primary { background: var(--gold); border-color: transparent; color: #1c1500; font-weight: 600; }
button.big { width: 100%; padding: 14px; font-size: 17px; }
button.icon { background: transparent; border: 0; font-size: 20px; padding: 6px 10px; line-height: 1; }
button.glass { background: var(--glass); border: 1px solid var(--line-2);
               -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
/* Conquest's buttons, for the menu: glass with a hairline, 14px corners; the
   one thing to do next is GOLD, a quiet button is a link, a danger one is red
   text and nothing else. */
.btn {
  font-size: 16px; font-weight: 600; padding: 12px 18px;
  border: .5px solid var(--line); border-radius: var(--r-mid);
  background: var(--glass-2); color: var(--ink);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  box-shadow: var(--shadow);
}
.btn-primary { background: var(--gold); color: #1a1200; border-color: transparent; }
.btn-danger  { background: transparent; border-color: transparent; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; color: var(--bad); }
.btn-quiet   { background: transparent; border-color: transparent; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; color: var(--link); }
.btn-wide    { display: block; width: 100%; margin-top: 18px; padding: 14px; font-size: 17px; }
.btn-test    { margin-left: auto; padding: 7px 14px; font-size: 14px; }

.card, .signin-card { border-radius: var(--r-card); padding: 16px; margin-bottom: 12px; }
.card.rows, .card.board-list { padding: 0; margin-bottom: 0; overflow: hidden; }
.card.prose { margin-bottom: 0; }
.prose a { color: var(--link); text-decoration: none; }
.fineprint { margin: 14px 6px 0; color: var(--dim); font-size: 12.5px; line-height: 1.55; }
.fineprint strong { color: var(--ink-2); font-weight: 600; }
.fineprint.top { margin: 0 4px 12px; }
.fineprint:empty { display: none; }
.card h2 { margin: 0 0 8px; font-size: 17px; }
.lede { color: var(--ink-2); margin: 0 0 14px; }
.fine { color: var(--dim); font-size: 13px; margin: 8px 0 0; }
/* Fine print that sits on open water rather than in a card takes the ink. */
.wrap > .fine, #boards > .fine { color: var(--ink-2); opacity: .8; }
.daily-head .fine { color: inherit; }

.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.row > .primary { flex: 1; }
.card > button.primary { display: block; width: 100%; margin-top: 10px; }
.field-label, .field { display: block; margin-bottom: 10px; }
.field span { display: block; font-size: 13px; color: var(--dim); margin-bottom: 4px; }
input[type=text] {
  width: 100%; font: inherit; padding: 10px 12px; color: var(--ink);
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-mid);
}
.code-input { flex: 1; text-transform: uppercase; letter-spacing: .3em; font-family: var(--font-num); }
.check { display: flex; gap: 10px; align-items: flex-start; margin: 10px 0; }
.check input { margin-top: 3px; }

.note-badge {
  background: var(--pill); border-radius: var(--r-mid); padding: 10px 12px;
  font-size: 13px; color: var(--ink-2); margin-bottom: 12px;
}

/* ── the sign-in screen ───────────────────────────────────────────────── */

#gate { align-items: center; justify-content: center; padding: 24px; }
.signin-card { max-width: 380px; text-align: center; }
.signin-card h1 { margin: 6px 0 8px; font-size: 30px; letter-spacing: -.02em; }
/* The app's icon, shown the way a home screen shows it: a rounded tile.
 * It IS icon-192.png — the three marbles bin/icons.js makes — rather than a
 * CSS drawing that would drift from it (the first version was a red disc
 * that outlived the icon it copied). */
.brand {
  display: block; width: 68px; height: 68px; margin: 0 auto; border-radius: 16px;
  box-shadow: 0 6px 18px rgba(0,0,0,.35), 0 0 0 1px rgba(255,255,255,.08);
}

/* ── the mode cards ───────────────────────────────────────────────────── */

.mode-cards { display: grid; gap: 12px; }
.mode-card {
  display: block; width: 100%; text-align: left;
  font: inherit; color: inherit; cursor: pointer;
  padding: 18px; border-radius: var(--r-card);
}
.mode-card:active { opacity: .8; }
/* THE DAILY IS THE FIRST CARD AND WEARS THE ACCENT: one board, once, today. */
.mode-card.daily { border-color: color-mix(in srgb, var(--gold) 55%, transparent); }
.mode-card-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.mode-card h3 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: -.3px; }
.mode-card .shape { color: var(--dim); font-size: 13px; font-family: var(--font-mono, ui-monospace, monospace); white-space: nowrap; }
.mode-card .blurb { margin: 6px 0 0; color: var(--ink-2); font-size: 14px; line-height: 1.45; }
.mode-card-stats {
  display: flex; gap: 18px; margin-top: 14px;
  padding-top: 13px; border-top: 1px solid var(--line-2);
}
.mode-card-stats div { display: flex; flex-direction: column; gap: 1px; }
.mode-card-stats .v { font-family: var(--font-num); font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }
.mode-card-stats .k { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .4px; }
.mode-card-stats .v.none { color: var(--dim-2); }
.mode-card-cta { margin: 12px 0 0; font-size: 13px; color: var(--link); font-weight: 600; }
.mode-card.done .mode-card-cta { color: var(--dim); font-weight: 500; }

.list { display: grid; gap: 8px; }
.entry {
  display: flex; gap: 10px; align-items: center; padding: 10px 12px;
  border-radius: var(--r-mid);
}
/* A daily row: the time is the headline, the shots the qualifier beside it —
 * budget's rule about a cost sitting next to the number rather than inside it.
 * A burial and a live game take no rank and say so in words. */
.entry .sub { font-size: 12px; color: var(--dim); font-family: var(--font-num); }
.entry.live .val, .entry.buried .val { font-weight: 500; color: var(--dim); }
.entry .rank.none { color: transparent; }
.daily-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.daily-head .fine { margin: 0; }
.entry .rank { width: 26px; color: var(--dim); font-family: var(--font-num); }
.entry .who { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.entry .val { font-family: var(--font-num); font-weight: 600; }
.entry.you { outline: 2px solid var(--gold); outline-offset: -2px; }

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

#screen-game { background: transparent; position: relative; }

/* The board's box, and in versus it is a ROW: your board, then theirs. The
 * canvas is inset from the top by the safe area so the ceiling row is never
 * under the status bar; the footer below takes its own height out of the flex.
 *
 * No padding on the field — the canvas is absolutely positioned inside
 * #board-box, so padding here would do nothing for it and would only be a
 * second number app.js could read instead of the right one. */
.field { flex: 1; display: flex; min-height: 0; gap: 6px;
         padding: 0 calc(6px + var(--sar)) 0 calc(6px + var(--sal)); }
#board-box { flex: 1; position: relative; min-width: 0; }
#canvas { position: absolute; inset: 0; display: block; touch-action: none; }
/* THE TANK: a pane of glass the board hangs in, over the sea. It is what
 * keeps ninety bubbles at one contrast while fish go by behind them; the
 * canvas draws the two walls and the death line inside it. Inset from the
 * top by the same variable the canvas is, so the pane and the board agree. */
#board-box::before {
  content: ""; position: absolute; inset: 0;
  background: var(--tank);
  border: 1px solid var(--tank-edge); border-bottom: 0;
  border-radius: var(--r-mid) var(--r-mid) 0 0;
  pointer-events: none;
}
#canvas { z-index: 1; }
/* THE BALL AS A BUTTON. In tap mode app.js lays this over the loaded bubble
 * so a real finger lands on a real <button> — which is the only thing iOS
 * will give a haptic for (haptics.js). It draws nothing itself: the ring on
 * the canvas is its whole appearance. `touch-action: manipulation` and no
 * preventDefault anywhere on it, so the overlay switch's native toggle is
 * left alone to tick. */
.fire {
  position: absolute; z-index: 2; border-radius: 50%;
  background: transparent; border: 0; padding: 0; margin: 0;
  touch-action: manipulation; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fire:active { opacity: 1; }

/* The opponent's column. A share of the width, not a layer over it. */
.mirror { width: 28%; max-width: 180px; flex: none; align-self: start; }

/* THE HEAD — minesweeper's game-head, one row above the tank: it takes the
 * safe area itself, so the canvas no longer needs an inset for the notch. */
.game-head {
  display: flex; align-items: center; gap: 8px;
  padding: calc(8px + var(--top)) calc(12px + var(--sar)) 8px calc(12px + var(--sal));
}
.game-stat { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.05; min-width: 0; margin-left: 6px; }
.game-stat-v { font-family: var(--font-num); font-size: 26px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.game-stat-k { font-size: 10.5px; color: var(--ink-2); opacity: .8; text-transform: uppercase; letter-spacing: .5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46vw; }
.game-head .drop-pip { margin-left: auto; margin-right: 6px; }

/* Shots until the ceiling comes down, as pips rather than a number: a player
 * mid-shot reads a shape faster than a digit, and this is the one piece of
 * information that decides whether to take a safe shot. */
.drop-pip { display: flex; gap: 4px; align-items: center; height: 26px; }
.drop-pip i { width: 7px; height: 7px; border-radius: 50%; background: var(--dim); opacity: .4; }
.drop-pip i.lit { background: var(--bad); opacity: 1; }

/* Their board, at the same aspect as yours so a glance at it means something.
 * ALWAYS VISIBLE, never behind a gesture: the glance is what makes versus
 * tense, and a board you have to ask for is a board you stop asking for. */
.mirror {
  border-radius: var(--r-mid); overflow: hidden;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: var(--shadow);
}
#mirror-canvas { display: block; width: 100%; }
.mirror-label {
  display: flex; justify-content: space-between; gap: 6px;
  padding: 5px 7px; font-size: 11px; color: var(--ink-2);
  background: var(--glass); border-top: 1px solid var(--line-2);
}
.mirror-say { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* THE SUPPORTER'S MARK — this game's own bubble, in the games' gold (the
 * colour minesweeper's flag and gems' diamond use). A ring with a highlight:
 * a bubble at glyph size, not a filled dot. Inline after the name, on every
 * board row, the mirror's label and the Settings line. */
.mark { display: inline-block; width: .8em; height: .8em; margin-left: 5px; vertical-align: -1px;
        border-radius: 50%; border: 1.5px solid #c48a12; position: relative; cursor: pointer; }
.mark::after { content: ''; position: absolute; left: 22%; top: 16%; width: 28%; height: 28%;
               border-radius: 50%; background: #c48a12; opacity: .8; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .mark { border-color: #f2b93a; } :root:not([data-theme="light"]) .mark::after { background: #f2b93a; } }
:root[data-theme="dark"] .mark { border-color: #f2b93a; }
:root[data-theme="dark"] .mark::after { background: #f2b93a; }
.mirror-label .mark { margin-left: 3px; vertical-align: -1px; }
#support-card .btn-wide { margin-top: 0; }
.fineprint.in-card { margin: 10px 0 0; }
.support-thanks { margin: 0 0 10px; }
.mirror.hit { animation: shake .32s ease; }
@keyframes shake {
  0%,100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

.overlay {
  position: absolute; inset: 0; z-index: 5;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(0,0,0,.45);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.overlay-card {
  background: var(--panel); border-radius: var(--r-card); padding: 20px;
  width: 100%; max-width: 340px; box-shadow: var(--shadow);
}
.overlay-card h2 { margin: 0 0 6px; font-size: 24px; }
.stat-row { display: flex; gap: 14px; margin: 12px 0; flex-wrap: wrap; }
.stat-row div { flex: 1; min-width: 70px; }
.stat-row b { display: block; font: 700 19px var(--font-num); }
.stat-row span { font-size: 11px; color: var(--dim); }

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

/* Full-screen, over whatever was there, the game underneath exactly where it
   was — conquest's, gems' and minesweeper's. A menu button opens it; ✕
   closes it. */
.sheet {
  position: fixed; inset: 0; z-index: 30; background: var(--bg);
  display: flex; flex-direction: column;
}
.sheet-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(12px + var(--top)) calc(16px + var(--sar)) 10px calc(16px + var(--sal));
  border-bottom: .5px solid var(--line-2);
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
}
.sheet-head h2 { margin: 0; font-size: 18px; }
.sheet-body {
  flex: 1; overflow: auto; -webkit-overflow-scrolling: touch;
  padding: 4px calc(16px + var(--sar)) calc(24px + var(--sab)) calc(16px + var(--sal));
  max-width: 560px; width: 100%; margin: 0 auto; box-sizing: border-box;
}
.sheet-body h2.sec:first-child, .sheet-body section:first-child h2.sec { margin-top: 14px; }
.prose { padding: 16px; }
.prose p { margin: 0 0 12px; font-size: 14px; line-height: 1.6; color: var(--ink-2); }
.prose p:last-child { margin-bottom: 0; }

/* settings rows */
.rows > .row {
  padding: 14px 16px;
  border-bottom: 1px solid var(--line-2);
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: nowrap;
}
.rows > .row:last-child { border-bottom: 0; }
.rows > .row.row-stack { flex-direction: column; align-items: stretch; gap: 8px; }
.row-head { display: flex; align-items: center; justify-content: space-between; 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; text-align: right; }
.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); }

/* the segmented control: gold means the one that is on */
.seg {
  display: flex; gap: 3px; padding: 3px;
  background: var(--glass-2); border-radius: var(--r-sm); border-color: var(--line-2); box-shadow: none;
}
.seg button {
  flex: 1; font: inherit; font-size: 14px; font-weight: 500;
  padding: 8px 6px; border: 0; border-radius: 8px;
  background: transparent; color: var(--ink); cursor: pointer;
}
.seg button[aria-selected="true"] { background: var(--gold); color: #1a1200; font-weight: 600; box-shadow: var(--shadow); }
.seg-sm { max-width: 230px; }
.seg-wide { max-width: none; }

/* the leaderboard */
.board-list { margin-top: 12px; }
.board-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px 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-meta { font-size: 11.5px; color: var(--dim); margin-top: 1px; }
.board-score { font-family: var(--font-num); font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }
.board-row.live .board-score, .board-row.buried .board-score { font-weight: 500; color: var(--dim); }
.board-empty { padding: 30px 20px; text-align: center; color: var(--dim); font-size: 14px; line-height: 1.5; }

.chip { display: inline-block; padding: 2px 8px; border-radius: 999px;
        background: var(--pill); font-size: 12px; color: var(--ink-2); }
.chip.good { background: color-mix(in srgb, var(--good) 20%, transparent); color: var(--good); }
.chip.warn { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn); }

/* ── the new-version banner ───────────────────────────────────────────── */

.banner {
  position: fixed; z-index: 20;
  left: calc(10px + var(--sal)); right: calc(10px + var(--sar));
  bottom: calc(10px + var(--sab));
  display: flex; gap: 10px; align-items: center;
  padding: 12px 14px; border-radius: var(--r-mid);
  background: var(--glass); border: 1px solid var(--line);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  box-shadow: var(--shadow);
}
.banner span { flex: 1; font-size: 14px; }

/* ── the sea ──────────────────────────────────────────────────────────────
 * Fixed and under everything, like bookworm's wall: a room the app moves in
 * front of, not a background that scrolls with a list. app.js's buildSea()
 * fills it from web/sea.svg. Every moving thing is a composited transform
 * on its own layer, so the whole scene costs the GPU a few quads; and every
 * one of them stops under prefers-reduced-motion, where a fish crossing
 * behind a menu is a distraction rather than a pleasure. */
.sea {
  position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  background: linear-gradient(180deg, var(--sea-top) 0%, var(--sea-mid) 48%, var(--sea-deep) 100%);
}
/* Light through the surface: soft slanted bands that drift, fading out by
   mid-depth. Drawn, not shipped — it is geometry. */
.sea-rays {
  position: absolute; inset: -20% -40%;
  background: repeating-linear-gradient(112deg,
    transparent 0 90px, var(--sea-ray) 90px 130px, transparent 130px 260px);
  -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,.9) 0%, transparent 62%);
  mask-image: linear-gradient(180deg, rgba(0,0,0,.9) 0%, transparent 62%);
  animation: rays 26s linear infinite;
}
@keyframes rays { from { transform: translateX(0); } to { transform: translateX(260px); } }

.sea svg { position: absolute; display: block; overflow: visible; }
.fish {
  top: var(--y); width: var(--s); height: var(--s); left: -12vw;
  opacity: var(--sea-fish);
  animation: swim var(--d) linear infinite; animation-delay: var(--delay);
}
.fish.back { transform: scaleX(-1); animation-name: swim-back; }
.fish > svg { position: static; width: 100%; height: 100%; animation: bob 3.6s ease-in-out infinite; animation-delay: var(--delay); }
@keyframes swim      { from { transform: translateX(0); } to { transform: translateX(124vw); } }
@keyframes swim-back { from { transform: translateX(124vw) scaleX(-1); } to { transform: translateX(0) scaleX(-1); } }
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9%); } }

.rise {
  left: var(--x); bottom: -8vh; width: var(--s); height: var(--s); opacity: .55;
  animation: rise var(--d) linear infinite; animation-delay: var(--delay);
}
@keyframes rise { from { transform: translateY(0); opacity: .5; } 80% { opacity: .5; } to { transform: translateY(-112vh); opacity: 0; } }

/* The floor: silhouettes of weed and rock, the weed itself, then the sand,
   each a row of symbols laid across the width by app.js. Sized to the bottom
   inset so the sand runs under the home indicator rather than stopping at it. */
.floor { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: flex-end; }
.floor svg { position: static; flex: none; }
.floor.far  { opacity: calc(var(--sea-weed) * .55); bottom: calc(var(--sab) + 44px); }
.floor.weed { opacity: var(--sea-weed); bottom: calc(var(--sab) + 36px); }
.floor.weed svg { transform-origin: 50% 100%; animation: sway 4.8s ease-in-out infinite; animation-delay: var(--delay); }
.floor.sand { opacity: var(--sea-floor); height: calc(var(--sab) + 52px); }
.floor.sand svg { height: 100%; width: auto; }
@keyframes sway { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }

@media (prefers-reduced-motion: reduce) {
  .sea-rays, .fish, .fish > svg, .rise, .floor.weed svg { animation: none; }
  .fish { left: var(--x, 20vw); }
  .rise { display: none; }
}

/* ── landscape ────────────────────────────────────────────────────────────
 * NOT a rotate trick. conquest draws itself sideways on an upright phone
 * because forty-two countries need the long edge; this board is the other
 * shape and is happiest upright, so a phone turned sideways gets a board
 * that is simply shorter, centred, with the mirror moved out of the corner
 * it would otherwise cover. Nothing is forced on anybody. */
@media (orientation: landscape) and (max-height: 520px) {
  .bar { padding-top: calc(var(--sat) + 6px); }
  .hud { padding-top: calc(var(--sat) + 6px); }
  /* Sideways there is width to spare and height there is not, so the
     opponent's column can afford to be bigger. */
  .mirror { width: 22%; }
}
