/* ================================================================
   Competitive Crosswords — editorial design system.
   Neutral newspaper energy, just sharper: OKLCH neutrals, a warm paper
   background, soft shadows, Schibsted Grotesk + Spline Sans Mono.
   Player colours (red/blue/green/…) come from identity.js as inline
   CSS variables; this sheet themes the chrome and all the functional
   cell/clue/power-up states around them.
   ================================================================ */

:root {
  --bg:          oklch(0.984 0.003 95);
  --panel:       #ffffff;
  --ink:         oklch(0.235 0.012 265);
  --ink-soft:    oklch(0.47 0.01 265);
  --ink-faint:   oklch(0.64 0.008 265);
  --line:        oklch(0.905 0.004 265);
  --line-strong: oklch(0.82 0.006 265);
  --block:       oklch(0.255 0.014 270);
  --amber:       oklch(0.895 0.07 88);
  --amber-strong:oklch(0.84 0.135 82);
  --accent:      oklch(0.56 0.17 25);
  --radius:      12px;
  --cell:        46px;
  --bar-h:       78px;   /* shared height of the small bars: clue · timer · colour/classic · score */
  --shadow-sm: 0 1px 2px oklch(0.2 0.02 265 / 0.06), 0 1px 1px oklch(0.2 0.02 265 / 0.04);
  --shadow-md: 0 4px 16px oklch(0.2 0.02 265 / 0.07), 0 1px 3px oklch(0.2 0.02 265 / 0.05);
  --shadow-lg: 0 18px 48px oklch(0.2 0.02 265 / 0.12), 0 4px 12px oklch(0.2 0.02 265 / 0.06);
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body { min-height: 100%; }

body {
  font-family: "Poppins", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.mono { font-family: "Spline Sans Mono", ui-monospace, monospace; }
.tnum { font-variant-numeric: tabular-nums; }
button { font-family: inherit; cursor: pointer; }
::selection { background: var(--amber-strong); }

.eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-faint);
}

.panel {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}

/* ================================================================
   TOP BAR — brand · room chip · live presence
   ================================================================ */

.topbar {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 18px;
  padding: 13px 26px;
  background: oklch(0.984 0.003 95 / 0.86);
  backdrop-filter: saturate(1.4) blur(10px);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 11px; flex: none; }
/* Invisible sizer: the Classic masthead (27px Playfair italic) is wider
   than the 16px Colour wordmark. Reserving its width here means the room
   chip and presence cluster sit in the same spot in both modes. */
.brand-name::after {
  content: "Competitive Crosswords";
  display: block; height: 0; overflow: hidden; visibility: hidden;
  font-family: "Playfair Display", Georgia, "Times New Roman", serif;
  font-style: italic; font-weight: 800; font-size: 27px;
  letter-spacing: 0; white-space: nowrap;
}
.brand-mark {
  width: 30px; height: 30px; border-radius: 8px;
  background: var(--ink); color: var(--bg);
  display: grid; place-items: center; flex: none;
  box-shadow: var(--shadow-sm);
}
.brand-mark svg { display: block; }
.brand-name { font-weight: 700; font-size: 16px; letter-spacing: -0.015em; }
.brand-name span { color: var(--ink-faint); font-weight: 600; }

.spacer { flex: 1; }

/* Invite chip (reuses #room-bar / #room-code so lobby.js + e2e are unchanged). */
.room-chip {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 6px 7px 6px 13px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: var(--shadow-sm); font-size: 13px; line-height: 1.3; color: var(--ink-soft);
}
.room-chip b { color: var(--ink); font-weight: 700; letter-spacing: 0.12em; }
.copy-btn {
  border: none; background: var(--ink); color: var(--bg);
  font-size: 12px; line-height: 1.3; font-weight: 600; padding: 6px 12px; border-radius: 999px;
  transition: transform .12s, background .2s;
}
.copy-btn:hover { transform: translateY(-1px); }
#copy-feedback { font-size: 12px; font-weight: 600; color: oklch(0.55 0.13 152); }

.presence { display: flex; align-items: center; }
.presence .avatars { display: flex; }
.av {
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  color: #fff; font-weight: 700; font-size: 12px;
  border: 2.5px solid var(--bg); margin-left: -9px;
  box-shadow: var(--shadow-sm); position: relative;
  transition: transform .14s; text-transform: uppercase;
}
.av:first-child { margin-left: 0; }
.av:hover { transform: translateY(-2px); z-index: 2; }
.av.you::after {
  content: ""; position: absolute; inset: -2.5px; border-radius: 50%;
  border: 2.5px solid var(--ink);
}
.av.offline { opacity: 0.4; }
.av.ghost { outline: 1px dashed var(--line-strong); outline-offset: 1px; }
.live-dot {
  position: absolute; right: -1px; bottom: -1px; width: 9px; height: 9px;
  border-radius: 50%; background: oklch(0.7 0.17 145); border: 2px solid var(--bg);
}
.presence-label { font-size: 12.5px; color: var(--ink-faint); margin-left: 14px; font-weight: 500; }

/* ================================================================
   STAGE — two sides in one grid:
     board side : clue banner · board · powerups · extras
     clue side  : Across | Down · scores bar (spans the powerup+extras rows)
   The clue side mirrors the board width via --board-w (set by game.js), so
   "Across | Down + gap" == board width.
   ================================================================ */

.stage { max-width: 1440px; margin: 0 auto; padding: 22px 26px 64px; }

.play-area {
  display: grid;
  grid-template-columns: max-content var(--board-w, max-content);
  grid-template-areas:
    "clue    clue"
    "board   clues"
    "actions lboard"
    "extras  lboard";
  gap: 14px;   /* uniform vertical + horizontal gap */
  align-items: start;
  justify-content: center;
}
/* Chaotic keeps its scoreboard in the top-right slot, so the clue banner only spans
   the board column there. (Solo/Competitive move the timer into the utility bar, so
   the slot is empty and the banner stretches the full width.) */
body[data-mode="chaotic"] .play-area {
  grid-template-areas:
    "clue    scores"
    "board   clues"
    "actions lboard"
    "extras  lboard";
}
.play-area > .board-controls { grid-area: actions; }
.play-area > #clue-banner   { grid-area: clue; }
.play-area > .board-wrap    { grid-area: board; }
.play-area > .board-extras  { grid-area: extras; }
.play-area > #scores-slot   { grid-area: scores; align-self: stretch; }
.play-area > .clue-columns  { grid-area: clues; height: var(--board-h); }
.play-area > #leaderboard-slot { grid-area: lboard; align-self: start; }

/* Board-side items must NOT widen the first column beyond the board: the board
   (board-wrap, width:max-content) alone sets the column width; the others fill
   it via the width:0 + min-width:100% trick (see board-and-bar history). */
.play-area > .board-controls,
.play-area > .board-extras { box-sizing: border-box; width: 0; min-width: 100%; }
/* The clue banner spans the whole top row (both columns), so it just stretches to
   fill its grid area; min-width:0 + overflow:hidden keep its content from widening
   the board column. (The width:0 trick above doesn't generalise to a 2-col span.) */
.play-area > #clue-banner { box-sizing: border-box; min-width: 0; }

/* Two stacked bars inside the controls wrapper. */
.board-controls { display: flex; flex-direction: column; gap: 8px; }

/* Power-up bar hidden outside Chaotic mode. */
body:not([data-mode="chaotic"]) #action-bar { display: none; }

.board-extras { display: flex; flex-direction: column; }

/* Two clue columns: Across | Down. Their combined width + gap == the clue side
   (== board width), so each column is half the board. */
.clue-columns {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
  align-items: stretch; min-width: 0;
}

@media (max-width: 1180px) {
  .play-area { display: flex; flex-direction: column; align-items: center; }

  /* Mobile stacking order:
     timer (centred, top) → clue banner → board → clue columns → leaderboard →
     utility + power-up bars → extras (toasts, hint) */
  .play-area > #scores-slot      { order: 1; align-self: center; }
  .play-area > #clue-banner      { order: 2; }
  .play-area > .board-wrap       { order: 3; }
  .play-area > .clue-columns     { order: 4; }
  .play-area > #leaderboard-slot { order: 5; }
  .play-area > .board-controls   { order: 6; }
  .play-area > .board-extras     { order: 7; }

  /* Centre player chips within the scores bar. */
  #scoreboard-list { justify-content: center; }

  /* In flex-column the grid-specific width:0/min-width:100% trick breaks —
     reset it and pin every child to the board's measured pixel width instead. */
  .play-area > .board-controls,
  .play-area > #clue-banner,
  .play-area > .board-extras { width: var(--board-w, max-content); min-width: 0; }
  .play-area > #scores-slot,
  .play-area > #leaderboard-slot,
  .play-area > .clue-columns { width: var(--board-w, max-content); }

  /* Keep Across | Down side by side at exactly half the board width each. */
  .clue-columns { grid-template-columns: 1fr 1fr; }
}

/* ================================================================
   CURRENT-CLUE BANNER
   ================================================================ */

.clue-banner {
  display: flex; align-items: stretch; gap: 0;
  height: var(--bar-h); overflow: hidden;
}
.cb-nav {
  border: none; background: transparent; color: var(--ink-soft);
  width: 42px; display: grid; place-items: center; transition: background .15s, color .15s;
}
.cb-nav:hover { background: oklch(0.95 0.004 265); color: var(--ink); }
/* Taller bar → centre the tag+clue vertically (padding no longer sets the height). */
.cb-body { flex: 1; padding: 0 8px; display: flex; align-items: center; gap: 12px; min-width: 0; }
.cb-tag {
  font-weight: 800; font-size: 23px; letter-spacing: -0.01em; flex: none;
  color: var(--me-edge, var(--accent));   /* --me-edge = your colour, set by game.js */
}
/* Classic view strips player colour from the board — keep the banner tag neutral too. */
body.no-colour .cb-tag { color: var(--ink-soft); }
.cb-clue { font-size: 23px; font-weight: 500; letter-spacing: -0.012em; line-height: 1.25; }
.cb-clue .enum { color: var(--ink-faint); font-weight: 500; }

/* ================================================================
   BOARD
   ================================================================ */

.board-wrap { padding: 14px; width: max-content; max-width: 100%; }

#grid {
  display: grid;
  /* grid-template-columns/rows set dynamically by game.js after puzzle loads */
  position: relative;   /* positioning context for .word-outline overlay */
  background: var(--line-strong);
  gap: 1px;
  border: 2px solid var(--block);
  border-radius: 4px;
  overflow: visible;
}

/* Local player's word outline — sits at the cell edges (z-index 2). */
.word-outline {
  position: absolute;
  pointer-events: none;
  box-sizing: border-box;
  border: 4px solid var(--accent);   /* colour overridden inline per player */
  border-radius: 3px;
  z-index: 2;
}

/* Remote player presence — SVG element; stroke/dasharray set inline by
   network.js. overflow:visible so the stroke centred on the cell edge isn't
   clipped. z-index keeps it below the local player's solid outline. */
.presence-outline {
  position: absolute;
  pointer-events: none;
  overflow: visible;
  z-index: 1;
}

.cell {
  width: var(--cell);
  height: var(--cell);
  position: relative;
  background: var(--panel);
}

.cell.black { background: var(--block); }
.cell.white { background: var(--panel); }

/* Player-typed cell (letter entered, word not yet scored) */
.cell.typed { background: var(--typed-bg, var(--panel)); }

/* Correctly scored cell — colour driven by the scoring player */
.cell.correct { background: var(--correct-bg, oklch(0.92 0.05 152)); }

/* Gave-up reveal: the solution shown neutrally — nobody scored it. */
.cell.revealed { background: oklch(0.93 0.003 265); }
.cell.revealed input { color: var(--ink-soft); }

/* Golden box (Chaotic): the lit word — claim it in time for double points.
   A game mechanic, not a player colour, so it shows in Classic view too. */
@keyframes goldenPulse {
  0%, 100% { background: #ffd23f; box-shadow: inset 0 0 0 3px #c8960a, 0 0 8px 1px rgba(214, 165, 10, 0.55); }
  50%      { background: #ffc400; box-shadow: inset 0 0 0 3px #a87900, 0 0 14px 3px rgba(214, 165, 10, 0.85); }
}
.cell.golden { background: #ffd23f; animation: goldenPulse 1.1s ease-in-out infinite; z-index: 1; }
.cell.golden input { color: #4a3800; font-weight: 700; }

/* A letter you bought (Chaotic) — your private in-progress letter, ringed gold. */
.cell.bought { box-shadow: inset 0 0 0 2px #d4a017; }

/* Block a word (Chaotic). Opponents see the frozen cells struck through and greyed —
   a hard, whole-cell lock. A game mechanic, so it shows in Classic view too. */
.cell.blocked { background: #e7e4ee; cursor: not-allowed; }
.cell.blocked input { color: #b3acc4; pointer-events: none; }
.cell.blocked::after {
  content: ''; position: absolute; left: 7%; right: 7%; top: 50%;
  height: 2px; background: #7c6fab; transform: rotate(-14deg);
  pointer-events: none; z-index: 2;
}
/* The blocker's OWN block — a subtle purple ring, no strike: still editable. */
.cell.blocked-mine { box-shadow: inset 0 0 0 2px #7c6fab; }

/* The bomb (Chaotic). Aim preview: your diamond is a bold red ring; an opponent's
   incoming diamond is a dashed orange ring. */
.cell.bomb-aim { box-shadow: inset 0 0 0 3px #e53935; background: rgba(229, 57, 53, 0.16); z-index: 1; }
.cell.bomb-aim-foe { box-shadow: inset 0 0 0 3px #fb8c00; z-index: 1; }
@keyframes bombBlast {
  0%   { background: #ff5252; box-shadow: inset 0 0 0 3px #b71c1c, 0 0 12px 3px rgba(229,57,53,.8); }
  100% { background: transparent; box-shadow: none; }
}
.cell.bomb-blast { animation: bombBlast 0.65s ease-out; z-index: 1; }

/* Active word cells carry .highlighted (no background — framed by .word-outline). */

/* The cursor cell: bright, saturated fill in the local player's colour. */
.cell.active { background: var(--active-bg, var(--accent)); }
.cell.active input { color: #fff; }
.cell.active .clue-num { color: rgba(255, 255, 255, 0.85); }

.cell input::selection { background: transparent; }

.cell input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: none; background: transparent; text-align: center;
  font-size: 20px; font-weight: 700; text-transform: uppercase;
  color: var(--ink); cursor: pointer; outline: none; caret-color: transparent;
  padding-top: 5px; letter-spacing: 0;
}

/* Bold bars marking word breaks inside multi-word answers. */
.cell.bar-left { box-shadow: inset 3px 0 0 0 #1a1a1a; }
.cell.bar-top  { box-shadow: inset 0 3px 0 0 #1a1a1a; }
.cell.bar-left.bar-top { box-shadow: inset 3px 0 0 0 #1a1a1a, inset 0 3px 0 0 #1a1a1a; }

/* clue number badge */
.cell .clue-num {
  position: absolute; top: 2px; left: 3px; font-size: 9px; font-weight: 600;
  line-height: 1; color: var(--ink-faint); pointer-events: none; user-select: none; z-index: 3;
}

.load-error { padding: 24px; font-size: 14px; color: var(--accent); }
.load-error code { font-family: "Spline Sans Mono", monospace; }

/* ================================================================
   BOARD TOOLBAR — Chaotic power-up bar + universal utility bar
   ================================================================ */

#action-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  padding: 7px 8px 7px 14px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}

#utility-bar {
  position: relative;
  display: flex; align-items: stretch; gap: 8px;
  height: var(--bar-h); padding: 11px 11px 11px 14px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
/* Colour/Classic toggle (left) and Show All (right) flow normally — Show All pinned
   right, as before. The timer (#solve-timer, injected by game.js in Solo/Competitive)
   is absolutely centred on top and pointer-transparent, so it neither affects their
   layout nor intercepts clicks — important on a narrow bar, where three in-flow items
   would otherwise overlap. */
#utility-bar > #giveup-btn  { margin-left: auto; }
/* Taller controls (the bar stretches them) carry slightly larger lettering. */
#utility-bar .seg-btn, #utility-bar #giveup-btn { font-size: 13.5px; }
#utility-bar > #solve-timer {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  pointer-events: none;
}
/* Segmented Colour / Classic control. */
.seg-control {
  display: grid; grid-template-columns: 1fr 1fr;
  border: 1px solid var(--line); border-radius: 9px;
  overflow: hidden; box-shadow: var(--shadow-sm);
}
.seg-btn {
  width: 100%; display: inline-flex; align-items: center; justify-content: center;
  padding: 7px 10px; font-size: 12.5px; line-height: 1.3; font-weight: 600; font-family: inherit;
  color: var(--ink-soft); background: var(--panel);
  border: none; border-radius: 0;
  cursor: pointer; transition: background .12s, color .12s;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--line); }
.seg-btn:hover:not(.seg-active) { background: oklch(0.96 0.004 265); color: var(--ink); }
.seg-btn.seg-active {
  background: var(--ink); color: #fff; cursor: default;
}

/* Mode-switch buttons: each segment is a tiny preview of the world it switches to,
   so the toggle reads the same in BOTH views. The chip carries the theme (dark vs.
   paper); the .seg-label carries the lettering (rainbow Poppins vs. inked serif).
   ID specificity wins over all .seg-btn and body.no-colour .seg-btn overrides. */
#colour-seg-colour,
#colour-seg-colour.seg-active {
  background: #14161c;
  text-transform: none;    /* pin: Classic view's .seg-btn would force UPPERCASE */
}
#colour-seg-colour .seg-label {
  /* The word "Colour", in colour — a vivid spectrum drawn from the player palette. */
  background: linear-gradient(95deg, #ff6b6b, #ffb000 26%, #2bd4a0 52%, #4aa3e6 76%, #cc79a7);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  font-family: "Poppins", system-ui, sans-serif; font-weight: 700; letter-spacing: -0.015em;
}
#colour-seg-classic,
#colour-seg-classic.seg-active {
  background: #f6efdd;      /* aged newsprint, independent of the active theme */
  text-transform: none;     /* …and never its UPPERCASE — both views read alike */
}
#colour-seg-classic .seg-label {
  color: #2b2113;
  font-family: "Playfair Display", Georgia, serif; font-style: italic; font-weight: 700;
  letter-spacing: normal;
}
/* Active = fully lit; inactive = dimmed + desaturated to show the switch still works
   (the control clips outer shadows, so opacity carries the active state). */
#colour-seg .seg-btn        { opacity: 0.5; filter: saturate(0.7); transition: opacity .15s, filter .15s; }
#colour-seg .seg-btn.seg-active { opacity: 1; filter: none; cursor: default; }
#colour-seg .seg-btn:not(.seg-active):hover { opacity: 0.8; }

.action-left { flex: 1; display: flex; gap: 6px; min-width: 0; position: relative; }
/* Each power-up button grows equally to fill the bar. */
#action-bar .action-btn { flex: 1; justify-content: center; }

.action-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 12px; font-size: 12.5px; line-height: 1.3; font-weight: 600; font-family: inherit;
  color: var(--ink-soft); background: var(--panel);
  border: 1px solid var(--line); border-radius: 9px;
  box-shadow: var(--shadow-sm); transition: all .14s;
}
.action-btn:hover { color: var(--ink); border-color: var(--line-strong); transform: translateY(-1px); }
.action-btn:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; transform: none; }
.flag-icon { flex: none; display: block; }

/* Chaotic-mode actions — hidden in other modes. Bomb confirm/cancel hidden until aiming. */
#buy-letter-btn, #block-word-btn, #bomb-btn,
#bomb-confirm, #bomb-cancel, #freeze-user-btn, #reverse-btn { display: none; }
body[data-mode="chaotic"] #buy-letter-btn,
body[data-mode="chaotic"] #block-word-btn,
body[data-mode="chaotic"] #bomb-btn,
body[data-mode="chaotic"] #freeze-user-btn,
body[data-mode="chaotic"] #reverse-btn { display: inline-flex; }

body.bomb-aiming #buy-letter-btn,
body.bomb-aiming #block-word-btn,
body.bomb-aiming #bomb-btn,
body.bomb-aiming #freeze-user-btn,
body.bomb-aiming #reverse-btn,
body.bomb-aiming #giveup-btn { display: none; }
body.bomb-aiming #bomb-confirm,
body.bomb-aiming #bomb-cancel { display: inline-flex; }

/* "To Bomb" is the danger confirm — red. */
.action-btn.bomb-go { color: #fff; background: #e53935; border-color: #e53935; }
.action-btn.bomb-go:hover { background: #d32f2f; color: #fff; }

/* Show All pressed — red, white flag. */
#giveup-btn.pressed { color: #fff; background: var(--accent); border-color: var(--accent); }
#giveup-btn.pressed .flag-icon path { stroke: var(--accent); }

/* "Block user" target menu — pops above the action bar. */
#freeze-menu {
  position: absolute; bottom: calc(100% + 6px); left: 0; z-index: 20;
  min-width: 160px; padding: 6px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: var(--shadow-md); display: flex; flex-direction: column; gap: 4px;
}
.freeze-menu-head { margin: 2px 4px 4px; font-size: 12px; font-weight: 700; color: var(--ink-soft); }
.freeze-menu-empty { margin: 4px; font-size: 12px; color: var(--ink-faint); }
.freeze-target {
  text-align: left; padding: 6px 8px; font-size: 13px; font-weight: 600; font-family: inherit;
  background: oklch(0.97 0.003 265); border: 1px solid var(--line); border-radius: 6px;
}
.freeze-target:hover { background: oklch(0.95 0.01 25); border-color: var(--line-strong); }

#giveup-tally {
  margin-top: 8px; font-size: 12px; color: var(--ink-faint); min-height: 15px; text-align: center;
}
#giveup-tally strong { color: var(--ink); }

/* Transient power-up toasts. */
#block-notice, #bomb-notice, #freeze-notice, #reverse-notice {
  margin-top: 7px; padding: 6px 12px; font-size: 13px; font-weight: 600;
  text-align: center; border-radius: 9px; border: 1px solid; align-self: center;
}
#block-notice  { color: #5f5191; background: #efeaf7; border-color: #ddd2ef; }
#bomb-notice   { color: #c62828; background: #fdecea; border-color: #f7c9c4; }
#freeze-notice { color: #2f74c0; background: #e8f1fb; border-color: #c7ddf5; }
#reverse-notice{ color: #7b2fa0; background: #f3e8fa; border-color: #e0c8ef; }

/* Lockout / room-wide banners (Chaotic). Fixed, top-centre, with a countdown. */
#freeze-banner, #reverse-banner {
  position: fixed; left: 50%; transform: translateX(-50%); z-index: 1000;
  padding: 9px 18px; font-size: 15px; font-weight: 700; color: #fff; border-radius: 999px;
}
#freeze-banner  { top: 14px; background: #2f74c0; box-shadow: 0 4px 14px rgba(47, 116, 192, 0.45); }
#reverse-banner { top: 54px; background: #8e44ad; box-shadow: 0 4px 14px rgba(142, 68, 173, 0.45); }

/* The keyboard hint under the toolbar. */
.hint { text-align: center; font-size: 12px; line-height: 1.4; color: var(--ink-faint); margin-top: 16px; }
.hint kbd {
  font-family: "Spline Sans Mono", monospace; font-size: 11px; background: var(--panel);
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px;
  padding: 1px 6px; color: var(--ink-soft);
}


/* ================================================================
   CLUE LISTS
   ================================================================ */

.clue-list { padding: 0; height: 100%; display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
.clue-head {
  position: sticky; top: 0; z-index: 1;
  background: oklch(0.984 0.003 95 / 0.92); backdrop-filter: blur(6px);
  padding: 10px 12px 9px 12px; border-bottom: 1px solid var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.clue-head .count { font-size: 11px; line-height: 15px; color: var(--ink-faint); font-weight: 600; white-space: nowrap; }

/* Per-column direction label (Across / Down). line-height pinned so the
   Classic 15px Playfair variant doesn't change the header height. */
.clue-dir {
  font-size: 11.5px; line-height: 15px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft);
}
.clue-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 0 8px; }
.clue-scroll::-webkit-scrollbar { width: 8px; }
.clue-scroll::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 8px; border: 2px solid var(--panel); }

#across-list, #down-list { list-style: none; }

#across-list li, #down-list li {
  display: grid; grid-template-columns: 26px 1fr; gap: 8px; align-items: start;
  padding: 7px 16px 7px 10px; font-size: 16px; line-height: 1.32;
  color: var(--ink); cursor: pointer; position: relative;
  border-left: 3px solid transparent; transition: background .12s;
}
#across-list li:hover, #down-list li:hover { background: oklch(0.965 0.004 265); }
.ci-num { font-size: 15px; font-weight: 700; color: var(--ink-soft); text-align: right; padding-top: 1px; }
.ci-text { font-size: 16px; line-height: 1.32; color: var(--ink); }
.ci-text .enum { color: var(--ink-faint); }
#across-list li.active-clue .ci-num, #down-list li.active-clue .ci-num { color: var(--ink); }

/* Active clue — local player's colour. Framed with a full border that echoes the
   .word-outline drawn around the selected word on the grid (same 4px, player
   colour, rounded). Uses an inset box-shadow so the frame doesn't shift the row. */
#across-list li.active-clue, #down-list li.active-clue {
  background: var(--active-clue-soft, var(--amber));
  border-left-color: transparent;
  color: var(--ink);
}
body:not(.no-colour) #across-list li.active-clue,
body:not(.no-colour) #down-list li.active-clue {
  /* Drop the 3px left border so the frame reaches the same left edge as the
     solved-clue background fill; add it back as padding to keep the text put. */
  border-left-width: 0; padding-left: 13px;
  border-radius: 3px;
  box-shadow: inset 0 0 0 4px var(--active-clue-bg, var(--accent));
}
body.no-colour #across-list li.active-clue, body.no-colour #down-list li.active-clue {
  background: oklch(0.93 0.05 232); border-left-color: oklch(0.56 0.17 232);
}

/* Opponent's active clue — a coloured left border. */
#across-list li.opponent-clue, #down-list li.opponent-clue {
  border-left: 3px solid var(--opponent-clue-bg, var(--ink-faint));
}
body.no-colour #across-list li.opponent-clue, body.no-colour #down-list li.opponent-clue {
  border-left-color: var(--ink-faint);
}

/* Solved clue (Colour mode) — highlighted in the scoring player's colour, to
   match the coloured answer on the grid. */
#across-list li.solved-clue, #down-list li.solved-clue {
  background: var(--solved-bg, oklch(0.92 0.05 152));
}
/* Solved AND currently active — the player's strong shade with light text. */
#across-list li.solved-clue.active-clue, #down-list li.solved-clue.active-clue {
  background: var(--solved-strong, var(--accent));
}
#across-list li.solved-clue.active-clue .ci-num, #down-list li.solved-clue.active-clue .ci-num,
#across-list li.solved-clue.active-clue .ci-text, #down-list li.solved-clue.active-clue .ci-text {
  color: #fff;
}

/* Classic view: no player colour; hand-drawn SVG pencil strike applied via
   .ci-text::before in the Classic block below. Only active-clue needs overrides here. */
body.no-colour #across-list li.solved-clue.active-clue, body.no-colour #down-list li.solved-clue.active-clue {
  background: oklch(0.93 0.05 232);
}
body.no-colour #across-list li.solved-clue.active-clue .ci-num, body.no-colour #down-list li.solved-clue.active-clue .ci-num,
body.no-colour #across-list li.solved-clue.active-clue .ci-text, body.no-colour #down-list li.solved-clue.active-clue .ci-text {
  color: var(--ink);
}

/* ================================================================
   SCORES BAR — ranked players, left → right (name above, big colour-coded score)
   ================================================================ */

/* The scoreboard panel sits beside the clue banner in the same row and must
   match its height exactly. min-height:0 prevents the slot from driving the
   row taller than the clue banner; align-self:stretch fills that height. */
#scores-slot { display: flex; min-height: 0; overflow: hidden; }
/* Empty in Solo/Competitive (the timer moved to the utility bar) → collapse it so the
   full-width clue banner has the top row to itself. */
#scores-slot:empty { display: none; }
/* Leaderboard sits below the clue columns (scoring modes); the play-area row-gap
   spaces it. Empty in Solo → no space. */
#leaderboard-slot { display: flex; }
#leaderboard-slot:empty { display: none; }
#scoreboard { flex: 1; height: var(--bar-h); padding: 8px 10px; display: flex; align-items: stretch; overflow: hidden; }

/* Players in rank order, flowing left → right. Chips stretch to the banner's full
   height so the vertical share bars share one track. */
#scoreboard-list {
  list-style: none; min-width: 0; flex: 1;
  display: flex; flex-wrap: nowrap; align-items: stretch; justify-content: center; gap: 5px;
}
/* Divider slot between chips: transparent here, inked in Classic. */
.score-chip + .score-chip { border-left: 1px solid transparent; }

/* One player: a full-height tile — name on top, big colour-coded score below, with a
   vertical share bar rising behind from the floor. */
.score-chip {
  position: relative; isolation: isolate;   /* contains the share bar (drawn behind) */
  flex: 1 1 0; min-width: 58px; max-width: 150px;
  display: flex; flex-direction: column; align-items: center; justify-content: space-between;
  padding: 6px 10px 5px; border-radius: 8px; will-change: transform;
}
/* Vertical share-of-points bar: a translucent fill rising from the bottom, its HEIGHT
   set to that player's proportion of all points (--share, 0–1) by renderScoreboard. */
.score-chip::before {
  content: ""; position: absolute; z-index: -1; inset: auto 0 0 0;
  height: calc(var(--share, 0) * 100%);
  background: color-mix(in srgb, var(--chip-fill, var(--ink-faint)) 38%, transparent);
  border-radius: 0 0 7px 7px; transition: height .45s cubic-bezier(.4, 0, .2, 1);
}
.score-name { font-size: 13.5px; line-height: 15px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink-soft); white-space: nowrap; display: flex; align-items: center; }
.score-name .ghost-badge { margin-left: 4px; font-size: 0.95em; }
.score-chip.ghost .score-name { font-style: italic; }
.score-points {
  font-size: 34px; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.score-chip.leader .score-name { color: var(--ink); font-weight: 800; }

/* Local player's chip — a coloured frame echoing the clue-banner box (Colour view). */
body:not(.no-colour) .score-chip.me {
  box-shadow: inset 0 0 0 3px var(--chip-frame, var(--accent));
}

/* Departed player: dimmed (their points still count). */
.score-chip.offline { opacity: 0.4; }
.score-chip.offline .score-name { font-style: italic; }

/* Frozen player (Block-user) — chilled tint + ❄ in the name (added by JS). */
.score-chip.frozen { background: rgba(47, 116, 192, 0.10); }

/* A chip flashes when that player buys a letter (their score also drops). */
@keyframes purchaseFlash { 0% { background: #ffe9a8; } 100% { background: transparent; } }
.score-chip.purchase-flash { animation: purchaseFlash 0.9s ease-out; }

/* Elapsed timer — lives centred in the utility bar (Solo + Competitive). */
.timer-display {
  font-size: 28px; font-weight: 500; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--ink);
}

/* Disabled / legacy incorrect-answer log (kept hidden — see TODO). */
#incorrect-log-section { display: none !important; }

/* ================================================================
   COLOUR OFF (Classic) — grid goes neutral newspaper
   ================================================================ */
body.no-colour .cell.correct, body.no-colour .cell.typed { background: var(--panel); }
body.no-colour .word-outline { display: none !important; }
body.no-colour .cell.highlighted { background: var(--panel); }
body.no-colour .cell.active { background: var(--panel); }
body.no-colour .cell.active input { color: var(--ink); }
body.no-colour .cell.active .clue-num { color: var(--ink-faint); }
body.no-colour .presence-outline { display: none !important; }

/* ================================================================
   LANDING (front door) — split-personality hero.
   Left half = Colour world, right half = Classic newsprint, meeting
   at a hand-drawn double rule. The landing carries its OWN palette
   (--land-*) so it looks identical whether or not body.no-colour is set.
   ================================================================ */

#landing {
  position: fixed; inset: 0; z-index: 200; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  --grid-w: 2px;   /* ruling thickness — shared by the gridlines and the cell gaps */
  /* Classic-world swatches (hardcoded: independent of body.no-colour) */
  --land-paper:      #e9dcb8;
  --land-paper-cell: #f1e6cb;
  --land-ink:        #2b2113;
  --land-ink-soft:   #5b482c;
  --land-line:       #cbb588;
  /* Colour-world swatches */
  --land-cool:       oklch(0.985 0.004 250);
  --land-cool-line:  oklch(0.936 0.006 265);
}

/* ── The two worlds + the seam ───────────────────────────────────
   The whole lobby is one crossword grid: a responsive --cell module (set by
   lobby.js) rules both halves, and the seam lands exactly on a grid column
   (--split-x = splitCol × cell), so foreground cells, gridlines, and the
   colour↔classic divide all share one framework. */
/* The two halves carry only the FIELD colour (cool world / paper world); the ruling is
   drawn once, continuously, on #land-bg above them — so the gridlines never break or
   shift at the seam. */
.land-half { position: absolute; top: 0; bottom: 0; }
.land-half-colour { left: 0; width: var(--split-x, 50%); background: var(--land-cool); }
.land-half-trad   { left: var(--split-x, 50%); right: 0; background: var(--land-paper); }

/* ── The one grid layer (built by lobby.js renderLanding) ───────────
   Its background paints ONE uniform ruling across the whole viewport (above the field
   colours, below the cells). Filler words, the logo, and the buttons are .gcell / .gbtn
   on top; placed cells are 1px smaller than the module so the ruling shows through. */
#land-bg {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  --grid-line: rgb(140 126 100 / 0.30);
  background:
    repeating-linear-gradient(90deg,  transparent 0 calc(var(--cell) - var(--grid-w)), var(--grid-line) calc(var(--cell) - var(--grid-w)) var(--cell)),
    repeating-linear-gradient(180deg, transparent 0 calc(var(--cell) - var(--grid-w)), var(--grid-line) calc(var(--cell) - var(--grid-w)) var(--cell));
}
.bg-layer { position: absolute; inset: 0; }
.gbtn, .gback { pointer-events: auto; }

.gcell {
  position: absolute; box-sizing: border-box;
  /* gap = the ruling width (--grid-w) so cell edges and gridlines line up exactly */
  width: calc(var(--cell) - var(--grid-w)); height: calc(var(--cell) - var(--grid-w));
  display: grid; place-items: center;
  font-size: calc(var(--cell) * 0.5);
}
.gcell b { font-weight: 700; line-height: 1; }
.gcell.colour  { background: #ffffff; }
.gcell.classic { background: var(--land-paper-cell); }

/* Filler: faded + animated, decorative palettes (colour side) / handwriting (classic). */
.gcell.filler { opacity: 0; animation: fillerType 22s linear infinite; }
.gcell.filler.colour  b { font-family: "Poppins", system-ui, sans-serif; }
.gcell.filler.classic { background: var(--land-paper-cell); }
.gcell.filler.classic b { font-family: "Architects Daughter", cursive; font-weight: 400; color: #4a4745; }
.gcell.filler.colour.pal-0 { background: #fde8e8; } .gcell.filler.colour.pal-0 b { color: #d62728; }
.gcell.filler.colour.pal-1 { background: #fff4d6; } .gcell.filler.colour.pal-1 b { color: #ffb000; }
.gcell.filler.colour.pal-2 { background: #e0f5ef; } .gcell.filler.colour.pal-2 b { color: #009e73; }
.gcell.filler.colour.pal-3 { background: #e0f0fa; } .gcell.filler.colour.pal-3 b { color: #0072b2; }
.gcell.filler.colour.pal-4 { background: #f9e8f3; } .gcell.filler.colour.pal-4 b { color: #cc79a7; }
.gcell.filler.colour.pal-5 { background: #eaecf0; } .gcell.filler.colour.pal-5 b { color: #8d99ae; }

/* Logo letters read as SOLVED crossword answers: a solid 2×2 answer cell sitting on
   top of the uniform grid. Colour side = the scoring player's fill (COMPETITIVE red,
   CROSSWORDS blue, crossing at the shared C) with a bold dark letter; classic side =
   the newsprint solve (paper + graphite handwriting), like solved cells in each view. */
.gcell.logo { z-index: 2; }
.gcell.logo b { font-weight: 800; }
.gcell.logo.colour.logo-x { background: #ee8888; }   /* COMPETITIVE — red answer (fill) */
.gcell.logo.colour.logo-y { background: #5aa0d4; }   /* CROSSWORDS — blue answer (fill) */
.gcell.logo.colour b { color: #1a1a2e; font-family: "Poppins", system-ui, sans-serif; }
.gcell.logo.classic { background: var(--land-paper-cell); }
.gcell.logo.classic b { color: #4a4745; font-family: "Architects Daughter", cursive; font-weight: 400; }

/* Mode buttons — SOLID grid-aligned blocks (a single fill, no cell tiles), sitting on
   top of the background crossword in the logo's open quadrant. */
.gbtn {
  position: absolute; z-index: 3; border: none; cursor: pointer;
  display: grid; place-items: center; border-radius: 14px; color: #fff;
  box-shadow: 0 6px 18px rgb(20 15 5 / 0.20);
  transition: transform .12s, box-shadow .12s, filter .12s;
}
.gbtn:hover { transform: translateY(-2px); filter: brightness(1.05); box-shadow: 0 12px 28px rgb(20 15 5 / 0.28); }
.gbtn:focus-visible { outline: 3px solid #111; outline-offset: 3px; }
.gbtn-solo                      { background: #009e73; }                      /* teal   */
.gbtn-play                      { background: #cc79a7; }                      /* rose   */
.gbtn-create                    { background: #0072b2; }                      /* blue   */
.gbtn-competitive               { background: #ffc12e; }                      /* yellow */
.gbtn-competitive .gbtn-label   { color: #1a1a2e; }                          /* dark on yellow */
.gbtn-label {
  font-family: "Poppins", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(22px, calc(var(--cell) * 1.15), 58px);
  letter-spacing: 0.01em; text-transform: uppercase; color: #fff;
}
.gback {
  position: absolute; z-index: 3; width: var(--cell); aspect-ratio: 1;
  border: none; border-radius: 50%; cursor: pointer;
  background: rgb(255 255 255 / 0.82); color: #1a1a1a;
  font-size: calc(var(--cell) * 0.5); line-height: 1;
  box-shadow: 0 2px 6px rgb(20 20 40 / 0.18);
}
.gback:hover { background: #fff; }

/* Create-page opt-in: lock colours (globally-unique). A hero child, so renderLanding
   grid-positions it (left/top/width) and #landing-hero > * makes it position:absolute +
   clickable. Shown only on the Create sub-page (lobby.js toggles [hidden]). */
.land-lock {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4em;
  white-space: nowrap; cursor: pointer;
  font-size: clamp(11px, calc(var(--cell) * 0.34), 15px); color: var(--land-ink, #2b2113);
  background: rgb(255 255 255 / 0.82); border-radius: 6px; padding: 4px 8px;
  box-shadow: 0 2px 6px rgb(20 20 40 / 0.18);
}
.land-lock[hidden] { display: none; }
.land-lock input { cursor: pointer; }

/* In-game badge for a lock-colours room (hidden by default; game.js toggles it). */
.colours-locked {
  font-size: 0.78rem; font-weight: 600; color: var(--ink-soft, #6a6256);
  letter-spacing: 0.02em; margin: 0 0 6px; opacity: 0.85;
}
.colours-locked[hidden] { display: none; }

@keyframes fillerType {
  0%        { opacity: 0; }
  3%        { opacity: 0.6; }   /* snap in fast, but stay subtler than the core */
  90%       { opacity: 0.6; }
  100%      { opacity: 0; }
}

/* ── Hero content ────────────────────────────────────────────── */
/* Secondary actions overlay — its children (code entry, leaderboard, message) are
   grid-positioned by renderLanding, so the hero is just a passthrough layer. */
#landing-hero { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
#landing-hero > * { position: absolute; pointer-events: auto; box-sizing: border-box; }

/* ── Logo frame: COMPETITIVE × CROSSWORDS crossword, framing the modes ──
   An 11-col × 10-row cell grid. COMPETITIVE lies along row 3 (.lc-x, by --col);
   CROSSWORDS along col 2 (.lc-y, by --row); they cross at the O (col2/row3). The
   open lower-right quadrant holds the three mode cards; the dateline fills the
   top-right void. The two word wrappers are display:contents so their letters
   are direct grid items (mobile overrides this — see the @media block). */
.logo-frame {
  --cell: clamp(32px, 5vw, 66px);
  --lframe: 3px;          /* black crossword gridline / frame width */
  --lgap: var(--lframe);  /* gap = frame so adjacent cell frames merge into one line */
  display: grid;
  grid-template-columns: repeat(11, var(--cell));
  grid-template-rows: repeat(10, var(--cell));
  gap: var(--lgap);
  justify-content: center; align-content: center;
  width: max-content; max-width: 100%;
  margin: 0 auto 8px; padding: 0; border: 0; min-inline-size: 0;
  /* Nudge the whole cross one cell to the left so the mode cards (cols 3–11) and
     dateline sit centred on the page, aligned with the actions below; the
     CROSSWORDS arm reaches into the left margin. */
  transform: translateX(calc(-1 * (var(--cell) + var(--lgap))));
}
.logo-frame legend {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.logo-across, .logo-down { display: contents; }

.lc {
  position: relative;
  display: grid; place-items: center;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: clamp(18px, 3.4vw, 44px); font-weight: 600; font-style: normal;
  color: #fff; border-radius: 0; border: 0;
  /* The black square behind each cell in the source SVG: a solid frame that
     meets its neighbours across the matching gap, forming crossword gridlines. */
  box-shadow: 0 0 0 var(--lframe) #111;
  animation: ltPop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
}
/* Guest default mirrors the supplied logo: red across, blue down. */
.lc-x { grid-row: 3; grid-column: var(--col); background: #d62728;
        animation-delay: calc(var(--col) * 40ms + 100ms); }
/* CROSSWORDS starts after COMPETITIVE finishes (~990ms) + a short beat. */
.lc-y { grid-column: 2; grid-row: var(--row); background: #0072b2;
        animation-delay: calc(var(--row) * 40ms + 1000ms); }
/* The shared O is drawn by the across word on desktop; the down copy only
   appears in the mobile wordmark. */
.lc-shared { display: none; }

.tl-num {
  position: absolute; top: 3px; left: 5px;
  font-size: clamp(9px, 1vw, 13px); font-weight: 700; font-style: normal; line-height: 1;
  opacity: 0.8;
}

/* The mode buttons sit in the open quadrant. The stack is size-capped and centred
   so the buttons stay put while the logo grows. */
.mode-stack {
  grid-area: 4 / 3 / 11 / 12; margin: 6px;
  align-self: stretch; justify-self: center;   /* hug the COMPETITIVE bar, centre on page */
  width: 100%; max-width: 470px; max-height: 380px; min-height: 230px;
  display: grid;   /* choices + sub overlay the same cell so the swap doesn't reflow */
  animation: landFadeUp 0.5s ease-out 1.05s backwards;
}
.mode-choices, .comp-sub {
  grid-area: 1 / 1; position: relative;
  display: flex; flex-direction: column; gap: 14px;
}
.comp-sub[hidden], .mode-choices[hidden] { display: none; }
.comp-sub { animation: landFadeUp 0.28s ease-out backwards; }
@keyframes ltPop {
  from { opacity: 0; transform: scale(0.5) translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes landFadeUp {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* ── Mode buttons: large coloured buttons in the logo frame ── */
.mode-btn {
  position: relative; flex: 1; min-height: 96px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 12px 18px; cursor: pointer; border: none; border-radius: 16px;
  background: var(--mode-col); color: var(--mode-ink, #fff);
  box-shadow: 0 2px 6px rgb(20 20 40 / 0.14); font-family: inherit;
  transition: transform 0.15s, box-shadow 0.15s, filter 0.15s;
}
.mode-btn:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgb(20 15 5 / 0.22); filter: brightness(1.04); }
.mode-btn:focus-visible { outline: 3px solid #111; outline-offset: 3px; }
.mode-name {
  font-family: "Poppins", system-ui, sans-serif;
  font-weight: 700; font-size: clamp(26px, 3vw, 44px);
  letter-spacing: -0.01em; line-height: 1; color: var(--mode-ink, #fff);
}
.mode-sub {
  font-family: "Poppins", system-ui, sans-serif; font-weight: 600;
  font-size: clamp(11px, 1.1vw, 13.5px); opacity: 0.85; color: var(--mode-ink, #fff);
}

/* Each button a distinct palette colour (the strong values from PLAYER_COLOURS),
   using colours not already spoken for by the red/blue logo. */
.mode-solo        { --mode-col: #009e73; }                      /* teal */
.mode-competitive { --mode-col: #cc79a7; }                      /* rose */
.mode-create      { --mode-col: #0072b2; }                      /* blue */
.mode-chaotic     { --mode-col: #ffb000; --mode-ink: #5c3d00; } /* amber (dark text) */

/* Back arrow above the Play / Create Game choice. */
.comp-back {
  position: absolute; top: -2px; left: -2px; z-index: 3;
  width: 34px; height: 34px; border: none; border-radius: 50%;
  background: rgb(255 255 255 / 0.85); color: #1a1a1a;
  font-size: 18px; line-height: 1; cursor: pointer;
  box-shadow: 0 2px 6px rgb(20 20 40 / 0.18);
  transition: transform .12s, background .15s;
}
.comp-back:hover { transform: translateX(-2px); background: #fff; }

/* Chaotic is hidden at launch. The `chaos-gated` class hides any element (the
   mode-picker card, the leaderboard tab) until the ?chaos dev flag un-hides it
   by removing the class — see client/flags.js. */
.chaos-gated { display: none !important; }

/* ── Actions row ─────────────────────────────────────────────── */
.landing-action {
  width: 100%; padding: 12px; font-size: 14px; font-weight: 700; font-family: inherit;
  background: var(--ink); color: var(--bg); border: none; border-radius: 10px;
  letter-spacing: 0.01em; transition: transform .12s, background .2s;
}
.landing-action:hover { transform: translateY(-1px); }
.landing-action.secondary { background: oklch(0.95 0.004 265); color: var(--ink); }
.landing-action.secondary:hover { background: oklch(0.92 0.004 265); }

/* Compact "have a code?" join row, centred under the hero. */
/* Code-entry row — a grid-aligned box (positioned/sized by renderLanding) within the
   logo's width. The input fills it; the Join button sits at the end. */
.join-form { display: flex; gap: var(--cell); }   /* a one-cell gap between the field and Join */
.join-form #join-code-input {
  flex: 1; min-width: 0; height: 100%; box-sizing: border-box; padding: 0 16px;
  font-size: clamp(14px, calc(var(--cell) * 0.5), 22px);
  font-family: "Spline Sans Mono", ui-monospace, monospace;
  background: #fff; border: 2px solid var(--land-cool-line); border-radius: 12px;
  outline: none; text-transform: uppercase; letter-spacing: 2px; text-align: center;
}
.join-form #join-code-input::placeholder { letter-spacing: 0; text-transform: none; }
.join-form #join-code-input:focus { border-color: var(--accent); }
.join-form #join-btn {
  flex: 0 0 auto; width: calc(4 * var(--cell) - var(--grid-w)); height: 100%;
  display: grid; place-items: center; border: none; border-radius: 12px;
  cursor: pointer; background: #0072b2; color: #fff;   /* palette blue */
  font-family: "Poppins", system-ui, sans-serif; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.02em;
  font-size: clamp(13px, calc(var(--cell) * 0.45), 20px);
}
.join-form #join-btn:hover { filter: brightness(1.15); }

/* Leaderboard — a pink grid button matching the mode buttons. */
#leaderboard-btn {
  display: grid; place-items: center; border: none; border-radius: 12px; cursor: pointer;
  background: #cc79a7; color: #fff; opacity: 1;   /* palette rose (red/pink) */
  font-family: "Poppins", system-ui, sans-serif; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.01em;
  font-size: clamp(15px, calc(var(--cell) * 0.7), 30px);
  box-shadow: 0 6px 18px rgb(20 15 5 / 0.20); transition: transform .12s, filter .12s;
}
#leaderboard-btn:hover { transform: translateY(-2px); filter: brightness(1.05); }
#landing-msg { font-size: 12px; font-weight: 600; color: var(--accent); text-align: center; }
.landing-msg { font-size: 12px; font-weight: 600; color: var(--accent); min-height: 16px; margin-top: 14px; }

/* ── Settings overlay — same card style as #identity-box ─────── */
#settings-overlay {
  position: fixed; inset: 0; z-index: 201;
  display: flex; align-items: center; justify-content: center;
  /* landing palette only needed for the split .land-half backgrounds */
  --land-paper: #e9dcb8; --land-paper-cell: #f1e6cb;
  --land-ink: #2b2113; --land-ink-soft: #5b482c; --land-line: #cbb588;
  --land-cool: oklch(0.985 0.004 250); --land-cool-line: oklch(0.936 0.006 265);
}
#settings-card {
  position: relative; z-index: 2;
  background: var(--panel); border-radius: 16px; padding: 32px; width: 340px;
  box-shadow: var(--shadow-lg);
}

/* Pre-room lobby — same split background + centred card as Settings. */
#prelobby-overlay {
  position: fixed; inset: 0; z-index: 201;
  display: flex; align-items: center; justify-content: center;
  --land-paper: #e9dcb8; --land-paper-cell: #f1e6cb;
  --land-ink: #2b2113; --land-ink-soft: #5b482c; --land-line: #cbb588;
  --land-cool: oklch(0.985 0.004 250); --land-cool-line: oklch(0.936 0.006 265);
}
#prelobby-card {
  position: relative; z-index: 2;
  background: var(--panel); border-radius: 16px; padding: 32px; width: 340px;
  box-shadow: var(--shadow-lg);
}
#prelobby-card h2 { font-size: 19px; font-weight: 800; letter-spacing: -0.01em; margin-bottom: 6px; }
.prelobby-counts { font-size: 13px; color: var(--ink-soft); margin: 0 0 22px; }
.prelobby-actions { display: flex; flex-direction: column; gap: 12px; }
.prelobby-actions .landing-action {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left;
}
.prelobby-sub { font-size: 11px; font-weight: 400; opacity: 0.8; }
.settings-back {
  display: block; background: none; border: none; cursor: pointer; padding: 0;
  font-size: 12px; color: var(--ink-faint); font-family: inherit; margin-bottom: 16px;
}
.settings-back:hover { color: var(--ink); }
#settings-card h2 { font-size: 19px; font-weight: 800; letter-spacing: -0.01em; margin-bottom: 22px; }
#settings-card label, #settings-card .swatch-label {
  display: block; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--ink-faint); margin-bottom: 7px;
}
#settings-name {
  width: 100%; padding: 10px 12px; font-size: 15px; font-family: inherit;
  border: 1.5px solid var(--line); border-radius: 10px; outline: none;
  margin-bottom: 20px; box-sizing: border-box;
}
#settings-name:focus { border-color: var(--accent); }
#settings-swatches { display: flex; justify-content: space-between; margin-bottom: 24px; }
#settings-save {
  width: 100%; padding: 12px; font-size: 14px; font-weight: 700; font-family: inherit;
  background: var(--ink); color: var(--bg); border: none; border-radius: 10px;
  letter-spacing: 0.01em; cursor: pointer;
}
#settings-save:disabled { background: var(--line-strong); cursor: not-allowed; }
#settings-save:not(:disabled):hover { transform: translateY(-1px); }

/* ── GDPR data-rights section (settings) ────────────────────── */
.settings-data { margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--line); }
.settings-data-h { font-size: 13px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft); margin: 0 0 6px; }
.settings-data-sub { font-size: 13px; color: var(--ink-faint); margin: 0 0 12px; }
.settings-data-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.settings-toggle { display: flex; align-items: center; gap: 9px; font-size: 14px; color: var(--ink); cursor: pointer; }
.settings-toggle input { width: 16px; height: 16px; cursor: pointer; }
.settings-data-btn {
  flex: 1 1 0; min-width: 140px; padding: 10px 12px; font-size: 13px; font-weight: 700;
  font-family: inherit; cursor: pointer; border-radius: 10px;
  background: var(--panel); color: var(--ink); border: 1.5px solid var(--line-strong);
}
.settings-data-btn:hover { border-color: var(--ink-soft); }
.settings-danger { color: oklch(0.55 0.22 25); border-color: oklch(0.55 0.22 25 / 0.4); }
.settings-danger:hover { background: oklch(0.55 0.22 25 / 0.08); border-color: oklch(0.55 0.22 25); }

/* ── Landing account chip (top-right of landing) ────────────── */
/* Top-left account chip (position set by renderLanding). Buttons are whole-cell sized
   (4 wide × 2 tall) with a ruling-width gap, so their edges land on the gridlines. */
.land-account {
  position: absolute; z-index: 3;
  display: flex; align-items: center; gap: var(--cell);   /* a cell between name + each button */
}
.land-acct-name {
  font-family: "Playfair Display", Georgia, "Times New Roman", serif;
  font-style: italic; font-size: 14px; color: var(--land-ink);
}
.land-acct-btn {
  width: calc(4 * var(--cell) - var(--grid-w)); height: calc(var(--cell) - var(--grid-w));
  display: grid; place-items: center; cursor: pointer;
  /* Classic / newsprint style: cream paper, inked serif italic, thin ink rule. */
  background: var(--land-paper-cell); color: var(--land-ink);
  border: 1.5px solid var(--land-ink); border-radius: 2px;
  font-family: "Playfair Display", Georgia, "Times New Roman", serif;
  font-weight: 700; font-style: italic; letter-spacing: normal;
  font-size: clamp(12px, calc(var(--cell) * 0.42), 18px);
  transition: background 0.15s, color 0.15s;
}
.land-acct-btn:hover { background: var(--land-ink); color: var(--land-paper-cell); }

/* ── Small screens ──
   The whole lobby is one responsive grid now, so it scales rather than collapsing —
   the layer (which holds the logo + buttons) must NOT be hidden. The secondary actions
   are grid-positioned, so nothing special is needed here. */
@media (max-width: 880px) {
  /* the lobby is a responsive grid; nothing special needed here now */
}

@media (prefers-reduced-motion: reduce) {
  .gcell.filler { animation: none; opacity: 0.5; }
}

/* ================================================================
   IDENTITY MODAL
   ================================================================ */

/* ── Confirm-leave modal ───────────────────────────────────── */
#confirm-leave-modal {
  position: fixed; inset: 0; background: oklch(0 0 0 / 0.4);
  display: flex; align-items: center; justify-content: center; z-index: 300;
}
#confirm-leave-box {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 16px; padding: 28px 32px;
  width: 320px; max-width: 90vw;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 8px;
}
.confirm-leave-heading { font-size: 18px; font-weight: 800; margin: 0; }
.confirm-leave-body    { font-size: 14px; color: var(--ink-soft); margin: 0 0 8px; }
.confirm-leave-actions { display: flex; gap: 10px; }
.confirm-leave-actions .landing-action { width: auto; padding: 10px 20px; flex: 1; }

#identity-modal {
  position: fixed; inset: 0; background: oklch(0.15 0.02 265 / 0.55);
  display: flex; align-items: center; justify-content: center; z-index: 250;
}
#identity-box {
  background: var(--panel); border-radius: 16px; padding: 32px; width: 340px;
  box-shadow: var(--shadow-lg);
}
#identity-box h2 { font-size: 19px; font-weight: 800; letter-spacing: -0.01em; margin-bottom: 22px; }
#identity-box label, #identity-box .swatch-label {
  display: block; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--ink-faint); margin-bottom: 7px;
}
#player-name-input {
  width: 100%; padding: 10px 12px; font-size: 15px; font-family: inherit;
  border: 1.5px solid var(--line); border-radius: 10px; outline: none; margin-bottom: 20px;
}
#player-name-input:focus { border-color: var(--accent); }
.swatch-label { margin-top: 4px; }
#colour-swatches { display: flex; justify-content: space-between; margin-bottom: 24px; }
.colour-swatch {
  width: 34px; height: 34px; border-radius: 50%; border: 2.5px solid transparent;
  cursor: pointer; padding: 0; outline: none; transition: transform 0.1s; box-shadow: var(--shadow-sm);
}
.colour-swatch:hover { transform: scale(1.1); }
.colour-swatch.selected { border-color: var(--ink); transform: scale(1.1); box-shadow: inset 0 0 0 2px #fff; }
.colour-swatch.taken { opacity: 0.25; cursor: not-allowed; }
.colour-swatch.taken:hover { transform: none; }
.colour-msg { font-size: 12px; color: var(--accent); min-height: 16px; margin-bottom: 14px; }
#start-btn {
  width: 100%; padding: 12px; font-size: 14px; font-weight: 700; font-family: inherit;
  background: var(--ink); color: var(--bg); border: none; border-radius: 10px; letter-spacing: 0.01em;
}
#start-btn:disabled { background: var(--line-strong); cursor: not-allowed; }
#start-btn:not(:disabled):hover { transform: translateY(-1px); }
#identity-back-btn {
  width: 100%; margin-top: 8px; padding: 9px; font-size: 13px; font-family: inherit;
  background: none; color: var(--ink-mid); border: 1.5px solid var(--line-strong);
  border-radius: 10px; cursor: pointer;
}
#identity-back-btn:hover { color: var(--ink); border-color: var(--ink-mid); }

/* ================================================================
   END-OF-GAME BANNER (completion) — bottom-pinned bar, not a modal,
   so the finished/revealed grid stays visible.
   ================================================================ */

#end-overlay {
  position: fixed; left: 0; right: 0; bottom: 0;
  display: flex; justify-content: center; z-index: 100; pointer-events: none;
}
#end-box {
  display: flex; align-items: center; gap: 22px; pointer-events: auto;
  background: var(--panel); border: 1px solid var(--line); border-bottom: none;
  border-radius: 16px 16px 0 0; padding: 16px 28px; box-shadow: var(--shadow-lg); max-width: 92vw;
}
#end-heading { font-size: 20px; font-weight: 800; color: oklch(0.52 0.14 152); margin: 0; white-space: nowrap; }
#end-detail { font-size: 15px; color: var(--ink-soft); margin: 0; }
#end-actions { display: flex; gap: 10px; flex-shrink: 0; }
#end-actions .landing-action { width: auto; padding: 10px 16px; }
#end-box { flex-wrap: wrap; }

/* Final standings (competitive end screen) — full-width row in the wrapping end-box. */
#end-standings {
  flex-basis: 100%; order: 4; list-style: none; margin: 2px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 1px; width: 100%;
}
.end-rank {
  display: flex; justify-content: space-between; align-items: baseline; gap: 18px;
  font-size: 13px; color: var(--ink-soft); padding: 4px 8px; border-radius: 7px;
}
.end-rank.me { color: var(--ink); font-weight: 700; background: var(--bg); }
.end-rank.ghost .end-rank-name { font-style: italic; }
.end-rank-score { font-variant-numeric: tabular-nums; white-space: nowrap; }
#end-actions { order: 5; }

/* ── Pre-game waiting room (full-screen modal over the board) ── */
#waiting-overlay {
  position: fixed; inset: 0; background: oklch(0 0 0 / 0.45);
  display: flex; align-items: center; justify-content: center; z-index: 200;
}
#waiting-box {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; padding: 26px 30px 22px;
  width: 460px; max-width: 92vw; max-height: 88vh; overflow-y: auto;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 14px;
}
#waiting-heading { font-size: 20px; font-weight: 800; margin: 0; color: var(--ink); }
.waiting-sub { margin: -8px 0 0; font-size: 14px; color: var(--ink-soft); }
.waiting-counts {
  margin: 2px 0 0; font-size: 13.5px; color: var(--ink-soft);
  display: inline-flex; align-items: center; gap: 8px; align-self: center;
  padding: 6px 14px; border-radius: 999px;
  background: oklch(0.96 0.02 150); border: 1px solid var(--line);
}
.waiting-counts strong { color: var(--ink); font-weight: 800; font-variant-numeric: tabular-nums; }
.waiting-countdown { font-size: 15px; color: var(--ink-soft); font-weight: 600; }
.waiting-countdown #waiting-seconds { font-size: 22px; font-weight: 800; color: var(--ink); }
.waiting-roster ul {
  list-style: none; margin: 8px 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.waiting-player {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 12px 5px 10px; border-radius: 999px;
  background: var(--bg); border: 1px solid var(--line);
  font-size: 13px; font-weight: 600; color: var(--ink);
}
.waiting-player .dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.waiting-player.is-me { border-color: var(--me-edge); }
.waiting-board { border-top: 1px solid var(--line); padding-top: 12px; }
.waiting-invite { display: flex; flex-direction: column; gap: 8px; }
.invite-layout { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.invite-main { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }

/* Room code as crossword cells — the whole strip is the copy button. */
.code-tiles {
  display: inline-flex; gap: 4px; cursor: pointer; border-radius: 8px;
  padding: 2px; transition: transform .12s;
}
.code-tiles:hover { transform: translateY(-1px); }
.code-tiles:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.code-cell {
  width: 38px; height: 44px; display: grid; place-items: center;
  font-family: "Poppins", system-ui, sans-serif; font-weight: 800; font-size: 24px;
  color: var(--ink); background: var(--panel);
  border: 2px solid var(--ink); border-radius: 6px;
  box-shadow: 0 2px 0 rgb(20 15 5 / 0.18);
}
.code-tiles:hover .code-cell { background: #fff; }
.waiting-copy-feedback { font-size: 12.5px; color: var(--ink-soft); }

/* QR of the invite link. */
.invite-qr {
  flex: 0 0 auto; line-height: 0; padding: 6px;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 2px 8px rgb(20 15 5 / 0.10);
}
.invite-qr svg { display: block; border-radius: 4px; }
.waiting-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.waiting-actions .landing-action { width: auto; flex: 1; padding: 11px 16px; }

/* While the waiting room is up, the board + clues are hidden so the puzzle can't
   be read (or pre-filled) before the game starts. The topbar sits outside .stage. */
body.pre-game .stage { visibility: hidden; }

/* ── Auth prompt (inside end overlay) ─────────────────────── */
#auth-prompt { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; width: 100%; padding-top: 2px; }
.auth-save-msg { margin: 0; font-size: 14px; color: var(--ink-soft); }
.auth-save-ok  { color: oklch(0.52 0.14 152); }
.auth-save-err { color: oklch(0.5 0.18 27); }
.auth-prompt-btn {
  padding: 8px 16px; font-size: 14px; font-weight: 600;
  background: var(--ink); color: var(--bg);
  border: none; border-radius: 8px; cursor: pointer; white-space: nowrap;
}
.auth-prompt-btn:hover { opacity: 0.85; }

/* ── Auth modal ────────────────────────────────────────────── */
#auth-modal {
  position: fixed; inset: 0; background: oklch(0 0 0 / 0.45);
  display: flex; align-items: center; justify-content: center; z-index: 200;
}
#auth-box {
  position: relative; background: var(--panel);
  border: 1px solid var(--line); border-radius: 18px;
  padding: 36px 40px 32px; width: 380px; max-width: 92vw;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 12px;
}
#auth-box h2 { font-size: 20px; font-weight: 800; margin: 0; letter-spacing: -0.01em; }
.auth-subtitle { font-size: 14px; color: var(--ink-soft); margin: 0; }
.auth-close {
  position: absolute; top: 14px; right: 16px;
  background: none; border: none; cursor: pointer;
  font-size: 18px; color: var(--ink-faint); line-height: 1; padding: 4px 8px;
}
.auth-close:hover { color: var(--ink); }
.auth-provider-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; padding: 11px 16px; font-size: 14px; font-weight: 600;
  border: 1.5px solid var(--line); border-radius: 10px; background: var(--bg);
  cursor: pointer; transition: background 0.12s, border-color 0.12s;
}
.auth-provider-btn:hover { background: var(--paper); border-color: var(--ink-faint); }
.auth-divider {
  display: flex; align-items: center; gap: 10px;
  color: var(--ink-faint); font-size: 12px;
}
.auth-divider::before, .auth-divider::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}
.auth-label { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.auth-email-input {
  width: 100%; box-sizing: border-box;
  padding: 10px 13px; font-size: 14px; font-family: inherit;
  border: 1.5px solid var(--line); border-radius: 10px; background: var(--bg);
  outline: none; transition: border-color 0.15s;
}
.auth-email-input:focus { border-color: var(--ink-faint); }
.auth-email-submit { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.auth-email-submit:hover { background: oklch(0.25 0 0); border-color: oklch(0.25 0 0); }
.auth-msg { font-size: 13px; margin: 0; min-height: 1.3em; }
.auth-msg-ok    { color: oklch(0.52 0.14 152); }
.auth-msg-error { color: oklch(0.5 0.18 27); }

/* ── Account chip (topbar) ─────────────────────────────────── */
.account-chip {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 12px; border-radius: 20px;
  border: 1.5px solid var(--line); background: var(--panel);
  font-size: 13px;
}
.account-name { font-weight: 600; color: var(--ink); }
.account-btn {
  background: none; border: none; cursor: pointer;
  font-size: 12px; color: var(--ink-faint); padding: 0;
  font-family: inherit;
}
.account-btn:hover { color: var(--ink); }
.account-signout { }

/* ── Stats modal ────────────────────────────────────────────── */
#stats-modal {
  position: fixed; inset: 0; background: oklch(0 0 0 / 0.45);
  display: flex; align-items: center; justify-content: center; z-index: 200;
}
#stats-box {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; padding: 28px 32px 24px;
  width: 460px; max-width: 92vw; max-height: 80vh;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 16px;
}
.stats-header {
  display: flex; align-items: center; justify-content: space-between;
}
.stats-header h2 { font-size: 19px; font-weight: 800; margin: 0; letter-spacing: -0.01em; }
.stats-close {
  background: none; border: none; cursor: pointer;
  font-size: 18px; color: var(--ink-faint); padding: 4px 8px; line-height: 1;
}
.stats-close:hover { color: var(--ink); }
.stats-table {
  width: 100%; border-collapse: collapse; font-size: 14px;
  overflow-y: auto;
}
.stats-th {
  text-align: left; font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-faint);
  padding: 0 12px 10px 0; border-bottom: 1px solid var(--line);
}
.stats-row:not(:last-child) .stats-cell { border-bottom: 1px solid var(--line); }
.stats-cell { padding: 10px 12px 10px 0; color: var(--ink); }
.stats-date  { color: var(--ink-soft); white-space: nowrap; }
.stats-mode  { font-weight: 600; }
.stats-score { font-variant-numeric: tabular-nums; }
.stats-place { color: var(--ink-soft); }
.stats-ghost { margin-left: 4px; font-size: 0.82em; opacity: 0.7; cursor: help; white-space: nowrap; }
.stats-loading, .stats-empty {
  text-align: center; color: var(--ink-faint); padding: 24px 0; font-size: 14px;
}

/* ── Leaderboard modal (reuses the stats table styles) ── */
#leaderboard-modal {
  position: fixed; inset: 0; background: oklch(0 0 0 / 0.45);
  display: flex; align-items: center; justify-content: center; z-index: 200;
}
#leaderboard-box {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; padding: 28px 32px 24px;
  width: 560px; max-width: 94vw; max-height: 84vh;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 14px;
}
.lb-sub { margin: -8px 0 0; font-size: 13px; color: var(--ink-faint); }
.lb-controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.lb-select {
  flex: 1 1 160px; min-width: 140px; padding: 7px 10px;
  border: 1px solid var(--line); border-radius: 9px; background: var(--bg);
  color: var(--ink); font: inherit; font-size: 13px;
}
.lb-toggle { display: inline-flex; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; }
.lb-tab {
  background: var(--bg); border: none; cursor: pointer;
  padding: 7px 12px; font: inherit; font-size: 13px; font-weight: 600;
  color: var(--ink-soft); border-left: 1px solid var(--line);
}
.lb-tab:first-child { border-left: none; }
.lb-tab.active { background: var(--ink); color: var(--panel); }
.lb-rank   { width: 2.5em; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.lb-num    { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
th.lb-num  { text-align: right; }

/* A subtle text-link action on the landing (Leaderboard). */
.land-link {
  margin-top: 14px; align-self: center;
  background: none; border: none; cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 600;
  color: var(--land-ink, var(--ink)); opacity: 0.85;
  padding: 6px 10px; border-radius: 8px;
}
.land-link:hover { opacity: 1; text-decoration: underline; }

/* Landing footer — Privacy / Terms as small grid buttons (position set by
   renderLanding), bottom-left. */
.land-footer {
  position: absolute; z-index: 3;
  display: flex; align-items: center; gap: calc(2 * var(--cell));   /* Privacy sits one cell further left */
}
.land-footer span { display: none; }   /* drop the middot separator */
.land-footer a {
  width: calc(3 * var(--cell) - var(--grid-w)); height: calc(1 * var(--cell) - var(--grid-w));
  display: grid; place-items: center; text-decoration: none;
  border-radius: 8px; background: #8d99ae; color: #fff;   /* palette slate grey */
  font-family: "Poppins", system-ui, sans-serif; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.02em;
  font-size: clamp(9px, calc(var(--cell) * 0.3), 12px);
  transition: filter 0.12s;
}
.land-footer a:hover { filter: brightness(1.06); }

/* ================================================================
   PLAYER COLOUR THEMING — Colour mode only (body:not(.no-colour))
   --my-tint and --my-fill are set on <body> by setLocalPlayer().
   Graduated intensities: page bg = full tint (most coloured);
   panels float above it, lighter; clue banner grades back up
   toward the fill shade to draw focus toward the action zone.
   Other players' colours are per-element inline styles in JS and
   are unaffected by these background rules.
   Fallbacks (white / var(--bg)) keep the UI neutral before a
   player identity is confirmed.
   ================================================================ */

body:not(.no-colour) {
  background-color: var(--my-tint, var(--bg));
}

/* Sticky bars: lighter than the page so they read as elevated surfaces,
   but still noticeably tinted. Preserve the semi-transparent base so
   the backdrop-filter blur still shows through when scrolling. */
body:not(.no-colour) .topbar {
  background: color-mix(in srgb, var(--my-tint, white) 55%, rgb(255 255 255 / 0.86));
}
body:not(.no-colour) #news-dateline {
  background: color-mix(in srgb, var(--my-tint, white) 55%, rgb(255 255 255 / 0.86));
}

/* Generic panels — lightest surfaces, clearest reading area. */
body:not(.no-colour) .panel {
  background: color-mix(in srgb, var(--my-tint, white) 30%, white);
}
body:not(.no-colour) #utility-bar {
  background: color-mix(in srgb, var(--my-tint, white) 35%, white);
}

/* Clue banner: kept plain white (in both colour and classic views) so the active
   clue reads cleanly and stands apart from the tinted panels around it. */
body:not(.no-colour) .clue-banner {
  background: #fff;
}

/* ================================================================
   ╔══════════════════════════════════════════════════════════╗
   ║  CLASSIC VIEW  ·  body.no-colour  ·  newspaper theme  ║
   ╚══════════════════════════════════════════════════════════╝
   Turns the whole page into an aged sepia broadsheet: warm tan
   paper with grain + vignette, a serif masthead, pencil-circled
   selected clues, wavy scribbled-out solved clues, inked black
   squares and an amber graphite cursor. Everything here is scoped
   to body.no-colour so Colour mode is untouched. Most chrome
   re-themes automatically via the variable overrides below; the
   rest is bespoke newspaper detailing.
   ================================================================ */

/* Animated angle used to "draw" the pencil circle around a selected clue.
   initial-value is 360deg (fully drawn) so the circle is visible even where
   @property angle animation isn't supported; the keyframes draw it in elsewhere. */
@property --news-draw { syntax: "<angle>"; inherits: false; initial-value: 360deg; }

body.no-colour {
  /* — Sepia palette (warm / tan) — */
  --bg:          #e3d0a3;   /* page paper            */
  --panel:       #f1e6cb;   /* lighter card paper    */
  --ink:         #2b2113;   /* near-black brown ink  */
  --ink-soft:    #5b482c;
  --ink-faint:   #8a7250;
  --line:        #cbb588;   /* hairline rules        */
  --line-strong: #a98f5f;
  --block:       #241a10;   /* inked black square    */
  --amber:       #e7cf8d;
  --amber-strong:#d7b75c;
  --accent:      #883225;   /* faded red press ink   */
  --radius:      2px;
  --shadow-sm: 0 1px 0 rgba(43, 33, 19, 0.10);
  --shadow-md: 0 1px 0 rgba(43, 33, 19, 0.10);
  --shadow-lg: 0 10px 30px rgba(43, 33, 19, 0.22);

  --news-display: "Playfair Display", Georgia, "Times New Roman", serif;
  --news-body:    "PT Serif", Georgia, "Times New Roman", serif;
  --news-hand:    "Architects Daughter", cursive;
  --pencil:       #5e4f3a;  /* graphite line colour  */

  background-color: var(--bg);
  font-family: var(--news-body);
  color: var(--ink);
}

/* — Aged paper: grain + vignette + corner stains.
     Fixed layer behind everything; pointer-transparent. — */
body.no-colour::before {
  content: "";
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-color: var(--bg);
  background-image:
    /* corner tea-stains */
    radial-gradient(circle at 88% 12%, rgba(120, 86, 38, 0.16), rgba(120, 86, 38, 0) 26%),
    radial-gradient(circle at 12% 96%, rgba(120, 86, 38, 0.12), rgba(120, 86, 38, 0) 30%),
    /* soft vignette darkening toward the edges */
    radial-gradient(120% 120% at 50% 42%, rgba(227,208,163,0) 56%, rgba(120, 92, 48, 0.28) 100%),
    /* fibre grain */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  background-size: auto, auto, auto, 220px 220px;
  background-blend-mode: multiply, multiply, multiply, soft-light;
}

/* Selection + scrollbars in sepia. */
body.no-colour ::selection { background: var(--amber-strong); }
/* …but never tint the grid cells' selected letter (game.js calls input.select()
   on the cursor cell, so this would otherwise paint a darker box behind it). */
body.no-colour .cell input::selection { background: transparent; }
body.no-colour .clue-scroll::-webkit-scrollbar-thumb {
  background: var(--line-strong); border: 2px solid var(--panel);
}

/* — Panels: flatten the soft white cards into ink-ruled paper. — */
body.no-colour .panel {
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  box-shadow: none;
}

/* ───────────── MASTHEAD: top bar nameplate + dateline rule ───────────── */

body.no-colour .topbar {
  background: var(--bg);
  -webkit-backdrop-filter: none; backdrop-filter: none;
  border-bottom: 1px solid var(--ink);
  box-shadow: inset 0 -4px 0 var(--bg), inset 0 -5px 0 var(--ink);
}
body.no-colour .brand-mark {
  background: var(--ink); color: var(--bg);
  border-radius: 3px; box-shadow: none;
}
body.no-colour .brand-name {
  font-family: var(--news-display);
  font-style: italic; font-weight: 800;
  font-size: 27px; letter-spacing: 0; line-height: 1; white-space: nowrap;
  color: var(--ink);
}
body.no-colour .brand-name span { color: var(--ink-soft); font-weight: 500; }

/* Dateline bar — shown in both modes. Colour mode: date + puzzle no only.
   Classic view: full newspaper strip with Vol., Late Edition, etc. */
#news-dateline {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 6px 13px;
  padding: 7px 26px 8px;
  background: oklch(0.984 0.003 95 / 0.86);
  backdrop-filter: saturate(1.4) blur(10px);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line);
  font-size: 11.5px; font-weight: 500; letter-spacing: 0.06em;
  line-height: 16px;   /* pinned: PT Serif vs Schibsted must not change bar height */
  color: var(--ink-soft);
}
/* Colour mode: hide Vol. MMXXVI and Late Edition (items 1, 2, 6, 7). */
#news-dateline > :nth-child(1),
#news-dateline > :nth-child(2),
#news-dateline > :nth-child(6),
#news-dateline > :nth-child(7) { display: none; }
#news-dateline .nd-item { white-space: nowrap; }
#news-dateline .nd-sep { letter-spacing: 0; }

/* Classic view: full newspaper strip. */
body.no-colour #news-dateline {
  background: var(--bg);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  /* Double rule via inset shadow (same trick as the topbar): border stays
     1px like Colour mode, so nothing below shifts when switching. */
  border-bottom-color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--bg), inset 0 -3px 0 var(--ink);
  font-family: var(--news-body);
  font-weight: 700; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--ink-soft);
}
body.no-colour #news-dateline > :nth-child(1),
body.no-colour #news-dateline > :nth-child(2),
body.no-colour #news-dateline > :nth-child(6),
body.no-colour #news-dateline > :nth-child(7) { display: inline; }
body.no-colour #news-dateline .nd-sep { color: var(--line-strong); }

/* Room chip + account chip → spare newspaper boxes. */
body.no-colour .room-chip,
body.no-colour .account-chip {
  background: var(--panel); border: 1px solid var(--line-strong);
  border-radius: 2px; box-shadow: none; color: var(--ink-soft);
}
body.no-colour .copy-btn,
body.no-colour .landing-action:not(.secondary) { background: var(--ink); color: var(--bg); }
body.no-colour .av { border-color: var(--bg); box-shadow: 0 0 0 1px var(--line-strong); }
body.no-colour .presence-label { font-family: var(--news-body); font-style: italic; }

/* ───────────── CLUE BANNER (current clue) ───────────── */

body.no-colour .clue-banner { background: var(--panel); }
body.no-colour .cb-tag {
  font-family: var(--news-display); font-weight: 900; color: var(--ink);
}
body.no-colour .cb-clue { font-family: var(--news-body); font-weight: 700; color: var(--ink); }
body.no-colour .cb-nav:hover { background: rgba(94, 79, 58, 0.12); color: var(--ink); }

/* ───────────── BOARD ───────────── */

body.no-colour #grid {
  background: var(--ink);            /* gridlines read as printed ink */
  border: 2px solid var(--ink);
  border-radius: 2px;
  /* gap must match Colour mode (1px) — the board is width:max-content and
     sets the whole column width, so a gap change shifts the entire layout. */
}
body.no-colour .cell.white { background: var(--panel); }

/* Inked black squares with a faint diagonal hatch texture. */
body.no-colour .cell.black {
  background-color: var(--block);
  background-image:
    repeating-linear-gradient(45deg, rgba(255,255,255,0.04) 0 2px, rgba(0,0,0,0) 2px 5px);
}

/* Letters: hand-written pencil. Soft graphite base — not pure ink-black. */
body.no-colour .cell input {
  font-family: var(--news-hand);
  font-weight: 400;
  font-size: 22px;
  color: #4a4745;
  letter-spacing: 0.01em;
}
/* Pencil pressure variance: a prime-7 cycle so the pattern doesn't align with
   row/column width and creates an organic, non-repeating feel across the grid.
   Excludes revealed cells so their muted colour isn't pushed below readability. */
body.no-colour #grid .cell:not(.revealed):nth-child(7n+1) input { opacity: 0.88; }
body.no-colour #grid .cell:not(.revealed):nth-child(7n+2) input { opacity: 0.76; }
body.no-colour #grid .cell:not(.revealed):nth-child(7n+3) input { opacity: 1.00; }
body.no-colour #grid .cell:not(.revealed):nth-child(7n+4) input { opacity: 0.84; }
body.no-colour #grid .cell:not(.revealed):nth-child(7n+5) input { opacity: 0.72; }
body.no-colour #grid .cell:not(.revealed):nth-child(7n+6) input { opacity: 0.95; }
body.no-colour #grid .cell:not(.revealed):nth-child(7n)   input { opacity: 0.90; }
body.no-colour .cell.correct,
body.no-colour .cell.typed,
body.no-colour .cell.revealed { background: var(--panel); }
body.no-colour .cell.revealed input { color: var(--ink-soft); }
body.no-colour .cell .clue-num { color: var(--ink-faint); font-family: var(--news-body); }

/* Active word: bold graphite pencil box with hand-drawn feTurbulence warp. */
body.no-colour .word-outline {
  display: block !important;
  border: 3px solid var(--pencil) !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  opacity: 0.95;
  filter: url(#pencil-warp);
}
/* Selected word: the whole word lifts to the pale parchment tint. */
/* Three-tier brightness: paper (#f1e6cb) → word (#faf3e0) → cursor (#ffffff). */
body.no-colour .cell.highlighted { background: #faf3e0; }

/* Cursor cell: pure white inside the bold dark graphite pencil ring. */
body.no-colour .cell.active { background: #ffffff; }
body.no-colour .cell.active input { color: var(--ink); }
body.no-colour .cell.active .clue-num { color: var(--ink-faint); }
body.no-colour .cell.active::before {
  content: "";
  position: absolute; inset: -1px; z-index: 3;
  pointer-events: none;
  border: 4px solid var(--pencil);
  border-radius: 1px;
  filter: url(#pencil-warp);
}

/* In Classic view, tint the bars to match the sepia ink. */
body.no-colour .cell.bar-left { box-shadow: inset 3px 0 0 0 var(--ink); }
body.no-colour .cell.bar-top  { box-shadow: inset 0 3px 0 0 var(--ink); }
body.no-colour .cell.bar-left.bar-top {
  box-shadow: inset 3px 0 0 0 var(--ink), inset 0 3px 0 0 var(--ink);
}

/* ───────────── CLUE LISTS ───────────── */

body.no-colour .clue-head {
  background: var(--panel);
  -webkit-backdrop-filter: none; backdrop-filter: none;
  /* Double rule via inset shadow — border width must match Colour mode (1px). */
  border-bottom-color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--panel), inset 0 -3px 0 var(--ink);
}
body.no-colour .clue-dir {
  font-family: var(--news-display); font-weight: 900;
  font-size: 15px; letter-spacing: 0.02em; text-transform: uppercase; color: var(--ink);
}
body.no-colour .clue-head .count { font-family: var(--news-body); font-style: italic; }

body.no-colour #across-list li,
body.no-colour #down-list li {
  font-family: var(--news-body);
  color: var(--ink);
  /* Keep base geometry (3px border + 10px padding); only hide the colour. */
  border-left-color: transparent;
}
body.no-colour #across-list li:hover,
body.no-colour #down-list li:hover { background: rgba(94, 79, 58, 0.07); }
body.no-colour .ci-num  { font-family: var(--news-body); font-weight: 700; color: var(--ink-soft); }
body.no-colour .ci-text { font-family: var(--news-body); }
body.no-colour .ci-text .enum { color: var(--ink-faint); font-style: italic; }

/* — Selected clue: a rough graphite ellipse, "drawn" in on selection. — */
body.no-colour #across-list li.active-clue,
body.no-colour #down-list li.active-clue {
  background: transparent;
  border-left-color: transparent;
  overflow: visible;
}
body.no-colour #across-list li.active-clue::after,
body.no-colour #down-list li.active-clue::after {
  content: "";
  position: absolute; inset: -3px 2px -3px 0; z-index: 4;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 60' preserveAspectRatio='none'%3E%3Cpath d='M9 31 C6 13 56 6 100 7 C152 8 197 13 192 29 C196 49 142 56 97 54 C47 53 4 51 9 29' fill='none' stroke='%235e4f3a' stroke-width='2.1' stroke-linecap='round' opacity='0.92'/%3E%3Cpath d='M12 34 C9 18 60 11 101 11 C150 11 190 17 188 31' fill='none' stroke='%235e4f3a' stroke-width='1.1' stroke-linecap='round' opacity='0.55'/%3E%3C/svg%3E");
  background-size: 100% 100%; background-repeat: no-repeat;
  -webkit-mask-image: conic-gradient(from -96deg, #000 0deg, #000 var(--news-draw), rgba(0,0,0,0) var(--news-draw));
          mask-image: conic-gradient(from -96deg, #000 0deg, #000 var(--news-draw), rgba(0,0,0,0) var(--news-draw));
  animation: newsDrawCircle 0.55s ease-out forwards;
}
@keyframes newsDrawCircle { from { --news-draw: 0deg; } to { --news-draw: 360deg; } }
@media (prefers-reduced-motion: reduce) {
  body.no-colour #across-list li.active-clue::after,
  body.no-colour #down-list li.active-clue::after {
    -webkit-mask-image: none; mask-image: none; animation: none;
  }
}

/* — Completed clue: wavy pencil scribble through the text; number kept. — */
body.no-colour #across-list li.solved-clue,
body.no-colour #down-list li.solved-clue {
  background: transparent;
  text-decoration: none;
  color: var(--ink-faint);
  position: relative;
}
body.no-colour #across-list li.solved-clue .ci-num,
body.no-colour #down-list li.solved-clue .ci-num { color: var(--ink-faint); }
body.no-colour #across-list li.solved-clue .ci-text,
body.no-colour #down-list li.solved-clue .ci-text { text-decoration: none; }

/* Solved-clue pencil strike (Classic view). NOT a CSS background: the Classic font
   wraps differently from Colour view, and a wrapped clue's last line is shorter than
   the column, so game.js measures each rendered line (Range.getClientRects) and drops
   a hand-drawn stroke (.ci-strike) over it that ends just past the last word.
   position:relative anchors those strokes to the text. */
body.no-colour #across-list li.solved-clue .ci-text,
body.no-colour #down-list li.solved-clue .ci-text {
  justify-self: start; position: relative;
}

/* One hand-drawn stroke per text line — left/top/width + the wave SVG are all set
   inline by game.js (per measured line, random wave per line). `top` is the line's
   vertical centre; translateY(-50%) centres the stroke on it. */
.ci-strike {
  position: absolute;
  height: 8px;
  transform: translateY(-50%);
  background-repeat: no-repeat;
  background-size: 100% 100%;
  pointer-events: none;
  z-index: 1;
  animation: pencilStrikeIn 0.4s ease-out forwards;
}
@keyframes pencilStrikeIn {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0% 0 0); }
}
@media (prefers-reduced-motion: reduce) {
  .ci-strike { animation: none; }
}

/* Solved + currently selected: keep both the circle and the strike, dark text. */
body.no-colour #across-list li.solved-clue.active-clue,
body.no-colour #down-list li.solved-clue.active-clue { background: transparent; }
body.no-colour #across-list li.solved-clue.active-clue .ci-num,
body.no-colour #down-list li.solved-clue.active-clue .ci-num,
body.no-colour #across-list li.solved-clue.active-clue .ci-text,
body.no-colour #down-list li.solved-clue.active-clue .ci-text { color: var(--ink); }

/* Opponent's active clue → a small graphite tick in the margin. */
body.no-colour #across-list li.opponent-clue,
body.no-colour #down-list li.opponent-clue {
  border-left: 2px solid var(--line-strong);
}

/* ───────────── SCORES → "STANDINGS" ───────────── */

body.no-colour #scoreboard { font-family: var(--news-body); }
body.no-colour .score-name {
  font-family: var(--news-body); font-weight: 700;
  letter-spacing: 0.02em; color: var(--ink-soft);
  text-transform: uppercase; font-size: 10.5px;
}
body.no-colour .score-points {
  font-family: var(--news-display); font-weight: 900;
  color: var(--ink) !important;   /* override inline player colour */
}
body.no-colour .score-chip + .score-chip { border-left-color: var(--line); }
body.no-colour .score-chip.leader .score-name { color: var(--ink); }
/* Share bar in graphite (no player colour in Classic). */
body.no-colour .score-chip::before {
  background: color-mix(in srgb, var(--pencil, #5e4f3a) 24%, transparent);
}
/* Local player's chip → a rough graphite BOX pencilled around it (no coloured frame),
   echoing the hand-drawn box used to highlight a clue. Static (no draw-in) as the board
   re-renders on every score change. */
body.no-colour .score-chip.me { overflow: visible; }
body.no-colour .score-chip.me::after {
  content: ""; position: absolute; inset: -6px -5px; z-index: 4; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 60' preserveAspectRatio='none'%3E%3Cpath d='M5 7 C55 4 140 4 195 7 C197 22 197 40 194 54 C140 57 55 57 6 54 C3 40 3 22 5 7' fill='none' stroke='%235e4f3a' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round' opacity='0.92'/%3E%3Cpath d='M9 11 C55 8 135 8 191 11 C193 24 192 40 190 50' fill='none' stroke='%235e4f3a' stroke-width='1.1' stroke-linecap='round' opacity='0.5'/%3E%3C/svg%3E");
  background-size: 100% 100%; background-repeat: no-repeat;
}
body.no-colour #utility-bar .timer-display { font-family: var(--news-display); }

/* ───────────── TOOLBAR (utility bar · seg control · buttons) ───────────── */

body.no-colour #utility-bar,
body.no-colour #action-bar {
  background: var(--panel); border: 1px solid var(--line-strong);
  border-radius: 2px; box-shadow: none;
}
body.no-colour .seg-control { border: 1px solid var(--ink); border-radius: 2px; box-shadow: none; }
body.no-colour .seg-btn {
  font-family: var(--news-body); font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft); background: var(--panel);
}
body.no-colour .seg-btn + .seg-btn { border-left: 1px solid var(--ink); }
body.no-colour .seg-btn.seg-active { background: var(--ink); color: var(--bg); }
body.no-colour .seg-btn:hover:not(.seg-active) { background: rgba(94, 79, 58, 0.1); color: var(--ink); }
body.no-colour .action-btn {
  font-family: var(--news-body); font-weight: 700;
  background: var(--panel); border: 1px solid var(--line-strong);
  border-radius: 2px; box-shadow: none; color: var(--ink-soft);
}
body.no-colour .action-btn:hover { color: var(--ink); border-color: var(--ink); transform: none; }

/* ───────────── HINT + MISC ───────────── */

body.no-colour .hint { font-family: var(--news-body); font-style: italic; color: var(--ink-soft); }
body.no-colour .hint kbd {
  font-family: var(--news-body); font-style: normal; background: var(--panel);
  border: 1px solid var(--line-strong); border-bottom-width: 2px; color: var(--ink-soft);
}
body.no-colour .eyebrow { font-family: var(--news-body); }
