/* ============================================================================
   Vector Puzzle — Mobile
   ----------------------------------------------------------------------------
   Portrait-first. Three rows: top bar, board, panel.

   The board is a display surface, not an input surface — the arrows and the
   word bank in the panel drive everything. That is what lets a 35×28 grid work
   on a phone where a cell renders at ~11px.

   Two mobile-specific notes:
   · Every height derives from --app-h, which JS sets from
     visualViewport.height — the genuinely visible area. `vh` is the EXPANDED
     viewport on iOS Safari, and even `dvh` does not account for the bottom
     toolbar overlaying content, which clipped the Immersive keyboard. The
     vh/dvh values below are only fallbacks for before JS runs.
   · Everything interactive is >= 44px, per the platform touch-target minimum.
   ========================================================================== */

:root {
  --bg:          #17120f;
  --bg-raised:   #221b16;
  --bg-sunken:   #100c0a;
  --line:        #3a2f26;
  --text:        #f2ece4;
  --text-dim:    #a3968a;
  --gold:        #d9a441;
  --gold-soft:   rgba(217,164,65,0.18);

  /* The colour every finished word sits on, taken from the piece art. */
  --piece-blue:  #c4e2ff;

  /* Slot highlight. Set as an RGB triple so one choice drives the border, the
     fill and the glow at three different alphas — see applyHighlight(). */
  --hl-rgb:      217,164,65;
  --green:       #6fbf73;
  --red:         #e0574f;

  --tap:         44px;
  --radius:      14px;

  /* Usable viewport height. Overwritten in px by setAppHeight(); these two are
     the pre-JS fallback, in ascending order of accuracy. */
  --app-h:       100vh;
  --app-h:       100dvh;

  --panel-min:   clamp(150px, calc(var(--app-h) * 0.28), 240px);

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  /* Overwritten in px by observeTopbarHeight(), which measures the real
     rendered #topbar. This 56px is only the pre-JS fallback (and matches the
     default rendering exactly) — the tablet breakpoint alone makes the topbar
     72px, which is exactly the kind of drift a hardcoded guess here invites. */
  --topbar-h: 56px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overscroll-behavior: none;       /* kill pull-to-refresh over the board */
  background: var(--bg);
  color: var(--text);
  font-family: 'Sen', system-ui, -apple-system, sans-serif;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
}

/* Rows: top bar · board (sized to its own 5:4 ratio) · panel (the rest).
   The board is width-constrained on every phone — 1750/1400 means it can never
   be taller than width/1.25 — so giving it `1fr` stranded ~230px of dead space.
   Sizing it to its natural height hands that space to the word bank instead. */
body {
  display: grid;
  grid-template-rows: auto auto 1fr;
  height: var(--app-h);
  padding: var(--safe-t) var(--safe-r) var(--safe-b) var(--safe-l);
}

[hidden] { display: none !important; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ══ Top bar ══════════════════════════════════════════════════════════════ */

#topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 6px 8px;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--line);
}

/* Timer and counter share a row, with the active game mode as a quiet
   sub-label beneath them. */
#topbar-stats {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 0;
}
#stats-row {
  display: flex;
  gap: 12px;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
}
#timer   { font-size: 17px; font-weight: 700; color: var(--gold); }
#counter { font-size: 14px; color: var(--text-dim); }

#mode-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  opacity: 0.75;
  line-height: 1;
  white-space: nowrap;
}

.icon-btn {
  min-width: var(--tap);
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 8px;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 16px;
  cursor: pointer;
}
.icon-btn:active { background: rgba(255,255,255,0.08); }
.icon-btn:disabled { opacity: 0.35; }
#peek-count { font-size: 13px; color: var(--text-dim); }
#vowel-btn { font-size: 13px; font-weight: 700; color: var(--gold); }

/* ══ Board ════════════════════════════════════════════════════════════════ */

/* The board is width-driven: aspect-ratio gives it its natural height and it
   fills the screen edge to edge with no letterboxing. The max-height is a
   FLOOR-GUARD, not a design size — it only bites when the panel underneath
   could not otherwise function, and then only by as much as it has to.

   It used to be a flat percentage per mode (0.58 / 0.42 / 0.40), which meant
   the board was clamped on every phone whether or not it needed to be: the
   grid rendered smaller with grey bars down both sides, and it changed size
   whenever you switched modes. Expressed as "whatever remains after the
   panel's real needs", a mainstream phone clears the natural height in all
   three modes, so aspect-ratio wins and the board is full width and identical
   everywhere. Only genuinely small screens shrink it. */
#board-area {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  background: var(--bg-sunken);
  width: 100%;
  aspect-ratio: 1750 / 1400;
  /* tabs 52 + stepper 50 + one row of words + gaps */
  --panel-need: 200px;
  max-height: calc(var(--app-h) - var(--topbar-h) - var(--panel-need));
}

/* Each mode declares only what its panel genuinely needs. Above that the board
   is left alone.

   These are re-derived from the ACTUAL CSS below, not eyeballed — a prior
   pass here was off by 4-16px per mode (verified against #panel's real
   padding/gap/child heights), which is exactly the kind of drift a
   hand-maintained estimate invites. #panel's own box: padding 6px 8px 0,
   gap 6px between every child (an empty child, e.g. Guided's #word-dots,
   contributes no gap). Shared children: #bank-tabs 52px (.seg padding 4+4
   plus .seg-btn min-height var(--tap)=44), #nav-row 50px (.nav-arrow
   height), #slot-clue 43px (padding 5+6 plus height: calc(2 * 1.3em) at
   12px font = 31.2px, rounded up).
   If any of those numbers change, re-sum this before trusting the reserve —
   nothing here checks itself against the real render. */

/* Guided: 6 + 52 + 6 + 50 + 6 + 43 + 6 + wgrid's own floor (2 rows of
   minmax(44px,1fr) + 10px gap = 98px) = 267, plus a few px of cushion. */
body.guided #board-area  { --panel-need: 270px; }

/* Classic: 6 + 52 + 6 + 50 + 6 + 43 + 6 + TWO rows of chips (2*40 + 7 gap =
   87) + 6 + #word-dots (min-height 18) = 280, plus a few px of cushion.
   Two rows, not three — this is a floor, not a target. The panel gets whatever
   the board does not take, so reserving less means the board wins more often
   and the pager simply deals the extra rows it does get. */
body.classic #board-area { --panel-need: 284px; }

/* Immersive carries the game's own keyboard, so the panel is taller and the
   layout is completely static — nothing slides over the board and nothing
   resizes mid-word. This is what keeps the bottom key row on screen even on a
   320x480 phone.
   This one IS a hard requirement: below it the bottom row of keys is cut off
   — and unlike Guided/Classic, it can't be a flat number: .key's own height
   is itself clamp(34px, var(--app-h) * 0.052, 44px), so the keyboard gets
   TALLER on a taller phone, growing from 3*34=102px up to 3*44=132px. A flat
   reserve sized for the 34px floor (as this used to be) is only safe up to
   ~680px of --app-h; every phone taller than that — which is most of them —
   would under-reserve and clip the bottom row. The same clamp() is repeated
   here so the reserve always matches what .key actually renders as:
   6 + 52 + 6 + 50 + 6 + 43 + 6 + #letter-boxes (50) + #keyboard (3 * .key's
   height + 2*5px gap + 6px padding-bottom) = 235 + 3 * key-height, plus a
   few px of cushion. */
body.letter-entry #board-area {
  --panel-need: calc(238px + 3 * clamp(34px, var(--app-h) * 0.052, 44px));
}

#board-viewport {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  touch-action: none;         /* pan and pinch are ours */
}

/* Fixed 1750×1400 coordinate space. JS sets --fit (scale to the viewport) plus
   --zoom/--px/--py for pinch and pan, so children position in raw board units
   straight out of solution-map.json. */
#board {
  position: absolute;
  top: 0; left: 0;
  width: 1750px;
  height: 1400px;
  transform-origin: 0 0;
  transform: translate(var(--px, 0px), var(--py, 0px))
             scale(calc(var(--fit, 0.2) * var(--zoom, 1)));
  will-change: transform;
}

#board-bg {
  position: absolute;
  inset: 0;
  background: #ffffff;
  box-shadow: 0 0 0 8px #2a2119, 0 10px 40px rgba(0,0,0,0.6);
}

#grid-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 200ms ease;
}
#board.no-grid #grid-layer { opacity: 0; }

/* The picture — the default pastel mosaic (outside.svg, drawn at its authored
   0.65 opacity) or a chosen theme photo — divided into a 10x8 grid of tiles,
   each cropped from the same full-board image via background-position. Every
   tile starts invisible; .revealed plays the same landing bounce used for
   word pieces (see .piece.landing below), so a placement reads as "the
   picture nudges forward" too, not just "a word appeared". See
   resetBgReveal()/revealNextBgTiles() in game.js. */
#bg-reveal-layer { position: absolute; inset: 0; pointer-events: none; }
.bg-tile {
  position: absolute;
  background-repeat: no-repeat;
  opacity: 0;
}
.bg-tile.revealed { opacity: 1; animation: land 380ms cubic-bezier(0.2,0.9,0.3,1.2); }

#slot-layer, #piece-layer { position: absolute; inset: 0; pointer-events: none; }

/* -- empty slots --
   Kept in the layer for hit geometry and for the highlight to attach to, but
   drawn as nothing. Outlining every unsolved word littered the board with
   dashed boxes that read as leftover gridlines, and they earn nothing here:
   the arrows move the highlight, so the player never aims at a slot. The
   border stays declared at transparent so the shake still has an edge to
   flash red and so nothing shifts by 3px when it does. */
.slot {
  position: absolute;
  border: 3px dashed transparent;
  border-radius: 6px;
  background: transparent;
}

/* The highlight is the only thing the player is tracking on the board, and at
   fit-to-screen a slot can be ~11px tall — so it is deliberately loud.

   It is filled with the same pale blue the finished word will sit on, so
   cycling through spots previews the shape and colour of what is about to
   land there rather than just marking a position. The ring around it is what
   carries the highlight colour, and it has to stay a strong outline: on a
   themed board the blue fill alone would not read as "you are here". */
.slot.highlight {
  border: 5px solid rgb(var(--hl-rgb));
  background: var(--piece-blue);
  box-shadow: 0 0 0 4px rgba(var(--hl-rgb), 0.30), 0 0 34px 6px rgba(var(--hl-rgb), 0.65);
  z-index: 3;
  animation: hl-breathe 1.9s ease-in-out infinite;
}
@keyframes hl-breathe {
  0%, 100% { box-shadow: 0 0 0 4px rgba(var(--hl-rgb), 0.30), 0 0 30px 4px rgba(var(--hl-rgb), 0.55); }
  50%      { box-shadow: 0 0 0 7px rgba(var(--hl-rgb), 0.42), 0 0 46px 10px rgba(var(--hl-rgb), 0.85); }
}

.slot.shake { animation: shake 400ms ease; border-color: var(--red); }
@keyframes shake {
  0%,100% { transform: translateX(0); }
  20%     { transform: translateX(-10px); }
  45%     { transform: translateX(9px); }
  70%     { transform: translateX(-5px); }
}

/* -- placed word art -- */
.piece {
  position: absolute;
  pointer-events: none;
  image-rendering: -webkit-optimize-contrast;
}
.piece.landing { animation: land 380ms cubic-bezier(0.2,0.9,0.3,1.2); }
@keyframes land {
  0%   { transform: scale(1.35); opacity: 0; filter: brightness(1.8); }
  60%  { transform: scale(0.96); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}

.letter-tile {
  position: absolute;
  width: 50px;
  height: 50px;
  pointer-events: none;
}
.letter-tile.landing { animation: land 320ms cubic-bezier(0.2,0.9,0.3,1.2) backwards; }

/* Stand-in when a letter SVG fails to load — styled to match the artwork
   (same pale blue ground, same dark glyph) so a failure is barely noticeable
   rather than a broken-image icon sitting in the middle of the board. */
.letter-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #c4e2ff;
  color: #111;
  font-family: 'Sen', system-ui, sans-serif;
  font-size: 34px;
  font-weight: 700;
  line-height: 1;
}

/* ══ Bottom panel ═════════════════════════════════════════════════════════ */

#panel {
  min-height: var(--panel-min);
  background: var(--bg-raised);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 6px 8px 0;
  gap: 6px;
}

.bank-count {
  font-size: 11px;
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}

/* -- slot stepper -- */
#nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* Oversized on purpose: these two buttons are the primary control of the
   entire game, and get pressed far more than anything else. */
.nav-arrow {
  flex: 0 0 auto;
  width: 62px;
  height: 50px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg-sunken);
  color: var(--gold);
  font: inherit;
  font-size: 30px;
  line-height: 1;
  cursor: pointer;
}
.nav-arrow:active { background: var(--gold); color: #1a1512; }
.nav-arrow:disabled { opacity: 0.3; }

#nav-info {
  flex: 1;
  min-width: 0;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
#slot-desc {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#slot-pos {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* -- word bank -- */
.scroller {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  justify-content: center;
  align-content: flex-start;
  overflow-x: hidden;
  overflow-y: auto;
  padding: 2px 2px 8px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.scroller::-webkit-scrollbar { display: none; }

.word {
  flex: 0 0 auto;
  /* Slightly under the 44px guideline, which buys an extra row per page and
     is still a comfortable target for a text chip in a list. */
  min-height: 40px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  cursor: pointer;
}
.word:active { transform: scale(0.95); }
.word.wrong  { animation: shake 400ms ease; border-color: var(--red); color: var(--red); }
.word.right  { border-color: var(--green); color: var(--green); }
.panel-hint {
  margin: 0;
  padding: 10px;
  width: 100%;
  font-size: 12px;
  color: var(--text-dim);
  text-align: center;
}

/* ══ Letter entry (Immersive) ═════════════════════════════════════════════ */

#letter-entry {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* One row, always. Wrapping to a second line stole ~43px of height from the
   keyboard on narrow phones — a 10-letter word at 360px could not fit across
   at a fixed 30px per box. The boxes now share the available width and only
   grow up to 34px, so even CONSCIOUSNESS (13) stays on one line. */
#letter-boxes {
  display: flex;
  flex-wrap: nowrap;
  gap: 4px;
  justify-content: center;
  width: 100%;
  padding: 4px 0 8px;
  margin-top: auto;
}
.lbox {
  flex: 1 1 0;
  min-width: 0;
  max-width: 34px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--line);
  border-radius: 7px;
  background: var(--bg-sunken);
  font-size: clamp(12px, 3.6vw, 18px);
  font-weight: 700;
}
/* Gold = earned from a crossing or a bought vowel, never the player's to type. */
.lbox.given  { color: var(--gold); border-color: var(--gold); background: var(--gold-soft); }
/* Green = the player typed it and a wrong guess confirmed it was right, so it
   survived. Distinct from gold so the two sources of a fixed letter read
   differently. */
.lbox.kept   { color: var(--green); border-color: var(--green); background: rgba(111,191,115,0.14); }
.lbox.cursor { border-color: var(--text); box-shadow: 0 0 0 2px rgba(242,236,228,0.25); }
.lbox.wrong  { animation: shake 400ms ease; border-color: var(--red); color: var(--red); }



.key.wide { flex: 1.6 1 0; font-size: 13px; }

/* ══ Overlays ═════════════════════════════════════════════════════════════ */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(16px + var(--safe-t)) 16px calc(16px + var(--safe-b));
  background: rgba(10,8,6,0.86);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.card {
  width: 100%;
  max-width: 380px;
  max-height: 100%;
  /* The start card is tall — logo, mode picker, blurb, timer toggle, buttons.
     On a short viewport (an iPhone SE with both Safari toolbars leaves ~480px)
     it would otherwise clip the Start button off the bottom with no way to
     reach it. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 22px 20px;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--bg-raised);
  text-align: center;
  position: relative;
}
.card.scrollable { overflow-y: auto; text-align: left; -webkit-overflow-scrolling: touch; }
.card h1 { margin: 6px 0 18px; font-size: 26px; font-weight: 800; }
.card h2 { margin: 0 0 12px; font-size: 21px; font-weight: 800; }

/* The Brand Offerings wordmark is ~6:1 (viewBox 227x37), so it has to be sized
   by WIDTH to read at all — the old 66px made it barely 11px tall. Sized to
   most of the card, capped so it never outgrows the panel on a tablet. */
.game-logo {
  display: block;
  width: min(120px, 45%);   /* ~20px tall — the single number to tune */
  height: auto;
  margin: 2px auto 8px;
  opacity: 0.95;
}

.field-label {
  margin: 16px 0 7px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: center;
}
.blurb {
  margin: 9px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-dim);
  min-height: 2.9em;
}

.seg { display: flex; gap: 4px; background: var(--bg-sunken); border-radius: 11px; padding: 4px; }
.seg-wide { width: 100%; }
.seg-btn {
  flex: 1;
  min-height: 40px;
  padding: 0 10px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.seg-btn.active { background: var(--gold); color: #1a1512; }
#bank-tabs .seg-btn { min-height: var(--tap); font-size: 14px; }

.switch-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 11px;
  min-height: var(--tap);
  margin-top: 14px;
  font-size: 15px;
  cursor: pointer;
}
.switch-row input { width: 21px; height: 21px; accent-color: var(--gold); }

.primary {
  width: 100%;
  min-height: 52px;
  margin-top: 18px;
  border: none;
  border-radius: 13px;
  background: var(--gold);
  color: #1a1512;
  font: inherit;
  font-size: 18px;
  font-weight: 800;
  cursor: pointer;
}
.primary:active { transform: scale(0.98); }

.text-btn {
  min-height: var(--tap);
  padding: 0 12px;
  border: none;
  background: transparent;
  color: var(--gold);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.text-btn.wide { display: block; width: 100%; margin-top: 8px; }
.text-btn.danger { color: var(--red); }

.close-btn {
  position: absolute;
  top: 8px; right: 8px;
  width: var(--tap); height: var(--tap);
  border: none; background: transparent;
  color: var(--text-dim);
  font-size: 27px; line-height: 1;
  cursor: pointer;
}

.howto-item { margin: 15px 0; font-size: 14px; line-height: 1.5; color: var(--text-dim); }
.howto-item b { display: block; color: var(--text); font-size: 15px; margin-bottom: 3px; }

/* -- bottom sheet -- */
.sheet-overlay { align-items: flex-end; padding: 0; }
.sheet {
  width: 100%;
  max-height: calc(var(--app-h) * 0.88);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 6px 18px calc(16px + var(--safe-b));
  border-radius: 22px 22px 0 0;
  background: var(--bg-raised);
  border-top: 1px solid var(--line);
  animation: sheet-up 260ms cubic-bezier(0.2,0.9,0.3,1);
  /* Vertical drags on the sheet are handled in JS (swipe to dismiss); this
     stops the browser claiming them for its own overscroll first. */
  touch-action: pan-y;
  overscroll-behavior: contain;
}
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
.sheet-grabber { width: 40px; height: 4px; margin: 4px auto 6px; border-radius: 2px; background: var(--line); }
.sheet-row { display: flex; align-items: center; gap: 8px; }

/* Resolve / Start over / Back to menu — one centred group rather than
   spread edge to edge, so the three read as related options. flex-wrap is a
   safety net for the narrowest phones (an iPhone SE at 320px) where three
   full-phrase buttons at once can't quite fit on one line; everywhere wider
   they sit on a single row. */
.sheet-row-actions {
  justify-content: center;
  flex-wrap: wrap;
  row-gap: 6px;
  margin-top: 12px;
}
.sheet-row-actions .text-btn { font-size: 12.5px; padding: 0 7px; }
/* Show solution reads slightly bigger than its two siblings — it's the one
   in the middle of the group and the one worth a beat more visual weight. */
.sheet-row-actions #reveal-btn { font-size: 16px; }
/* Its own line above the transport buttons, not sharing a row with them
   (the button row centres its own three icons the same as everywhere else
   in the menu). display:block + width:100% gives text-overflow something
   to actually clip against — a bare inline <span> has no bounded width, so
   a long title would just spill past the sheet edge instead of ellipsising. */
.track-name {
  display: block;
  width: 100%;
  margin: 0 0 5px;
  font-size: 13px;
  color: var(--text-dim);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sheet-credit { margin: 10px 0 0; text-align: center; font-size: 11px; color: var(--text-dim); }
/* Sits directly under the track-change row now, with no label of its own —
   a small top margin keeps it from touching the buttons above. */
#music-vol { width: 100%; margin-top: 4px; accent-color: var(--gold); height: var(--tap); }

/* .field-label/.sheet-row/.primary are shared with the start screen and
   celebration card, which want their own (larger) spacing — so the tighter
   margins below are scoped to the menu sheet only, which needs to fit six
   sections without scrolling on most phones. (.field-label and .switch-row
   centre everywhere already — see their base rules above.) The
   horizontally-scrolling theme strip is left out of the row centring on
   purpose: centring a strip you scroll would start it off-screen. */
#menu .field-label { margin: 12px 0 6px; }
#menu .switch-row   { margin-top: 10px; }
#menu .sheet-row:not(.sheet-row-actions) { justify-content: center; }
#menu .primary      { margin-top: 10px; }

/* The theme picker stays a single horizontal strip. */
.theme-scroller {
  height: 52px;
  flex: 0 0 auto;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  justify-content: flex-start;
  align-items: center;
  align-content: center;
}
.theme-swatch {
  flex: 0 0 auto;
  width: 46px; height: 46px;
  border-radius: 10px;
  border: 2px solid transparent;
  background-size: cover;
  background-position: center;
  cursor: pointer;
  padding: 0;
}
/* The plain black swatch has no artwork to read as a button by — without an
   outline it all but disappears against the menu's own dark background. */
.theme-swatch[data-theme="black"] { border-color: var(--line); }
.theme-swatch.active { border-color: var(--gold); }

/* .scroller's overflow-x:hidden/overflow-y:auto exist for the gameplay word
   bank, which is height-capped and genuinely needs to clip and scroll. #hl-row
   has no such cap — it just wraps within the menu sheet's own scroll — so
   that overflow was only ever clipping the active ring's box-shadow below,
   which bleeds ~6px past the swatch edge, against 2px of row padding. */
#hl-row { overflow: visible; }

/* Highlight swatches. Smaller than the theme swatches — they are flat colours
   with nothing to look at — and shown as circles so the two rows are never
   mistaken for each other. Each previews the ring the board will use. */
.hl-swatch {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 3px solid transparent;
  background: var(--piece-blue);
  cursor: pointer;
  padding: 0;
  position: relative;
}
/* A 44px touch target without a 44px circle. */
.hl-swatch::after { content: ''; position: absolute; inset: -8px; border-radius: 50%; }
.hl-swatch.active { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px currentColor; }

/* ══ Celebration ══════════════════════════════════════════════════════════ */

#confetti { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
#celebration .card { z-index: 1; }
.celeb-time { font-size: 30px; font-weight: 800; color: var(--gold); margin: 12px 0 2px; font-variant-numeric: tabular-nums; }
.celeb-best { font-size: 13px; color: var(--text-dim); margin: 0; }

/* ══ Toast ════════════════════════════════════════════════════════════════ */

#toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--panel-min) + 18px + var(--safe-b));
  transform: translateX(-50%);
  z-index: 200;
  max-width: 88vw;
  padding: 11px 17px;
  border-radius: 11px;
  border: 1px solid var(--line);
  background: rgba(20,16,12,0.96);
  color: var(--text);
  font-size: 14px;
  line-height: 1.4;
  text-align: center;
  animation: toast-in 200ms ease;
}
#toast b { color: var(--gold); letter-spacing: 0.08em; }
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 12px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* ══ Landscape ════════════════════════════════════════════════════════════
   Vertical space is scarce; put the panel beside the board rather than under
   it. No "please rotate" nudge — both orientations are first-class. */

/* ══ Tablet ═══════════════════════════════════════════════════════════════
   iPads run this build too. The board is already width-constrained, so it
   cannot grow any further in portrait — which leaves spare height under the
   word bank. Rather than stretch the board, the controls scale up to suit a
   larger device and the bank is centred so the leftover room is shared evenly
   instead of pooling at the bottom. */

@media (min-width: 700px) and (orientation: portrait) {
  :root { --tap: 52px; }

  #topbar { padding: 10px 14px; }
  #timer   { font-size: 21px; }
  #counter { font-size: 17px; }
  #mode-label { font-size: 12px; }

  /* Bigger card on a tablet, so the wordmark can breathe with it. */
  .card { max-width: 460px; padding: 28px 26px; }
  .game-logo { width: min(150px, 45%); }

  #panel { padding: 10px 14px 0; gap: 10px; }
  #bank-tabs .seg-btn { min-height: 54px; font-size: 17px; }
  .bank-count { font-size: 13px; }

  .nav-arrow { width: 80px; height: 62px; font-size: 38px; }
  #slot-desc { font-size: 19px; }
  #slot-pos  { font-size: 13px; }

  /* Centred rather than top-aligned, so the spare height sits above and below
     the bank instead of all beneath it. */
  .scroller { align-content: center; gap: 10px; }
  .word { min-height: 52px; font-size: 17px; padding: 0 20px; border-radius: 13px; }

  .lbox { width: 40px; height: 50px; font-size: 24px; }
  }

/* Tablet landscape — the board would otherwise be capped at 58dvh with wide
   empty margins either side. Side by side uses the width properly, the same
   shape as the phone-landscape rule below but at tablet proportions. */
@media (min-width: 900px) and (min-height: 561px) and (orientation: landscape) {
  :root { --tap: 52px; }

  body {
    grid-template-rows: auto 1fr;
    grid-template-columns: 1fr minmax(340px, 38vw);
  }
  #topbar { grid-column: 1 / -1; padding: 10px 14px; }
  #board-area { aspect-ratio: auto; max-height: none; height: 100%; }
  #panel {
    min-height: 0;
    border-top: none;
    border-left: 1px solid var(--line);
    padding: 12px 16px 0;
    gap: 10px;
  }
  #bank-tabs .seg-btn { min-height: 52px; font-size: 16px; }
  .nav-arrow { width: 74px; height: 58px; font-size: 34px; }
  #slot-desc { font-size: 18px; }
  .scroller { align-content: flex-start; gap: 9px; }
  .word { min-height: 50px; font-size: 16px; padding: 0 18px; }
  .lbox { width: 38px; height: 46px; font-size: 22px; }
  #toast { bottom: 24px; }
}

@media (orientation: landscape) and (max-height: 560px) {
  body {
    grid-template-rows: auto 1fr;
    grid-template-columns: 1fr minmax(250px, 36vw);
  }
  #topbar { grid-column: 1 / -1; }
  #board-area { aspect-ratio: auto; max-height: none; height: 100%; }
  #panel { min-height: 0; border-top: none; border-left: 1px solid var(--line); }
  #toast { bottom: 18px; }
  .nav-arrow { height: 44px; font-size: 24px; width: 54px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Guided: an eliminated option fades to a dimmed, disabled state rather than
   vanishing — it stays put in its cell, still legible as one of the four
   options tried, just visibly ruled out. Matches the .icon-btn:disabled
   dimming used elsewhere rather than inventing a second "off" look. */
.word.eliminated {
  transition: opacity 240ms ease;
  opacity: 0.35;
  cursor: default;
}


/* ══ Peek ═════════════════════════════════════════════════════════════════
   Hold the Peek button to see the finished puzzle; release to dismiss. Same
   gesture as the desktop reference thumbnail. */

#peek-btn {
  font-size: 13px;
  font-weight: 700;
  color: var(--gold);
  /* Stops iOS raising its long-press callout over a button whose whole
     purpose is being held down. */
  -webkit-touch-callout: none;
  touch-action: manipulation;
}

#peek-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: calc(16px + var(--safe-t)) 16px calc(16px + var(--safe-b));
  background: rgba(8, 6, 4, 0.94);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  /* Purely a display layer — every touch belongs to the button being held. */
  pointer-events: none;
  animation: peek-in 140ms ease;
}
@keyframes peek-in { from { opacity: 0; } to { opacity: 1; } }

#peek-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 6px;
  background: #fff;
  box-shadow: 0 10px 50px rgba(0, 0, 0, 0.7);
}

#peek-caption {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ══ Guided clue ══════════════════════════════════════════════════════════
   The definition of the word that belongs in the highlighted spot, read as
   the clue for the multiple choice beneath it. Guided only — in Classic or
   Immersive it would simply name the answer. */
/* The definition of the word belonging in the highlighted spot, shown in every
   mode. Height is RESERVED at exactly two lines rather than fitting the text:
   definitions run 35-75 characters, which is one or two lines depending on the
   spot, and letting the box resize would shunt the whole panel up and down as
   the player cycles through. Two lines is the worst case at this size on a
   320px phone, so nothing is ever clipped. */
#slot-clue {
  margin: 0;
  padding: 5px 12px 6px;
  font-size: 12px;
  line-height: 1.3;
  height: calc(2 * 1.3em);
  box-sizing: content-box;
  font-style: italic;
  text-align: center;
  color: var(--text);
  opacity: 0.92;
  flex: 0 0 auto;
  overflow: hidden;
}

/* Never let the board collapse to nothing, even on the shortest phone. On a
   screen that short the panel is squeezed instead and the pager simply runs to
   more pages — a cramped list still plays, an invisible board does not. */
#board-area {
  min-height: 96px;
}


/* ══ In-app keyboard ══════════════════════════════════════════════════════
   The game supplies its own keys rather than raising the phone's. That keeps
   the layout completely static — no system keyboard sliding over the board,
   and the letter boxes always sit directly above the keys.

   It was clipped in an earlier version, but the cause was the layout sizing
   itself with dvh, which overstates the usable height on Safari. Heights now
   come from the measured visual viewport, and the key height scales with it,
   so the bottom row fits down to a 320x480 phone. */
#keyboard {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding-bottom: 6px;
  margin-top: auto;          /* sits at the bottom, boxes directly above */
}
.krow {
  display: flex;
  gap: 4px;
  justify-content: center;
}
.key {
  flex: 1 1 0;
  min-width: 0;
  /* Scales with the real viewport; the 34px floor keeps it tappable on the
     smallest phone, the 44px ceiling stops it ballooning on a tablet. */
  height: clamp(34px, calc(var(--app-h) * 0.052), 44px);
  border: none;
  border-radius: 7px;
  background: #33291f;
  color: var(--text);
  font: inherit;
  font-size: clamp(14px, calc(var(--app-h) * 0.023), 19px);
  font-weight: 700;
  cursor: pointer;
  touch-action: manipulation;   /* no double-tap-to-zoom delay on the keys */
}
.key:active { background: var(--gold); color: #1a1512; }
.key.wide { flex: 1.6 1 0; font-size: 13px; }
.key:disabled { opacity: 0.3; }

/* Boxes sit right on top of the keys. */
#letter-boxes { margin-top: auto; }


/* ══ Word bank pager ══════════════════════════════════════════════════════
   Classic lists 28 words; only about 12 fit on an iPhone and 6 on a smaller
   Android, so a vertical scroll hid most of the bank. The list now pages
   sideways instead, and snaps, so the gesture is the browser's own
   horizontal scroll — smoother than tracking touches by hand, and it works
   with a trackpad and a screen reader too. */
#word-list {
  flex: 1;
  min-height: 0;
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;   /* don't chain into a page-back gesture */
  scrollbar-width: none;
}
#word-list::-webkit-scrollbar { display: none; }

.wpage {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  justify-content: center;
  align-content: flex-start;
  padding: 2px 2px 4px;
}

/* Classic only (Guided's 2x2 is never paginated, so it stays full width):
   each page is a little narrower than the viewport, so the next page's edge
   peeks in as a standing reminder that there's more to swipe to — the pager
   dots alone were easy to miss entirely. */
.wpage:not(.wgrid) { flex-basis: 90%; }

/* Guided's fixed 2x2. The row height is bounded at both ends: tall enough to
   be a generous target on a roomy phone, capped so four options never stretch
   into slabs when the panel has height to spare. */
.wgrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(2, minmax(44px, 1fr));
  gap: 10px;
  align-content: center;
  max-height: 100%;
}
.wgrid .word {
  min-width: 0;
  height: 100%;
  max-height: 76px;
  padding: 0 8px;
  font-size: clamp(13px, 4vw, 17px);
  /* A long option must shrink rather than overflow its cell. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#word-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 7px;
  padding: 2px 0 8px;
  flex: 0 0 auto;
  min-height: 18px;
}
#word-dots:empty { display: none; padding: 0; min-height: 0; }

/* Small visually, but each carries a 44px touch target via the pseudo-element
   so the dots are usable as buttons, not just indicators. */
.wdot {
  position: relative;
  width: 7px;
  height: 7px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--line);
  cursor: pointer;
  transition: background 160ms ease, transform 160ms ease;
}
.wdot::after {
  content: '';
  position: absolute;
  inset: -18px -8px;
}
.wdot.active { background: var(--gold); transform: scale(1.25); }
