/* ========================================
   GLOBAL PAGE VISIBILITY LOGIC (CRITICAL)
   ======================================== */

/* By default, all pages are hidden */
.page {
  display: none !important;
  width: 100%;
  height: 100%;
}

/* Base active state - Flexbox fallback */
.page.active {
  display: flex !important;
}

/* ========================================
   GAME BOARD: SYMMETRICAL LAYOUT (RESTORED)
   This is the specific 16-column layout from your original file.
   Layout: [1fr] [2 ctrl] [1 gap] [8 board] [1 gap] [2 ctrl] [1fr]
   ======================================== */

:root {
  /* Calculation for the game board */
  --height-based-size: calc(100vh / 9);
  --width-based-size: calc(100vw / 15);
  --optimal-cell-size: min(var(--height-based-size), var(--width-based-size));
  
  --grid-row-height: var(--optimal-cell-size);
  --board-cell-size: var(--optimal-cell-size);
  --control-width: var(--optimal-cell-size);
  
  --frame-width: calc(var(--optimal-cell-size) * 0.25);
  --frame-height: calc(var(--optimal-cell-size) * 0.25);
  
  /* Colors */
  --bg1: #2b2b2b;
  --bg2: #1f1f1f;
  --panel: #3a3a3a;
  --frame-color: #1a1a1a;
  --txt: #fff;
  --square-light: #f0d9b5;
  --square-dark: #b58863;
  --square-selected: #ffd700;
  --square-valid: rgba(0, 255, 0, 0.4);
  --square-check: rgba(255, 0, 0, 0.5);

  /* ========================================
     DESIGN SYSTEM TOKENS (gui-redesign-spec.md §1)
     New variables — not yet wired into existing rules.
     Layout/color migration happens in steps 2–8.
     ======================================== */

  /* Size grid (§1.1) */
  --unit: var(--optimal-cell-size);
  --gap: calc(var(--unit) * 0.15);

  /* Color palette (§1.2) — state, not category */
  --bg: #1a2332;
  --surface: #2a3a52;
  --surface-hover: #354766;
  --action-primary: #3b6fb8;
  --state-selected: #4CAF50;
  --state-rest: #ff9800;
  --state-rest-active: #FFC107;
  --state-danger: #d32f2f;
  --text: #ffffff;
  --text-muted: #a8b5c8;
  --border: rgba(255, 255, 255, 0.12);
  --border-selected: rgba(76, 175, 80, 0.6);

  /* Typography (§1.3) — 14px floor for accessibility */
  --font-button: max(14px, calc(var(--unit) * 0.20));
  --font-title:  max(14px, calc(var(--unit) * 0.45));
  --font-body:   max(14px, calc(var(--unit) * 0.18));

  /* Borders (§1.4) */
  --radius: calc(var(--unit) * 0.10);
  --border-width: 2px;

  /* RTL column variables (§1.5). Values are LTR; the grid container itself
     mirrors automatically under direction:rtl (inherited from <html dir="rtl">),
     which renumbers grid lines so column 1 is the visual right edge. The spec
     prescribed a manual variable swap, but combined with the auto-flip that
     was a double-flip — the game page came out un-mirrored while the settings
     page (hardcoded grid-column lines, no override) mirrored correctly. So we
     keep one set of values and let the grid do the flip. -mid is the inner
     grid line used by single-column slots (turn indicators). */
  --action-col-start: 14;
  --action-col-mid: 15;
  --action-col-end: 16;
  --info-col-start: 2;
  --info-col-mid: 3;
  --info-col-end: 4;
}

/* Main Game Page Container - Applied ONLY when active */
.page.adaptive-grid-page.active {
  display: grid !important;
  grid-template-columns: 
    1fr                                    /* Col 1: Left margin */
    repeat(2, var(--board-cell-size))      /* Col 2-3: Left controls */
    var(--board-cell-size)                 /* Col 4: Gap */
    repeat(8, var(--board-cell-size))      /* Col 5-12: Board */
    var(--board-cell-size)                 /* Col 13: Gap */
    repeat(2, var(--board-cell-size))      /* Col 14-15: Right controls */
    1fr;                                   /* Col 16: Right margin */
  grid-template-rows: repeat(8, var(--grid-row-height));
  gap: 0;
  padding: var(--frame-height) var(--frame-width);
  background: var(--frame-color);
  box-sizing: border-box;
  overflow: hidden;
}

/* --- Game Board Components --- */

.chess-board-wrapper {
  grid-column: 5 / 13;
  grid-row: 1 / 9;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  direction: ltr !important;
}

/* Positioning context for the two-phase quadrant overlays — sized
   exactly to its only sized child (#chessBoard), so absolute overlays
   sized in % map to half a board. */
.chess-board-container {
  position: relative;
}

/* Turn-indicator wrapper. Default: invisible to layout — its two
   indicator children (#whiteIndicator / #blackIndicator) take their
   own grid cells via .grid-control-left-1-col1/col2 on desktop.
   The mobile media query later in this file flips it to flex row
   so the indicators become a horizontal strip below the board. */
.turn-indicators-wrap {
  display: contents;
}

.chess-board {
  display: grid;
  grid-template-columns: repeat(8, var(--board-cell-size));
  grid-template-rows: repeat(8, var(--board-cell-size));
  border: none;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
  direction: ltr !important;
}

.chess-square {
  width: var(--board-cell-size);
  height: var(--board-cell-size);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--board-cell-size) * 0.7);
  cursor: pointer;
  transition: all 0.2s;
  position: relative;
}

.chess-square.light { background: var(--square-light); }
.chess-square.dark { background: var(--square-dark); }
.chess-square.selected { background: var(--square-selected) !important; box-shadow: inset 0 0 0 4px rgba(255, 215, 0, 0.8); }
.chess-square.valid-move::after { content: ''; position: absolute; width: 40%; height: 40%; background: var(--square-valid); border-radius: 50%; pointer-events: none; }
.chess-square.valid-capture::after { content: ''; position: absolute; width: 90%; height: 90%; border: 6px solid var(--square-valid); border-radius: 50%; pointer-events: none; }
.chess-square.in-check { background: var(--square-check) !important; }
.chess-square.last-move { box-shadow: inset 0 0 0 4px rgba(255, 165, 0, 0.7); }
.chess-square:hover { filter: brightness(1.1); }

.piece { font-size: calc(var(--board-cell-size) * 0.7); pointer-events: none; }

/* --- Two-Phase Selection: zoomed quadrant view (spec §4.2) --- */
/* When the board has .zoomed-XX, the active quadrant's 16 squares fill
   the same screen space the 8×8 board normally occupies — cells are
   2× linear (4× area). Same DOM, same IDs; only visibility and grid
   metrics change, so click/dwell coords still match algebraic squares. */
.chess-board.zoomed-NW,
.chess-board.zoomed-NE,
.chess-board.zoomed-SW,
.chess-board.zoomed-SE {
  grid-template-columns: repeat(4, calc(var(--board-cell-size) * 2));
  grid-template-rows: repeat(4, calc(var(--board-cell-size) * 2));
}
.chess-board.zoomed-NW .chess-square,
.chess-board.zoomed-NE .chess-square,
.chess-board.zoomed-SW .chess-square,
.chess-board.zoomed-SE .chess-square {
  width: calc(var(--board-cell-size) * 2);
  height: calc(var(--board-cell-size) * 2);
  font-size: calc(var(--board-cell-size) * 1.4);
}
.chess-board.zoomed-NW .chess-square .piece,
.chess-board.zoomed-NE .chess-square .piece,
.chess-board.zoomed-SW .chess-square .piece,
.chess-board.zoomed-SE .chess-square .piece {
  font-size: calc(var(--board-cell-size) * 1.4);
}
.chess-board.zoomed-NW .chess-square:not([data-quadrant="NW"]),
.chess-board.zoomed-NE .chess-square:not([data-quadrant="NE"]),
.chess-board.zoomed-SW .chess-square:not([data-quadrant="SW"]),
.chess-board.zoomed-SE .chess-square:not([data-quadrant="SE"]) {
  display: none;
}

/* Back button (two-phase): orange — visually distinct from neutral
   surface buttons, signals "undo current selection" (spec §3.3). */
.grid-control-btn.back-btn {
  background: var(--state-rest);
}
.grid-control-btn.back-btn:hover {
  background: var(--state-rest);
  filter: brightness(1.1);
}

/* --- Two-Phase Selection: quadrant overlays --- */
/* Default: hidden so Direct mode is unaffected (no event capture, no
   visual change). updateQuadrantOverlays() adds .visible in two-phase
   mode and toggles .selectable / .non-selectable per phase. Inline
   top/left/right/bottom is set in JS to map algebraic quadrant →
   visual corner based on board orientation. */
.quadrant-overlay {
  position: absolute;
  width: 50%;
  height: 50%;
  box-sizing: border-box;
  display: none;
  z-index: 5;
  background: transparent;
  cursor: pointer;
}
.quadrant-overlay.visible {
  display: block;
}
.quadrant-overlay.selectable {
  border: 2px solid rgba(76, 175, 80, 0.6);
}
/* Non-selectable still captures pointer-events (faint border, no dwell)
   so gaze can't bleed through to the squares behind. */
.quadrant-overlay.non-selectable {
  border: 2px solid rgba(255, 255, 255, 0.05);
}
.piece.white { color: #ffffff; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; }
.piece.black { color: #000000; text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; }

/* Turn Indicators (room-management-spec §4.1).
   Two-column layout: text block on the left (color label + name
   stacked), piece icon on the right. The text block is flex:1 so
   long names can ellipsize without pushing the icon out. */
.turn-indicator {
  border: 3px solid #666;
  border-radius: 8px;
  padding: 4px 8px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: #444;
  transition: all 0.3s;
  height: calc(var(--board-cell-size) - 6px);
  width: 100%;
  overflow: hidden;
  box-sizing: border-box;
}
.turn-indicator.active { background: #4CAF50; border-color: #4CAF50; box-shadow: 0 0 15px rgba(76, 175, 80, 0.5); }
.indicator-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.piece-icon {
  font-size: calc(var(--grid-row-height) * 0.5);
  flex-shrink: 0;
  line-height: 1;
}
.indicator-label {
  font-size: clamp(10px, calc(var(--grid-row-height) * 0.16), 16px);
  font-weight: 700;
  text-transform: uppercase;
  line-height: 1.1;
}
/* Player name on the second line of the text block. Empty in Local
   mode (the :empty rule collapses the line so the box reads as a
   single-line color label). */
.indicator-name {
  font-size: clamp(11px, calc(var(--grid-row-height) * 0.18), 18px);
  font-weight: 600;
  color: var(--text);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.1;
}
.indicator-name:empty { display: none; }

/* Info Panel — Mode (and optionally Code in online mode). AAC tile look:
   --surface bg matching the action buttons, white border, --text-muted
   small caption + --text large bold value, both centered. */
.game-info-panel {
  background: var(--surface);
  border: var(--border-width) solid #fff;
  border-radius: var(--radius);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  height: calc(var(--board-cell-size) - 6px);
  width: 100%;
  box-sizing: border-box;
}
.game-info {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: baseline;
  justify-content: center;
  gap: 4px;
  text-align: center;
  width: 100%;
  min-width: 0;
}
.game-info-title {
  color: var(--text-muted);
  font-size: clamp(11px, calc(var(--grid-row-height) * 0.16), 16px);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  line-height: 1.1;
  white-space: nowrap;
  flex-shrink: 0;
}
.game-info-title::after {
  content: ":";
}
.game-info-value {
  color: var(--text);
  font-weight: 700;
  font-size: clamp(13px, calc(var(--grid-row-height) * 0.18), 18px);
  line-height: 1.1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Game Buttons */
.control-btn, .grid-control-btn {
  border: 3px solid #fff;
  border-radius: 8px;
  padding: 8px;
  color: var(--txt);
  font-size: clamp(12px, calc(var(--grid-row-height) * 0.16), 18px);
  font-weight: 700;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
  position: relative;
  background: var(--surface);
  height: calc(var(--board-cell-size) - 6px);
  width: 100%;
  overflow: visible;
  box-sizing: border-box;
}
.control-btn:hover, .grid-control-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); background: var(--surface-hover); }
/* Disabled action button — used for "New Game" on the joiner's side
   in online mode (only the room creator can reset). pointer-events:none
   blocks both click and dwell-target hover detection so the eye-gaze
   ring never even starts. */
.control-btn.disabled, .grid-control-btn.disabled {
  opacity: 0.4;
  pointer-events: none;
  cursor: not-allowed;
}
.control-btn.disabled:hover, .grid-control-btn.disabled:hover { transform: none; box-shadow: 0 4px 10px rgba(0,0,0,0.3); background: var(--surface); }
.btn-icon, .grid-btn-icon { font-size: calc(var(--grid-row-height) * 0.35); }
.btn-label, .grid-btn-label { font-size: clamp(11px, calc(var(--grid-row-height) * 0.14), 16px); font-weight: 700; }

/* State styling (per gui-redesign-spec.md §1.2 — color is for state, not category) */
#restAfterMoveBtn.active {
  background: var(--state-selected) !important;
  border-color: var(--border-selected) !important;
}

/* Positioning Classes — RTL-aware via grid auto-flip (§1.5).
   The .adaptive-grid-page container inherits direction:rtl from <html>,
   which renumbers grid lines: column 1 becomes the visual right edge. So
   information (cols 2-3, LTR-left) ends up on the visual right in RTL, and
   actions (cols 14-15, LTR-right) end up on the visual left — eye-gaze
   users in Hebrew/Arabic see the mirrored layout they expect. The board
   itself is forced direction:ltr below so files stay a→h. */
.grid-board-area { grid-column: 5 / 13; grid-row: 1 / 9; display: flex; align-items: flex-start; justify-content: center; direction: ltr !important; }

/* Information area.
   Row 1-2: stacked turn indicators (room-management-spec §4.1) —
   each full-width with color label + player name. Replaced the prior
   half-row split (grid-control-left-1-col1 / -col2 retained below
   for any legacy callers but no longer used by chess.html).
   Row 3: game-info-panel (Mode / Level / Code).
   Row 4: two-phase Back button. */
.grid-control-left-1 { grid-column: var(--info-col-start) / var(--info-col-end); grid-row: 1; }
.grid-control-left-2 { grid-column: var(--info-col-start) / var(--info-col-end); grid-row: 2; }
.grid-control-left-3 { grid-column: var(--info-col-start) / var(--info-col-end); grid-row: 3; }
.grid-control-left-4 { grid-column: var(--info-col-start) / var(--info-col-end); grid-row: 4; }
/* Legacy half-row positions — kept for backward compat. */
.grid-control-left-1-col1 { grid-column: var(--info-col-start); grid-row: 1; }
.grid-control-left-1-col2 { grid-column: var(--info-col-mid); grid-row: 1; }

/* Action area — 6 buttons stacked, all full-width (2 cols × 1 row each).
   Deviation from §2.4 Option B: Settings/Fullscreen are NOT halved into a
   single shared row, because square 1u buttons read as undersized next to
   the 2-col neighbors. Easier to dwell, visually consistent. */
.grid-control-right-1 { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 1; }
.grid-control-right-2 { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 2; }
.grid-control-right-3 { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 3; }
.grid-control-right-4 { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 4; }
/* Resign sits at row 5 in the action column (between New Game at
   row 4 and Settings, which moves to row 6 to make room).
   resign-and-mobile-fix-spec §2.2 Option C. Exit Room (online-only,
   room-mgmt §8) takes the previously-empty row 8 below Fullscreen. */
.grid-control-action-resign { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 5; }
.grid-control-right-5 { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 6; }
.grid-control-right-6 { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 7; }
.grid-control-action-exit { grid-column: var(--action-col-start) / var(--action-col-end); grid-row: 8; }

/* Resign button — destructive action, --state-danger background.
   Same chrome (border, radius, font) as other action buttons. */
.grid-control-btn.resign-btn {
  background: var(--state-danger);
  border-color: var(--state-danger);
}
.grid-control-btn.resign-btn:hover {
  background: #b71c1c;
  border-color: #b71c1c;
}

/* Exit Room button — destructive action (leaves the room), shares
   the resign-btn styling. The icon + label distinguish them. */
.grid-control-btn.exit-room-btn {
  background: var(--state-danger);
  border-color: var(--state-danger);
}
.grid-control-btn.exit-room-btn:hover {
  background: #b71c1c;
  border-color: #b71c1c;
}

/* ========================================
   MODALS — centered AAC card, board visible behind
   ========================================
   The notification (#modalOverlay) and promotion (#promotionOverlay)
   popups appear as a centered card over a dimmed (but visible) board.
   Sized in --unit (board cell size) so the card scales with the board:
   game-over fits ~6u wide, promotion ~5u wide × ~5.5u tall to host the
   2x2 piece grid. Buttons are AAC-style tiles, comfortably sized for
   eye-gaze dwelling but no longer screen-dominating.

   #eyeGazeSettingsOverlay also uses .modal-overlay but its inner child
   (#eyeGazeSettingsContainer) does NOT have .modal class. Its container
   is 100vw×100vh with a solid var(--bg), so it visually covers the
   dimmed backdrop entirely — the .modal rules below leave it alone. */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 9999;
  box-sizing: border-box;
}
.modal-overlay.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* On touch devices, the eye-gaze panel renders at 100vh — but mobile
   browser chrome (URL bar, nav) reduces the visual viewport below
   that, so the bottom rows fall behind the chrome and can't be
   reached. Make this specific overlay scrollable and top-align its
   child so content at the bottom is dragged up into view.
   ID specificity (#…+.active = 1,1,0) beats .modal-overlay.active
   (0,2,0) so the centering is correctly overridden here. */
@media (hover: none) and (pointer: coarse) {
  #eyeGazeSettingsOverlay {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  #eyeGazeSettingsOverlay.active {
    align-items: flex-start;
  }
}

.modal {
  background: var(--bg);
  border: var(--border-width) solid var(--border);
  border-radius: calc(var(--radius) * 2);
  padding: calc(var(--gap) * 2);
  width: clamp(320px, calc(var(--unit) * 6), 70vw);
  max-width: 92vw;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  box-sizing: border-box;
  text-align: center;
  position: relative;
  z-index: 10000;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
}

/* Centered text block — sits above the action row. */
.modal-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gap);
  padding: var(--gap) var(--gap) calc(var(--gap) * 1.5);
}
.modal-content h2 {
  margin: 0;
  color: var(--text);
  font-weight: 700;
  font-size: max(22px, calc(var(--unit) * 0.42));
}
.modal-content p {
  margin: 0;
  color: var(--text-muted);
  font-size: max(16px, calc(var(--unit) * 0.26));
}

/* Action row: 1 or 2 AAC tiles. Each is flex:1 so when Play Again is
   hidden by JS (style.display='none'), Close grows to fill — no
   special case in CSS. Row height is ~1.1u, tile is comfortably
   tappable but doesn't dominate the modal. */
.modal-buttons {
  display: flex;
  gap: var(--gap);
  height: calc(var(--unit) * 1.1);
  margin: 0;
}

.modal-btn {
  flex: 1;
  background: var(--surface);
  border: var(--border-width) solid #fff;
  border-radius: var(--radius);
  color: var(--text);
  font-weight: 700;
  font-size: max(16px, calc(var(--unit) * 0.28));
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  box-sizing: border-box;
  padding: var(--gap);
  transition: background 0.2s, border-color 0.2s, transform 0.2s;
  overflow: visible;
  min-width: 0;
}
.modal-btn:hover {
  background: var(--surface-hover);
  transform: translateY(-2px);
}
/* Play Again is the primary affirmative action — color emphasis. */
#playAgainBtn {
  background: var(--action-primary);
  border-color: var(--action-primary);
}
#playAgainBtn:hover {
  background: var(--action-primary);
  filter: brightness(1.15);
}
/* Close keeps the default --surface; .secondary kept for legacy markup. */
.modal-btn.secondary {
  background: var(--surface);
}

/* Promotion modal: title at top, 2x2 grid of piece tiles below. Slightly
   narrower than the notification modal (5u vs 6u) since piece tiles
   look best near-square. */
.modal.promotion-modal {
  width: clamp(320px, calc(var(--unit) * 5), 60vw);
}
.promotion-buttons {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: var(--gap);
  height: calc(var(--unit) * 4);
  margin: 0;
}
.promotion-btn {
  background: var(--surface);
  border: var(--border-width) solid #fff;
  border-radius: var(--radius);
  color: var(--text);
  font-weight: 700;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(var(--gap) * 0.5);
  padding: var(--gap);
  font-size: max(14px, calc(var(--unit) * 0.22));
  position: relative;
  box-sizing: border-box;
  transition: background 0.2s, border-color 0.2s, transform 0.2s;
  overflow: visible;
  min-width: 0;
  min-height: 0;
}
.promotion-btn:hover {
  background: var(--surface-hover);
  transform: translateY(-2px);
}
.promotion-icon {
  font-size: max(40px, calc(var(--unit) * 0.95));
  line-height: 1;
}

/* ========================================
   SETTINGS PAGE: 12-COLUMN GRID (NEW)
   This only applies when .settings-grid-page AND .active are present
   ======================================== */

.page.settings-grid-page.active {
  /* 4-col grid: a narrow caption column on the left + 3 button columns
     (AAC tile aesthetic). The caption col uses 0.5fr so it stays clearly
     narrower than buttons; auto-sizing keeps it readable for the longest
     label ("Board Design"). Row height fits 6 rows into 100vh exactly:
     6 rows + 5 internal gaps + 2 outer (top/bottom padding) gaps = 100vh.
     Layout is STATIC across modes — row 4 is reserved for the second
     line of AI difficulty levels (when vs AI is selected); for Local /
     Online it stays empty rather than reflowing the rest of the page.
     The player-name field (room-management-spec §3.1) sits in col 1
     of row 3, alongside the context's first row of buttons (Beginner /
     Easy / Medium for vs AI; Create / Join for Online). Hidden in
     Local mode by JS — the cell was empty in Local before too, so
     no layout shift. */
  --s-cell: min(calc(100vh / 11), calc(100vw / 13));

  display: grid !important;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: calc((100vh - 7 * var(--gap)) / 6);

  gap: var(--gap);
  padding: var(--gap);
  background: var(--bg);
  box-sizing: border-box;
  overflow: hidden;
}

/* Row label column. Non-interactive — no border, no background, no hover
   or dwell affordance. Left-aligned muted text, distinct from the
   button tiles to its right. */
.settings-caption {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 0 calc(var(--gap) * 0.5);
  color: var(--text-muted);
  font-weight: 600;
  font-size: calc(var(--s-cell) * 0.3);
  pointer-events: none;
  user-select: none;
}

/* Settings button styling — monochrome per spec §1.2: every button is
   --surface by default, --state-selected when chosen. No category colors.
   box-sizing:border-box keeps padding inside the rendered width so a
   row-1 button (e.g. White) and a row-3 button (e.g. Create) end up the
   same width — both fill their grid cell / flex share identically. */
.settings-grid-btn {
  background: var(--surface);
  border: var(--border-width) solid #fff;
  border-radius: var(--radius);
  color: var(--text);
  font-weight: 700;
  font-size: calc(var(--s-cell) * 0.35);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  box-shadow: 0 4px 10px rgba(0,0,0,0.3);
  transition: transform 0.2s, background 0.2s;
  text-align: center;
  padding: 5px;
  box-sizing: border-box;
}
.settings-grid-btn:hover { transform: translateY(-2px); background: var(--surface-hover); }
.settings-grid-btn.selected { background: var(--state-selected); border-color: var(--border-selected); box-shadow: inset 0 0 15px rgba(0,0,0,0.2); }

/* Settings layout — items flow in HTML order through the 3-col grid.
   Most rows are filled by three siblings (color/mode/theme), so they don't
   need explicit positioning. Only context (full-width span, see below) and
   start (col-3 only, leaves middle empty) override the natural flow. */

/* The context row uses a CSS subgrid so its inner cells (e.g. Create / Join
   / display-slot) share the exact same column tracks as the rows above
   (White / Black / Rest) and below (Classic / Green / Blue). Without
   subgrid the inner row was a flex container — flex's "divide by 3" and
   the page grid's "divide by 3" round subpixels independently and the
   inner cells drift 1px from the outer columns. Subgrid eliminates the
   drift entirely.

   Spans cols 2-4 (the three button columns) so it aligns with the
   buttons in the rows above/below; the caption column (col 1) stays
   blank for this row. */
.settings-context {
  grid-column: 2 / 5;
  /* Always rows 3-4 so the page layout is static across modes —
     row 4 is the AI difficulty's second line when vs AI; for Local /
     Online it just stays empty. grid-template-rows: subgrid inherits
     the outer row tracks (and their gap) so the two AI rows have
     proper visual separation. */
  grid-row: 3 / span 2;
  display: grid;
  grid-template-columns: subgrid;
  grid-template-rows: subgrid;
}

/* Player-name input (room-management-spec §3.1). Lives in col 1 of
   row 3 — the cell that was an empty caption before — so in vs AI
   it sits to the left of Beginner/Easy/Medium and in Online to the
   left of Create/Join. Hidden via display:none in Local. Sized to
   match .context-input (the game-code box) — same physical
   dimensions, lighter font so 20 chars fit. */
.settings-name-input {
  grid-column: 1;
  grid-row: 3;
  width: 100%;
  height: 100%;
  font-size: clamp(14px, calc(var(--s-cell) * 0.4), 22px);
  padding: 0 calc(var(--gap) * 0.5);
  border-radius: var(--radius);
  border: var(--border-width) solid #fff;
  background: #333;
  color: var(--text);
  box-sizing: border-box;
  text-align: center;
}
.settings-name-input:focus {
  outline: 2px solid var(--state-selected);
  outline-offset: 2px;
}

/* Pin theme + bottom-row items to fixed grid rows so they don't
   flow into the spanning context's free cells. Without these the
   board-design caption ended up in row 4 col 1 (next to the
   spanning context) and theme buttons spilled into row 5 mostly,
   pushing Eye Gaze / Language / Start out of place. */
.page.settings-grid-page.active .settings-caption[data-i18n="chess.settings.captionBoardDesign"],
.page.settings-grid-page.active [data-setting="theme"] {
  grid-row: 5;
}
.page.settings-grid-page.active #backToExplorerBtn {
  grid-column: 1; grid-row: 6;
}
.page.settings-grid-page.active #eyeGazeSettingsBtn {
  grid-column: 2; grid-row: 6;
}
.page.settings-grid-page.active #languageBtn {
  grid-column: 3; grid-row: 6;
}
.page.settings-grid-page.active #startGameBtn {
  grid-column: 4; grid-row: 6;
}

/* Context area internals — exactly one of #aiControls / #onlineControls /
   #localControls has .active. We use display:contents on the active
   wrapper so its children become direct grid items of .settings-context
   (and thus inherit the subgrid columns). The wrapper itself disappears
   from layout — no extra flex/grid sizing math, no drift. */
#aiControls, #onlineControls, #localControls { display: none; }
#aiControls.active, #onlineControls.active, #localControls.active { display: contents; }

/* Local mode info treatment — full-row read-only display, blends with the
   page background so it visually distinguishes from interactive button
   rows. No border, muted italic text. Spans the 3 button columns of the
   subgrid (which itself starts at col 2 of the parent). */
#localControls.active > div {
  grid-column: 1 / 4;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  color: var(--text-muted);
  font-size: calc(var(--s-cell) * 0.3);
  font-style: italic;
}

/* Online mode display slot (col 3 of the row). Wraps both the code-display
   and the code-input sections; one is shown at a time, the other is .hidden. */
.context-display-slot { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.context-display-slot > div { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.context-display-slot > div.hidden { display: none; }

.context-btn { border-radius: var(--radius); border: var(--border-width) solid #fff; background: var(--surface); color: var(--text); font-size: calc(var(--s-cell) * 0.3); font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 5px; box-sizing: border-box; position: relative; }
.context-btn.selected { background: var(--state-selected); border-color: var(--border-selected); }
.context-input { width: 100%; height: 100%; font-size: calc(var(--s-cell) * 0.6); font-family: 'Courier New', monospace; letter-spacing: 4px; text-align: center; text-transform: uppercase; border-radius: var(--radius); border: var(--border-width) solid #fff; background: #333; color: var(--text); }
.context-text-display { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: calc(var(--s-cell) * 0.6); font-family: 'Courier New', monospace; letter-spacing: 4px; background: #333; border-radius: var(--radius); border: var(--border-width) solid #fff; color: var(--state-selected); font-weight: bold; }

/* Share button — its own grid cell at col 3 row 2 of the
   .settings-context subgrid (i.e. directly below the code in row 4
   of the page). Same full-cell size as the code text box above it.
   Visible only when #onlineControls is active AND a code has been
   created (#createdGameSection un-hidden). The :has() rule keeps
   visibility purely CSS-driven so we don't have to mirror a dozen
   classList toggles across the three game files. */
/* Share buttons — appear in row 4 across all four columns when
   Online mode is selected AND a code has been created. Lives at
   the .settings-grid-page level (not inside .settings-context),
   so they can occupy cols 1-4 (the subgrid only has cols 2-4).
   The :has() rule on the page both shows the buttons and shrinks
   .settings-context to just row 3 — without that, the context
   would still take rows 3-4 and overlap row 4. AAC tile aesthetic
   matches the bottom utility-row buttons. */
.settings-share-btn {
  grid-row: 4;
  display: none;
  background: var(--surface);
  border: var(--border-width) solid #fff;
  border-radius: var(--radius);
  color: var(--text);
  font-weight: 700;
  font-size: clamp(13px, calc(var(--s-cell) * 0.34), 24px);
  align-items: center; justify-content: center;
  cursor: pointer; position: relative;
  box-shadow: 0 4px 10px rgba(0,0,0,0.3);
  transition: transform 0.2s, background 0.2s;
  padding: 4px;
  box-sizing: border-box;
}
.settings-share-btn:hover { transform: translateY(-2px); background: var(--surface-hover); }
#shareSmsBtn       { grid-column: 1; }
#shareMessengerBtn { grid-column: 2; }
#shareWhatsappBtn  { grid-column: 3; }
#shareExplorerBtn  { grid-column: 4; gap: 8px; }

/* The Join-In Pro logo is black-on-transparent — invisible directly
   on --surface, so it sits in a small white pill that doubles as a
   subtle "branded" badge. height tracks the button height so it
   stays legible at any viewport. */
.share-btn-icon {
  height: clamp(28px, calc(var(--s-cell) * 0.45), 56px);
  width: auto;
  background: #fff;
  border-radius: 6px;
  padding: 3px 6px;
  flex-shrink: 0;
  object-fit: contain;
}

/* Visibility used to be driven by a :has() rule pinning onlineControls.active
   + createdGameSection:not(.hidden). Chromium versions vary in how reliably
   they re-evaluate :has() across descendant class changes (and Electron 33's
   bundled Chromium ate the rule entirely in field testing), so each game's
   JS toggles .share-row-active on the page via a MutationObserver on
   onlineControls and createdGameSection. Plain class match — no surprises. */
.settings-grid-page.share-row-active .settings-share-btn { display: flex; }
.settings-grid-page.share-row-active .settings-context { grid-row: 3 / span 1; }

/* Bottom row: caption col 1 is empty (no caption per spec), Eye Gaze
   auto-places at col 2, Language at col 3, Start at col 4. */
.settings-start    { grid-column: 4; font-size: calc(var(--s-cell) * 0.5); background: var(--action-primary); border-color: var(--action-primary); }
/* Language button — same neutral chrome as Eye Gaze, no special accent. */
.settings-language { font-size: calc(var(--s-cell) * 0.4); }

/* Touch devices: hide the eye-gaze panel and its trigger button.
   Dwell input requires hover events that touch doesn't fire, so the
   whole eye-gaze flow is unreachable / unhelpful on touch. The
   Language button stays visible and routes to the same popup, so
   touch users can still change language. The (hover: none) and
   (pointer: coarse) combo specifically targets touch-primary devices
   while leaving touchscreen laptops (which also have hover) alone. */
@media (hover: none) and (pointer: coarse) {
  #eyeGazeSettingsBtn,
  #eyeGazeSettingsOverlay {
    display: none !important;
  }
  /* With Eye Gaze hidden on touch, Language spans cols 2-3 for
     visual balance with Start at col 4. Higher-specificity selector
     to beat the .settings-grid-page.active #languageBtn rule above. */
  .page.settings-grid-page.active #languageBtn {
    grid-column: 2 / 4;
  }
}

/* Rest button styling — same chrome as other unselected tiles when off,
   yellow when rest mode is active. The .rest-active class is toggled by a
   restModeChanged listener in each game's HTML. */
#settingsRestBtnContainer button,
#gameRestBtnContainer button {
  width: 100% !important;
  padding: 0 !important;
  min-width: 0 !important;
  min-height: 0 !important;
  border: var(--border-width) solid #fff !important;
  background: var(--surface) !important;
  color: var(--text) !important;
}
#settingsRestBtnContainer button.rest-active,
#gameRestBtnContainer button.rest-active {
  background: var(--state-rest-active) !important;
  border-color: var(--state-rest-active) !important;
  color: #000 !important;
}
#settingsRestBtnContainer button {
  height: 100% !important;
  font-size: calc(var(--s-cell) * 0.3) !important;
  border-radius: var(--radius) !important;
}
#gameRestBtnContainer button {
  height: calc(var(--board-cell-size) - 6px) !important;
  font-size: clamp(12px, calc(var(--grid-row-height) * 0.16), 18px) !important;
  border-radius: 8px !important;
}

/* AI difficulty: 5 inline tiles flowing through the 2-row × 3-col
   .settings-context subgrid (rows 3-4, cols 2-4 of the page). Row
   1 = Beginner / Easy / Medium, row 2 = Hard / Expert / (empty).
   Static layout — the 2-row span exists across all modes; for Local
   / Online the second row sits empty. Only the .selected styling
   needs an override here; sizing comes from .context-btn. */
.ai-level-btn.selected {
  background: var(--state-selected);
  border-color: var(--border-selected);
}

/* Resign confirmation modal — singleton in <body>, lazy-created on
   first Resign click. Uses the standard .modal-overlay / .modal
   shell. The Yes button is red (destructive), Cancel keeps the
   neutral surface so it reads as the safer choice. */
.resign-confirm-modal .modal-btn.resign-confirm-yes {
  background: var(--state-danger);
  border-color: var(--state-danger);
}
.resign-confirm-modal .modal-btn.resign-confirm-yes:hover {
  background: #b71c1c;
  border-color: #b71c1c;
}

/* Helper */
.hidden { display: none !important; }

/* Mobile Portrait Adjustment */
/* Comma-separated media queries are the classic OR syntax — universal
   support. The Level-4 `or` keyword used previously can fail silently
   on older iOS Safari, dropping the entire block. */
@media (max-width: 600px), (orientation: portrait) {
  /* Aggressive baseline reset for phone-portrait. The base .page
     rule (chess-working.css line 6 + the inline <style> block in
     index.html) sets height:100%, which together with browser-
     default body margin clamps the page to one viewport — content
     beyond 100vh isn't reachable because body doesn't grow with
     overflowing children. Force html/body/page to expand with
     content here, and prevent horizontal scroll from any rounding
     overshoot on the board. */
  html, body {
    margin: 0 !important;
    padding: 0 !important;
    height: auto !important;
    min-height: 100% !important;
    overflow-x: hidden !important;
  }
  /* Match body BG to the page so any area that the page's box doesn't
     actually paint (e.g. children rendering beyond the flex container's
     intrinsic height) shows the same dark color instead of body's
     default white. Both the chess settings page (--bg) and the game
     page (--frame-color) are very dark; --bg is a fine common ground. */
  body {
    background: var(--bg, #1a2332) !important;
  }
  .page {
    height: auto !important;
    min-height: 100vh !important;
  }

  .page.settings-grid-page.active {
    display: flex !important;
    flex-direction: column;
    height: auto;
    min-height: 100vh;
    overflow-y: auto;
    padding: 10px;
    gap: 8px;
  }
  .settings-grid-btn, .settings-context, .settings-name-input, .control-btn {
    min-height: 80px;
    width: 100%;
    margin-bottom: 10px;
  }
  /* On mobile the cell-relative sizing of the name input collapses;
     give it a fixed floor so it stays usable in the flex column. */
  .page.settings-grid-page.active .settings-name-input {
    height: 44px;
    font-size: 16px;
  }

  /* Captions on mobile: readable above each row of stacked buttons.
     Desktop's calc(var(--s-cell) * 0.3) collapses to ~8px on phone,
     unreadable. Use a fixed 14px floor instead. Empty caption divs
     (the blank-caption rows for context + bottom row) get zero
     height so they don't introduce stray gaps. */
  .page.settings-grid-page.active .settings-caption {
    font-size: 14px;
    min-height: 24px;
    padding: 8px 4px 0;
  }
  .page.settings-grid-page.active .settings-caption:empty {
    min-height: 0;
    padding: 0;
  }

  /* Rest container: in desktop it's a grid cell with auto height; in
     flex column it has no intrinsic size and the inner button (which
     uses height:100%) collapses to 0. The collapsed button overlaps
     into the next row. Give the container an explicit floor and
     bottom-margin to match the other stacked buttons. The desktop's
     "height:100%" on the inner button resolves against the container,
     but min-height alone isn't a definite height — override the
     button's height directly here. */
  .page.settings-grid-page.active .settings-rest {
    width: 100%;
    min-height: 80px;
    margin-bottom: 10px;
  }
  .page.settings-grid-page.active #settingsRestBtnContainer button {
    height: 80px !important;
    min-height: 80px !important;
    font-size: 16px !important;
  }

  /* Context row buttons (Beginner / Intermediate / Advanced and
     Create / Join) need their own min-height — .context-btn isn't
     covered by the .settings-grid-btn rule above. */
  .page.settings-grid-page.active .context-btn,
  .page.settings-grid-page.active .context-input,
  .page.settings-grid-page.active .context-text-display {
    min-height: 60px;
    width: 100%;
    margin-bottom: 8px;
  }
  /* The code-display (Create flow) and code-input (Join flow) have
     height: 100% from the desktop rule, which on mobile flex column
     makes them fill the entire row's vertical space (~150-200px) and
     look oversized vs the surrounding 60px buttons. Clamp to 60px
     so the vertical rhythm matches. Per resign-and-mobile-fix-spec
     §1. Also collapse the wrapper .context-display-slot to content
     height — height:100% from desktop leaves a tall empty gap below
     Join even before the user clicks Create / Join (visually pushes
     the next row's caption away). On mobile we want the slot to be
     0 px when empty and 60 px when populated.  */
  .page.settings-grid-page.active .context-text-display,
  .page.settings-grid-page.active .context-input {
    height: 60px !important;
    max-height: 60px !important;
  }
  .page.settings-grid-page.active .context-display-slot,
  .page.settings-grid-page.active .context-display-slot > div {
    height: auto !important;
  }
  /* Active mode wrapper (#aiControls / #onlineControls / #localControls)
     uses display:contents on desktop so children inherit the subgrid.
     In flex column, switch to a normal block so children stack with
     proper dimensions. */
  .page.settings-grid-page.active #aiControls.active,
  .page.settings-grid-page.active #onlineControls.active,
  .page.settings-grid-page.active #localControls.active {
    display: block;
  }
  .page.settings-grid-page.active #localControls.active > div {
    width: 100%;
    min-height: 50px;
    padding: 8px;
  }

  /* Game page on phone-portrait: stack vertically. Zero horizontal
     padding so the board reaches edge-to-edge — action buttons and
     info panels below get their 10px gutter via margin instead. The
     desktop --optimal-cell-size divides 100vw by 15 (action cols +
     gap + 8 board cols + gap + action cols). Here the action cols
     stacked below the board, so the board scales by /8 to fill
     100vw. overflow:visible (rather than the desktop's overflow:
     hidden) lets the body / window scroll if content is taller than
     the visible viewport. */
  .page.adaptive-grid-page.active {
    display: flex !important;
    flex-direction: column;
    height: auto;
    min-height: 100vh;
    overflow: visible;
    padding: 10px 0;
    gap: 8px;
    /* --board-cell-size is still used by font-size and other scaling
       calcs throughout — keep it around for those. Board layout no
       longer relies on it though (see .chess-board override below).
       96vw / 8 is approximate sizing for fonts; the real board width
       comes from the percentage-based container below. */
    --width-based-size: calc(96vw / 8);
    --optimal-cell-size: calc(96vw / 8);
    --board-cell-size: calc(96vw / 8);
    --grid-row-height: calc(96vw / 8);
    --unit: calc(96vw / 8);
  }
  /* Reset desktop grid placements; flex flow follows DOM order. */
  .page.adaptive-grid-page.active > * {
    grid-column: unset !important;
    grid-row: unset !important;
  }
  /* Everything except the board gets a 10px horizontal gutter via
     margin. width:auto + align-self:stretch (default) makes them
     fill (100vw - 20px). */
  .page.adaptive-grid-page.active > *:not(.grid-board-area) {
    margin-left: 10px;
    margin-right: 10px;
    width: auto;
  }
  /* Board area: explicit 96vw width matching the cell-size math
     (8 × 96vw/8 = 96vw board). Auto-margins on both sides give
     true symmetric centering — the chess-board inside fills the
     container exactly via the desktop-default repeat(8, cell-size)
     grid template, no extra CSS needed for the board itself. */
  .page.adaptive-grid-page.active .grid-board-area {
    margin: 0 auto;
    width: 96vw;
  }
  /* Action buttons: comfy 60px touch height, override any
     desktop-derived sizes. */
  .page.adaptive-grid-page.active .grid-control-btn {
    min-height: 60px;
    height: auto !important;
  }
  /* Rest button uses height: calc(--board-cell-size - 6px) on
     desktop, which collapses to ~42px on phone (cell ~48px). Force
     a comfy touch height. */
  .page.adaptive-grid-page.active #gameRestBtnContainer {
    min-height: 60px;
  }
  .page.adaptive-grid-page.active #gameRestBtnContainer button {
    height: 60px !important;
    min-height: 60px !important;
  }
  /* Info panels (turn indicators): comfy touch height. */
  .page.adaptive-grid-page.active .turn-indicator {
    height: auto !important;
    min-height: 50px;
  }

  /* Mobile spec (Looking at Mobile.txt):
       above board    — indicator of the player whose pieces sit at the
                        TOP of the board layout (Black if playerColor='white')
       board
       below board    — the other indicator + Undo, Resign, New Game,
                        Settings, Exit Room
       hidden         — Rest, Rest After Move, Fullscreen, mode panel,
                        Back btn (two-phase mode)
     Indicator order is set inline by updateIndicatorPositions() in
     chess.html based on playerColor — defaults below assume
     whiteAtBottom (the common case).

     turn-indicators-wrap was a flex row (both indicators side-by-side
     below the board) — switch to display:contents so the two children
     flow as siblings of the page's flex column and order: works
     independently per indicator. */
  .page.adaptive-grid-page.active .turn-indicators-wrap {
    display: contents;
  }
  .page.adaptive-grid-page.active .turn-indicators-wrap > .turn-indicator {
    width: auto !important;
  }
  .page.adaptive-grid-page.active #blackIndicator { order: 1; }   /* default: above board */
  .page.adaptive-grid-page.active .grid-board-area { order: 2; }
  .page.adaptive-grid-page.active #whiteIndicator { order: 3; }   /* default: below board */
  .page.adaptive-grid-page.active .grid-control-right-2 { order: 4; }       /* Undo */
  .page.adaptive-grid-page.active .grid-control-action-resign { order: 5; } /* Resign */
  .page.adaptive-grid-page.active .grid-control-right-4 { order: 6; }       /* New Game */
  .page.adaptive-grid-page.active .grid-control-right-5 { order: 7; }       /* Settings */
  .page.adaptive-grid-page.active .grid-control-action-exit { order: 8; }   /* Exit Room */

  /* Hide on mobile per spec. */
  .page.adaptive-grid-page.active .grid-control-right-1,  /* Rest */
  .page.adaptive-grid-page.active .grid-control-right-3,  /* Rest After Move */
  .page.adaptive-grid-page.active .grid-control-right-6,  /* Fullscreen */
  .page.adaptive-grid-page.active .grid-control-left-3,   /* mode panel */
  .page.adaptive-grid-page.active .grid-control-left-4 {  /* Back btn (two-phase) */
    display: none !important;
  }

  /* Modal action buttons (game-over Play Again / Close, error
     dismiss, opponent-joined info) — desktop's calc(var(--unit) *
     1.1) collapses to ~52px on phone where unit is small. Spec §6
     wants ≥60px for comfortable tapping. */
  .modal-buttons {
    height: 60px !important;
  }
  .modal-btn {
    min-height: 60px;
  }
}