/* ==========================================================================
 * moyako-layout.css — Sprint 2 foundation (P3 tokens + AppShell primitives)
 * --------------------------------------------------------------------------
 * Spec:    docs/games-moyako-spec-v2.md §Design tokens + §Shared components
 * Scope:   ALL rules scoped under [data-moyako-v2] — live pages without the
 *          attribute are untouched (A30 / A31 cascade-safety).
 * Status:  Foundation only — no live page links this yet. Sprint 3 migrates
 *          screens onto AppShell by adding <body data-moyako-v2>.
 * ========================================================================== */

/* ============ P3 design tokens — scoped ============ */
[data-moyako-v2] {
  /* Backgrounds — Variant C structure */
  --bg-primary:     #0F1729;
  --bg-surface:     #1A2744;
  --bg-surface-2:   rgba(255, 255, 255, 0.06);

  /* Semantic actions — live brand hue roles */
  --action:         #4CAF50;   /* Primary CTA — green, live brand */
  --action-dark:    #388E3C;   /* hover / pressed */
  --accent:         #FF9800;   /* Streak, highlights — orange, live brand */
  --accent-dark:    #F57C00;
  --success:        #66BB6A;   /* Win, correct, selected */
  --error:          #F87171;   /* Loss, wrong, danger */
  --info:           #93C5FD;   /* AI, Expert, info drawer — blue, live brand */

  /* Text — contrast-annotated against --bg-primary */
  --text-primary:   #ECEFF4;   /* 13.4:1 AAA */
  --text-secondary: #CBD5E1;   /* 9.5:1 AAA  */
  --text-tertiary:  #9AA3B7;   /* 5.5:1 AA   */

  /* Borders */
  --border:         rgba(255, 255, 255, 0.12);
  --border-strong:  rgba(255, 255, 255, 0.22);

  /* Ad slot placeholder (blue-tinted) */
  --ad-placeholder: rgba(147, 197, 253, 0.10);

  /* Typography */
  --font-sans:       'Inter', system-ui, Arial, sans-serif;
  --font-size-h1:    22px;
  --font-size-h2:    18px;
  --font-size-h3:    16px;
  --font-size-body:  14px;
  --font-size-small: 12px;
  --font-size-micro: 10px;

  /* Spacing (8-pt grid) */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* Radii */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;

  /* ===== Design-system tokens (Sprint 1, opt-in via
   * body[data-page-brand-label="Sudoku"]). Other games keep
   * their existing values until propagation. ===== */

  /* Type scale (5-step, Material 3 + Apple HIG aligned) */
  --ds-font-xs:  11px;
  --ds-font-sm:  13px;
  --ds-font-md:  15px;
  --ds-font-lg:  18px;
  --ds-font-xl:  24px;
  --ds-font-2xl: 32px;

  /* Font weights — locked to 3 only */
  --ds-fw-body:    400;
  --ds-fw-label:   600;
  --ds-fw-heading: 800;

  /* Line heights — 3 only */
  --ds-lh-heading:  1.2;
  --ds-lh-body:     1.4;
  --ds-lh-numerals: 1.0;

  /* Letter-spacing — only 2 used */
  --ds-ls-uppercase: 0.5px;
  --ds-ls-default:   0;

  /* Spacing scale (4-pt baseline) */
  --ds-space-1: 4px;
  --ds-space-2: 8px;
  --ds-space-3: 12px;
  --ds-space-4: 16px;
  --ds-space-5: 24px;
  --ds-space-6: 32px;
  --ds-space-7: 48px;

  /* Radius hierarchy */
  --ds-radius-button: 6px;
  --ds-radius-chip:   8px;
  --ds-radius-card:   12px;

  /* Density scale — drops on smaller / shorter viewports.
   * Surfaces use `calc(var(--ds-font-md) * var(--ds-density))`
   * to shrink consistently. */
  --ds-density: 1;
}

/* Density per breakpoint */
@media (max-width: 1023px) {
  :root { --ds-density: 0.95; }
}
@media (max-width: 640px) and (orientation: portrait) {
  :root { --ds-density: 0.92; }
}
@media (orientation: landscape) and (max-height: 500px) {
  :root { --ds-density: 0.85; }
}

/* ============================================================
 * SUDOKU — Sprint 1 token migration (opt-in, scoped).
 * Other games inherit the tokens but don't apply them yet.
 * ============================================================ */
body[data-moyako-v2] .page {
  /* --- Cards (12 px radius, 16 px padding) ---------------- */
}
body[data-moyako-v2] .page .pane-left,
body[data-moyako-v2] .page .pane-right {
  padding: var(--ds-space-4);
  border-radius: var(--ds-radius-card);
}

/* --- Pane-left typography ---------------------------------- */
body[data-moyako-v2] .page .pane-left-title {
  font-size: calc(var(--ds-font-lg) * var(--ds-density));
  font-weight: var(--ds-fw-heading);
  line-height: var(--ds-lh-heading);
  letter-spacing: var(--ds-ls-default);
}
body[data-moyako-v2] .page .pane-left-message {
  font-size: calc(var(--ds-font-sm) * var(--ds-density));
  font-weight: var(--ds-fw-body);
  line-height: var(--ds-lh-body);
}
body[data-moyako-v2] .page .pane-left-skills-title {
  font-size: calc(var(--ds-font-xs) * var(--ds-density));
  font-weight: var(--ds-fw-label);
  text-transform: uppercase;
  letter-spacing: var(--ds-ls-uppercase);
  line-height: var(--ds-lh-heading);
}

/* --- Info-column tile typography (canonical recipe) -------- */
body[data-moyako-v2] .page .number-pad-info .board-lower-stat-label {
  font-size: calc(var(--ds-font-xs) * var(--ds-density));
  font-weight: var(--ds-fw-label);
  text-transform: uppercase;
  letter-spacing: var(--ds-ls-uppercase);
  line-height: var(--ds-lh-numerals);
}
body[data-moyako-v2] .page .number-pad-info .board-lower-stat-value {
  font-size: calc(var(--ds-font-sm) * var(--ds-density));
  font-weight: var(--ds-fw-heading);
  line-height: var(--ds-lh-numerals);
}
body[data-moyako-v2] .page .number-pad-info .board-lower-stat,
body[data-moyako-v2] .page .number-pad-info .board-lower-matchup {
  border-radius: var(--ds-radius-chip);
  padding: var(--ds-space-2) var(--ds-space-3);
}
body[data-moyako-v2] .page .number-pad-info .board-lower-stats-pair {
  gap: var(--ds-space-2);
}
body[data-moyako-v2] .page .number-pad-info {
  gap: var(--ds-space-2);
}

/* --- Action row: solid-fill instead of metallic gradient
       on secondary buttons; keep green gradient ONLY on the
       primary CTA. Per "demote metallic gradient from action
       buttons → solid-fill + subtle shadow". --------- */
body[data-moyako-v2] .page .board-lower-action-btn:not(.board-lower-action-btn--primary) {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.20);
  text-shadow: none;
  border-radius: var(--ds-radius-button);
  font-size: calc(var(--ds-font-sm) * var(--ds-density));
  font-weight: var(--ds-fw-label);
}
[data-theme="light"] body[data-moyako-v2] .page .board-lower-action-btn:not(.board-lower-action-btn--primary) {
  background: rgba(15, 23, 42, 0.04);
  border-color: rgba(15, 23, 42, 0.14);
  color: #0F172A;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.10);
}
body[data-moyako-v2] .page .board-lower-action-btn--primary {
  border-radius: var(--ds-radius-button);
  font-size: calc(var(--ds-font-sm) * var(--ds-density));
  font-weight: var(--ds-fw-heading);
}

/* --- Colour discipline: only the difficulty pill keeps a
       coloured tier gradient. Stat tiles flip to neutral
       silver (light) / dark slate (dark) with a coloured
       value text, instead of every tile competing for the
       eye. ------------------------------------------------- */
body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="time"],
body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="errors"],
body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="score"] {
  background: linear-gradient(160deg, #3A4258 0%, #1F2433 100%);
  border-color: rgba(255, 255, 255, 0.12);
}
body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="time"]  .board-lower-stat-value { color: #64B5F6; }
body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="errors"] .board-lower-stat-value { color: #EF5350; }
body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="score"] .board-lower-stat-value { color: #66BB6A; }

[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="time"],
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="errors"],
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="score"] {
  background: linear-gradient(160deg, #F4F6FB 0%, #D7DEEC 100%);
  border-color: rgba(15, 23, 42, 0.14);
  color: #1F2A44;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(15, 23, 42, 0.08),
    0 2px 6px rgba(15, 23, 42, 0.10);
}
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="time"]  .board-lower-stat-label,
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="errors"] .board-lower-stat-label,
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="score"] .board-lower-stat-label {
  color: #475569;            /* readable secondary on silver */
  opacity: 1;                /* override the default 0.7 from the canonical rule */
}

/* Light-mode score-label contrast across ALL games — labels were
   barely visible against the tier-coloured (red/green) gradient
   backgrounds. Forces a slate-900 base + opacity:1 + bold weight
   so the labels hit WCAG-AA. */
[data-theme="light"] body[data-moyako-v2] .page .number-pad-info .board-lower-stat .board-lower-stat-label {
  color: #1F2937 !important;
  opacity: 1 !important;
  font-weight: 700 !important;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
[data-theme="light"] body[data-moyako-v2] .page .number-pad-info .board-lower-stat .board-lower-stat-value {
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="time"]  .board-lower-stat-value { color: #1565C0; }
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="errors"] .board-lower-stat-value { color: #C62828; }
[data-theme="light"] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad-info .board-lower-stat[data-stat="score"] .board-lower-stat-value { color: #2E7D32; }

/* --- Play-mode focal CSS — when actively playing, side
       panes mute slightly so the board reads as the focal
       point (matches Lichess / Chess.com convention). ----- */
body[data-page-brand-label="Sudoku"][data-state="playing"][data-moyako-v2] .page .pane-left,
body[data-page-brand-label="Sudoku"][data-state="playing"][data-moyako-v2] .page .pane-right {
  opacity: 0.92;
  transition: opacity 0.4s ease;
}
body[data-page-brand-label="Sudoku"][data-state="playing"][data-moyako-v2] .page .pane-left:hover,
body[data-page-brand-label="Sudoku"][data-state="playing"][data-moyako-v2] .page .pane-right:hover {
  opacity: 1;
}

/* ============================================================
 * SUDOKU — 2-container layout (canonical mobile + landscape
 * pattern, to be reused by all games once approved).
 *
 * Container A: board + 1-9 numpad (everything you need to
 *              actually play)
 * Container B: header bar + tiny swap button + game info chips
 *              + control keys + action buttons (everything
 *              else)
 *
 * Landscape: A | B as 2 columns (swap button toggles which
 *            side B is on via body.sudoku-swapped — left-/
 *            right-handed)
 * Portrait : A on top, B below as 2 rows
 *
 * Achieved without HTML changes by using `display: contents`
 * on intermediate wrappers (.board-lower, .board-lower-slot,
 * .number-pad--triple) so the leaf nodes (.board-upper,
 * .number-pad-numbers, .number-pad-info, .number-pad-keys,
 * .board-lower-actions) become grandchildren of .board-center
 * and can be placed via grid-area.
 *
 * Per user iteration "we should have 2 main container … board
 * fully utilized … control keys 1 to 9 under the board at first
 * container".
 * ============================================================ */
@media (max-width: 1024px) {
  /* Hide left + right side panels on all v2 games at mobile/tablet
     widths so the .moyako-board-center takes the full width. The
     coach narrative + stats card live inside Container B (info row)
     on mobile, so the side panels would only be redundant. */
  body[data-moyako-v2] .page .pane-left,
  body[data-moyako-v2] .page .pane-right,
  body[data-moyako-v2] .page .moyako-coach-panel,
  body[data-moyako-v2] .page .moyako-stats-panel,
  body[data-moyako-v2] .gameplay-pane > .pane-left,
  body[data-moyako-v2] .gameplay-pane > .pane-right,
  body[data-moyako-v2] .gameplay-pane > aside {
    display: none !important;
  }
  /* Force the .gameplay-pane to single-column on mobile so the
     .moyako-board-center spans full viewport — the desktop 1:3:1
     grid otherwise reserves space for the now-hidden panes. */
  body[data-moyako-v2] .gameplay-pane,
  body[data-moyako-v2] .page .gameplay-pane {
    grid-template-columns: minmax(0, 1fr) !important;
    grid-template-rows: minmax(0, 1fr) !important;
    grid-template-areas: "center" !important;
    /* Use the full viewport height between brand bar and bottom
       safe area (only a small margin) per user "utilize the bottom
       space just small margin enough rest can be used by main
       containers a and b". */
    min-height: calc(100dvh - 60px) !important;
    padding: 4px 4px 6px 4px !important;
    box-sizing: border-box !important;
  }
  body[data-moyako-v2] .page .moyako-board-center {
    height: 100% !important;
    min-height: 0 !important;
  }

  /* Container A:B ratio reverted to 1:1 default for backgammon
     per user "revert 1:1". Per-game overrides can opt in here. */

  /* Hide bottom nav on game pages (the data-no-bottom-nav attribute
     should already do this in shared styles, but enforce here for
     legacy game pages that didn't propagate the rule). */
  body[data-moyako-v2][data-no-bottom-nav] .bottom-nav,
  body[data-moyako-v2][data-no-bottom-nav] .moyako-bottom-nav,
  body[data-moyako-v2][data-no-bottom-nav] [data-bottom-nav] {
    display: none !important;
  }

  body[data-moyako-v2] .page .moyako-board-center {
    /* Parametric A:B ratio — sudoku default 1:1. Override per game
       by setting --container-a-flex / --container-b-flex on body.
       Use fr units (e.g. 1fr / 1fr) — fr in calc() is invalid.
       !important required because sudoku.html's legacy M3 mobile
       shell forces display:flex !important on .moyako-board-center
       at min-aspect-ratio 5/8. */
    --container-a-flex: 1fr;
    --container-b-flex: 1fr;
    display: grid !important;
    /* Tight gap + zero outer padding so Container A's board extends
       all the way to the viewport edges per user "reduce the margins
       on container a to the edges to max the area". */
    gap: var(--ds-space-1);
    padding: 0 !important;
  }
  /* Strip board-upper's own padding so the sudoku grid bleeds to
     Container A's edges. */
  body[data-moyako-v2] .page .moyako-board-center > .board-upper {
    padding: 0 !important;
    margin: 0 !important;
  }
  /* Flatten intermediate wrappers so leaves become grandchildren
     of .board-center for grid placement. */
  body[data-moyako-v2] .page .moyako-board-center > .board-lower,
  body[data-moyako-v2] .page .moyako-board-center .board-lower-slot[data-slot="controls"],
  body[data-moyako-v2] .page .moyako-board-center .board-lower-slot[data-slot="actions"],
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple {
    display: contents;
  }
  /* Hide the slot-info empty placeholder. */
  body[data-moyako-v2] .page .moyako-board-center .board-lower-slot[data-slot="info"] {
    display: none;
  }

  /* ---- Info container — 2 rows (shared portrait + landscape).
     Row 1 (whole-width): Match-up (You vs Sudoku) — diff pill
     overlays the same cell (justify-self:end) so the matchup
     background coloring also covers the difficulty info per user
     "players info will include difficulty to apply the same
     background coloring to difficulty info".
     Row 2: dominant Timer + smaller Errors and Score. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info {
    display: grid !important;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    /* 2:3 split between matchup row and stats row per user 2026-04-28
       'row ratios for player 1 vs player 2 info container, score
       container and new game buttons container a,b,c = 2:3:1'. The
       actions row (slot 3) takes the '1' portion separately below. */
    grid-template-rows: 2fr 3fr;
    grid-template-areas:
      "matchup matchup matchup matchup"
      "time    errors  score   level";
    gap: 4px;
    align-items: stretch;
    overflow: visible !important;
    height: auto !important;
    min-height: 100px;       /* 2fr matchup ≈ 40 + 3fr stats ≈ 60 */
  }
  /* Level tile area (4th tile alongside Time/Moves/Best). */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat[data-stat="level"] { grid-area: level; }
  /* Mobile 4-tile stack: stacked label/value matches desktop pattern.
     Selector includes .number-pad--triple to beat the per-game rule
     (memory.html etc.) that sets flex-direction: row !important. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat,
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat {
    flex-direction: column !important;
    justify-content: center !important;
    align-items: center !important;
    gap: 1px !important;
    padding: 4px 4px !important;
    height: auto !important;
    min-height: 36px !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat .board-lower-stat-label,
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat .board-lower-stat-label {
    font-size: 9px !important;
    text-align: center !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat .board-lower-stat-value,
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat .board-lower-stat-value {
    font-size: 13px !important;
    font-weight: 800 !important;
    text-align: center !important;
  }
  /* Override the legacy `.number-pad--triple > div { overflow: hidden }`
     rule from sudoku.html that clips our info container. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple > div {
    overflow: visible !important;
  }
  /* Compact matchup row for mobile. Per user 2026-04-28 'row ratios
     a,b,c = 2:3:1' the matchup row gets 2fr of .number-pad-info via
     grid-template-rows above; remove the 28px height clamp here so
     fr units actually drive the row size. Min-height keeps it tappable.
     Stat tiles use the new 4-tile stacked layout above (do NOT include
     them here — was overriding flex-direction: column). */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup {
    padding: 2px 6px !important;
    min-height: 32px !important;
    height: auto !important;
    display: flex;
    align-items: center;
    gap: 6px;
  }
  /* Actions row gets the '1' part of the 2:3:1 ratio — kept slim
     while still tappable. */
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions {
    min-height: 32px;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stats-pair {
    display: contents;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup    { grid-area: matchup; position: relative; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill  { grid-area: matchup; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat[data-stat="time"]   { grid-area: time; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat[data-stat="errors"] { grid-area: errors; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat[data-stat="score"]  { grid-area: score; }

  /* Diff pill — transparent inline RAG chip on matchup row right edge.
     !important on width/justify-self because the legacy
     `.number-pad--triple .number-pad-info .board-lower-stats-pair
     .board-lower-diff-pill { width: 100% }` was stretching it
     full-width and hiding the label. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill {
    padding: 0 8px !important;
    min-height: 0;
    height: 24px !important;
    width: auto !important;
    align-self: center !important;
    /* v290.8: pill on the LEFT to mirror mobile. */
    justify-self: start !important;
    font-size: 12px !important;
    font-weight: 700;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    gap: 4px;
    background: transparent !important;
    border: 0 !important;
    box-shadow: none !important;
    color: inherit;
    margin-left: 8px;
    margin-right: 0;
    z-index: 2;
    position: relative;
    display: inline-flex !important;
    align-items: center;
    flex: 0 0 auto !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="beginner"],
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="easy"]     { color: #66BB6A; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="medium"]   { color: #FFB74D; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="hard"],
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="expert"]   { color: #EF5350; }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="beginner"],
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="easy"]     { color: #2E7D32; }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="medium"]   { color: #E65100; }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="hard"],
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill[data-tier="expert"]   { color: #C62828; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji {
    font-size: calc(var(--ds-font-sm) * var(--ds-density));
  }
  /* Time — dominant. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat[data-stat="time"] .board-lower-stat-value {
    font-size: calc(var(--ds-font-lg) * var(--ds-density));
    font-weight: var(--ds-fw-heading);
  }
  /* Errors / Score — less dominant. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat[data-stat="errors"] .board-lower-stat-value,
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat[data-stat="score"]  .board-lower-stat-value {
    font-size: calc(var(--ds-font-sm) * var(--ds-density));
    font-weight: var(--ds-fw-label);
  }

  /* ---- Standard button height — every action surface in
     Container B (numbers / keys / actions) shares the SAME height
     for consistency per user "game button heights are they aligning
     to the standards seem heights are too much compare to others".
     36px is the canonical compact mobile tap target. */

  /* ---- Numpad — 3×3 grid for Sudoku ONLY. Other games (backgammon
     dice, memory categories, word-puzzle keyboard, etc.) own their
     own grid layout for the .number-pad-numbers area. */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .moyako-board-center .number-pad-numbers {
    display: grid !important;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 36px);
    gap: 4px;
    align-content: start;
    min-height: 116px;
    overflow: visible !important;
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .moyako-board-center .number-pad-numbers .number-btn {
    height: 36px !important;
    min-height: 36px !important;
    max-height: 36px !important;
    aspect-ratio: auto !important;
    padding: 0 !important;
    font-size: calc(var(--ds-font-md) * var(--ds-density));
    font-weight: var(--ds-fw-heading);
    line-height: 1;
  }
  /* For non-sudoku games — let .number-pad-numbers' own CSS drive its
     layout. We just guarantee it's visible and not clipped. */
  body[data-moyako-v2]:not([data-page-brand-label="Sudoku"]) .page .moyako-board-center .number-pad-numbers {
    overflow: visible !important;
    min-height: 0 !important;
  }

  /* ---- Keys row — Notes / Erase / Check / Hint horizontal. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys {
    display: flex !important;
    flex-direction: row;
    gap: 4px;
    align-items: stretch;
    height: 36px;
    padding: 0 !important;
    overflow: visible !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys .control-btn {
    flex: 1 1 0;
    min-width: 0 !important;
    width: auto;
    aspect-ratio: auto !important;
    height: 36px !important;
    min-height: 36px !important;
    max-height: 36px !important;
    padding: 0 4px !important;
    font-size: 11px !important;
    line-height: 1.1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ---- Action row at the bottom. */
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions {
    height: 36px;
    gap: 4px;
    display: flex;
    flex-direction: row;
  }
  body[data-moyako-v2] .page .moyako-board-center .board-lower-action-btn {
    flex: 1 1 0;
    height: 36px !important;
    min-height: 36px !important;
    max-height: 36px !important;
    padding: 0 6px !important;
    font-size: calc(var(--ds-font-xs) * var(--ds-density));
    line-height: 1.1;
    white-space: nowrap;
  }
}

/* PORTRAIT — Single column. Per user "player vs player info, difficulty
   time and score above the board container, and action buttons below
   the board container as 3 container".
   Order: info (top) → board → numpad → keys → actions (bottom).
   Mobile-landscape header is hidden here (top brand bar is the header
   on portrait). */
@media (orientation: portrait) and (max-width: 1024px) {
  body[data-moyako-v2] .page .moyako-board-center {
    grid-template-columns: minmax(0, 1fr) !important;
    /* v236 (2026-05-09): board row is `minmax(0, 1fr)` — takes ALL
     * leftover vertical space after info/numpad/keys/actions size to
     * their content. Pre-v236 used `auto auto auto auto auto`; the
     * board's `aspect-ratio:1` at full column width pushed actions
     * row off-screen on phones <700 px tall (iPhone SE 375×667 was
     * cutting Quit/New Game by ~24 px). Per user "play game screen
     * board spaces causing cut on control panel keys to exit the
     * game". The board now shrinks to fit the leftover row instead
     * of demanding full-column height; controls always reach the
     * viewport bottom. */
    grid-template-rows: minmax(0, 1fr) auto auto auto auto;
    /* Per user "in portrait also move player info to container b":
       Container A = board (top); Container B = info → numpad → keys
       → actions stacked below. Info no longer eats the top row. */
    grid-template-areas:
      "board"
      "info"
      "numpad"
      "keys"
      "actions";
  }
  body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-header { display: none; }
  /* v236: board cell shrinks; min-height:0 lets the 1fr row drop below
   * its intrinsic content size when other rows demand the space. */
  body[data-moyako-v2] .page .moyako-board-center > .board-upper {
    grid-area: board;
    flex: 0 0 auto;
    min-height: 0;
    align-self: center;
    justify-self: center;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-numbers { grid-area: numpad; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info     { grid-area: info; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys     { grid-area: keys; }
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions { grid-area: actions; }
  /* v236: square board sized to MIN(col-width, row-height). With
   * width 100% + max-height 100% + aspect-ratio:1, the browser picks
   * the smaller of the two constraints to satisfy the ratio — so on
   * tall viewports the board reaches col-width; on short ones it
   * shrinks to leftover row-height instead of overflowing. */
  body[data-moyako-v2] .page .moyako-board-center > .board-upper > * {
    width: 100%;
    height: auto;
    aspect-ratio: 1;
    max-width: 100%;
    max-height: 100%;
  }
  /* Hide the landscape swap button on portrait. */
  body[data-moyako-v2] .page .sudoku-swap-btn,
  body[data-moyako-v2] .page .moyako-swap-btn { display: none; }

  /* Backgammon uses the sudoku golden template verbatim — no per-
     game grid override. The .number-pad-keys is hidden (backgammon
     has no dedicated keys column) but its grid track is collapsed
     to 0 so no gap shows between numpad and actions rows. */
  body[data-page-brand-label="Backgammon"][data-moyako-v2] .page .moyako-board-center .number-pad-keys { display: none !important; }

  /* Memory has no .number-pad-numbers element (no numpad column).
     Categories live in .number-pad-keys and need vertical room —
     give the keys row 1fr so it expands and accommodates the 3×3
     category grid without pushing actions row off-screen.
     Per Sprint C "categories overflow into actions row". */
  body[data-page-brand-label="Memory Game"][data-moyako-v2] .page .moyako-board-center .number-pad-numbers { display: none !important; }
  /* Override the shared 36px keys-row height — memory's category
     grid needs the full 1fr track height for 3×3 buttons. */
  body[data-page-brand-label="Memory Game"][data-moyako-v2] .page .moyako-board-center .number-pad-keys {
    height: auto !important;
    align-self: stretch !important;
  }
}
@media (orientation: landscape) and (max-width: 1024px) {
  /* Landscape rows: info / numpad / keys / actions. Memory: numpad
     hidden so its track collapses; keys gets 1fr. */
  body[data-page-brand-label="Memory Game"][data-moyako-v2] .page .moyako-board-center {
    grid-template-rows: auto 0 1fr auto !important;
  }
  /* Override shared 36px keys-row height for memory in landscape too. */
  body[data-page-brand-label="Memory Game"][data-moyako-v2] .page .moyako-board-center .number-pad-keys {
    height: auto !important;
    align-self: stretch !important;
  }
}
@media (orientation: portrait) and (max-width: 1024px) {
  /* Per user 2026-04-28: chess / sudoku / memory should have
     Container A : Container B = 1:1 height ratio on mobile portrait.
     Board takes 50dvh; chrome rows (info+numpad+keys+actions) share
     the remaining 50dvh. dvh (dynamic viewport height) accounts for
     mobile address-bar resize. */
  body[data-page-brand-label="Chess"][data-moyako-v2] .page .moyako-board-center > .board-upper,
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .moyako-board-center > .board-upper,
  body[data-page-brand-label="Memory Game"][data-moyako-v2] .page .moyako-board-center > .board-upper {
    height: 50dvh !important;
    max-height: 50dvh !important;
    min-height: 0 !important;
    flex: 0 0 50dvh !important;
    /* Center the board-wrapper within board-upper so a square that's
       smaller than the row stays vertically centred. */
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  /* Chess + Sudoku: board element must be the largest SQUARE that
     fits inside board-upper. Use container queries — without explicit
     sizing the chess wrapper collapsed to min-content (iPhone 16
     Safari portrait: tiny thumbnail) and the sudoku grid stretched
     to parent height (clipping the bottom row). cqw/cqh refer to
     board-upper's dimensions. */
  body[data-page-brand-label="Chess"][data-moyako-v2] .page .moyako-board-center > .board-upper,
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .moyako-board-center > .board-upper {
    container-type: size;
  }
  body[data-page-brand-label="Chess"][data-moyako-v2] .page .moyako-board-center > .board-upper > .board-wrapper {
    width: min(100cqw, calc(100cqh - 22px)) !important;
    height: auto !important;
    aspect-ratio: 1 !important;
    max-width: 100% !important;
    max-height: 100% !important;
    flex: 0 0 auto !important;
    margin: 0 auto !important;
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .moyako-board-center > .board-upper .sudoku-grid {
    width: min(100cqw, 100cqh) !important;
    height: auto !important;
    aspect-ratio: 1 !important;
    max-width: 100% !important;
    max-height: 100% !important;
    margin: 0 auto !important;
  }
  /* Chess: let info / actions size to content; the move-history
     container expands to fill the remaining row (1fr) so it gets
     the dominant share of Container B per user 2026-04-28 'increase
     the move history container size as defined rule 2:3:1'. */
  body[data-page-brand-label="Chess"][data-moyako-v2] .page .moyako-board-center {
    grid-template-rows: 50dvh auto 1fr auto !important;
    grid-template-areas:
      "board"
      "info"
      "numpad"
      "actions" !important;
  }
  body[data-page-brand-label="Chess"][data-moyako-v2] .page .moyako-board-center .number-pad-numbers--list {
    min-height: 0;
    height: 100%;
  }
  /* Portrait rows: board (50dvh) / info / numpad(0) / keys(1fr) / actions.
     Per user 2026-04-28: 4fr board row was 303px while board-upper
     content was forced to 406 by 50dvh — last row of cards overflowed
     into the matchup row below. Pin the board row to 50dvh too so the
     grid track matches the content. */
  body[data-page-brand-label="Memory Game"][data-moyako-v2] .page .moyako-board-center {
    grid-template-rows: 50dvh auto 0 1fr auto !important;
  }
}
@media (max-width: 1024px) {
}
/* Per user "actually align the size and margins with sudoku, they
   should be shared as sudoku is the main template standard" —
   chess and all other v2 games inherit the default portrait recipe
   above (auto rows, zero outer padding). No per-game overrides. */

/* LANDSCAPE (mobile/tablet, ≤1024) — A | swap | B as 3 grid
   columns (board fully fills A; B is the controls stack with a
   tiny header bar at the top). Uses the 6:4 parametric ratio
   from --container-a-flex / --container-b-flex.

   Container B layout (top→bottom): header → info → keys → actions.
   The header bar is landscape-only (per user "header bar on the
   second container only for mobile landscape view"). */
@media (orientation: landscape) and (max-width: 1024px) {
  /* Container B shaded backdrop — uses the EXACT same .sudoku-group
     recipe as Container A so both panels behave identically across
     dark + light themes. Per user "not sure why they behave
     differently, they should be shared". Implemented via ::before
     grid item that spans all 4 B rows in column 2. */
  body[data-moyako-v2] .page .moyako-board-center::before {
    content: '';
    grid-column: 2;
    grid-row: 1 / span 4;
    /* Dark theme = canonical .sudoku-group recipe. */
    background: linear-gradient(180deg, #2A2F3D 0%, #232838 100%);
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 12px;
    box-shadow:
      0 4px 12px rgba(0,0,0,0.45),
      0 1px 0 rgba(255,255,255,0.04) inset,
      0 -1px 0 rgba(0,0,0,0.3) inset;
    z-index: 0;
    margin: -4px;
    pointer-events: none;
  }
  /* Light theme — same overrides .sudoku-group uses (line 1420 of
     sudoku.html) so Container A + B flip together. */
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center::before {
    background: linear-gradient(180deg, #FFFFFF 0%, #F3F5F9 100%) !important;
    border: 1px solid rgba(0,0,0,0.08) !important;
    box-shadow:
      0 4px 12px rgba(0,0,0,0.08),
      0 1px 0 rgba(255,255,255,0.6) inset !important;
  }
  /* Container B children sit ABOVE the shaded backdrop. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info,
  body[data-moyako-v2] .page .moyako-board-center .number-pad-numbers,
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys,
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions {
    position: relative;
    z-index: 1;
  }

  body[data-moyako-v2] .page .moyako-board-center {
    /* Columns: A (1fr) | B (1fr) | swap rail (24px on right edge of B).
       1:1 A:B ratio. Per user "swipe button on the right edge of the
       container b" + "header bar should be only on top of container b,
       container a should use the header space for more game board area".
       Container A: board (full height — no header eats the top).
       Container B: header → info → numpad → keys → actions stacked. */
    grid-template-columns: minmax(0, var(--container-a-flex, 1fr)) minmax(180px, var(--container-b-flex, 1fr)) 24px !important;
    /* Row sizing: info / actions sized to content (auto); numpad+keys
       expand together (1fr / auto with no gap between them) per user
       "merge hint button container to numpad as last row" — they
       visually read as one container. */
    grid-template-rows: auto 1fr auto auto;
    grid-template-areas:
      "board   info     swap"
      "board   numpad   swap"
      "board   keys     swap"
      "board   actions  swap";
  }
  /* Swapped (left-handed) — A on right, B on left, swap on left edge
     of B (= far left of viewport). Triggered by body.moyako-swapped
     (toggled in engagement.js when .moyako-swap-btn is clicked).
     Sudoku's legacy body.sudoku-swapped also matches for backward
     compatibility. */
  body[data-moyako-v2].moyako-swapped .page .moyako-board-center,
  body[data-moyako-v2].sudoku-swapped .page .moyako-board-center {
    grid-template-columns: 24px minmax(180px, var(--container-b-flex, 1fr)) minmax(0, var(--container-a-flex, 1fr)) !important;
    grid-template-rows: auto 1fr auto auto;
    grid-template-areas:
      "swap   info      board"
      "swap   numpad    board"
      "swap   keys      board"
      "swap   actions   board";
  }
  /* Backgammon inherits sudoku's landscape grid — no per-game
     override. The .number-pad-keys is display:none (game-specific
     keys column not used) so its row collapses cleanly. */
  /* Merge: kill the gap between numpad and keys so they read as one
     shared container (4th row of numpad = the keys). */
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys {
    margin-top: calc(-1 * var(--ds-space-1)) !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-header { grid-area: header; display: flex; }
  body[data-moyako-v2] .page .moyako-board-center > .board-upper        { grid-area: board; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-numbers   { grid-area: numpad; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-info      { grid-area: info; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys      { grid-area: keys; }
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions  { grid-area: actions; }
  body[data-moyako-v2] .page .moyako-board-center .sudoku-swap-btn,
  body[data-moyako-v2] .page .moyako-board-center .moyako-swap-btn      { grid-area: swap; display: flex; align-items: center; justify-content: center; }
  /* Generic swap button shared visual style (matches the sudoku-swap-btn
     recipe so games look consistent). The min-width override is
     critical — backgammon's .btn class otherwise sets min-width to
     the 44px tap-target-md, ballooning the swap rail into the next
     column when swapped to the left. */
  body[data-moyako-v2] .page .moyako-board-center .moyako-swap-btn {
    width: 24px;
    min-width: 24px !important;
    max-width: 24px;
    border: 1px solid rgba(255,255,255,0.08);
    background: linear-gradient(180deg, #2A2F3D 0%, #232838 100%);
    border-radius: 6px;
    cursor: pointer;
    box-shadow:
      0 2px 4px rgba(0,0,0,0.3),
      inset 0 1px 0 rgba(255,255,255,0.04);
    position: relative;
    padding: 0;
    flex: 0 0 24px;
  }
  body[data-moyako-v2] .page .moyako-board-center .moyako-swap-btn::before {
    content: '<';
    color: #B8C5D8;
    font-weight: 800;
    font-size: 16px;
    line-height: 1;
  }
  body[data-moyako-v2].moyako-swapped .page .moyako-board-center .moyako-swap-btn::before {
    content: '>';
  }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .moyako-swap-btn {
    background: linear-gradient(180deg, #FFFFFF 0%, #F3F5F9 100%);
    border-color: rgba(0,0,0,0.08);
    box-shadow: 0 2px 4px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.6);
  }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .moyako-swap-btn::before {
    color: #475569;
  }
  body[data-moyako-v2] .page .moyako-board-center > .board-upper > * {
    width: auto; height: 100%;
    aspect-ratio: 1; max-width: 100%; max-height: 100%;
    box-sizing: border-box;
    overflow: hidden;
  }
  /* Hard cap on the board container so the 9×9 grid never bleeds
     past Container A's bounds, regardless of cell content. */
  body[data-moyako-v2] .page .moyako-board-center > .board-upper {
    max-height: 100%;
    max-width: 100%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* Container B header bar — title + ℹ button, compact. */
  body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-header {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--ds-space-1);
    padding: var(--ds-space-1) var(--ds-space-2);
    min-height: 28px;
    background: linear-gradient(160deg, #3A4258 0%, #1F2433 100%);
    border: 1px solid rgba(255,255,255,0.12);
    border-radius: var(--ds-radius-button, 8px);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,0.18),
      inset 0 -1px 0 rgba(0,0,0,0.30),
      0 2px 6px rgba(0,0,0,0.25);
  }
  body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-title {
    margin: 0;
    font-size: calc(var(--ds-font-sm) * var(--ds-density));
    font-weight: var(--ds-fw-heading);
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: #ECEFF4;
    text-shadow: 0 1px 0 rgba(0,0,0,0.35);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-header .info-icon-inline {
    flex: 0 0 auto;
    width: 22px; height: 22px;
    font-size: 12px;
  }
}

/* v246 (2026-05-09): landscape phone chrome compression — full
 * compression pass after v245 still left the action row clipped by
 * .moyako-board-center's overflow:hidden. board-center height 244;
 * v245 chrome stack still summed to 272 (info 76 + numpad 116 + keys
 * 36 + actions 32 + gaps 12). v246 trims info → 56, keys → 28,
 * actions → 28 → chrome stack 240, fits in 244 with 4 px clear.
 *
 * Compression order (DESIGN-RULES §3.22):
 *   1. info row first  — slimmed 100 → 56
 *   2. keys row next   — 36 → 28
 *   3. actions row     — 32 → 28
 *   Numpad row stays — smaller buttons hurt thumb ergonomics.
 *
 * 2:3 split between matchup (You vs Sudoku) and stats (Time/Errors/
 * Score/Level) preserved: matchup row 24 px, stats row 32 px. */
@media (orientation: landscape) and (max-height: 500px) {
  /* v264 (2026-05-09): board column shrinks to fit content + 4 px
   * column-gap. Was `1fr 1fr 24px` — board column claimed 50 % of
   * the available width (~410 px on Pixel 9a landscape) but the
   * board itself is height-locked square at ~341 px, so ~70 px sat
   * empty inside the board column.
   *
   * Per user "too much space on the board container reduce the
   * margins to 4 px to release space to control container", the
   * board column is now `auto` (sizes to the board's intrinsic
   * width — modern Chrome computes max-content from
   * aspect-ratio:1 + height-bound row), and column-gap drops from
   * implicit 0 to a tight 4 px. The controls column gets the freed
   * width for a wider numpad / control rail. */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center {
    grid-template-columns: auto minmax(180px, 1fr) 24px !important;
    column-gap: 4px !important;
  }
  /* Same swap (controls on left, board on right). */
  html[data-moyako-native] body[data-moyako-v2].moyako-swapped .page .moyako-board-center,
  html[data-moyako-native] body[data-moyako-v2].sudoku-swapped .page .moyako-board-center {
    grid-template-columns: 24px minmax(180px, 1fr) auto !important;
    column-gap: 4px !important;
  }

  /* v256/v262 (2026-05-09): info section pinned — matchup 36 px (1
   * row), stats 45 px (1.25× matchup). Total info = 85 px. Was 64 px
   * (24 stats); user feedback "score container 1.25 row" — same ratio
   * as portrait v261, so cap-shell now has one consistent compact
   * info-section discipline across orientations.
   *
   * v272 (2026-05-10): scope tightened to `html[data-moyako-native]`
   * — these compact pins are cap-shell-specific. Web games at
   * landscape phone height (rare) keep their natural sizes per the
   * shared rule at line ~380. Audit Section E. */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-info {
    min-height: 0 !important;
    height: 85px !important;
    max-height: 85px !important;
    grid-template-rows: 36px 45px !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat,
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat {
    min-height: 0 !important;
    height: 45px !important;
    max-height: 45px !important;
    padding: 0 4px !important;
    overflow: hidden !important;
  }
  /* v262: with 45 px tiles (was 24), label/value font sizes inherit
   * the shared portrait values 9 px / 13 px (~line 412/418). The
   * earlier 8/11 override was just to fit in 24 px tiles and is no
   * longer needed. */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup {
    min-height: 0 !important;
    height: 36px !important;       /* matches the grid row pin above */
    max-height: 36px !important;
    flex-wrap: nowrap !important;
    overflow: hidden !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys {
    min-height: 28px !important;   /* was 36 */
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad-keys .control-btn {
    min-height: 28px !important;
    padding: 4px 8px !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions {
    min-height: 28px !important;   /* was 32 */
  }
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions .board-lower-action-btn,
  body[data-moyako-v2] .page .moyako-board-center .board-lower-actions button {
    min-height: 28px !important;
    padding: 4px 8px !important;
  }
}

/* v261 (2026-05-09): cap-shell PORTRAIT — same compact discipline as
 * the v256 landscape-phone block. The shared portrait rule earlier
 * (~line 380) uses `grid-template-rows: 2fr 3fr` + `min-height: 100px`,
 * which lets `.number-pad-info` balloon when the parent gives it
 * vertical slack — the matchup row wraps to 2 lines and stats tiles
 * grow to ~80 px each, eating the keyboard's space.
 *
 * Pin matchup row to 36 px (1 row, nowrap, overflow hidden) and stats
 * row to 45 px (1.25× matchup per user "Player vs screen bar expanded
 * keep 1 row, score container expanded keep 1.25 row to fit in the
 * keyboard"). Total info section = 36 + 45 + 4 (gap) = 85 px,
 * leaving the rest of the .board-lower track for the keyboard. */
@media (orientation: portrait) {
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-info {
    min-height: 0 !important;
    height: 85px !important;
    max-height: 85px !important;
    grid-template-rows: 36px 45px !important;
  }
  /* Matchup row — 1 row, nowrap, clip overflow. Inherits the
   * existing flex layout from the shared rule (~line 434); we just
   * lock the height + nowrap + overflow here. */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup {
    min-height: 0 !important;
    height: 36px !important;
    max-height: 36px !important;
    flex-wrap: nowrap !important;
    overflow: hidden !important;
  }
  /* Stats tiles — 45 px row, 4 columns side-by-side (grid-template-
   * columns from the shared rule). Tile labels + values stay readable
   * but the tile no longer claims 60 % of an inflated info section. */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat,
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-stat {
    min-height: 0 !important;
    height: 45px !important;
    max-height: 45px !important;
    overflow: hidden !important;
  }
}

/* Light-mode header recipe (matches metallic silver gradient). */
@media (orientation: landscape) and (max-width: 1024px) {
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-header {
    background: linear-gradient(160deg, #F4F6FB 0%, #D7DEEC 100%);
    border-color: rgba(15,23,42,0.14);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,0.85),
      inset 0 -1px 0 rgba(15,23,42,0.10),
      0 2px 6px rgba(15,23,42,0.14);
  }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-title {
    color: #0F172A;
    text-shadow: 0 1px 0 rgba(255,255,255,0.65);
  }
}

/* Hide the mobile header on desktop/tablet (>1024) — the .pane-left
   is the header there. Also hide the moyako-swap-btn on desktop —
   it's a mobile-landscape-only affordance and was rendering as a
   44px empty band above the board on desktop. */
@media (min-width: 1025px) {
  body[data-moyako-v2] .page .moyako-board-center .board-center-mobile-header,
  body[data-moyako-v2] .page .moyako-board-center .moyako-swap-btn,
  body[data-moyako-v2] .page .moyako-board-center .sudoku-swap-btn { display: none !important; }
}

/* (Old Sprint-1 portrait block removed — replaced by the
 * 2-container grid layout above.) */
@media (orientation: portrait) and (max-width: 768px) and (min-width: 99999px) {
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .moyako-board-center {
    padding: var(--ds-space-2) var(--ds-space-2);
    gap: var(--ds-space-2);
  }

  /* Board-upper — the sudoku grid. Take only what the square
     board needs (aspect-ratio 1). */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .board-center > .board-upper {
    flex: 0 0 auto;
    padding: var(--ds-space-2);
    overflow: visible;
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .board-center > .board-upper > * {
    width: 100%;
    height: auto;
    aspect-ratio: 1;
    max-width: 100%;
    max-height: none;
  }

  /* Board-lower — fills remaining height, vertical stack inside. */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .board-center > .board-lower {
    flex: 1 1 0;
    padding: 0;
    gap: var(--ds-space-2);
    overflow: visible;
  }

  /* Triple-split flips to vertical column. info row on top
     (compact horizontal strip), keys row middle, numbers (numpad
     3×3) at the bottom. */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple {
    flex-direction: column;
    gap: var(--ds-space-2);
    height: auto;
  }

  /* Info column → compact horizontal strip. matchup + tier pill +
     stats all in one row. */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-info {
    flex: 0 0 auto;
    flex-direction: row;
    align-items: stretch;
    justify-content: space-between;
    gap: var(--ds-space-1);
    height: auto;
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-info .board-lower-matchup {
    flex: 1 1 auto;
    min-height: 0;
    padding: var(--ds-space-1) var(--ds-space-2);
    font-size: calc(var(--ds-font-xs) * var(--ds-density));
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-info .board-lower-stats-pair {
    flex: 1 1 auto;
    grid-template-columns: 1fr 1fr;
    gap: var(--ds-space-1);
    min-height: 0;
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-info .board-lower-stat,
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-info .board-lower-stats-pair .board-lower-diff-pill,
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-info .board-lower-stats-pair .board-lower-stat--hero {
    flex: 1 1 auto;
    padding: var(--ds-space-1) var(--ds-space-2);
    min-height: 0;
  }

  /* Keys column → horizontal row (Notes / Erase / Check / Hint
     across the full width). */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-keys {
    flex: 0 0 auto;
    flex-direction: row;
    gap: var(--ds-space-1);
    justify-content: stretch;
    align-items: stretch;
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-keys .control-btn {
    flex: 1 1 0;
    width: auto;
    aspect-ratio: auto;
    min-height: 36px;
    padding: var(--ds-space-2) var(--ds-space-1);
    font-size: calc(var(--ds-font-xs) * var(--ds-density));
  }

  /* Numpad — 3×3 grid filling full available width. Big tap
     targets per Apple HIG mobile guidance. */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-numbers {
    flex: 1 1 auto;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--ds-space-1);
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .number-pad--triple .number-pad-numbers .number-btn {
    min-height: 44px;
    aspect-ratio: auto;
    padding: var(--ds-space-2) 0;
    font-size: calc(var(--ds-font-md) * var(--ds-density));
    font-weight: var(--ds-fw-heading);
  }

  /* Action row at the bottom — full width, equal split. */
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .board-lower-actions {
    height: auto;
    gap: var(--ds-space-1);
  }
  body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .board-lower-action-btn {
    min-height: 40px;
    padding: var(--ds-space-2) var(--ds-space-1);
    font-size: calc(var(--ds-font-xs) * var(--ds-density));
  }
}

/* ===== Layout — Layer 1 shell vars (games-moyako-layout-spec.md) ===== */
/* Tiered chrome heights per docs/CAP-SHELL-LAYOUT.md §4 block 1
 * (2026-05-08, v150). --header-h / --footer-h respond to viewport class:
 * tiny phone, phone portrait (default), phone landscape, tablet, desktop.
 * Floor everywhere is 44px (WCAG 2.2 tap target). Heights are CONTENT
 * (box-sizing: content-box on .topbar / #cap-nav) — sys-info reserves
 * are added as padding on top, never overlapping .middle. */
[data-moyako-v2] {
  --header-h: 48px;          /* phone portrait default */
  --footer-h: 56px;
  --ad-slot-h: 60px;         /* phone default */
  --gap: 12px;
  --pad: 12px;
}

@media (max-width: 360px) {                                        /* tiny phone */
  [data-moyako-v2] { --header-h: 44px; --footer-h: 52px; }
}
@media (orientation: landscape) and (max-height: 500px) {          /* phone landscape */
  [data-moyako-v2] { --header-h: 44px; --footer-h: 48px; }
}
@media (min-width: 768px) and (min-height: 800px) {                /* tablet */
  [data-moyako-v2] { --header-h: 56px; --footer-h: 56px; }
}
@media (min-width: 1280px) and (min-height: 800px) {               /* desktop */
  [data-moyako-v2] { --header-h: 64px; --footer-h: 56px; }
}

/* Tablet+ banner reserves more vertical space for the leaderboard ad
 * format. Phone keeps 60px (mobile banner). Per spec §Layer 1 shell vars. */
@media (min-width: 768px) {
  [data-moyako-v2] { --ad-slot-h: 90px; }
}

/* ============ Light-theme palette override ============
 * Flipping the P3 tokens when [data-theme="light"] is on <html>. Without
 * these, only elements with explicit `[data-theme="light"]` selectors
 * (the slider, etc.) would lighten — the rest of the page stays dark
 * because every Layer 1 / components rule reads from the dark token set
 * defined above. */
[data-theme="light"] [data-moyako-v2] {
  /* Light mode = WHITE page bg with NAVY tiles and ORANGE accents.
   * Page surfaces stay white; tiles/cards reach for the dark-theme
   * navy palette so they pop on the white bg. */
  --bg-primary:     #FFFFFF;
  --bg-surface:     #1A2744;       /* navy tile bg */
  --bg-surface-2:   rgba(255, 152, 0, 0.08);

  /* Text on the white page bg — dark; text on navy tiles uses
   * --text-primary too. To support both, --text-primary stays light
   * (works on navy tiles) and we add a separate page-text override. */
  --text-primary:   #ECEFF4;
  --text-secondary: #CBD5E1;
  --text-tertiary:  #9AA3B7;

  --border:         rgba(255, 152, 0, 0.20);
  --border-strong:  rgba(255, 152, 0, 0.35);

  --ad-placeholder: rgba(255, 152, 0, 0.10);

  /* Brand role swap: orange takes the primary action slot. */
  --action:        #FF9800;
  --action-dark:   #E65100;
  --accent:        #4CAF50;
  --accent-dark:   #388E3C;
  --success:       #2E7D32;
  --info:          #1976D2;
}
/* Page-level text on white bg — pages that put content directly under
 * .page > .middle (no .bg-surface tile around it) need dark text on
 * the white page. Scope to the .middle so tile interiors (which sit on
 * navy) keep the light --text-primary. */
[data-theme="light"] [data-moyako-v2] .page > .middle { color: #1F2937; }

/* ============ v2 viewport reset ============
 * Ensures .page exactly fills the visible viewport and the body doesn't
 * scroll behind it (otherwise bottom-nav falls below the fold on mobile
 * and the theme-init gradient peeks above the topbar). */
/* App-shell pages (those that wrap content in .page) — lock viewport,
 * no body scroll, solid AppShell bg. :has(> .page) scopes this to pages
 * that actually adopt the AppShell. Marketing pages with data-moyako-v2
 * but no .page child (e.g. index.html option-a migration) keep their
 * natural scroll + gradient bg. */
body[data-moyako-v2]:has(> .page) {
  /* !important needed to override legacy shared/styles.css mobile rule
   * `body { padding-top: 50px !important }` at @media (max-width: 768px) —
   * that rule reserves space for the legacy fixed brand bar, which v2
   * AppShell pages don't use (AppShell owns chrome). Without !important
   * a 50px gap appears above .topbar on mobile. */
  margin: 0 !important;
  padding: 0 !important;
  height: 100dvh;
  overflow: hidden;
  /* Override theme-init's body gradient on v2 pages — .page owns the bg. */
  background: var(--bg-primary) !important;
  background-attachment: initial !important;
}

/* ============================================================
   Layer 1 — outer shell (games-moyako-layout-spec.md)
   ------------------------------------------------------------
   .page is a flex column with FOUR children:
     .header   — 56px shared partial (top-bar.js)
     .middle   — flex 1, page content / Layer 2 state machine
     .footer   — 56px shared partial (bottom-nav.js)
     .ad-slot  — 60px (phone) / 90px (tablet+) banner
   Header + Footer are identical on every page.
   Middle direction flips column→row at landscape ≥900 only.
   .ad-slot is hidden together with .footer in .focus-mode.

   .topbar / .bottom-nav are kept as legacy aliases of .header / .footer
   during the page-by-page migration. Same flex sizing applies to both
   names. Aliases dropped in the unscoping cleanup PR after acceptance.
   ============================================================ */

[data-moyako-v2] .page {
  display: flex;
  flex-direction: column;
  /* !important because some legacy init paths (observed on games.html)
   * set an inline `height: auto !important` on .page, which collapses
   * the AppShell flex layout. Source unknown; matching specificity fixes
   * it across all migrated pages without hunting the offender. */
  height: 100dvh !important;
  overflow: hidden;
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--font-size-body);
}

/* Structural sizing — spec-canonical .header / .footer / .ad-slot
 * with .topbar / .bottom-nav as legacy aliases. */
[data-moyako-v2] .page .header,
[data-moyako-v2] .page .topbar      { flex: 0 0 var(--header-h); }
[data-moyako-v2] .page .footer,
[data-moyako-v2] .page .bottom-nav  { flex: 0 0 var(--footer-h); }
[data-moyako-v2] .page .ad-slot     { flex: 0 0 var(--ad-slot-h); }

/* Honor [hidden] on the ad-slot — moyako-components.css sets `display:flex`
 * on .ad-slot for interior layout, which overrides the UA `[hidden]{display:none}`
 * rule by specificity. Pages without a banner ad render `.ad-slot[hidden]`
 * to keep the 4-child structure consistent without reserving height. */
[data-moyako-v2] .page .ad-slot[hidden] { display: none; }

/* Middle — flex 1, default column.
 * Hosts page content directly OR the Layer 2 state machine on game pages.
 * `position: relative` anchors `.results-pill` (Layer 2 state 3).
 * `.two-pane` was the Sprint 4 modifier; folded into .middle here so every
 * page gets the same shape. Modifier kept as a no-op alias for now. */
[data-moyako-v2] .page .middle,
[data-moyako-v2] .page .middle.two-pane {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: stretch;
  position: relative;
  overflow: hidden;
}

/* Landscape flip — Middle interior only, ≥900px. Outer .page stays column. */
@media (orientation: landscape) and (min-width: 900px) {
  [data-moyako-v2] .page .middle,
  [data-moyako-v2] .page .middle.two-pane { flex-direction: row; }
}

/* Hand-swap — flips Middle children order only.
 * data-swap="right" (default): primary visual first.
 * data-swap="left": flips for left-handed players. */
[data-moyako-v2] .page[data-swap="left"] .middle,
[data-moyako-v2] .page[data-swap="left"] .middle.two-pane {
  flex-direction: column-reverse;
}
@media (orientation: landscape) and (min-width: 900px) {
  [data-moyako-v2] .page[data-swap="left"] .middle,
  [data-moyako-v2] .page[data-swap="left"] .middle.two-pane {
    flex-direction: row-reverse;
  }
}

/* Scrollable middle — for content pages (Games list, Assessment list,
 * Leaderboard, Profile, Dashboard, Home, About, Contact, auth pages).
 * Internal overflow, not a shell scroll. */
[data-moyako-v2] .page.scrollable .middle { overflow-y: auto; }

/* Content pages stay COLUMN at every breakpoint. The Layer 1 row flip
 * (landscape ≥ 900) is for game pages where .middle's child is a
 * .gameplay-pane that arranges its own 3-pane grid; for content pages
 * with multiple vertically-stacked children (subhead / page content /
 * footer), the flip would put them side-by-side which is wrong.
 * .scrollable is the existing "I'm a content page" signal (game pages
 * use focus-mode / gameplay-pane, not scrollable), so suppression here
 * stays in shared CSS rather than per-page. */
@media (orientation: landscape) and (min-width: 900px) {
  [data-moyako-v2] .page.scrollable .middle,
  [data-moyako-v2] .page.scrollable .middle.two-pane {
    flex-direction: column;
  }
}

/* Content pages — direct children of .middle stretch to full width AND
 * keep their natural height (no flex-shrink). Without this:
 *   - a flex-column .middle with `align-items: stretch` lets children
 *     with explicit max-width shrink to intrinsic content width;
 *     symptom: games.html `.games-grid auto-fit` collapsed to 1 column
 *     because .container shrunk to 1 tile wide.
 *   - flex-shrink defaults to 1, so when total children height exceeds
 *     .middle (which has overflow-y:auto from `.scrollable`), tall
 *     children compress instead of overflowing into scroll;
 *     symptom: index.html .hero-slider collapsed to 1px because the
 *     subsequent How-It-Works + Skills + Featured-Games + Sample-Report
 *     sections together pushed it to shrink.
 * Game pages don't carry .scrollable — their .gameplay-pane has its
 * own grid-template-columns so this rule doesn't apply. */
[data-moyako-v2] .page.scrollable .middle > * {
  width: 100%;
  flex-shrink: 0;
}

/* ============ Focus mode ============
 * Hides Footer + Ad-slot during active gameplay / assessment question.
 * Spec §Layer 1: `.page.focus-mode .footer, .page.focus-mode .ad-slot { display: none; }`.
 *
 * Trigger surfaces, kept in sync by shared/js/moyako-focus.js:
 *   1. Per-page: .page.focus-mode (chess, since Sprint 4)
 *   2. Universal: body[data-focus-mode="on"] (PR-6b.1 onward)
 *
 * Per spec, .ad-slot is now hidden together with the footer (display:none).
 * Prior behaviour used visibility:hidden on the ad-slot to preserve reserved
 * height (PR-6b.1) — that no longer applies because the ad-slot is now a
 * fixed-height shell child rather than content inside .middle, so display:none
 * on it doesn't cause CLS in .middle. */
[data-moyako-v2] .page.focus-mode .header,
[data-moyako-v2] .page.focus-mode .topbar { /* header stays visible by default */ }

[data-moyako-v2] .page.focus-mode .footer,
[data-moyako-v2] .page.focus-mode .bottom-nav,
[data-moyako-v2] .page.focus-mode .ad-slot {
  display: none;
}

body[data-focus-mode="on"] .footer,
body[data-focus-mode="on"] .bottom-nav,
body[data-focus-mode="on"] .moyako-bottom-nav,
body[data-focus-mode="on"] .ad-slot,
body[data-focus-mode="on"] .moyako-ad-slot--banner {
  display: none;
}

/* ============ Background style — seasonal overlays ============
 * Shared object: settings-modal.js applies html[data-bg="<id>"] from
 * the user's selection (persisted in localStorage moyako_bg). Each
 * style adds a tiny elegant overlay layer on top of the existing
 * theme bg without replacing it — so dark/light flip still works.
 *
 * Default: no overlay (the theme's base bg is the design).
 * Spring : pastel petal/dot dust at very low opacity. Tiny cloud of
 *          soft pink / mint dots on a fixed-attached pseudo layer
 *          so it doesn't scroll with content; pointer-events:none
 *          keeps it inert.
 *
 * Layered on html::before (z-index 0) so positioned children inside
 * .page (z-index >=1) sit above. Page body bg (.page sets
 * --bg-primary solid) is set on .page itself, which sits ABOVE the
 * html::before overlay — meaning AppShell pages still show their
 * solid bg with the seasonal overlay only visible on marketing
 * pages (e.g. home) where body is transparent over html. To make
 * AppShell pages also pick it up, .page bg is layered with the
 * overlay via background-image stack on html. */

/* Spring overlay — repeating tiny pastel petal/dot pattern. Uses
 * a small tile (160x160) so the dots feel scattered evenly without
 * being a noisy texture. background-attachment: fixed so the pattern
 * doesn't scroll with content. */
html[data-bg="spring"] body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background-image:
    radial-gradient(circle at 25px 35px, rgba(216, 27, 96, 0.50) 0 2.5px, transparent 3px),
    radial-gradient(circle at 110px 70px, rgba(46, 125, 50, 0.50) 0 2px, transparent 2.5px),
    radial-gradient(circle at 60px 120px, rgba(245, 124, 0, 0.45) 0 1.5px, transparent 2px),
    radial-gradient(circle at 140px 140px, rgba(216, 27, 96, 0.45) 0 2px, transparent 2.5px);
  background-size: 160px 160px;
  background-repeat: repeat;
}

/* AppShell pages: .page has its own solid bg that would cover the
 * overlay above. Re-emit the overlay inside .page so the seasonal
 * tint is visible on every page, not only marketing. */
html[data-bg="spring"] body[data-moyako-v2] .page::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image:
    radial-gradient(circle at 25px 35px, rgba(216, 27, 96, 0.50) 0 2.5px, transparent 3px),
    radial-gradient(circle at 110px 70px, rgba(46, 125, 50, 0.50) 0 2px, transparent 2.5px),
    radial-gradient(circle at 60px 120px, rgba(245, 124, 0, 0.45) 0 1.5px, transparent 2px),
    radial-gradient(circle at 140px 140px, rgba(216, 27, 96, 0.45) 0 2px, transparent 2.5px);
  background-size: 160px 160px;
  background-repeat: repeat;
}
/* Dark mode — the darker motif alphas above were a light-mode
 * fix; dark mode looked fine at the original alphas. Per user
 * "when I said darker color background motifs it was for light
 * theme, not dark one, dark one was fine revert that to original
 * one". */
html[data-bg="spring"][data-theme="dark"] body::after,
html[data-bg="spring"][data-theme="dark"] body[data-moyako-v2] .page::after {
  background-image:
    radial-gradient(circle at 25px 35px, rgba(244, 143, 177, 0.22) 0 2.5px, transparent 3px),
    radial-gradient(circle at 110px 70px, rgba(165, 214, 167, 0.22) 0 2px, transparent 2.5px),
    radial-gradient(circle at 60px 120px, rgba(255, 213, 79, 0.18) 0 1.5px, transparent 2px),
    radial-gradient(circle at 140px 140px, rgba(244, 143, 177, 0.18) 0 2px, transparent 2.5px);
}
/* Make the .page positioned so the ::after anchors to it, and keep
 * shell children above the overlay. */
html[data-bg="spring"] body[data-moyako-v2] .page { position: relative; }
html[data-bg="spring"] body[data-moyako-v2] .page > * { position: relative; z-index: 1; }

/* ============ Banner footer hide — sitewide ============
 * Per user: hide the bottom banner ad-slot above the nav bar on ALL
 * pages (games, Assessment, Leaderboard, Profile, Home, Games list,
 * Dashboard, etc). CSS-only hide (display:none) keeps the shared
 * shell DOM intact — no DOM removal.
 *
 * Also covers #moyako-ad-bottom (legacy fixed-position ad div that
 * components.js renders at the body level outside .page) so the
 * 56 px gap below the bottom-nav goes away on mobile landscape. */
[data-moyako-v2] .page .moyako-ad-slot--banner,
[data-moyako-v2] .page > .ad-slot,
/* Doubled #id selectors bump the ID-column specificity to (2,_,_)
 * which beats the legacy
 * `body[data-no-bottom-nav][data-page-brand-label]:not(...)
 *   #moyako-ad-bottom` rule (specificity 1,4,1) in shared/styles.css
 * that was forcing display: flex !important on the ad container. */
#moyako-ad-bottom#moyako-ad-bottom,
#ad-banner-bottom#ad-banner-bottom {
  display: none !important;
}
/* When the legacy ad-bottom is hidden, the components.js inline
 * style pins #moyako-bottom-nav at bottom: 56px (sitting above the
 * now-removed ad). Drop it to bottom: 0 so the nav remains visible
 * on mobile landscape where every pixel counts. */
#moyako-bottom-nav {
  bottom: 0 !important;
}

/* ============ Right-rail ad hide — Featured Games + Home + Assessment ============
 * Per user: remove the floating right-top 300x250 ad next to the
 * game tiles on the Featured Games and Assessment pages. Also
 * covers the same slot on Home so nothing floats over the marketing
 * content. CSS-only hide; the slot DOM remains so it can be
 * re-enabled later. */
[data-moyako-v2] .page.games-list .moyako-ad-slot--rightRail,
[data-moyako-v2] .page.assessment .moyako-ad-slot--rightRail,
[data-moyako-v2] .page.home .moyako-ad-slot--rightRail {
  display: none !important;
}
/* Cancel the right-gutter that ads.css reserves for a fixed rail. */
body:has(> .page.games-list) .page.games-list > .middle,
body:has(> .page.assessment) .page.assessment > .middle,
body:has(> .page.home) .page.home > .middle {
  padding-right: 0 !important;
}

/* ============ Small-phone landscape hard-lock ============
 * Avoids reflowing 360×640 designs into 640×360. Spec §Layer 1 small-phone.
 * Scoped to v2 pages so legacy non-v2 pages aren't affected. */
@media (max-width: 767px) and (orientation: landscape) {
  body[data-moyako-v2]::before {
    content: "";
    position: fixed; inset: 0;
    background: var(--bg-primary, #0b0b0b);
    z-index: 9999;
  }
  body[data-moyako-v2]::after {
    content: "Please rotate your device";
    position: fixed; inset: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--text-primary, #fff);
    font-size: 18px;
    font-family: var(--font-sans, system-ui, sans-serif);
    z-index: 10000;
  }
  body[data-moyako-v2] .page { display: none; }
}

/* ============ Accessible keyboard focus ring ============ */
[data-moyako-v2] :focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ============================================================
   Sprint 4 — game-page scaffold (spec §Sprint 4)
   ------------------------------------------------------------
   Canonical game-page shape:
     .page.{slug}
       .topbar
       .middle.two-pane
         section.difficulty-pane     (picker state — inline card stack)
         section.gameplay-pane        (play state — 3-pane landscape / 3-row portrait)
           aside.pane-left
           .board-center              (square-aspect board wrapper)
           aside.pane-right
         .results-pill                (post-game inline card overlaying gameplay)
       .bottom-nav
       .ad-slot
   State visibility driven by [hidden] attribute — engine toggles.
   Focus-mode (.page.focus-mode) hides nav + ad during play — see Sprint 2
   rules above; JS toggles .focus-mode in step with the active state.
   ============================================================ */

/* Middle stacks its state children (only one visible at a time via [hidden]).
 * Layer 1 §Middle now provides display/flex/flex-direction/position/overflow
 * for `.middle` (and the `.two-pane` legacy alias). This block is left as a
 * deliberate no-op for traceability — Sprint 4 used to set those properties
 * here, but they are now centralised in Layer 1 to honour the spec's
 * "no per-page CSS touches .middle structural props" rule. */

/* Honor the [hidden] attribute on state panes — our authored `display:`
 * rules have higher specificity than the UA `[hidden] { display: none }`,
 * so we must re-assert none here. Without this the play pane + picker +
 * results-pill all render simultaneously. */
[data-moyako-v2] .page .difficulty-pane[hidden],
[data-moyako-v2] .page .gameplay-pane[hidden],
[data-moyako-v2] .page .results-pill[hidden] {
  display: none !important;
}

/* ===== Difficulty pane (picker state) =====
   Portrait: single-column card stack, centered.
   Landscape ≥900: two-column — info card left, difficulty card right. */
[data-moyako-v2] .page .difficulty-pane {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3);
  grid-template-columns: minmax(0, 1fr);
  /* Portrait: game-info-card sizes to content, difficulty-card absorbs
   * the remaining viewport so its buttons never get cut off by an
   * arbitrary 50/50 split. */
  grid-template-rows: auto minmax(0, 1fr);
  align-content: stretch;
  box-sizing: border-box;
}
/* Landscape (mobile or desktop): 2-card side-by-side layout.
 * Per user "in mobile landscape also has similar view as in this
 * desktop view" — share the same 2-col template at every landscape
 * width. Both columns use the same minmax range so the cards read
 * as equal-sized siblings.
 *
 * 2026-05-07 update (user feedback): "play screen containers not
 * centrally aligned and utilizing the free space between header
 * and navbar." Changed grid-template-rows from `auto` to `1fr`
 * and align-content from `center` to `stretch` so the cards fill
 * the entire vertical space between the topbar and bottom-nav,
 * eliminating the empty bands the user observed.
 *
 * 2026-05-07 update #2 (user): "containers should have equal
 * margins on sides, and not overlapped by header / navbars."
 *   - Cap-nav is `position: fixed` so it overlays the picker's
 *     bottom 56px. Reserved that space + safe-area inset on
 *     body.has-cap-nav so the cards stop above the nav.
 *   - Symmetric horizontal padding (--space-4 each side) wins
 *     over the prior `var(--space-3)` base rule for cleaner
 *     visual balance with the topbar's 15% inset. */
@media (orientation: landscape) {
  [data-moyako-v2] .page .difficulty-pane {
    /* v193 (2026-05-08): cards size to content, group centered in pane.
     * Per user "keep the objects compact and adjust the container
     * accordingly as in sign in screen" — the previous 1fr rows
     * stretched both cards to full pane height, leaving big empty
     * bands inside the smaller game-info-card. Now:
     *   grid-template-rows: auto         row sized to tallest card's content
     *   align-content: center            row centered in pane vertically
     *   align-items: stretch             both cards still equal-height
     * The cards become "as tall as needed" instead of "as tall as the
     * pane", giving the same compact feel as the sign-in mask. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: auto;
    align-content: center;
    align-items: stretch;
    justify-items: stretch;
    justify-content: stretch;
    column-gap: var(--space-4);
    max-width: 880px;
    margin-left: auto !important;
    margin-right: auto !important;
    width: 100%;
    padding-left: var(--space-4) !important;
    padding-right: var(--space-4) !important;
    box-sizing: border-box;
  }
  /* Children — width stretch only; height follows the auto row,
   * not 100% of the pane (v193 compaction). */
  [data-moyako-v2] .page .difficulty-pane > .game-info-card,
  [data-moyako-v2] .page .difficulty-pane > .difficulty-card {
    align-self: stretch;
    justify-self: stretch;
    width: 100%;
    min-height: 0;
    min-width: 0;
    box-sizing: border-box;
  }
  /* Cap-nav clearance — reserve 56px + safe-area at the bottom of
   * the picker so the fixed cap-nav doesn't overlay the cards. */
  body.has-cap-nav [data-moyako-v2] .page .difficulty-pane {
    padding-bottom: calc(var(--space-3) + 56px + env(safe-area-inset-bottom, 0px)) !important;
  }
}

/* (Desktop landscape upsizing rule moved below the landscape
 * compaction block so it wins on source order. See line ~1100.) */

/* Portrait — cards stacked as content-sized rows (not 1fr 1fr
 * stretching to fill the pane). Per user "container heights at
 * mobile portrait view to be checked for difficulty selections
 * screens, buttons heights too much seems stretched" — letting
 * the cards size to natural content keeps pills ~44 px and
 * action buttons proportional, instead of stretching them when
 * the pane is full-screen tall. */
@media (orientation: portrait) {
  [data-moyako-v2] .page .difficulty-pane {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto;
    align-items: start;
    align-content: center;
    row-gap: var(--space-3);
  }
  [data-moyako-v2] .page .game-info-card,
  [data-moyako-v2] .page .difficulty-card {
    align-self: stretch;
  }
  /* Inside difficulty-card, pills + actions size to their natural
   * heights (no flex: 1 1 0 stretch). Top + bottom edges still
   * align because both columns share the same difficulty-card
   * height anchored to the pills' natural total height. */
  [data-moyako-v2] .page .difficulty-card .difficulty-list > * {
    flex: 0 0 auto;
    min-height: 44px;
  }
  [data-moyako-v2] .page .difficulty-card .difficulty-actions > * {
    flex: 1 1 0;        /* still split the card height across 3 buttons */
    min-height: 44px;
  }
}

[data-moyako-v2] .page .game-info-card,
[data-moyako-v2] .page .difficulty-card {
  position: relative;
  /* Slate slightly lighter than TopBar / BottomNav so the cards
   * pop against the chrome (per user reference screenshot —
   * cards visibly lifted from the bg, with a subtle 1px border). */
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 0;
  overflow-y: auto;                   /* internal scroll if content outgrows card */
  box-sizing: border-box;
}
/* Light theme — silver/gray metallic 3D tile (per user "may be
 * silver/gray metallic background at tiles at light mode" + "3d
 * shaded container views"). Same vertical gradient + inset
 * highlight as games tiles for cross-page parity. */
[data-theme="light"] [data-moyako-v2] .page .game-info-card,
[data-theme="light"] [data-moyako-v2] .page .difficulty-card {
  background: linear-gradient(180deg, #F2F5FB 0%, #D8DFEC 100%);
  border: 1px solid rgba(15, 23, 42, 0.10);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 -1px 0 rgba(15, 23, 42, 0.04),
    0 2px 6px rgba(15, 23, 42, 0.08),
    0 8px 18px rgba(15, 23, 42, 0.06);
  color: #0F172A;
}
/* Match the base-rule specificity (.page .game-info-card .X = 0,3,1)
 * by including .game-info-card in the override so source-order wins. */
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-title,
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-skill,
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty-actions .btn-secondary,
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty-actions .btn-return {
  color: #0F172A;
}
/* Difficulty pills + primary action in light mode — silver
 * metallic 3D bg matching the tiles, with tier-coloured left
 * border accent for the pills. Per user "black background
 * difficulty buttons not suited well same for Start game button". */
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty {
  background: linear-gradient(180deg, #F2F5FB 0%, #D8DFEC 100%);
  border: 1px solid rgba(15, 23, 42, 0.10);
  border-left-width: 4px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 -1px 0 rgba(15, 23, 42, 0.04),
    0 1px 3px rgba(15, 23, 42, 0.06);
}
/* Tier-coloured left border per pill */
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty[data-tier="beginner"] { border-left-color: #4DD0E1; }
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty[data-tier="easy"]     { border-left-color: #66BB6A; }
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty[data-tier="medium"]   { border-left-color: #FF9800; }
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty[data-tier="hard"]     { border-left-color: #E53935; }
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty[data-tier="expert"]   { border-left-color: #9C27B0; }
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty .difficulty-label,
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty .difficulty-emoji {
  color: #0F172A;
}
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty.active,
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .difficulty.is-selected {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 1),
    0 4px 12px rgba(15, 23, 42, 0.12);
  transform: translateY(-1px);
}

/* Start Game (primary action) — green metallic in light mode
 * (matches the brand "play / go" colour and reads vivid against
 * the silver picker). */
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .btn-action {
  background: linear-gradient(180deg, #66BB6A 0%, #2E7D32 100%);
  border: 1px solid rgba(15, 23, 42, 0.12);
  color: #FFFFFF;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.40),
    inset 0 -1px 0 rgba(15, 23, 42, 0.10),
    0 2px 6px rgba(46, 125, 50, 0.30);
}
[data-theme="light"] [data-moyako-v2] .page .difficulty-card .btn-action .btn-sub {
  color: rgba(255, 255, 255, 0.92);
}
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-description {
  color: #475569;       /* secondary slate for body copy */
}
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-skill {
  background: rgba(15, 23, 42, 0.06);
  border-color: rgba(15, 23, 42, 0.10);
}
[data-theme="light"] [data-moyako-v2] .page .game-info-card .info-icon-inline {
  color: #0F172A;
  border-color: rgba(15, 23, 42, 0.20);
}
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-face--back {
  background: #FFFFFF;
  color: #0F172A;
}
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-back-heading {
  color: #2E7D32;       /* darker green for legibility on white */
}

/* Light theme — gameplay pane chrome readable + metallic 3D
 * (matches picker tile aesthetic for visual cohesion). Per user
 * "align with the light mode colors and the contrasts for the
 * readability". */
[data-theme="light"] [data-moyako-v2] .page .pane-left,
[data-theme="light"] [data-moyako-v2] .page .pane-right,
[data-theme="light"] [data-moyako-v2] .page .board-center > .board-upper,
[data-theme="light"] [data-moyako-v2] .page .board-center > .board-lower {
  background: linear-gradient(180deg, #F2F5FB 0%, #D8DFEC 100%);
  border: 1px solid rgba(15, 23, 42, 0.10);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 -1px 0 rgba(15, 23, 42, 0.04),
    0 2px 6px rgba(15, 23, 42, 0.08);
  border-radius: var(--radius-lg);
  color: #0F172A;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-title {
  color: #0F172A;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-message {
  color: #475569;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-skills-title {
  color: #475569;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-skills-list {
  color: #0F172A;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-skills-list li {
  background: rgba(15, 23, 42, 0.06);
  border-color: rgba(15, 23, 42, 0.10);
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .info-icon-inline {
  color: #0F172A;
  border-color: rgba(15, 23, 42, 0.20);
}
/* Board area — keep the board surface white in light mode for
 * clarity; only the surrounding card uses the metallic gradient. */
[data-theme="light"] [data-moyako-v2] .page .board-center {
  color: #0F172A;
}
[data-theme="light"] [data-moyako-v2] .page .board-center .board-upper,
[data-theme="light"] [data-moyako-v2] .page .board-center .board-lower {
  color: #0F172A;
}
[data-moyako-v2] .page .game-info-card {
  /* v192 (2026-05-08): reverted v191 space-between back to center.
   * v191 'justified' reading produced awkward gaps; the v190 centred
   * layout was what the user meant by 'both containers justified
   * vertically'. */
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  justify-content: center;
}
/* Title row — inline title + right-edge info icon (spec §Sprint 4
 * template line 754: "InfoIcon — top right"). Interpreted as "inline
 * with the title at the right edge", which reads cleaner than a float-
 * floating corner icon when content is vertically centered. */
[data-moyako-v2] .page .game-info-card .game-info-title-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  /* No position: relative here — the ℹ icon anchors to the
   * .game-info-card (top-right corner of the whole tile), not to
   * the title row. Per user "info icon still not on the top right
   * corner of the info tile". */
}
/* Info icon — anchored to the top-right corner of the card with
 * a small margin (per user "move the info to the right top corner
 * with some margins"). The previous behaviour pinned it inline at
 * the right edge of the title row mid-line; this lifts it into the
 * corner so the title can centre cleanly. z-index keeps it above
 * the absolute-positioned back face overlay so the × close button
 * stays clickable when flipped. */
[data-moyako-v2] .page .game-info-card .info-icon-inline {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  transform: none;
  z-index: 2;
}

/* Description text — tile-style line under the title (per user
 * "add some text info as in the tile for the game"). */
[data-moyako-v2] .page .game-info-card .game-info-description {
  font-size: var(--font-size-small);
  color: var(--text-secondary);
  text-align: center;
  line-height: 1.45;
  margin: 0;
  max-width: 32ch;
}

/* In-tile flip — clicking the ℹ icon swaps the front face for a
 * back face containing the drawer-content (history / rules / etc).
 * Same tile, no slide-in drawer. Per user "Info button on right
 * top corner at the tile should show the info in the same tile". */

/* Front face — natural flow under the always-visible icon + title. */
[data-moyako-v2] .page .game-info-card .game-info-face--front {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  flex: 1 1 auto;
}

/* Back face — absolute-positioned overlay so the card height does
 * NOT grow to fit long drawer content. Fills the card area below
 * the icon + title; the inner .game-info-back-body scrolls when
 * the drawer HTML is taller than the available height — keeps the
 * heading fixed at top (per user "starting below the header"). */
[data-moyako-v2] .page .game-info-card .game-info-face--back {
  position: absolute;
  inset: calc(var(--space-3) + 56px) var(--space-3) var(--space-3);
  text-align: left;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-size: var(--font-size-small);
  line-height: 1.45;
  color: var(--text-primary);
  /* Opaque cover for the front face. Use --bg-primary (page bg)
   * combined with the same translucent tint as the card so the
   * back face reads as one with the card surface. */
  background: var(--bg-primary, #0F1729);
  border-radius: var(--radius-md);
  padding: var(--space-2);
}
/* Honour the [hidden] attribute — browser default `display: none`
 * was being overridden by the `display: flex` above due to
 * specificity. Without this rule the back face stays visible
 * after × is clicked. Per user "close button not working when
 * closing the info text stays". */
[data-moyako-v2] .page .game-info-card .game-info-face--back[hidden] {
  display: none !important;
}
/* Scrollable body — heading lives OUTSIDE this div so the slim
 * scrollbar starts BELOW the heading. */
[data-moyako-v2] .page .game-info-card .game-info-back-body {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  padding-right: 4px;
  /* Firefox slim themed scrollbar (per user "scroll bar is too
   * tick align the dark mode and a bit slim"). */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}
/* WebKit / Blink */
[data-moyako-v2] .page .game-info-card .game-info-back-body::-webkit-scrollbar {
  width: 6px;
}
[data-moyako-v2] .page .game-info-card .game-info-back-body::-webkit-scrollbar-track {
  background: transparent;
}
[data-moyako-v2] .page .game-info-card .game-info-back-body::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.22);
  border-radius: 3px;
}
[data-moyako-v2] .page .game-info-card .game-info-back-body::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.32);
}
/* Light theme — darker thumb for legibility on white background. */
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-back-body {
  scrollbar-color: rgba(15, 23, 42, 0.30) transparent;
}
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-back-body::-webkit-scrollbar-thumb {
  background: rgba(15, 23, 42, 0.30);
}
[data-theme="light"] [data-moyako-v2] .page .game-info-card .game-info-back-body::-webkit-scrollbar-thumb:hover {
  background: rgba(15, 23, 42, 0.45);
}

/* Mobile landscape — text on the back face matches the front
 * description size (per user "same align the text size in mobile
 * landscape"). The base var(--font-size-small) is already used,
 * but explicit values here override any per-page <p>/<li> rules
 * that bleed into the .game-info-face--back overlay. */
@media (orientation: landscape) and (max-height: 500px) {
  [data-moyako-v2] .page .game-info-card .game-info-face--back,
  [data-moyako-v2] .page .game-info-card .game-info-face--back p,
  [data-moyako-v2] .page .game-info-card .game-info-face--back li,
  [data-moyako-v2] .page .game-info-card .game-info-face--back section {
    font-size: 11px;
    line-height: 1.4;
  }
  [data-moyako-v2] .page .game-info-card .game-info-back-heading {
    font-size: 0.92rem;
  }
}
[data-moyako-v2] .page .game-info-card .game-info-face--back h1,
[data-moyako-v2] .page .game-info-card .game-info-face--back h2,
[data-moyako-v2] .page .game-info-card .game-info-face--back h3 {
  font-size: var(--font-size-body);
  margin: 0 0 4px;
}
/* Back-face paragraphs match the front .game-info-description font
 * size + line-height (per user "align the info text size to initial
 * text size on the game info container") — was rendering at the
 * browser default 16 px because user-agent <p> styling won the
 * cascade over the back container's 12 px. */
[data-moyako-v2] .page .game-info-card .game-info-face--back p,
[data-moyako-v2] .page .game-info-card .game-info-face--back li,
[data-moyako-v2] .page .game-info-card .game-info-face--back .drawer-section,
[data-moyako-v2] .page .game-info-card .game-info-face--back section {
  font-size: var(--font-size-small);
  line-height: 1.45;
  margin: 0 0 8px;
}
/* When flipped — keep icon, title-row, front face IN FLOW
 * (visibility: hidden) so the card height + width stay exactly
 * the same as before the flip. Per user "when info clicked the
 * size and location of the containers shouldn't change" + "in
 * mobile landscape the size changes shouldn't be". The back face
 * is absolute-positioned so it overlays them visually without
 * collapsing the card layout. */
[data-moyako-v2] .page .game-info-card.is-flipped .game-info-face--front,
[data-moyako-v2] .page .game-info-card.is-flipped .game-info-icon,
[data-moyako-v2] .page .game-info-card.is-flipped .game-info-title-row {
  visibility: hidden;
}
/* Back face fills the whole card area when flipped — covers the
 * (now invisible) icon + title-row visually, while the in-flow
 * elements above keep the card's height stable. */
[data-moyako-v2] .page .game-info-card.is-flipped .game-info-face--back {
  inset: var(--space-3);
}
[data-moyako-v2] .page .game-info-card .game-info-back-heading {
  margin: 0 0 8px;
  padding-right: 36px;     /* leaves room for the × close button at the corner */
  font-size: 1rem;
  font-weight: 700;
  /* Brand green — matches the cognitive-skills section headings
   * in the drawer-content files (per user "align the header
   * coloring to green at info page"). --success is green in both
   * dark (#66BB6A) and light (#2E7D32) modes; --accent is orange
   * in dark mode so we use --success here. */
  color: var(--success);
}
/* Back-compat: any .info-icon that is NOT .info-icon-inline stays in
 * its legacy top-right corner (pane-right uses this shape). */
[data-moyako-v2] .page .game-info-card .info-icon:not(.info-icon-inline) {
  position: absolute; top: var(--space-3); right: var(--space-3);
}
[data-moyako-v2] .page .game-info-card .game-info-icon {
  font-size: 56px; line-height: 1;
}
[data-moyako-v2] .page .game-info-card .game-info-title {
  font-size: 26px; font-weight: 800; letter-spacing: 2px;
  color: var(--text-primary); line-height: 1;
}
[data-moyako-v2] .page .game-info-card .game-info-skills {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2);
}
[data-moyako-v2] .page .game-info-card .game-info-skill {
  font-size: var(--font-size-small); font-weight: 600;
  padding: 4px var(--space-2);
  border-radius: 12px;
  background: var(--bg-surface-2);
  border: 1px solid var(--border);
  color: var(--text-primary);
  white-space: nowrap;
}
[data-moyako-v2] .page .game-info-card .game-info-tier {
  font-size: 11px; font-weight: 800; letter-spacing: 1.4px;
  color: #fff; padding: 5px 14px; border-radius: 14px;
  text-shadow: 0 1px 2px rgba(0,0,0,0.3);
  box-shadow: 0 2px 6px rgba(0,0,0,0.3);
  background: linear-gradient(135deg, var(--action) 0%, var(--action-dark) 100%);
}
[data-moyako-v2] .page .game-info-card .game-info-tier[data-tier="beginner"] { background: linear-gradient(135deg, #4DD0E1 0%, #0097A7 100%); }
[data-moyako-v2] .page .game-info-card .game-info-tier[data-tier="easy"]     { background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%); }
[data-moyako-v2] .page .game-info-card .game-info-tier[data-tier="medium"]   { background: linear-gradient(135deg, #FF9800 0%, #E65100 100%); }
[data-moyako-v2] .page .game-info-card .game-info-tier[data-tier="hard"]     { background: linear-gradient(135deg, #E53935 0%, #B71C1C 100%); }
[data-moyako-v2] .page .game-info-card .game-info-tier[data-tier="expert"]   { background: linear-gradient(135deg, #9C27B0 0%, #4A148C 100%); }

/* .difficulty-card — base layout is the same at every viewport:
 * 2-col internal grid with difficulty-list on the left and the
 * action buttons on the right. Per user "in portrait view also 2
 * tiles should have similar size and button order should be as in
 * desktop 2 column" + "in portrait view buttons should be the
 * same as landscape one, 2nd column buttons not aligned" —
 * action column uses 1fr so buttons share the available width
 * uniformly, with white-space: nowrap on the buttons themselves
 * so labels don't wrap. */
/* Universal — difficulty-card is a single column flex stack:
   difficulty-list (2-col tier grid) on top, .difficulty-actions
   (Return / Friend / Start row) directly underneath. Per user
   "play buttons should be closer to other buttons" we use
   flex-start (not space-between) so the action row sits flush
   against the bottom of the difficulty grid instead of being
   pushed to the card bottom. */
[data-moyako-v2] .page .difficulty-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  align-items: stretch;
  /* v192 (2026-05-08): reverted v191 space-between back to center
   * (v190 state). 'Justified' interpreted as space-between produced
   * awkward gaps between rows; centring was what the user meant. */
  justify-content: center;
}
[data-moyako-v2] .page .difficulty-card .btn-action,
[data-moyako-v2] .page .difficulty-card .btn-secondary,
[data-moyako-v2] .page .difficulty-card .btn-return {
  white-space: nowrap;
  width: 100%;
  text-align: center;
}

/* Stacked-label primary action — "Start Game" on top, "vs AI"
 * underneath as a smaller subtitle (per user "vs AI could be
 * second row on the button"). */
[data-moyako-v2] .page .difficulty-card .btn-action--stacked {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  line-height: 1.15;
  padding-top: 6px;
  padding-bottom: 6px;
}
[data-moyako-v2] .page .difficulty-card .btn-action--stacked .btn-sub {
  font-size: 0.78em;
  font-weight: 600;
  opacity: 0.92;
}
/* Both columns stretch to the full card height and split it
 * proportionally — 5 pills in col 1, 3 actions in col 2 — so the
 * top of Start Game aligns with the top of Beginner and the
 * bottom of Return aligns with the bottom of Expert. Each action
 * naturally renders ~1.67× the pill height. Per user "increase
 * the 2nd column buttons height a bit to align first column
 * buttons top and bottom edges". */
[data-moyako-v2] .page .difficulty-card .difficulty-list {
  display: grid;
  grid-template-columns: 1fr 1fr;     /* 2-col grid per user "make difficulty levels buttons 2 column" */
  gap: 6px;
  align-self: stretch;
}
[data-moyako-v2] .page .difficulty-card .difficulty-actions {
  display: flex;
  flex-direction: row;                /* horizontal action row at the bottom */
  gap: 6px;
  margin-top: var(--space-2);
  align-self: stretch;
  flex-wrap: nowrap;
}
[data-moyako-v2] .page .difficulty-card .difficulty-actions > * {
  flex: 1 1 0;
  min-width: 0;
  /* Fluid font size — shrinks on narrow widths so labels never
     overflow the button (per user "reduce the text size where
     not fitting to button", "mobile landscape overflows the
     texts", and "same for portrait"). Sub-text shrinks too. */
  font-size: clamp(9px, 2.4vw, 13px);
  padding: 8px 4px;
  /* Allow 2-line wrap on narrow phones so labels like "Play
     with Friend" don't get ellipsis-cut. line-height tuned so
     2 lines fit a 44 px tap target. */
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.15;
  text-align: center;
}
[data-moyako-v2] .page .difficulty-card .difficulty-actions .btn-action.btn-action--stacked .btn-sub {
  font-size: clamp(7px, 1.6vw, 10px);
}
/* Bottom row order: Return → Play with Friend → Start Game.
   Per user "return - friend - new game order". CSS `order` flips
   the visual order. Match BOTH the picker's default #startGameBtn
   AND the per-game renamed id (sudoku → #landingStartBtn,
   backgammon → #bg-start-game-btn, etc.) via class fallback on
   .btn-action. */
[data-moyako-v2] .page .difficulty-card .difficulty-actions > .btn-return         { order: 1; }
[data-moyako-v2] .page .difficulty-card .difficulty-actions > #playWithFriendBtn  { order: 2; }
[data-moyako-v2] .page .difficulty-card .difficulty-actions > .btn-secondary      { order: 2; }
[data-moyako-v2] .page .difficulty-card .difficulty-actions > #startGameBtn,
[data-moyako-v2] .page .difficulty-card .difficulty-actions > #landingStartBtn,
[data-moyako-v2] .page .difficulty-card .difficulty-actions > #bg-start-game-btn,
[data-moyako-v2] .page .difficulty-card .difficulty-actions > .btn-action          { order: 3; }
/* Edge-alignment stretch (top of Start Game = top of Beginner,
 * bottom of Return = bottom of Expert) applies at every viewport.
 * Per user "play button size heights changed align as in portrait
 * top and end edge should be matching". */
[data-moyako-v2] .page .difficulty-card .difficulty-list > *,
[data-moyako-v2] .page .difficulty-card .difficulty-actions > * {
  flex: 1 1 0;
}

/* (Removed 2026-05-08) The landscape-specific compaction block that
 * tightened .difficulty-card and .game-info-card padding, pill
 * sizing, fonts, and action heights has been deleted per user rule:
 * "container 2 in portrait view should have the same content layout
 * at landscape view — do not touch inside the container 2 - content
 * when rotating." This is the shell vs content boundary
 * (DESIGN-RULES §3.15) applied to the picker: the SHELL flips rows
 * ↔ columns on rotation, but Container 2's internal content layout
 * (pill sizes, action heights, gaps, fonts) stays identical.
 *
 * Trade-off: phone landscape (~308 px Container 2 height) may show
 * Container 2 content tightly packed or with a hair of internal
 * scroll if all 3 sections (difficulty list + actions + guest CTA)
 * exceed the available height. Accepted per the rule above. Desktop
 * landscape (≥ 1024 px) keeps its own upsize rule below. */
[data-moyako-v2] .page .difficulty-card .btn-action,
[data-moyako-v2] .page .difficulty-card .btn-secondary {
  /* Compact action button (v161, 2026-05-08, user "all other buttons
   * to be resized to make some space to those [sign-in] buttons"):
   * padding 12 var(--space-4) → 8 var(--space-3), min-height 44 → 36.
   * Frees ~16 px vertical for the sign-in row to render full text
   * without truncation. Same as the .difficulty pill rationale. */
  padding: 8px var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-size: var(--font-size-body); font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  min-height: 36px;
}
[data-moyako-v2] .page .difficulty-card .btn-action {
  background: linear-gradient(135deg, var(--action) 0%, var(--action-dark) 100%);
  color: #fff;
}
[data-moyako-v2] .page .difficulty-card .btn-secondary {
  background: var(--bg-surface-2);
  border-color: var(--border);
  color: var(--text-primary);
}
[data-moyako-v2] .page .difficulty-card .btn-return {
  background: transparent; border: 1px solid var(--border);
  color: var(--text-secondary); font-weight: 600; font-size: var(--font-size-small);
  border-radius: var(--radius-md);   /* match Start Game / Play with Friend */
  padding: 6px 14px;
}

/* Desktop landscape — bigger picker cards + content for
 * readability (per user "at desktop view those containers seems
 * like smaller than mobile view, should be increased a bit more
 * at desktop" + "you can increase the container height to keep
 * aspect ratio"). Sits AFTER the landscape compaction block so
 * it wins on source order. Mobile landscape keeps compact size. */
@media (orientation: landscape) and (min-width: 1024px) {
  [data-moyako-v2] .page .difficulty-pane {
    /* v189 (2026-05-08): switched from repeat(2, minmax(420px, 600px))
     * to repeat(2, minmax(0, 1fr)) per DESIGN-RULES §3.19 — desktop
     * upsize previously beat v188's web-picker fix because of source
     * order. max-width: 1300 still caps the overall pane width on
     * huge screens; cards now share that width equally instead of
     * being capped at 600 each (which left empty space). */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-5, 32px);
    max-width: 1300px;
  }
  [data-moyako-v2] .page .difficulty-card,
  [data-moyako-v2] .page .game-info-card {
    padding: var(--space-5, 24px);
    gap: var(--space-3);
    /* v193 (2026-05-08): dropped min-height: 360px. With the v193 pane
     * change to grid-template-rows: auto, the cards size to content;
     * a forced 360-px floor reintroduced empty bands in the
     * game-info-card. */
  }
  [data-moyako-v2] .page .game-info-card .game-info-icon { font-size: 56px; }
  [data-moyako-v2] .page .game-info-card .game-info-title { font-size: 28px; letter-spacing: 1.5px; }
  [data-moyako-v2] .page .game-info-card .game-info-description { font-size: 15px; line-height: 1.55; max-width: 42ch; }
  [data-moyako-v2] .page .game-info-card .game-info-skill { padding: 5px 12px; font-size: 13px; }
  [data-moyako-v2] .page .game-info-card .game-info-tier { padding: 6px 16px; font-size: 12px; }
  [data-moyako-v2] .page .difficulty-card .difficulty {
    padding: 12px 16px;
    min-height: 56px;
  }
  [data-moyako-v2] .page .difficulty-card .difficulty-emoji { font-size: 22px; }
  [data-moyako-v2] .page .difficulty-card .difficulty-label { font-size: 15px; }
  [data-moyako-v2] .page .difficulty-card .btn-action,
  [data-moyako-v2] .page .difficulty-card .btn-secondary,
  [data-moyako-v2] .page .difficulty-card .btn-return {
    font-size: 15px;
    min-height: 56px;
  }
}

/* ===== Gameplay pane (play state) — golden rule v2 =====
   Sudoku-derived spec applied to every game (chess / sudoku /
   memory / maze / word-puzzle / block-puzzle / backgammon).

   Portrait <900: 3-row stack (left top strip / board middle / right bottom strip).
   Landscape ≥900: 3-column grid in 1:3:1 ratio (left | center | right).

   Horizontal alignment: the gameplay pane uses the SAME left/right
   gutter as the TopBar (`var(--space-4)` = 16 px). That makes the
   left edge of the left pane line up with the right edge of the
   logo (logo is anchored to the topbar's `padding-left: var(--space-4)`),
   and the right edge of the right pane mirrors it on the other side.

   Per user "the game board should start from end of logo as
   starting point and same size space at right end. place the 3
   column game screen 1:3:1 aspect ratio". */
[data-moyako-v2] .page .gameplay-pane {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  gap: var(--space-2);
  padding: var(--space-2);
  box-sizing: border-box;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "left"
    "center"
    "right";
}
@media (min-width: 900px) and (orientation: landscape) {
  [data-moyako-v2] .page .gameplay-pane {
    grid-template-columns: minmax(220px, 1fr) minmax(0, 3fr) minmax(220px, 1fr);
    grid-template-rows: minmax(0, 1fr);
    grid-template-areas: "left center right";
    max-width: none;            /* span the full row inside the topbar gutter */
    margin: 0 auto;
    width: 100%;
    padding: var(--space-3) var(--space-4);   /* matches TopBar horizontal padding */
    gap: var(--space-3);
  }
  /* Sudoku (and any future game wrapping coach/board/stats in
   * .moyako-game-shell) — override the legacy shell grid so the
   * 1:3:1 ratio + topbar-aligned gutters apply per the gameplay
   * golden rule. Per user "screen not fit to defined area, put
   * Sudoku title panda and skills container (game info) into 1
   * container, game board and control keys into second container,
   * and player info as 3rd container" + "give the same margin on
   * the right as in the left, then divide screen into 3
   * containers 1:3:1 aspect ratios". */
  [data-moyako-v2] .page .gameplay-pane .moyako-game-shell {
    grid-template-columns: minmax(220px, 1fr) minmax(0, 3fr) minmax(220px, 1fr);
    max-width: none;
    padding: var(--space-3) var(--space-4);
    margin: 0;
    width: 100%;
    gap: var(--space-3);
  }
}

/* Standardised content slots for GameplayPane.renderLeftPane().
 * Title row (green band) with title + ℹ icon both INSIDE the band,
 * panda narrative + skills training list below.
 *
 * Per user "Sudoku title green band should include info icon" —
 * the green gradient lives on .pane-left-head so both title and
 * info button share the band background. The legacy
 * .moyako-game-title band-styling is neutralised when inside this
 * head wrapper so it doesn't render a second band. */
[data-moyako-v2] .page .pane-left .pane-left-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  position: relative;
  margin-bottom: var(--space-3);
  padding: 8px 12px;
  border-radius: var(--radius-md);
  background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  color: #FFFFFF;
}
[data-moyako-v2] .page .pane-left .pane-left-head .moyako-game-title {
  /* Defuse the legacy green-band styling — band lives on the
   * wrapper now, the title is just text inside it. */
  background: transparent;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
}
[data-moyako-v2] .page .pane-left .pane-left-title {
  font-size: var(--font-size-h2);
  font-weight: 800;
  letter-spacing: 1.5px;
  margin: 0;
  text-transform: uppercase;
  flex: 1 1 auto;
  text-align: center;
  color: #FFFFFF;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
[data-moyako-v2] .page .pane-left .pane-left-head .info-icon-inline {
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.30);
  color: #FFFFFF;
  font-size: 13px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  transition: background 0.2s ease;
}
[data-moyako-v2] .page .pane-left .pane-left-head .info-icon-inline:hover {
  background: rgba(255, 255, 255, 0.28);
}
[data-moyako-v2] .page .pane-left .pane-left-narrative {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  margin: var(--space-2) 0;
}
[data-moyako-v2] .page .pane-left .pane-left-panda {
  width: 96px; height: 96px;
  border-radius: 50%;
  flex: 0 0 auto;
}
[data-moyako-v2] .page .pane-left .pane-left-message {
  font-size: var(--font-size-small);
  color: var(--text-secondary);
  line-height: 1.45;
  margin: 0;
}
[data-moyako-v2] .page .pane-left .pane-left-skills {
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}
[data-moyako-v2] .page .pane-left .pane-left-skills-title {
  font-size: var(--font-size-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-2);
}
[data-moyako-v2] .page .pane-left .pane-left-skills-list {
  list-style: none;
  margin: 0; padding: 0;
  display: flex; flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  font-size: var(--font-size-small);
  color: var(--text-primary);
}
[data-moyako-v2] .page .pane-left .pane-left-skills-list li {
  /* Canonical metallic recipe — same shape as the .number-pad
     tiles + action buttons. 8 px radius, 10 px label, weight 700,
     uppercase. Per "design consistency" memory rule. */
  background: linear-gradient(160deg, #3A4258 0%, #1F2433 100%);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  padding: 4px 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: #ECEFF4;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -1px 0 rgba(0, 0, 0, 0.25),
    0 2px 4px rgba(0, 0, 0, 0.18);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.30);
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-skills-list li {
  background: linear-gradient(160deg, #F4F6FB 0%, #D7DEEC 100%);
  border-color: rgba(15, 23, 42, 0.14);
  color: #0F172A;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(15, 23, 42, 0.08),
    0 2px 4px rgba(15, 23, 42, 0.10);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}

/* NarrativeCarousel — cross-fade between slides; pinned to the
 * pane-left narrative slot. Slides absolute-stacked so swap is
 * smooth with no layout shift. */
[data-moyako-v2] .page .narrative-carousel {
  position: relative;
  min-height: 160px;
  flex: 0 0 auto;
}
[data-moyako-v2] .page .narrative-carousel .narrative-slide {
  opacity: 0;
  transition: opacity 0.45s ease;
  width: 100%;
}
[data-moyako-v2] .page .narrative-carousel .narrative-slide.is-active {
  opacity: 1;
}
[data-moyako-v2] .page .narrative-carousel[data-slide-kind="ad"] .narrative-slide,
[data-moyako-v2] .page .narrative-carousel[data-slide-kind="news"] .narrative-slide,
[data-moyako-v2] .page .narrative-carousel[data-slide-kind="tip"] .narrative-slide {
  font-size: var(--font-size-small);
  line-height: 1.5;
  text-align: center;
}
/* Pane-left flip — front face hides when .is-flipped, back face
 * (game info) shows. Mirrors the picker info-card flip behaviour. */
[data-moyako-v2] .page .pane-left .pane-left-face--front {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1 1 auto;
}
[data-moyako-v2] .page .pane-left .pane-left-face--back {
  flex: 1 1 auto;
  overflow-y: auto;
  font-size: var(--font-size-small);
  line-height: 1.55;
  color: #ECEFF4;            /* light text on dark slate (dark mode) */
  padding: var(--space-2) var(--space-2) var(--space-2) 0;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}
[data-moyako-v2] .page .pane-left .pane-left-face--back p,
[data-moyako-v2] .page .pane-left .pane-left-face--back li,
[data-moyako-v2] .page .pane-left .pane-left-face--back section {
  font-size: var(--font-size-small);
  line-height: 1.55;
  margin: 0 0 8px;
  color: inherit;
}
[data-moyako-v2] .page .pane-left .pane-left-face--back h1,
[data-moyako-v2] .page .pane-left .pane-left-face--back h2,
[data-moyako-v2] .page .pane-left .pane-left-face--back h3 {
  color: var(--success);
  font-size: 1rem;
  margin: 0 0 6px;
}
[data-moyako-v2] .page .pane-left .pane-left-face--back::-webkit-scrollbar { width: 6px; }
[data-moyako-v2] .page .pane-left .pane-left-face--back::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.22); border-radius: 3px;
}
[data-moyako-v2] .page .pane-left .pane-left-face--back[hidden] {
  display: none !important;
}
[data-moyako-v2] .page .pane-left.is-flipped .pane-left-face--front {
  display: none;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back {
  color: #0F172A;          /* dark text on the silver/navy metallic bg */
  scrollbar-color: rgba(15, 23, 42, 0.30) transparent;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back p,
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back li,
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back section {
  color: #1F2937;
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back h1,
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back h2,
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back h3 {
  color: #2E7D32;          /* darker green for legibility on white */
}
[data-theme="light"] [data-moyako-v2] .page .pane-left .pane-left-face--back::-webkit-scrollbar-thumb {
  background: rgba(15, 23, 42, 0.30);
}

[data-moyako-v2] .page .pane-left {
  grid-area: left;
  display: flex; flex-direction: column; gap: var(--space-2);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  min-height: 0;
  min-width: 0;
  box-sizing: border-box;
  overflow: hidden;
}
[data-moyako-v2] .page .pane-right {
  grid-area: right;
  display: flex; flex-direction: column; gap: var(--space-2);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  min-height: 0;
  min-width: 0;
  box-sizing: border-box;
  overflow: hidden;
}

[data-moyako-v2] .page .board-center {
  grid-area: center;
  display: flex; flex-direction: column;
  align-items: stretch; justify-content: stretch;
  min-height: 0; min-width: 0;
  position: relative;
  gap: var(--space-3);
}
[data-moyako-v2] .page .board-center > * {
  max-width: 100%;
}
/* 2-container split inside center column — board takes the
 * available height, control panel hugs its content height so all
 * 3 slots (info / controls / actions) are guaranteed to render
 * without clipping. Original 8:2 target stays as a visual prefer
 * via min-height: 0 on board-lower so the board still gets the
 * lion's share when controls are small. */
/* board-upper:board-lower split is parametric per game via the
 * --board-upper-flex / --board-lower-flex CSS custom properties.
 * Defaults are 7:3; individual games override on their own
 * .board-center (e.g. block-puzzle uses 8:2 because its tall
 * playfield needs more vertical room and its controls compress
 * tightly without the keys column).
 *
 * To override in a game: scope the variables on .board-center
 *   .board-center { --board-upper-flex: 8; --board-lower-flex: 2; }
 */
[data-moyako-v2] .page .board-center {
  --board-upper-flex: 7;
  --board-lower-flex: 3;
}
[data-moyako-v2] .page .board-center > .board-upper {
  flex: var(--board-upper-flex) 1 0;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  overflow: hidden;             /* clip any child that exceeds the flex share */
}
[data-moyako-v2] .page .board-center > .board-lower {
  flex: var(--board-lower-flex) 1 0;
  min-height: 0;
  padding: var(--space-3) var(--space-4);
  overflow: hidden;
}
/* Game board fits inside the .board-upper with the configured
 * padding. Direct children clamp to 100% in both axes. For
 * specifically-square boards (sudoku grid / chess board / etc.),
 * we drop the explicit width so the aspect-ratio + max-w/h
 * combination picks the LARGEST square that fits the available
 * area — preventing the board from overflowing the container
 * (per user "board shouldn't overflow the container, should be
 * fitting into the container with some margin"). */
/* Square board fits inside .board-upper using height-driven
 * sizing: height: 100% picks up the container's available
 * height, aspect-ratio: 1 mirrors that as width, max-width: 100%
 * caps when the container is wider than tall (board stops
 * growing past the container's width). */
[data-moyako-v2] .page .board-center > .board-upper > * {
  max-width: 100%;
  max-height: 100%;
}
[data-moyako-v2] .page .board-center > .board-upper .sudoku-grid,
[data-moyako-v2] .page .board-center > .board-upper .board,
[data-moyako-v2] .page .board-center > .board-upper .game-board,
[data-moyako-v2] .page .board-center > .board-upper .board-aspect {
  height: 100% !important;
  width: auto !important;
  max-width: 100% !important;
  max-height: 100% !important;
  min-width: 0;
  min-height: 0;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
}
/* Canvas-based games (block-puzzle, maze, etc.) — height-driven
 * sizing. The canvas keeps its intrinsic drawing resolution
 * (e.g. block-puzzle 360×720) but the displayed pixel box scales
 * to fit the container. height: 100% forces the rendered height
 * to match .board-upper; width: auto preserves aspect ratio.
 * Per user "board should not overflow the container — same for
 * all games".
 *
 * NOTE: .board-wrapper used to be in this selector but it's the
 * backgammon piece-grid wrapper, NOT a canvas. It was getting
 * min-height: 0 from this rule which killed backgammon's desktop
 * min-height: min(560px, calc(100dvh - 200px)). Removed. */
[data-moyako-v2] .page .board-center > .board-upper .game-board-frame,
[data-moyako-v2] .page .board-center > .board-upper .game-board-container {
  max-width: 100% !important;
  max-height: 100% !important;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
[data-moyako-v2] .page .board-center > .board-upper canvas {
  display: block;
  height: 100% !important;
  width: auto !important;
  max-width: 100% !important;
  max-height: 100% !important;
}

/* Mobile (≤1024 width) — opt-in hide for any pane via the
 * `data-mobile-hide` attribute. Per user "could be some attribute
 * to decide hidden". A pane page just adds the attribute on the
 * panes it wants collapsed at narrow widths; the gameplay-pane
 * grid auto-recomputes its track count via :has() so the
 * remaining panes spread to fill the row.
 *
 * Sudoku adds it to .pane-left (no need for game info on mobile).
 * Other games can opt in similarly (e.g. add to .pane-right when
 * stats fit better in a collapsed drawer). */
@media (max-width: 1024px) {
  [data-moyako-v2] .page .gameplay-pane > [data-mobile-hide] {
    display: none;
  }
  /* Both side panes hidden — center fills the row. */
  [data-moyako-v2] .page .gameplay-pane:has(> .pane-left[data-mobile-hide]):has(> .pane-right[data-mobile-hide]) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);   /* single row fills available height */
    grid-template-areas: "center";
  }
  /* Only .pane-left hidden. */
  [data-moyako-v2] .page .gameplay-pane:has(> .pane-left[data-mobile-hide]):not(:has(> .pane-right[data-mobile-hide])) {
    grid-template-columns: minmax(0, 3fr) minmax(220px, 1fr);
    grid-template-areas: "center right";
  }
  /* Only .pane-right hidden. */
  [data-moyako-v2] .page .gameplay-pane:has(> .pane-right[data-mobile-hide]):not(:has(> .pane-left[data-mobile-hide])) {
    grid-template-columns: minmax(220px, 1fr) minmax(0, 3fr);
    grid-template-areas: "left center";
  }
}

/* .board-lower hosts 3 reorderable slots (info / controls /
 * actions). Slots use CSS order keyed off the data-layout attr
 * on the .board-lower wrapper; default order is info → controls
 * → actions. Per user "control panel should have 3 container
 * inside and based on the layout can be swapped". */
[data-moyako-v2] .page .board-lower {
  display: flex;
  flex-direction: column;
  gap: 6px;
  height: 100%;
  min-height: 0;
}
[data-moyako-v2] .page .board-lower [data-slot="controls"] {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: stretch;
}
/* Action row pinned to a compact fixed height — doesn't grow
 * with the controls band so the Pause/Exit/Undo/New Game row
 * stays slim even when board:controls is 7:3. */
[data-moyako-v2] .page .board-lower [data-slot="actions"] {
  flex: 0 0 auto;
  min-height: 0;
  display: flex;
  align-items: stretch;
}
[data-moyako-v2] .page .board-lower [data-slot="actions"] .board-lower-actions {
  width: 100%;
  height: 26px;
  gap: 4px;
}
[data-moyako-v2] .page .board-lower [data-slot="actions"] .board-lower-action-btn {
  height: 100%;
  padding: 0 8px;
  font-size: 11px;
  line-height: 1;
}
/* Each of the 3 slots (info / controls / actions) gets its own
 * 3D-shaded gradient sub-container so they read as distinct
 * surfaces matching the picker-card / pane-right / chip aesthetic
 * (per user "create 3 sub containers with 3d-shaded gradiend
 * style as others inside the control container"). Dark mode by
 * default; light mode overridden below. */
[data-moyako-v2] .page .board-lower-slot[hidden] { display: none !important; }
[data-moyako-v2] .page .board-lower-slot {
  display: block;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.02) 100%);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--radius-md, 10px);
  padding: 8px 10px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    inset 0 -1px 0 rgba(0, 0, 0, 0.25),
    0 2px 8px rgba(0, 0, 0, 0.22);
}
[data-theme="light"] [data-moyako-v2] .page .board-lower-slot {
  background: linear-gradient(160deg, rgba(15, 23, 42, 0.05) 0%, rgba(15, 23, 42, 0.01) 100%);
  border: 1px solid rgba(15, 23, 42, 0.10);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 -1px 0 rgba(15, 23, 42, 0.06),
    0 2px 8px rgba(15, 23, 42, 0.10);
}
[data-moyako-v2] .page .board-lower[data-layout="info-controls-actions"] [data-slot="info"]     { order: 1; }
[data-moyako-v2] .page .board-lower[data-layout="info-controls-actions"] [data-slot="controls"] { order: 2; }
[data-moyako-v2] .page .board-lower[data-layout="info-controls-actions"] [data-slot="actions"]  { order: 3; }
[data-moyako-v2] .page .board-lower[data-layout="controls-info-actions"] [data-slot="controls"] { order: 1; }
[data-moyako-v2] .page .board-lower[data-layout="controls-info-actions"] [data-slot="info"]     { order: 2; }
[data-moyako-v2] .page .board-lower[data-layout="controls-info-actions"] [data-slot="actions"]  { order: 3; }
[data-moyako-v2] .page .board-lower[data-layout="info-actions-controls"] [data-slot="info"]     { order: 1; }
[data-moyako-v2] .page .board-lower[data-layout="info-actions-controls"] [data-slot="actions"]  { order: 2; }
[data-moyako-v2] .page .board-lower[data-layout="info-actions-controls"] [data-slot="controls"] { order: 3; }

/* controls-actions — info slot hidden, controls on top, actions
 * row at the bottom (per user "remove the first row" + "pause
 * exit row would stay as is"). Freed vertical space from the
 * removed info row flows up into .board-upper. */
[data-moyako-v2] .page .board-lower[data-layout="controls-actions"] [data-slot="controls"] { order: 1; }
[data-moyako-v2] .page .board-lower[data-layout="controls-actions"] [data-slot="actions"]  { order: 2; }

/* Header row inside the .board-lower container — matchup +
 * difficulty pill (left) + game-info chips (right). Compact
 * spacing per user "they should be compact and condensed". */
[data-moyako-v2] .page .board-center > .board-lower > .board-lower-slot[data-slot="info"] > .board-lower-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin: 0;
  flex-wrap: wrap;
}
[data-moyako-v2] .page .board-lower-diff-pill {
  display: inline-flex; align-items: center; gap: 6px;
  background: linear-gradient(135deg, var(--action) 0%, var(--action-dark) 100%);
  border: 0;
  border-radius: 999px;
  padding: 4px 10px;
  color: #fff;
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.30),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    0 2px 6px rgba(0, 0, 0, 0.22);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.20);
  line-height: 1.2;
}
[data-moyako-v2] .page .board-lower-diff-pill[data-tier="beginner"] { background: linear-gradient(135deg, #4DD0E1 0%, #0097A7 100%); }
[data-moyako-v2] .page .board-lower-diff-pill[data-tier="easy"]     { background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%); }
[data-moyako-v2] .page .board-lower-diff-pill[data-tier="medium"]   { background: linear-gradient(135deg, #FF9800 0%, #E65100 100%); }
[data-moyako-v2] .page .board-lower-diff-pill[data-tier="hard"]     { background: linear-gradient(135deg, #E53935 0%, #B71C1C 100%); }
[data-moyako-v2] .page .board-lower-diff-pill[data-tier="expert"]   { background: linear-gradient(135deg, #9C27B0 0%, #4A148C 100%); }
/* Matchup chip — "You vs Opponent". Compact 3D vivid bg matching
 * the rest of the header. */
[data-moyako-v2] .page .board-lower-matchup {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  background: linear-gradient(135deg, #5C6BC0 0%, #283593 100%);
  color: #FFFFFF;
  font-size: 12px;
  font-weight: 700;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.30),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    0 2px 6px rgba(0, 0, 0, 0.22);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.20);
  line-height: 1.2;
}
[data-moyako-v2] .page .board-lower-matchup .matchup-side {
  display: inline-flex; align-items: center; gap: 3px;
}
[data-moyako-v2] .page .board-lower-matchup .matchup-avatar {
  font-size: 14px; line-height: 1;
}
[data-moyako-v2] .page .board-lower-matchup .matchup-vs {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: rgba(255, 255, 255, 0.78);
  font-weight: 800;
  padding: 0 2px;
}
/* Light mode — matchup keeps its vivid gradient (white text reads
 * fine on the indigo gradient). No override needed. */

/* Stats row beside the difficulty pill — Time / Errors / Score
 * (or whatever fields a given game tracks). Each chip has a bold
 * 3D-shaded coloured background per user "both should have
 * dominant shaded 3d vivid colors" + "they should be compact
 * and condensed". */
[data-moyako-v2] .page .board-lower-stats {
  display: flex; gap: 6px; align-items: center;
  flex-wrap: wrap;
}
[data-moyako-v2] .page .board-lower-stat {
  display: inline-flex; flex-direction: column; align-items: center;
  padding: 3px 10px;
  border-radius: 10px;
  color: #FFFFFF;
  min-width: 54px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.30),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    0 2px 6px rgba(0, 0, 0, 0.22);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.20);
  line-height: 1.15;
}
[data-moyako-v2] .page .board-lower-stat[data-stat="time"]   { background: linear-gradient(135deg, #1E88E5 0%, #1565C0 100%); }
[data-moyako-v2] .page .board-lower-stat[data-stat="errors"] { background: linear-gradient(135deg, #E53935 0%, #B71C1C 100%); }
[data-moyako-v2] .page .board-lower-stat[data-stat="score"]  { background: linear-gradient(135deg, #43A047 0%, #1B5E20 100%); }
[data-moyako-v2] .page .board-lower-stat-label {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  font-weight: 800;
  opacity: 0.95;
}
[data-moyako-v2] .page .board-lower-stat-value {
  font-size: 14px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* Action row — Pause / Exit / New Game directly under the
 * numpad / control keys (per user "plus the new game, pause,
 * exit buttons near the control keys"). */
[data-moyako-v2] .page .board-lower-actions {
  display: flex;
  gap: 6px;
  margin: 0;
  justify-content: center;
  align-items: center;
}
/* 3D shaded metallic gradient — matches the .number-btn /
 * .control-btn style on the controls slot. Per user "pause exit
 * new game buttons 3d-metallic gray gradient style as others". */
[data-moyako-v2] .page .board-lower-action-btn {
  flex: 1 1 0;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: linear-gradient(160deg, #3A4258 0%, #1F2433 100%);
  color: #ECEFF4;
  font-size: var(--font-size-small);
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 -1px 0 rgba(0, 0, 0, 0.35),
    0 2px 6px rgba(0, 0, 0, 0.30);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.40);
  transition: transform 0.1s ease, filter 0.2s ease;
}
[data-moyako-v2] .page .board-lower-action-btn:hover {
  filter: brightness(1.15);
  transform: translateY(-1px);
}
[data-moyako-v2] .page .board-lower-action-btn--primary {
  background: linear-gradient(160deg, #66BB6A 0%, #2E7D32 100%);
  color: #FFFFFF;
  border-color: rgba(0, 0, 0, 0.18);
  flex: 1 1 0;
}
[data-theme="light"] [data-moyako-v2] .page .board-lower-action-btn:not(.board-lower-action-btn--primary) {
  border: 1px solid rgba(15, 23, 42, 0.28);
  background: linear-gradient(160deg, #E2E8F0 0%, #BCC4D4 100%);
  color: #0F172A;             /* WCAG-AA contrast on the silver gradient */
  font-weight: 800;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(15, 23, 42, 0.14),
    0 2px 6px rgba(15, 23, 42, 0.16);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.65);
}
[data-theme="light"] [data-moyako-v2] .page .board-lower-action-btn:not(.board-lower-action-btn--primary) {
  background: rgba(15, 23, 42, 0.06);
  color: #0F172A;
}
/* Primary CTA keeps the canonical green gradient in BOTH themes
   per Sprint 1 colour discipline. Was being overridden to silver
   on light mode by the rule above (which lacked :not). */
[data-theme="light"] [data-moyako-v2] .page .board-lower-action-btn--primary {
  background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%) !important;
  color: #FFFFFF !important;
  border: 1px solid rgba(56, 142, 60, 0.6) !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.25),
    0 2px 6px rgba(56, 142, 60, 0.30) !important;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.20);
}

/* Pane-right — two nested containers stacked vertically: player 1
 * (top half) + player 2 / ad slot (bottom half). Per user "right
 * container, remove the diffiuclty level and the game info like
 * time errors etc, just keep user info for the player as half
 * part of the container as nexted container and second part for
 * the second player ad 2nd nested container. when playing with
 * AI that could be ads to display". */
[data-moyako-v2] .page .pane-right {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
[data-moyako-v2] .page .pane-right .pane-right-player,
[data-moyako-v2] .page .pane-right .pane-right-slot {
  flex: 1 1 0;
  min-height: 0;
  display: flex; flex-direction: column;
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  padding: var(--space-3);
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 2px 6px rgba(0, 0, 0, 0.18);
}

/* Player card content — bigger, bolder, more readable. Per user
 * "make those user info badges a bit dominant readable". */
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-row {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: var(--space-2);
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-avatar {
  font-size: 36px; line-height: 1;
  width: 48px; height: 48px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-info {
  display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto;
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-name {
  font-size: 16px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: 0.3px;
  display: flex; align-items: center; gap: 6px;
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-sub {
  font-size: 11px;
  color: rgba(236, 239, 244, 0.75);   /* readable secondary */
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  margin-top: 2px;
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-turn-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--success, #66BB6A);
  box-shadow: 0 0 6px rgba(102, 187, 106, 0.6);
  flex: 0 0 auto;
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-scores-row {
  display: flex; gap: 6px; flex-wrap: wrap;
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-score-badge {
  /* Canonical metallic chip — matches the .number-pad info tiles
     and skills pills (160deg gradient + inset highlight + drop
     shadow). 8 px radius, 11 px label, weight 700. */
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 8px;
  border-radius: 8px;
  background: linear-gradient(160deg, #3A4258 0%, #1F2433 100%);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: #ECEFF4;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.3px;
  flex: 1 1 auto;
  justify-content: center;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -1px 0 rgba(0, 0, 0, 0.25),
    0 2px 4px rgba(0, 0, 0, 0.18);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.30);
}
[data-moyako-v2] .page .pane-right .pane-right-player .moyako-score-badge strong {
  font-size: 13px;
  font-weight: 800;
  color: var(--success, #66BB6A);
}
[data-theme="light"] [data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-name {
  color: #0F172A;
}
[data-theme="light"] [data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-sub {
  color: #1F2A44;             /* WCAG-AA over the silver pane-right surface */
}
[data-theme="light"] [data-moyako-v2] .page .pane-right .pane-right-player .moyako-hero-avatar {
  background: rgba(15, 23, 42, 0.06);
}
[data-theme="light"] [data-moyako-v2] .page .pane-right .pane-right-player .moyako-score-badge {
  background: linear-gradient(160deg, #F4F6FB 0%, #D7DEEC 100%);
  border-color: rgba(15, 23, 42, 0.14);
  color: #0F172A;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(15, 23, 42, 0.08),
    0 2px 4px rgba(15, 23, 42, 0.10);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
[data-theme="light"] [data-moyako-v2] .page .pane-right .pane-right-player .moyako-score-badge strong {
  color: #2E7D32;
}
[data-moyako-v2] .page .pane-right .pane-right-slot--ad {
  align-items: center; justify-content: center;
}
[data-moyako-v2] .page .pane-right .pane-right-slot--ad .moyako-ad-slot {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  position: relative;
}
[data-theme="light"] [data-moyako-v2] .page .pane-right .pane-right-player,
[data-theme="light"] [data-moyako-v2] .page .pane-right .pane-right-slot {
  background: rgba(15, 23, 42, 0.04);
  border-color: rgba(15, 23, 42, 0.08);
}

/* ===== Gameplay containers — shared 3D tile aesthetic =====
 * Same slate translucent + inset highlight + drop shadow as the
 * picker cards (.game-info-card / .difficulty-card) so all five
 * surfaces in the gameplay screen read as one design family. Per
 * user "those containers should be aligned with tile shade-
 * gradient and color and 3d style". */
[data-moyako-v2] .page .pane-left,
[data-moyako-v2] .page .pane-right,
[data-moyako-v2] .page .board-center > .board-upper,
[data-moyako-v2] .page .board-center > .board-lower {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    inset 0 -1px 0 rgba(0, 0, 0, 0.12),
    0 2px 6px rgba(0, 0, 0, 0.18);
  box-sizing: border-box;
}

/* ===== Results pill — inline card overlaying gameplay =====
   Not fixed-position, not a full-screen modal. Lives in .middle, positioned
   absolutely relative to it so the board remains visible behind the ad
   content. Internally scrollable so "no scroll on page" still holds. */
[data-moyako-v2] .page .results-pill {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: min(480px, calc(100% - var(--space-4) * 2));
  max-height: min(720px, calc(100% - var(--space-4) * 2));
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0,0,0,0.5);
  padding: var(--space-4);
  overflow-y: auto;
  z-index: 40;
  display: flex; flex-direction: column; gap: var(--space-3);
  box-sizing: border-box;
}
[data-moyako-v2] .page .results-pill[hidden] { display: none; }

/* Legacy `.btn` / `.btn-primary` / `.btn-secondary` theme override —
 * chess.html (and likely the other 6 game pages, once migrated) ships
 * light-theme button CSS (`background: white; color: var(--text-light)`)
 * which collapses to white-on-white on dark v2 pages. Scope this override
 * to ANY `.btn*` inside `.page` so `.pane-left`, `.pane-right`,
 * `.results-pill`, `.difficulty-card`, and `.game-info-card` all get
 * the P3-token styling for free. */
[data-moyako-v2] .page .btn,
[data-moyako-v2] .page .btn-primary,
[data-moyako-v2] .page .btn-secondary {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 10px var(--space-3);
  border-radius: var(--radius-md);
  font-family: inherit; font-size: var(--font-size-body); font-weight: 700;
  min-height: 40px;
  cursor: pointer;
  border: 1px solid transparent;
  text-decoration: none;
}
[data-moyako-v2] .page .btn-primary {
  background: linear-gradient(135deg, var(--action) 0%, var(--action-dark) 100%);
  color: #fff;
  border-color: var(--action-dark);
}
[data-moyako-v2] .page .btn-secondary {
  background: var(--bg-surface-2);
  color: var(--text-primary);
  border-color: var(--border);
  font-weight: 600;
}

/* ===== Board aspect-ratio tokens — per-game =====
   Use `--board-ratio` on .page.{slug} to drive board square-ness without
   inline sizing from engine code. */
[data-moyako-v2] .page.chess          { --board-ratio: 1 / 1; }
[data-moyako-v2] .page.sudoku         { --board-ratio: 1 / 1; }
[data-moyako-v2] .page.memory         { --board-ratio: 1 / 1; }
[data-moyako-v2] .page.word-puzzle    { --board-ratio: 5 / 6; }
[data-moyako-v2] .page.block-puzzle   { --board-ratio: 1 / 1; }
[data-moyako-v2] .page.backgammon     { --board-ratio: 1 / 1; }
[data-moyako-v2] .page.maze           { --board-ratio: 1 / 1; }
[data-moyako-v2] .page .board-center .board-aspect {
  aspect-ratio: var(--board-ratio, 1 / 1);
  width: 100%; height: auto;
  max-height: 100%;
  max-width: min(600px, 100%);
}
@media (min-width: 900px) and (orientation: landscape) {
  [data-moyako-v2] .page .board-center .board-aspect {
    height: 100%; width: auto;
    max-width: 100%;
    aspect-ratio: var(--board-ratio, 1 / 1);
  }
}

/* ============================================================
   GAME-OVER / RESULTS overlay — shared metallic gradient style
   ============================================================
   Each game has its own overlay container (.overlay /
   .modal / .results-pill / #gameCompleteOverlay /
   #gameOverOverlay / #levelCompleteOverlay) and an inner card
   (.overlay-content / .modal-content). The canonical look:
   - Outer scrim: dark translucent backdrop
   - Inner card: 3D metallic shaded gradient panel (matches the
     gameplay shell tile recipe — 160deg gradient + inset
     highlight + bottom inset shadow + drop shadow)
   - Compact padding, 12 px radius, 360 px max-width
   - Action buttons inside use the canonical metallic pill
     style consistent with the in-game action row
   Per user "game over layout design not align with other design
   standards in general. Redesign them to align and have some
   compact better shaded metallic gradient style". */
[data-moyako-v2] .page #gameCompleteOverlay,
[data-moyako-v2] .page #gameOverOverlay,
[data-moyako-v2] .page #levelCompleteOverlay,
[data-moyako-v2] .page #allCompleteOverlay,
[data-moyako-v2] .page #winOverlay,
[data-moyako-v2] .page #loseOverlay,
[data-moyako-v2] .page #blockGameOverOverlay {
  position: fixed;
  inset: 0;
  /* Reset legacy transform cascade — chess.html ships
     #gameOverOverlay with class="results-pill" which carries
     transform: translate(-50%, -50%) for its small-pill layout.
     Without an explicit reset that translate moves the full-screen
     scrim off-viewport (-240,-360), so the overlay never shows
     and the board appears frozen on checkmate. */
  transform: none;
  width: auto; height: auto;
  max-height: none;
  background: rgba(10, 12, 18, 0.78);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 16px;
}
[data-moyako-v2] .page #gameCompleteOverlay.active,
[data-moyako-v2] .page #gameOverOverlay.active,
[data-moyako-v2] .page #levelCompleteOverlay.active,
[data-moyako-v2] .page #allCompleteOverlay.active,
[data-moyako-v2] .page #winOverlay.active,
[data-moyako-v2] .page #loseOverlay.active,
[data-moyako-v2] .page #blockGameOverOverlay.active {
  display: flex;
}
[data-moyako-v2] .page #gameCompleteOverlay > .overlay-content,
[data-moyako-v2] .page #gameOverOverlay > .overlay-content,
[data-moyako-v2] .page #gameOverOverlay > .modal-content,
[data-moyako-v2] .page #levelCompleteOverlay > .overlay-content,
[data-moyako-v2] .page #allCompleteOverlay > .overlay-content,
[data-moyako-v2] .page #winOverlay > .overlay-content,
[data-moyako-v2] .page #loseOverlay > .overlay-content,
[data-moyako-v2] .page #completionOverlay > .overlay-content,
[data-moyako-v2] .page #blockGameOverOverlay > .overlay-content {
  max-width: 360px;
  width: 100%;
  padding: 20px 22px;
  border-radius: 12px;
  background: linear-gradient(160deg, #3A4258 0%, #1F2433 100%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #ECEFF4;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 -1px 0 rgba(0, 0, 0, 0.30),
    0 8px 32px rgba(0, 0, 0, 0.45);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.30);
  text-align: center;
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  /* Default — portrait 2-row stack: results card on top
     (panda + title + stats + leaderboard), actions card on the
     bottom. The grid is 1 column with all non-button children
     auto-flowing in a results bucket and the .overlay-buttons
     row docked at the end. */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}
/* Landscape ≥ 700 px — 2 columns side-by-side. Non-button
   children (panda / title / leaderboard / etc.) stack in column
   1; .overlay-buttons sits as a vertical action card in column
   2 spanning all rows. Per user "split game over into 2
   containers panda game over and down to rank 1st container,
   starting from top players and the buttons second container,
   and in landscape display those would be displayed as 2
   column, in portrait as 2 rows". */
@media (orientation: landscape) {
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content,
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content,
  [data-moyako-v2] .page #gameOverOverlay > .modal-content,
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content,
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content,
  [data-moyako-v2] .page #winOverlay > .overlay-content,
  [data-moyako-v2] .page #loseOverlay > .overlay-content,
  [data-moyako-v2] .page #completionOverlay > .overlay-content,
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content {
    max-width: 720px;
    /* 2026-04-28: 1:1 ratio per user "2 container a and b 1:1 ratio
       fitting to the screen". Was 1.5fr 1fr (results-heavy split). */
    grid-template-columns: 1fr 1fr;
    grid-template-rows: minmax(0, 1fr);
    gap: 18px;
    text-align: left;
    /* "Fitting to the screen" + "no long container with scrolls" —
       cap card to viewport, clip overflow at the card level so the
       inner result-card and actions-card each manage their own
       internal scroll. */
    max-height: calc(100dvh - 24px);
    overflow: hidden;
  }
  /* All non-button children → column 1 (results card).
     Note: with the new pattern (chess uses .overlay-results-card as a
     single wrapper), this rule still routes any legacy-pattern direct
     children that aren't wrapped — backwards compatible. */
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #gameOverOverlay > .modal-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #winOverlay > .overlay-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #loseOverlay > .overlay-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #completionOverlay > .overlay-content > *:not(.overlay-buttons),
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content > *:not(.overlay-buttons) {
    grid-column: 1;
    grid-row: 1;
    min-height: 0;
    max-height: 100%;
    overflow-y: auto;
  }
  /* Buttons → column 2. Sits in the FIRST implicit row track so it
     stays top-aligned with the results card. The previous
     `grid-row: 1 / 999` trick worked when column-1 had ≤4 items but
     broke at scale: 999 row tracks × 18px gap = ~17,000px of phantom
     space pushing the button cluster ~5,000px below the viewport on
     content-heavy overlays (chess has 9+ column-1 items). */
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #gameOverOverlay > .modal-content > .overlay-buttons,
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #winOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #loseOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #completionOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content > .overlay-buttons {
    grid-column: 2;
    grid-row: 1;
    min-height: 0;
    max-height: 100%;
    overflow-y: auto;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    gap: 8px;
    justify-content: center;
  }
}
/* Compact second-tier "card" panel for the action column on
   landscape — shaded surface inside the outer card. Inset
   highlight + thin border so it reads as a distinct sub-pane. */
@media (orientation: landscape) {
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #gameOverOverlay > .modal-content > .overlay-buttons,
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #winOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #loseOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #completionOverlay > .overlay-content > .overlay-buttons,
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content > .overlay-buttons {
    padding: 14px 12px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  }
  [data-theme="light"] [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #gameOverOverlay > .overlay-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #gameOverOverlay > .modal-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #allCompleteOverlay > .overlay-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #winOverlay > .overlay-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #loseOverlay > .overlay-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #completionOverlay > .overlay-content > .overlay-buttons,
  [data-theme="light"] [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content > .overlay-buttons {
    background: rgba(15, 23, 42, 0.04);
    border-color: rgba(15, 23, 42, 0.08);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45);
  }
}
[data-theme="light"] [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content,
[data-theme="light"] [data-moyako-v2] .page #gameOverOverlay > .overlay-content,
[data-theme="light"] [data-moyako-v2] .page #gameOverOverlay > .modal-content,
[data-theme="light"] [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content,
[data-theme="light"] [data-moyako-v2] .page #allCompleteOverlay > .overlay-content,
[data-theme="light"] [data-moyako-v2] .page #winOverlay > .overlay-content,
[data-theme="light"] [data-moyako-v2] .page #loseOverlay > .overlay-content,
[data-theme="light"] [data-moyako-v2] .page #completionOverlay > .overlay-content,
[data-theme="light"] [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content {
  background: linear-gradient(160deg, #F4F6FB 0%, #D7DEEC 100%);
  border-color: rgba(15, 23, 42, 0.14);
  color: #1F2A44;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(15, 23, 42, 0.10),
    0 8px 32px rgba(15, 23, 42, 0.20);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
/* Title / subtitle / icon — compact + readable. */
[data-moyako-v2] .page .overlay-icon,
[data-moyako-v2] .page #gameCompleteOverlay .overlay-icon,
[data-moyako-v2] .page #gameOverOverlay .overlay-icon {
  font-size: 44px;
  margin-bottom: 6px;
  display: block;
  line-height: 1;
}
[data-moyako-v2] .page .overlay-title,
[data-moyako-v2] .page #gameCompleteOverlay .overlay-title,
[data-moyako-v2] .page #gameOverOverlay .overlay-title {
  font-size: 22px;
  font-weight: 800;
  margin-bottom: 4px;
  letter-spacing: 0.2px;
}
[data-moyako-v2] .page .overlay-subtitle,
[data-moyako-v2] .page #gameCompleteOverlay .overlay-subtitle,
[data-moyako-v2] .page #gameOverOverlay .overlay-subtitle {
  font-size: 13px;
  opacity: 0.85;
  margin-bottom: 14px;
  line-height: 1.35;
}
/* Action buttons inside the overlay — match the in-game
   .board-lower-action-btn metallic pill. */
[data-moyako-v2] .page #gameCompleteOverlay .btn,
[data-moyako-v2] .page #gameOverOverlay .btn,
[data-moyako-v2] .page #levelCompleteOverlay .btn,
[data-moyako-v2] .page #allCompleteOverlay .btn,
[data-moyako-v2] .page #winOverlay .btn,
[data-moyako-v2] .page #loseOverlay .btn,
[data-moyako-v2] .page #completionOverlay .btn,
[data-moyako-v2] .page #blockGameOverOverlay .btn {
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: linear-gradient(160deg, #3A4258 0%, #1F2433 100%);
  color: #ECEFF4;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 4px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 -1px 0 rgba(0, 0, 0, 0.30),
    0 2px 6px rgba(0, 0, 0, 0.25);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.30);
  transition: filter 0.2s ease, transform 0.1s ease;
}
[data-moyako-v2] .page #gameCompleteOverlay .btn:hover,
[data-moyako-v2] .page #gameOverOverlay .btn:hover {
  filter: brightness(1.15);
  transform: translateY(-1px);
}
[data-moyako-v2] .page #gameCompleteOverlay .btn-primary,
[data-moyako-v2] .page #gameOverOverlay .btn-primary,
[data-moyako-v2] .page #levelCompleteOverlay .btn-primary,
[data-moyako-v2] .page #completionOverlay .btn-primary,
[data-moyako-v2] .page #blockGameOverOverlay .btn-primary {
  background: linear-gradient(160deg, #66BB6A 0%, #2E7D32 100%);
  color: #FFFFFF;
  border-color: rgba(0, 0, 0, 0.18);
}
[data-theme="light"] [data-moyako-v2] .page #gameCompleteOverlay .btn:not(.btn-primary),
[data-theme="light"] [data-moyako-v2] .page #gameOverOverlay .btn:not(.btn-primary) {
  background: linear-gradient(160deg, #F4F6FB 0%, #D7DEEC 100%);
  border-color: rgba(15, 23, 42, 0.18);
  color: #1F2A44;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(15, 23, 42, 0.10),
    0 2px 6px rgba(15, 23, 42, 0.14);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}

/* ============================================================
   COMPACT GAME-OVER OVERLAY — generic opt-in design rule
   (2026-04-28, applied first to chess; rolling out to other 6
   games next).

   How a game opts in:
     1. Wrap the result content in:
          <div class="overlay-content overlay-content--compact"> ... </div>
     2. Use the structured class names below for the inner blocks:
          .overlay-stats--compact         (4-up grid, hero spans full row)
          .overlay-stat / .overlay-stat--hero
          .overlay-stats--compact .overlay-stat-label / .overlay-stat-value
          .overlay-pills / .overlay-pill  (inline meta row)
          .overlay-leaderboard            (collapsed <details> block)
          .mini-lb-row                    (rows inside leaderboard)
     3. Keep .overlay-buttons as the action cluster — primary CTA
        gets full-width; secondaries pair up.

   The shared overlay grid (defined above) handles outer layout:
     - Portrait              → 1 column stack (results above actions)
     - Any landscape         → 2 columns side-by-side (no width gate)
   so this rule set only governs the inner-block sizing, never the
   2-row vs 2-col container split.
   ============================================================ */
[data-moyako-v2] .page .overlay-content--compact {
  gap: 8px !important;
  padding: 10px 12px !important;
  /* Fit-to-screen guarantee — overall card never exceeds viewport. */
  max-height: calc(100dvh - 12px) !important;
  overflow: hidden !important;
  /* Entrance polish — gentle scale + fade so it doesn't jolt the
     player. Industry-standard 200ms ease-out. */
  animation: overlayEnterCompact 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* v322 — compact spacing rules per user "make the screen more compact".
 * Tighter padding, smaller font sizes, less gap between elements so
 * the overlay fits comfortably in landscape WITHOUT scrolling. */
[data-moyako-v2] .page .overlay-content--compact .overlay-hero {
  gap: 4px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-title {
  font-size: 18px !important;
  margin: 0 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-subtitle {
  font-size: 12px !important;
  margin: 0 !important;
  line-height: 1.3 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-pills {
  gap: 6px !important;
  margin: 4px 0 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-pill {
  font-size: 11px !important;
  padding: 4px 10px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero {
  padding: 8px !important;
  margin: 2px 0 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero .overlay-stat-value {
  font-size: 28px !important;
  line-height: 1 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stats--secondary {
  gap: 8px !important;
  margin-top: 6px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stats--secondary .overlay-stat {
  padding: 8px 6px !important;
  min-width: 0;          /* allow flex/grid items to shrink without overlap */
  overflow: hidden;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stats--secondary .overlay-stat-label,
[data-moyako-v2] .page .overlay-content--compact .overlay-stats--secondary .overlay-stat-value {
  display: block !important;
  text-align: center !important;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* v328 — score breakdown row has proper top margin so it doesn't
 * crowd into the stats grid above. Force block layout + center so the
 * "98 × Beginner 0.7× = 69" text never overlaps the cell labels.
 * Previously the inline-block sb-* spans could wrap awkwardly into
 * an apparent overlay over the stats row when font sizes mismatched. */
[data-moyako-v2] .page .overlay-content--compact .score-breakdown-row {
  display: block !important;
  margin: 10px 0 0 !important;
  padding: 8px 10px !important;
  text-align: center !important;
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  font-size: 12px !important;
  line-height: 1.4 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-narrative {
  padding: 8px !important;
  font-size: 12px !important;
  line-height: 1.35 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-narrative-panda img {
  width: 48px !important;
  height: 48px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-leaderboard {
  padding: 6px 8px !important;
  font-size: 11px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-leaderboard-title {
  font-size: 11px !important;
  margin-bottom: 4px !important;
}
@keyframes overlayEnterCompact {
  from { opacity: 0; transform: scale(0.94) translateY(6px); }
  to   { opacity: 1; transform: scale(1)    translateY(0); }
}
@media (orientation: landscape) {
  [data-moyako-v2] .page .overlay-content--compact { max-width: 760px !important; }
}
@media (orientation: portrait) {
  [data-moyako-v2] .page .overlay-content--compact { max-width: 360px !important; }
}

/* Container A — results card. Wraps every non-button child so the
   parent grid has exactly 2 grid items (results · actions), required
   for a true 1:1 landscape ratio. Manages its own internal scroll
   if content overflows the column height.
   Padding + space-between match container B so top/bottom edges of
   inner content align across the two columns (per user 2026-04-28
   "a and b content start end points to be aligned"). */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
  max-height: 100%;
  overflow-y: auto;
  padding: 12px 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  justify-content: space-between;
  /* Override shared landscape `text-align: left` so icon/stars/title
     stay centred per user 2026-04-28. */
  text-align: center !important;
  align-items: stretch;
}
[data-theme="light"] [data-moyako-v2] .page .overlay-content--compact .overlay-results-card {
  background: rgba(15, 23, 42, 0.04);
  border-color: rgba(15, 23, 42, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45);
}
/* Force-centre the icon explicitly because the shared #gameOverOverlay
   .overlay-icon rule (specificity 0,2,1) sets display:block which
   together with text-align could otherwise sit it left in some
   themes. */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card .overlay-icon {
  text-align: center;
  align-self: center;
}

/* Container B — .overlay-actions-card. Wraps narrative (top, 4fr)
   + buttons (bottom, 1fr) so the parent grid sees exactly 2 grid
   items (results · actions), preserving the 1:1 landscape ratio. */
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card {
  /* Card decoration only — layout is governed by subgrid in
     landscape (4 rows inherited from .overlay-content--compact)
     and falls back to flex column in portrait. */
  min-height: 0;
  max-height: 100%;
  padding: 12px 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
/* Portrait fallback — flex column, items at natural heights. */
@media (orientation: portrait) {
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
}
[data-theme="light"] [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card {
  background: rgba(15, 23, 42, 0.04);
  border-color: rgba(15, 23, 42, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

/* Narrative block (top of container B) — panda mascot + message.
   In landscape the narrative spans rows 1-2 of the subgrid (matches
   hero + pills in container A) so Top 5 lands precisely under it
   at the same Y as Score. No manual min-height tuning needed. */
[data-moyako-v2] .page .overlay-content--compact .overlay-narrative {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
  min-height: 0;
  overflow: hidden;
}

/* Hero block in container A — wraps icon + stars + title + subtitle
   so the whole thing participates as a single subgrid row. */
[data-moyako-v2] .page .overlay-content--compact .overlay-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

/* Secondary stats — Time / Moves / Captures only (Score is now a
   sibling element, not the hero row of this grid). Stretches to
   fill its subgrid row 4 so its bottom edge aligns with the
   buttons cluster's bottom edge in container B. */
[data-moyako-v2] .page .overlay-stats--secondary {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 6px;
  align-self: stretch;
  height: 100%;
}
[data-moyako-v2] .page .overlay-stats--secondary .overlay-stat {
  padding: 6px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.10);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
[data-moyako-v2] .page .overlay-stats--secondary .overlay-stat-label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  opacity: 0.75;
  margin-bottom: 2px;
}
[data-moyako-v2] .page .overlay-stats--secondary .overlay-stat-value {
  font-size: 16px;
  font-weight: 800;
  color: #ECEFF4;
}
[data-theme="light"] [data-moyako-v2] .page .overlay-stats--secondary .overlay-stat-value {
  color: #1F2A44;
}

/* Standalone Score hero — outside any stats grid now. Same visual
   recipe as it had inside the .overlay-stats--compact pattern. */
[data-moyako-v2] .page .overlay-content--compact > .overlay-results-card > .overlay-stat--hero {
  padding: 8px 10px;
  border-radius: 8px;
  background: linear-gradient(160deg, rgba(76, 175, 80, 0.18) 0%, rgba(46, 125, 50, 0.18) 100%);
  border: 1px solid rgba(76, 175, 80, 0.32);
  text-align: center;
}
[data-moyako-v2] .page .overlay-content--compact > .overlay-results-card > .overlay-stat--hero .overlay-stat-label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  opacity: 0.75;
  margin-bottom: 2px;
}
[data-moyako-v2] .page .overlay-content--compact > .overlay-results-card > .overlay-stat--hero .overlay-stat-value {
  font-size: 28px;
  font-weight: 800;
  color: #4CAF50;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-narrative-panda {
  flex-shrink: 0;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-narrative-panda img {
  width: 140px;
  height: auto;
  display: block;
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.5));
  animation: pandaFloat 3s ease-in-out infinite;
}
@media (max-width: 480px) {
  /* Mobile portrait — slightly smaller panda so container B doesn't
     dominate vertical space below container A. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-narrative-panda img {
    width: 110px;
  }
}
[data-moyako-v2] .page .overlay-content--compact .overlay-narrative-text {
  font-size: 12px;
  line-height: 1.4;
  opacity: 0.92;
  padding: 6px 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  /* Speech-bubble feel — gentle left-quote indent. */
  position: relative;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-narrative-text strong {
  color: var(--moyako-primary, #4CAF50);
}

@keyframes pandaFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-6px); }
}

/* Landscape — back to 2-col (results 1fr · actions 1fr) since panda
   now lives inside container B (no middle column needed). */
@media (orientation: landscape) {
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #winOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #loseOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #completionOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content--compact {
    grid-template-columns: 1fr 1fr !important;
    /* 4-row template — both cards inherit via subgrid so rows 3
       (Score / Top 5) and 4 (stats secondary / buttons) align across
       columns at the same Y. Per user 2026-04-28 "score and top 5
       player container heights should be same and aligned start end
       points same for buttons and scores containers". */
    grid-template-rows: auto auto auto auto !important;
    align-items: stretch !important;
    gap: 12px !important;
  }
  [data-moyako-v2] .page .overlay-content--compact > .overlay-results-card,
  [data-moyako-v2] .page .overlay-content--compact > .overlay-actions-card {
    /* Inherit the parent's 4 row tracks via subgrid — exact alignment
       between columns without manual height tuning. */
    grid-template-rows: subgrid !important;
    grid-row: 1 / span 4 !important;
    /* Single-column inner grid so children stretch to card width.
       Per user 2026-04-28 "align the widths of two containers
       contents" — without this children auto-size to content
       (~258px) instead of the full card inner width (~333px). */
    grid-template-columns: 1fr !important;
    height: 100%;
    display: grid;
    row-gap: 8px;
  }
  [data-moyako-v2] .page .overlay-content--compact > .overlay-results-card {
    grid-column: 1;
  }
  [data-moyako-v2] .page .overlay-content--compact > .overlay-actions-card {
    grid-column: 2 !important;
  }
  /* Container A — explicit row placement. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-hero        { grid-row: 1; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-pills       { grid-row: 2; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stat--hero  { grid-row: 3; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stats--secondary { grid-row: 4; }
  /* Container B — narrative spans rows 1-2 (matches hero + pills
     stack height in A), top5 sits in row 3, buttons in row 4. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-narrative          { grid-row: 1 / span 2; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-leaderboard--card  { grid-row: 3; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons            { grid-row: 4; }
  /* Buttons inside the actions-card — 3-col × 2-row grid. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons {
    display: grid !important;
    grid-template-columns: 1fr 1fr 1fr !important;
    grid-auto-rows: minmax(36px, auto);
    gap: 6px !important;
    flex-direction: unset !important;
    align-content: end;
    padding: 0 !important;
    background: transparent !important;
    border: none !important;
    box-shadow: none !important;
    overflow-y: visible !important;
    max-height: none !important;
  }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons .btn {
    margin: 0 !important;
    padding: 8px 6px !important;
    font-size: 12px !important;
    min-height: 36px !important;
  }

  /* v336 (2026-05-11) — landscape game-end overlay regression repair.
   *
   * v331 injected .moyako-guest-cta as a NEW first child of
   * .overlay-actions-card. The subgrid layout (4 row tracks · narrative
   * 1/span2 · leaderboard 3 · buttons 4) only places 3 of the now-4
   * children, so CSS grid auto-flow planted the CTA in an unexpected
   * cell and pushed siblings around. Result on cap-shell landscape:
   * panda mascot overflowed the card top (pandaFloat -6px animation
   * + 140px width amplified the bump), CTA card extended past the
   * column's right edge clipping the SIGN UP button, and the
   * 6-button 2-row grid collapsed to a single clipped row.
   *
   * Fix in 3 parts, all scoped to landscape orientation in cap-shell:
   *   1. Promote the grid template to 5 rows so the CTA gets its own
   *      track at row 1 + push narrative/leaderboard/buttons one
   *      row down. Explicit placement → no more auto-flow surprises.
   *   2. Constrain the panda mascot — width 60px (down from 140),
   *      static (no pandaFloat translateY) so its glyph can't ever
   *      bump past the card top.
   *   3. Force the CTA card to width:100% of its column + the .btn
   *      pair to flex-wrap if their natural width exceeds the
   *      available column (no more truncated "S" Sign Up button).
   *      Buttons row stays 3-col grid with explicit min-width:0 so
   *      labels ellipsis-clip instead of overflowing the cell.
   */
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #winOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #loseOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #completionOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content--compact {
    /* v336: 5 row tracks — CTA · narrative · narrative · top5 · buttons.
     * Old 4-track template still works when CTA isn't present (the
     * leading auto track collapses to 0). */
    grid-template-rows: auto auto auto auto auto !important;
  }
  [data-moyako-v2] .page .overlay-content--compact > .overlay-results-card,
  [data-moyako-v2] .page .overlay-content--compact > .overlay-actions-card {
    grid-row: 1 / span 5 !important;
  }
  /* Container A — bump each row by 1 to align with B's narrative
   * starting at row 2 (CTA reserves row 1). */
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-hero        { grid-row: 2 !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-pills       { grid-row: 3 !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stat--hero  { grid-row: 4 !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stats--secondary { grid-row: 5 !important; }
  /* v341 (2026-05-12): right column order now matches the chess
   * canonical layout — panda at the TOP (row 1-2), speech inside
   * narrative, leaderboard row 3, CTA tucked above the buttons in
   * row 4, buttons row 5. Pre-v341 the CTA at row 1 pushed panda
   * down, breaking visual parity with chess (whose right column
   * starts with panda flush at top). Container A's left-column
   * "icon + title" now aligns with the right column's "panda +
   * speech" instead of with a CTA. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-narrative       { grid-row: 1 / span 2 !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-leaderboard--card { grid-row: 3 !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .moyako-guest-cta         { grid-row: 4 !important; width: 100% !important; box-sizing: border-box !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons         { grid-row: 5 !important; }

  /* Panda mascot — cap to 60px + freeze pandaFloat (so the -6px
   * translateY can't make the head clip the card top). */
  [data-moyako-v2] .page .overlay-content--compact .overlay-narrative-panda img {
    width: 60px !important;
    height: 60px !important;
    object-fit: contain !important;
    animation: none !important;
    transform: none !important;
  }
  /* CTA action buttons — allow wrap so a narrow column shows them
   * stacked instead of clipping the Sign Up button. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .moyako-guest-cta {
    flex-wrap: wrap !important;
  }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .moyako-guest-cta .moyako-guest-cta__actions {
    flex-shrink: 1 !important;
    flex-wrap: wrap !important;
  }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .moyako-guest-cta .btn {
    min-width: 0 !important;
    flex: 1 1 auto !important;
  }
  /* Buttons row — keep 3-col grid but let cells shrink + clip cleanly. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons .btn {
    min-width: 0 !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
  }
}

/* ============================================================
   v337 (2026-05-11) — COMPACT-NOT-STRETCHED PASS
   ============================================================
   Per user "make the objects compact, do not stretch". Pre-v337
   the landscape grid + subgrid pattern forced the two cards
   (results · actions) AND their children (hero · pills · score ·
   stats · narrative · leaderboard · buttons) to stretch and fill
   the viewport-height rectangle. The result was a sparse,
   blown-up overlay with awkward gaps between each block.

   Goal: every block sizes to its natural content. The cards
   shrinkwrap. The grid columns shrinkwrap. The whole overlay
   centres in the viewport at min-content height instead of
   fighting to fill it.

   Scope: applies in BOTH orientations (portrait and landscape).
   Portrait was already mostly flex-column natural-flow; this
   pass mainly disarms the landscape subgrid stretch + the
   "justify-content: space-between" on the cards.

   The change is layered on top of existing rules so the chess
   golden template stays the canonical shape — only the
   stretch directives are unwound.
   ============================================================ */

/* Outer overlay — natural height, capped to viewport. */
[data-moyako-v2] .page .overlay-content--compact {
  /* dvh cap still in force so content never exceeds viewport;
   * height is now driven by content, not the cap.
   *
   * v338 (2026-05-11): equal-share row template so the two cards
   * always render at the same height (per user "top bottom
   * container to be equal size"). The grid splits the available
   * height 50/50 in portrait + 50/50 columns in landscape;
   * align-items: stretch fills each card to its allotted track. */
  height: auto !important;
  align-items: stretch !important;
}

/* Portrait — equal-share rows (1fr 1fr) so the results card on top
 * and actions card on bottom are exactly the same height. The
 * shorter card grows to match the taller one's natural content;
 * neither stretches beyond its allotted half.
 *
 * 1fr 1fr only distributes space when the parent has a defined
 * height. min-height: min(85dvh, max-content) gives the grid a
 * floor large enough that both 1fr tracks have room to grow to
 * the taller card's natural height, while the dvh cap keeps the
 * overall overlay inside the viewport. */
@media (orientation: portrait) {
  [data-moyako-v2] .page .overlay-content--compact {
    grid-template-rows: 1fr 1fr !important;
    min-height: 85dvh !important;
    max-height: calc(100dvh - 12px) !important;
  }
}

/* Cards — shrinkwrap content vertically inside their allotted
 * grid track; both cards end up the same outer size via the parent
 * grid's stretch + equal-share rows/columns. */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card,
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card {
  /* `height: 100%` lets each card fill its track exactly, so the
   * two cards end up the same height regardless of whose content
   * is taller (the parent's 1fr 1fr split + stretch is the source
   * of truth). max-height: none keeps internal overflow visible
   * instead of cropping when content briefly exceeds the track. */
  height: 100% !important;
  max-height: none !important;
  justify-content: flex-start !important;
  overflow-y: visible !important;
}

/* Stats grid — natural row height, no fill-to-card. */
[data-moyako-v2] .page .overlay-content--compact .overlay-stats--secondary {
  height: auto !important;
  align-self: auto !important;
}

/* Landscape — neutralise the subgrid stretch + explicit per-row
 * placements that forced children to occupy fixed slots. With the
 * stretch gone, each child sizes to content; grid auto-flow keeps
 * source order. align-items: start on the parent grid means the
 * two columns no longer match each other's height. */
@media (orientation: landscape) {
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #winOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #loseOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #completionOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content--compact {
    grid-template-rows: auto !important;
    align-items: start !important;
  }
  [data-moyako-v2] .page .overlay-content--compact > .overlay-results-card,
  [data-moyako-v2] .page .overlay-content--compact > .overlay-actions-card {
    grid-template-rows: none !important;
    grid-row: auto !important;
    /* v338: fill the column track height so both cards are equal
     * size — the parent grid's `align-items: stretch` + `1fr 1fr`
     * columns split width 50/50 and stretch height to match the
     * taller card. */
    height: 100% !important;
    align-self: stretch !important;
  }
  /* Drop per-row placements — let auto-flow do its job. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-hero,
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-pills,
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stat--hero,
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stats--secondary,
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .moyako-guest-cta,
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-narrative,
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-leaderboard--card,
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons {
    grid-row: auto !important;
  }
}

/* ============================================================
   v339 (2026-05-12) — GAME-END OVERLAY TYPOGRAPHY ALIGNMENT
   ============================================================
   Per user "button and text size align with the ones in games".
   The overlay was using its own font sizes (13px buttons, 16px
   stat values, 10px labels, 8px radius, 10-14px padding) which
   are slightly off the gameplay-surface design tokens.

   Aligned to the canonical recipe used by .number-pad-info,
   .board-lower-action-btn, and .board-lower-stat-{label,value}:

     - Stat label   → --ds-font-xs (11px) × density
     - Stat value   → --ds-font-sm (13px) × density
     - Button label → --ds-font-sm (13px) × density
     - Button radius→ --ds-radius-button (6px)
     - Button padding → --ds-space-2 / --ds-space-3 (8 / 12)
     - Tile padding → --ds-space-2 / --ds-space-3 (8 / 12)
     - Font weights → --ds-fw-label / --ds-fw-heading

   Exception: .overlay-stat--hero score value keeps its larger
   celebration size (28px) — it's intentionally dramatic, not a
   stat tile. The label inside it still uses the canonical xs/heading.
   ============================================================ */

/* Stat tiles — labels + values + padding + radius match gameplay. */
[data-moyako-v2] .page .overlay-content--compact .overlay-stat-label {
  font-size: calc(var(--ds-font-xs) * var(--ds-density)) !important;
  font-weight: var(--ds-fw-label) !important;
  text-transform: uppercase !important;
  letter-spacing: var(--ds-ls-uppercase) !important;
  line-height: var(--ds-lh-numerals) !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stat-value {
  font-size: calc(var(--ds-font-sm) * var(--ds-density)) !important;
  font-weight: var(--ds-fw-heading) !important;
  line-height: var(--ds-lh-numerals) !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stats--secondary .overlay-stat {
  border-radius: var(--ds-radius-chip) !important;
  padding: var(--ds-space-2) var(--ds-space-3) !important;
}

/* Score-hero — keep the 28px celebration value, but tighten the
 * label + padding + radius to match the rest of the tile system. */
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero {
  border-radius: var(--ds-radius-chip) !important;
  padding: var(--ds-space-2) var(--ds-space-3) !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero .overlay-stat-label {
  font-size: calc(var(--ds-font-xs) * var(--ds-density)) !important;
}

/* Action buttons — font size, weight, radius, padding all token.
 *
 * v340 (2026-05-12): weight downgraded from --ds-fw-heading (800)
 * to --ds-fw-label (600) for the SECONDARY row + the tertiary
 * EXIT + REPLAY.
 * v344 (2026-05-12): further dropped to --ds-fw-body (400 regular).
 * v346 (2026-05-12): font size reduced from --ds-font-sm (13px) to
 * --ds-font-xs (11px) per user "text size needs to be reduced" —
 * "NEXT MATCH" label was overflowing the 1/3 column on portrait
 * (scrollWidth 105 in 84px button = cropped 'H'). 11px font shrinks
 * the label to fit and matches the gameplay-surface label-tier
 * typography used by .board-lower-stat-label and overlay-pill. */
[data-moyako-v2] .page .overlay-content--compact .overlay-action-btn {
  /* v346b: shrink to 10px + 4px horizontal padding so the longest
   * label ("Next Match") fits the 1/3 column at 375px portrait
   * without ellipsis. Matches the densest typography in
   * gameplay (--ds-font-2xs if it existed; using a raw 10px). */
  font-size: 10px !important;
  font-weight: var(--ds-fw-body) !important;
  border-radius: var(--ds-radius-button) !important;
  padding: var(--ds-space-2) 4px !important;
  letter-spacing: 0 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-action-btn.btn-primary {
  font-weight: var(--ds-fw-label) !important;
}

/* v342 (2026-05-12): button-row gap → 2px per user "reduce the
 * space between the button rows at right panel to 2px". The
 * parent .overlay-actions-card has flex `gap: 6px`; combined with
 * the secondary's margin-bottom: 0, the gap was 6px. Override the
 * card's gap and add a negative margin compensation so the visible
 * gap between the two rows lands at 2px exactly. */
[data-moyako-v2] .page .overlay-content--compact .overlay-buttons--secondary {
  margin-bottom: 0 !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-buttons--primary {
  margin-top: 2px !important;
}
/* Cancel the parent flex/grid row-gap that would otherwise add to
 * the 2px margin. The other actions-card children (narrative,
 * leaderboard, CTA) still get the 6px gap via specific selectors. */
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons--secondary + .overlay-buttons--primary,
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card .overlay-buttons + .overlay-buttons {
  margin-top: 2px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card {
  gap: 0 !important;
  row-gap: 0 !important;
}
/* Restore the cross-block spacing that the card-level `gap: 0`
 * removed — narrative ↔ leaderboard ↔ CTA ↔ buttons groups still
 * need a 6-8px breathing room; only the inter-button-row gap is 2px. */
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-narrative {
  margin-bottom: 6px;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-leaderboard--card {
  margin-bottom: 6px;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .moyako-guest-cta {
  margin-bottom: 6px;
}

/* v358 (2026-05-12) — score breakdown moved INSIDE the score-hero
 * container as the last row, styled inline with a 2px bottom margin.
 * Per user "put into the score container as last row with 2px
 * margin to the bottom, no additional container needed for it".
 *
 * Hide breakdowns that aren't inside the score-hero (legacy locations
 * outside the green tile). Show ONLY the one nested in stat--hero. */
[data-moyako-v2] .page .overlay-content--compact .score-breakdown-row {
  display: none !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero > .score-breakdown-row,
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero #scoreBreakdown {
  display: block !important;
  margin: 4px 0 2px 0 !important;
  padding: 0 !important;
  background: transparent !important;
  border: 0 !important;
  font-size: 10px !important;
  line-height: 1.3 !important;
  text-align: center !important;
  opacity: 0.85 !important;
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
}
/* Hide when empty (no breakdown content yet). */
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero > .score-breakdown-row:empty,
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero #scoreBreakdown:empty {
  display: none !important;
}

/* v343 (2026-05-12) — orientation-independent inner alignment.
 * v345 (2026-05-12) — symmetric top/bottom insets per user
 * "bottom margin to be aligned top margin on the containers" +
 * "button containers should align the score container".
 *
 * Goal: the FIRST child of each card has 13px top inset from card
 * edge (matching the card's padding), and the LAST child has 13px
 * bottom inset — symmetric breathing room. Score container on the
 * left and combined buttons container on the right end up at
 * matching y-positions.
 */
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero,
[data-moyako-v2] .page .overlay-content--compact .overlay-leaderboard--card {
  /* v347: 92px floor — Score = TOP-5 = combined buttons height. */
  min-height: 92px !important;
}

/* v360b (2026-05-12) — TOP 5 card content full-width per user
 * "top 5 width to be aligned". Was 317px inner content inside
 * 335px card due to 8px horizontal padding + 1px border. Now
 * 4px horizontal padding so inner content stretches to 327px. */
[data-moyako-v2] .page .overlay-content--compact .overlay-leaderboard--card {
  padding: 6px 4px !important;
}

/* v361 (2026-05-12) — landscape row pairing per user "push time below
 * score and align with view row, row below space for sign in text":
 *
 *   Row 4: stats (Time/Errors/Hints) on left  ↔  View row on right
 *   Row 5: Sign-in CTA on left                 ↔  Exit row on right
 *
 * v361b: row-gap split so the visible gaps match portrait (4/2):
 *   Score → Time / TOP5 → View   = 4px (row-gap before row 4)
 *   Time → CTA   / View → Exit   = 2px (row-gap before row 5)
 * Achieved by row-gap: 4 + negative margin-top on row 5 children
 * (or per-row margins; here we use row-gap 2 + extra margin on row 4
 * to widen its top-gap to 4). */
@media (orientation: landscape) {
  /* 5-row template with explicit gap-control rows.
   * Insert "gap rows" of fixed height between content rows so we
   * can have per-row visible spacing without margin tricks fighting
   * subgrid stretch.
   *
   * Row 1: hero / narrative-row1
   * Row 2: pills / narrative-row2
   * Row 3: score / TOP 5            (subgrid auto)
   * (4px gap absorbed by row-gap)
   * Row 4: stats / View row         (subgrid auto)
   * (2px gap absorbed by row-gap)
   * Row 5: CTA / Exit row           (subgrid auto)
   *
   * row-gap can't differ per row in standard grid. Use 2px row-gap
   * (matches button-row gap 2) and put 2px extra margin-bottom on
   * row-3 elements to create 4px above row 4. */
  [data-moyako-v2] .page .overlay-content--compact:has(.moyako-guest-cta) {
    grid-template-rows: auto auto minmax(56px, auto) minmax(36px, auto) minmax(36px, auto) !important;
    row-gap: 2px !important;
  }
  /* +2px below row-3 elements so visible gap from row 3 → row 4 = 4px
   * (row-gap 2 + margin-bottom 2). Row 4 → row 5 stays at 2px (row-gap). */
  [data-moyako-v2] .page .overlay-content--compact:has(.moyako-guest-cta) .overlay-results-card > .overlay-stat--hero,
  [data-moyako-v2] .page .overlay-content--compact:has(.moyako-guest-cta) .overlay-actions-card > .overlay-leaderboard--card {
    margin-bottom: 2px !important;
  }
  /* v362: bottom margin on the hero (well done!) and narrative
   * (well played!) containers — gives breathing room before the
   * pills row / TOP-5 block below. 8px symmetric on both sides. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-hero,
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-narrative {
    margin-bottom: 8px !important;
  }
  [data-moyako-v2] .page .overlay-content--compact:has(.moyako-guest-cta) > .overlay-results-card,
  [data-moyako-v2] .page .overlay-content--compact:has(.moyako-guest-cta) > .overlay-actions-card {
    grid-row: 1 / span 5 !important;
  }
  /* Stats sits at row 4 only (single row, aligning with View row). */
  [data-moyako-v2] .page .overlay-content--compact:has(.moyako-guest-cta) .overlay-results-card > .overlay-stats--secondary {
    grid-row: 4 !important;
    align-self: stretch !important;
  }
  /* CTA sits at row 5 (aligns with Exit row on right). */
  [data-moyako-v2] .page .overlay-content--compact:has(.moyako-guest-cta) .overlay-results-card > .moyako-guest-cta {
    grid-row: 5 !important;
    align-self: stretch !important;
    /* override v355b's compact font sizing — this is no longer the
     * cramped 6th row but a full row matching the buttons row. */
    font-size: 12px !important;
    min-height: 36px !important;
    padding: 6px 10px !important;
  }
}

/* v360 (2026-05-12) — explicit inter-block gaps tuned per user:
 *
 *   Score → Time (stats):           4px
 *   Time → Sign-in CTA:             2px
 *   TOP 5 → View button row:        4px
 *   View row → Exit button row:     2px
 *
 * Strategy: card flex gap = 0, explicit margin-top on each child
 * controls its gap. justify-content stays space-between so the
 * FIRST and LAST visible children anchor to top/bottom (13px inset).
 * In between, gap: 0 means the slack distribution happens BETWEEN
 * other siblings; we add margin-top to override those.
 *
 * Actually space-between with gap:0 still distributes slack across
 * EVERY gap. To get exact 4/2 gaps, force most spacing via margin-
 * top and make ONE element absorb the slack (the one element that
 * has no margin-top constraint).
 *
 * Implementation: anchor pills at top (no margin), anchor the
 * buttons-primary / stats at bottom (margin-top: auto), and pin
 * each in-between element with explicit margin-top.
 */

/* Card gap → 0; margins control all spacing */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card,
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card {
  gap: 0 !important;
  row-gap: 0 !important;
}

/* LEFT card — hero, pills, score: natural spacing (6px each).
 * Then score → stats: 4px, stats → CTA: 2px, CTA: auto-margin top
 * absorbs slack to push to bottom (results in CTA exactly 13px from
 * card bottom via card padding). */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-pills {
  margin-top: 6px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stat--hero {
  margin-top: 6px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stats--secondary {
  margin-top: 4px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .moyako-guest-cta {
  margin-top: 2px !important;
  margin-bottom: auto !important; /* absorb downstream slack */
}
/* If no CTA, the stats becomes last child; push it slack-free
 * to bottom by giving it margin-top: auto via :last-child. */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stats--secondary:last-child {
  margin-top: auto !important;
}

/* RIGHT card — narrative, leaderboard: natural 6px. TOP 5 → View: 4px,
 * View → Exit: 2px, Exit anchored at bottom by being last child
 * (the card's space-between handles this). */
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-leaderboard--card {
  margin-top: 6px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons--secondary {
  margin-top: 4px !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons--primary {
  margin-top: 2px !important;
}

/* v345b: card uses `justify-content: space-between` so the first
 * visible child anchors at the top (13px inset = card padding) and
 * the last visible child anchors at the bottom (13px inset =
 * card padding). middle children distribute their natural spacing.
 *
 * This replaces v345's margin-top:auto attempt, which was clamped to
 * a tiny value (~3.9px) because the card's `gap: 6px` already
 * consumed the inter-child slack — margin-top:auto then had nothing
 * left to redistribute.
 *
 * space-between produces variable mid-row gaps (3 visible children +
 * 1 absorber); per user that's the acceptable trade-off for
 * symmetric edges. */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card,
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card {
  justify-content: space-between !important;
}
/* v345c: chess.html inline <style> has `.overlay-stats { margin-bottom: 24px }`
 * that bleeds into our compact overlay's .overlay-stats--secondary and
 * pushes stats 24px above the card's bottom edge — defeating the
 * space-between bottom anchor. Override both stats AND any sibling
 * with stray margins so the cards' first + last children sit flush
 * against their card-padding edges. */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card > *,
[data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > * {
  margin-bottom: 0 !important;
}

/* v348 (2026-05-12) — pin button rows to bottom via margin-top:auto
 * on the secondary row. Defense-in-depth: even if a game's overlay
 * is missing the leaderboard, the buttons stay tight (2px gap) at
 * the card bottom instead of inheriting space-between's distribution.
 * Primary fix is to ADD the leaderboard HTML to every game's overlay
 * (memory + maze level-complete need it). */
/* v348 margin-top:auto safety net removed in v360 — TOP 5 is now
 * present in every game's actions-card, so the secondary button
 * row has explicit margin-top from v360. */

/* v355 (2026-05-12) — guest-CTA inside results-card after stats row.
 * Visually "below the time row" — the stats row is the last
 * content in the left card and the CTA sits immediately below.
 *
 * Landscape: parent grid extended to 6 rows; LEFT card subgrid
 * spans 1-6 (with CTA in row 6), RIGHT card subgrid spans 1-5
 * (no content at row 6 on right → empty space there).
 *
 * Portrait: card is a flex column; CTA flows naturally as the
 * last child after stats. */
[data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .moyako-guest-cta {
  /* v360 override: margin-top now controlled by the v360 rule above.
   * Keep only flex/align/width here. */
  flex: 0 0 auto !important;
  align-self: stretch !important;
  width: auto !important;
}
/* v361 supersedes the previous landscape-row-6 placement (v355b).
 * CTA now lives at row 5 (paired with Exit row), stats at row 4
 * (paired with View row). Template defined later in this file by
 * the v361 rule block. */

/* v344b: stats secondary min-height removed. v343b's 92px floor
 * caused the 3 tiles to stretch absurdly tall in landscape (each
 * tile ballooned to ~122px instead of natural ~44px). Visual
 * "alignment with buttons" is now achieved by anchoring the stats
 * to the BOTTOM of its 2-row span (align-self: end) so its bottom
 * edge meets the right-card buttons bottom, while tiles stay
 * natural-sized at the bottom of the left card. */

/* v342: score hero + secondary stats row STRETCH to fill their
 * subgrid track so they match the right column's TOP 5 box (row 3)
 * and combined button rows (row 4). Per user:
 *   "increase the size of score box to align Top 5 box on the right"
 *   "align the score tiles height on the left to the total height
 *    of buttons on the right"
 * This re-introduces a controlled stretch — the children pack at
 * top inside their row track, then `align-self: stretch` on the
 * container makes the OUTER box fill the track height. Internal
 * content stays compact. */
@media (orientation: landscape) {
  /* Restore the subgrid + explicit row placements that v337's
   * compact pass disabled. Equal-cards (1fr 1fr cols) preserved;
   * row template now defines proportional heights so Score↔TOP-5
   * and Stats↔Buttons line up across columns. */
  [data-moyako-v2] .page #gameOverOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #gameCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #levelCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #allCompleteOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #winOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #loseOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #completionOverlay > .overlay-content--compact,
  [data-moyako-v2] .page #blockGameOverOverlay > .overlay-content--compact {
    /* 5 row tracks shared by both columns via subgrid:
     *   row 1: hero / narrative-row1
     *   row 2: pills / narrative-row2
     *   row 3: stat--hero (Score) / leaderboard (TOP-5)
     *   row 4: stats--secondary (spans 4-5) / buttons-secondary
     *   row 5: stats--secondary (continues) / buttons-primary
     * 2px row-gap = the inter-button-row spacing the user asked for.
     * Margins on earlier rows restore breathing space between hero,
     * pills, score, and stats. */
    grid-template-rows: auto auto minmax(56px, auto) minmax(36px, auto) minmax(36px, auto) !important;
    row-gap: 2px !important;
    column-gap: 12px !important;
  }
  /* Breathing room above row-3 (Score / TOP-5) and row-4 (Stats /
   * Buttons-secondary). Doesn't affect the inter-button-row 2px
   * because rows 4 and 5 are sibling button rows with only the
   * parent 2px row-gap between them. */
  [data-moyako-v2] .page .overlay-content--compact > .overlay-results-card > .overlay-pills,
  [data-moyako-v2] .page .overlay-content--compact > .overlay-actions-card > .overlay-narrative {
    margin-bottom: 6px !important;
  }
  [data-moyako-v2] .page .overlay-content--compact > .overlay-results-card > .overlay-stat--hero,
  [data-moyako-v2] .page .overlay-content--compact > .overlay-actions-card > .overlay-leaderboard--card {
    margin-bottom: 6px !important;
  }
  /* Cards span all 5 rows + use subgrid so their children align
   * row-by-row across columns. CRITICAL: must be `display: grid`,
   * not the flex default the cards have outside landscape — without
   * grid, subgrid template-rows is silently ignored. */
  [data-moyako-v2] .page .overlay-content--compact > .overlay-results-card,
  [data-moyako-v2] .page .overlay-content--compact > .overlay-actions-card {
    display: grid !important;
    grid-template-columns: 1fr !important;
    grid-template-rows: subgrid !important;
    grid-row: 1 / span 5 !important;
    row-gap: 2px !important;
  }
  /* Container A — left column. Stats span rows 4-5 so its outer
   * height equals the combined buttons-secondary + buttons-primary
   * tracks on the right. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-hero            { grid-row: 1 !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-pills           { grid-row: 2 !important; }
  /* v344b: score-hero keeps stretch+center so it visually matches
   * TOP-5's height. Stats secondary aligns to END of its 2-row span
   * (rows 4-5) so its BOTTOM edge meets the actions-card's primary
   * button bottom, but the tile CHILDREN stay natural ~44px height
   * — no stretching to absurd 122px each. */
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stat--hero      { grid-row: 3 !important; align-self: stretch !important; height: 100% !important; display: flex !important; flex-direction: column !important; justify-content: center !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-results-card > .overlay-stats--secondary { grid-row: 4 / span 2 !important; align-self: end !important; height: auto !important; }
  /* Container B — right column. Buttons-secondary on row 4,
   * buttons-primary on row 5; the 2px row-gap defined on the parent
   * grid is the entire inter-row spacing between them. CTA lands
   * with buttons-secondary when present (auto-flow, guest only). */
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-narrative          { grid-row: 1 / span 2 !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-leaderboard--card  { grid-row: 3 !important; align-self: stretch !important; height: 100% !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .moyako-guest-cta          { grid-row: 4 !important; align-self: start !important; margin-bottom: 4px !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons--secondary { grid-row: 4 !important; align-self: stretch !important; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-actions-card > .overlay-buttons--primary   { grid-row: 5 !important; align-self: stretch !important; }
}

/* Pills — label-sized type to match the stat labels. */
[data-moyako-v2] .page .overlay-content--compact .overlay-pill {
  font-size: calc(var(--ds-font-xs) * var(--ds-density)) !important;
  padding: var(--ds-space-1, 4px) var(--ds-space-3) !important;
}

/* Title / subtitle — gameplay h-stack uses --ds-font-lg for titles
 * and --ds-font-sm for subtitles; mirror that so the hero block reads
 * at the same scale as the gameplay headings. */
[data-moyako-v2] .page .overlay-content--compact .overlay-title {
  font-size: calc(var(--ds-font-lg, 16px) * var(--ds-density)) !important;
  font-weight: var(--ds-fw-heading) !important;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-subtitle {
  font-size: calc(var(--ds-font-sm) * var(--ds-density)) !important;
}



[data-moyako-v2] .page .overlay-content--compact .overlay-icon {
  font-size: 28px;
  margin: 0;
  line-height: 1;
}
/* Star row (1/2/3) — Candy Crush / King-style dopamine. Each star
   pops in with a small stagger so the player notices them. */
[data-moyako-v2] .page .overlay-content--compact .moyako-stars {
  display: flex;
  justify-content: center;
  gap: 4px;
  font-size: 22px;
  margin: 2px 0 0;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
}
[data-moyako-v2] .page .overlay-content--compact .moyako-stars > * {
  display: inline-block;
  animation: starPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
}
[data-moyako-v2] .page .overlay-content--compact .moyako-stars > *:nth-child(1) { animation-delay: 200ms; }
[data-moyako-v2] .page .overlay-content--compact .moyako-stars > *:nth-child(2) { animation-delay: 360ms; }
[data-moyako-v2] .page .overlay-content--compact .moyako-stars > *:nth-child(3) { animation-delay: 520ms; }
@keyframes starPop {
  0%   { opacity: 0; transform: scale(0.4) rotate(-15deg); }
  60%  { opacity: 1; transform: scale(1.18) rotate(4deg); }
  100% { opacity: 1; transform: scale(1)    rotate(0); }
}
/* Score hero pulses gently when first revealed — celebration cue. */
[data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero .overlay-stat-value {
  animation: scorePulse 700ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
  animation-delay: 380ms;
}
@keyframes scorePulse {
  0%   { opacity: 0; transform: scale(0.6); }
  60%  { opacity: 1; transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); }
}
/* Reduced motion — disable celebration animations for users with
   prefers-reduced-motion. */
@media (prefers-reduced-motion: reduce) {
  [data-moyako-v2] .page .overlay-content--compact,
  [data-moyako-v2] .page .overlay-content--compact .moyako-stars > *,
  [data-moyako-v2] .page .overlay-content--compact .overlay-stat--hero .overlay-stat-value,
  [data-moyako-v2] .page .overlay-content--compact > .overlay-panda img {
    animation: none !important;
  }
}
[data-moyako-v2] .page .overlay-content--compact .overlay-title {
  font-size: 17px;
  font-weight: 800;
  margin: 0;
  line-height: 1.1;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-subtitle {
  font-size: 11px;
  opacity: 0.85;
  margin: 0 0 2px;
  line-height: 1.25;
}
/* Slim native-ad slot inside the overlay — caps the post-game ad
   block at 80px so it cannot dominate the card. */
[data-moyako-v2] .page .overlay-content--compact #moyako-game-complete-ad,
[data-moyako-v2] .page .overlay-content--compact .moyako-native-ad-slot {
  max-height: 88px;
  overflow: hidden;
}
[data-moyako-v2] .page .overlay-content--compact #moyako-game-complete-ad:empty,
[data-moyako-v2] .page .overlay-content--compact .moyako-native-ad-slot:empty {
  display: none;
}

/* 4-up stats grid: hero stat spans the full row, three secondary
   stats share row 2. */
[data-moyako-v2] .page .overlay-stats--compact {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 6px;
  margin: 0;
}
[data-moyako-v2] .page .overlay-stats--compact .overlay-stat {
  padding: 6px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.10);
  text-align: center;
}
[data-moyako-v2] .page .overlay-stats--compact .overlay-stat--hero {
  grid-column: 1 / -1;
  padding: 8px 10px;
  background: linear-gradient(160deg, rgba(76, 175, 80, 0.18) 0%, rgba(46, 125, 50, 0.18) 100%);
  border-color: rgba(76, 175, 80, 0.32);
}
[data-moyako-v2] .page .overlay-stats--compact .overlay-stat-label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  opacity: 0.75;
  margin-bottom: 2px;
}
[data-moyako-v2] .page .overlay-stats--compact .overlay-stat-value {
  font-size: 16px;
  font-weight: 800;
  color: #ECEFF4;
}
[data-moyako-v2] .page .overlay-stats--compact .overlay-stat--hero .overlay-stat-value {
  font-size: 28px;
  color: #4CAF50;
}
[data-theme="light"] [data-moyako-v2] .page .overlay-stats--compact .overlay-stat-value {
  color: #1F2A44;
}

/* Inline meta pills — single-line row, wraps if needed. */
[data-moyako-v2] .page .overlay-pills {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: center;
}
[data-moyako-v2] .page .overlay-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 700;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
}

/* Collapsed-by-default top players — saves ~120px on mobile. */
[data-moyako-v2] .page .overlay-leaderboard {
  font-size: 12px;
  text-align: left;
  background: rgba(76, 175, 80, 0.06);
  border: 1px solid rgba(76, 175, 80, 0.18);
  border-radius: 8px;
  padding: 6px 10px;
}
/* Always-visible "Top 5" variant — same visual weight as the
   .overlay-stat--hero score card (per user 2026-04-28 "same space
   as score for Top 5 players"). Sits inside container B, below
   the narrative. Fixed height for exactly 5 rows. */
[data-moyako-v2] .page .overlay-leaderboard--card {
  background: linear-gradient(160deg, rgba(76, 175, 80, 0.18) 0%, rgba(46, 125, 50, 0.18) 100%);
  border: 1px solid rgba(76, 175, 80, 0.32);
  /* Match Score hero height (~77px) so the Top 5 card's top AND
     bottom edges align with the Score card across columns
     (per user 2026-04-28 "align the score container to top 5
     container start end point"). */
  padding: 6px 10px;
  flex-shrink: 0;
  height: 77px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
[data-moyako-v2] .page .overlay-leaderboard--card .overlay-leaderboard-title {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  opacity: 0.75;
  /* v356: pinned 2px from card top per user "align top 5 title to
   * top border with 2px margin". Was margin-bottom 4 only — title
   * was centred via parent's justify-content:center. */
  margin: 2px 0 4px 0;
  text-align: center;
  color: #ECEFF4;
}
/* v356: anchor leaderboard-card content to TOP so the title sits at
 * the 2px margin from the top border, not vertically centred. */
[data-moyako-v2] .page .overlay-leaderboard--card {
  justify-content: flex-start !important;
}
[data-theme="light"] [data-moyako-v2] .page .overlay-leaderboard--card .overlay-leaderboard-title {
  color: #1F2A44;
}
[data-moyako-v2] .page .overlay-leaderboard--card .overlay-leaderboard-list {
  margin-top: 0;
  font-size: 11px;
  line-height: 1.4;
  /* 2-col 3-row grid, column-first flow per user 2026-04-28
     "column 1 player 1,2,3, column 2 players 4,5,you". */
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(3, auto);
  grid-auto-flow: column;
  gap: 2px 10px;
}
[data-moyako-v2] .page .overlay-leaderboard--card .overlay-leaderboard-list .mini-lb-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px;
  border-radius: 4px;
  min-width: 0;
}
[data-moyako-v2] .page .overlay-leaderboard--card .overlay-leaderboard-list .mini-lb-row > span:first-child {
  width: 22px;
  flex-shrink: 0;
  opacity: 0.7;
  font-weight: 600;
  text-align: right;
}
[data-moyako-v2] .page .overlay-leaderboard--card .overlay-leaderboard-list .mini-lb-row > strong {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
[data-moyako-v2] .page .overlay-leaderboard--card .overlay-leaderboard-list .mini-lb-row > span:last-child {
  flex-shrink: 0;
  font-weight: 700;
  opacity: 0.85;
}

/* Highlighted row — the current user's spot. Brighter green so the
   player's eye lands on it immediately. */
[data-moyako-v2] .page .overlay-leaderboard--card .mini-lb-row.current-user {
  background: linear-gradient(160deg, rgba(76, 175, 80, 0.35) 0%, rgba(46, 125, 50, 0.35) 100%);
  border: 1px solid rgba(76, 175, 80, 0.55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
/* Pinned 6th row (when user is below rank 5 OR signed-out) — spans
   both columns so it reads as a divider summary line. */
[data-moyako-v2] .page .overlay-leaderboard--card .mini-lb-row--pinned {
  grid-column: 1 / -1;
  margin-top: 2px;
  border-top: 1px dashed rgba(255, 255, 255, 0.18);
  padding-top: 4px;
  border-radius: 0 0 4px 4px;
}
[data-moyako-v2] .page .overlay-leaderboard--card .mini-lb-row--signin {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.12);
  font-style: italic;
  opacity: 0.85;
}
[data-moyako-v2] .page .overlay-leaderboard summary {
  cursor: pointer;
  font-weight: 700;
  font-size: 12px;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
[data-moyako-v2] .page .overlay-leaderboard summary::-webkit-details-marker {
  display: none;
}
[data-moyako-v2] .page .overlay-leaderboard summary::after {
  content: '▾';
  font-size: 10px;
  opacity: 0.6;
}
[data-moyako-v2] .page .overlay-leaderboard[open] summary::after { content: '▴'; }
[data-moyako-v2] .page .overlay-leaderboard-list {
  margin-top: 6px;
  font-size: 11px;
  line-height: 1.6;
}
[data-moyako-v2] .page .mini-lb-row {
  display: flex;
  justify-content: space-between;
  padding: 1px 0;
}
[data-moyako-v2] .page .mini-lb-row span:first-child {
  width: 18px;
  opacity: 0.55;
}
[data-moyako-v2] .page .mini-lb-row strong {
  flex: 1;
  margin: 0 6px;
  color: inherit;
  font-weight: 700;
}

/* 3-col button grid inside the action card — top row carries 3
   secondaries (Undo · View · Back), bottom row carries Share +
   Next Level + Play Again per user 2026-04-28. Buttons retain
   source order via DOM; primaries no longer span the full row. */
[data-moyako-v2] .page .overlay-content--compact .overlay-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 6px;
  flex-direction: unset;
}
[data-moyako-v2] .page .overlay-content--compact .overlay-buttons .btn {
  font-size: 12px;
  padding: 8px 6px;
  min-height: 36px;
  width: auto;
  margin: 0;
}

/* Mobile portrait (≤480px) — tighten further. */
@media (max-width: 480px) {
  [data-moyako-v2] .page .overlay-content--compact {
    padding: 14px 14px 12px !important;
    gap: 8px !important;
  }
  [data-moyako-v2] .page .overlay-content--compact .overlay-icon { font-size: 28px; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-title { font-size: 17px; }
  [data-moyako-v2] .page .overlay-content--compact .overlay-subtitle { font-size: 11px; }
  [data-moyako-v2] .page .overlay-stats--compact .overlay-stat--hero .overlay-stat-value { font-size: 24px; }
  [data-moyako-v2] .page .overlay-stats--compact .overlay-stat-value { font-size: 14px; }
}

/* ============================================================
   Engagement layer — shared visuals for the 6 retention features
   delivered by /shared/components/engagement.js
   ============================================================ */
/* 3-star rating row (#starRating) — large gold stars centred. */
[data-moyako-v2] .page .moyako-stars,
[data-moyako-v2] .page #starRating {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin: 4px 0 12px;
  font-size: 28px;
  line-height: 1;
}
[data-moyako-v2] .page .moyako-star {
  color: rgba(255, 255, 255, 0.20);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.30);
  transition: transform 0.15s ease;
}
[data-moyako-v2] .page .moyako-star.filled {
  color: #FFC107;
  text-shadow: 0 0 8px rgba(255, 193, 7, 0.55), 0 1px 1px rgba(0, 0, 0, 0.30);
  transform: scale(1.1);
}
[data-theme="light"] [data-moyako-v2] .page .moyako-star {
  color: rgba(15, 23, 42, 0.20);
}
[data-theme="light"] [data-moyako-v2] .page .moyako-star.filled {
  color: #F59E0B;
  text-shadow: 0 0 6px rgba(245, 158, 11, 0.45);
}

/* Level badge — "Level 3 / 10" indicator.
   Hosts: (preferred) inside the BEST score tile in the .number-pad-info
   row, (fallback) pane-left-head for legacy pages. */
[data-moyako-v2] .page .moyako-level-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  background: linear-gradient(135deg, #5C6BC0 0%, #283593 100%);
  color: #FFFFFF;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.30),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    0 2px 4px rgba(0, 0, 0, 0.18);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.20);
}
/* When hosted inside the BEST score tile, shrink slightly to fit
   alongside the BEST label + value without overflow. */
[data-moyako-v2] .page .board-lower-stat[data-stat="score"] .moyako-level-badge {
  padding: 1px 6px;
  font-size: 9px;
  letter-spacing: 0.4px;
  flex: 0 0 auto;
}
/* Desktop: stack the LVL chip above the BEST label+value row so
   the score tile reads vertically — chip / label / value — and
   the chip never crowds the value. Per user 2026-04-27. */
@media (min-width: 1025px) {
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat[data-stat="score"] {
    flex-direction: column !important;
    justify-content: center;
    align-items: stretch;
    gap: 2px;
    padding: 4px 8px;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat[data-stat="score"] .moyako-level-badge {
    align-self: center;
    margin-bottom: 2px;
  }
}

/* Share-result button in overlays. */
[data-moyako-v2] .page .share-result-btn,
[data-moyako-v2] .page #shareResultBtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: linear-gradient(160deg, #1E88E5 0%, #0D47A1 100%);
  color: #FFFFFF;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.30),
    inset 0 -1px 0 rgba(0, 0, 0, 0.20),
    0 2px 6px rgba(0, 0, 0, 0.22);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.30);
  transition: filter 0.2s ease, transform 0.1s ease;
}
[data-moyako-v2] .page .share-result-btn:hover,
[data-moyako-v2] .page #shareResultBtn:hover {
  filter: brightness(1.15);
  transform: translateY(-1px);
}
[data-moyako-v2] .page .share-result-btn.copied,
[data-moyako-v2] .page #shareResultBtn.copied {
  background: linear-gradient(160deg, #43A047 0%, #1B5E20 100%);
}

/* v308 — Auto tier no longer gets a special teal-gradient background.
 * Previously (v304-v305) the Auto button had:
 *   background: linear-gradient(135deg, #00BCD4 0%, #006064 100%);
 *   color: #FFFFFF;
 *   + white dot override for contrast
 * User feedback 2026-05-11: "remove white background from Auto button".
 * Reverted to neutral button styling — same look as Beginner / Easy /
 * Medium / Hard / Expert. Auto's identity comes from its cyan
 * #29B6F6 .difficulty-dot (from the v304 palette) and the "Auto"
 * label only. No background tint, no special dot override. */

/* Locked theme option — used by Block-puzzle / Maze theme
   selectors when the level milestone hasn't been hit yet. */
[data-moyako-v2] .page select option[data-locked="true"] {
  color: rgba(120, 144, 156, 0.6);
}
[data-moyako-v2] .page .theme-option--locked {
  opacity: 0.45;
  cursor: not-allowed;
  position: relative;
}
[data-moyako-v2] .page .theme-option--locked::after {
  content: '🔒';
  margin-left: 4px;
}

/* ============================================================
   Desktop info-row (≥1025px) — overlay diff pill on the matchup
   row exactly like mobile, so the picker shows ONE matchup line
   that includes the difficulty chip on the right edge. The
   3-stat row (Time / Errors / Score) sits below.

   Structure mirrors mobile rules at ~lines 380–415 but with
   desktop-friendly row heights and fonts. Per user "in desktop,
   difficulty should be inside the Player vs Player title as in
   mobile for all games" (2026-04-27).
   ============================================================ */
@media (min-width: 1025px) {
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info {
    display: grid !important;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "matchup matchup matchup matchup"
      "time    errors  score   level";
    gap: 8px;
    align-items: stretch;
    overflow: visible !important;
    /* Equal-width allocation with .number-pad-keys per user 2026-04-27
       'utilize the container width equally' — both columns share the
       triple width 50/50; height stays at row height. */
    flex: 1 1 0 !important;
    width: auto !important;
    min-width: 0;
  }
  /* Names inside matchup ellipsis-truncate if cramped — never bleed
     into the diff pill overlay zone. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup {
    overflow: hidden;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup .matchup-side,
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup .matchup-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Flatten stats-pair so its children become grandchildren of
     .number-pad-info for grid-area placement. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stats-pair {
    display: contents;
  }
  /* v290.8 (2026-05-11): diff pill on the LEFT to match mobile
     layout (matchup row's universal "left = diff, right = avatars"
     convention). Was justify-self: end + padding-right reservation
     on desktop only — flipped to justify-self: start + padding-left
     reservation so all viewports look the same. Per user
     "difficulty icon on the right, align it to the left as in mobile". */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup {
    grid-area: matchup;
    position: relative;
    min-height: 36px;
    padding: 6px 10px 6px 70px;     /* left reservation for the pill */
    font-size: 12px;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill {
    grid-area: matchup;
    align-self: stretch !important;
    justify-self: start !important;
    width: auto !important;
    /* v290.10 (2026-05-11): pill spans full row height to match
     * mobile (where universal rule sets height: 100% / max-height: 64).
     * Was height: 20px which left the pill floating as a small chip
     * with empty bar above/below it on desktop. */
    height: 100% !important;
    max-height: 64px !important;
    min-height: 0 !important;
    padding: 0 6px !important;
    font-size: 9px !important;
    font-weight: 800;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    gap: 2px;
    background: transparent !important;
    border: 0 !important;
    box-shadow: none !important;
    color: inherit;
    margin-left: 4px;
    margin-right: 0;
    z-index: 2;
    position: relative;
    display: inline-flex !important;
    align-items: center;
    flex: 0 0 auto !important;
  }
  /* Diff pill colour — matches sudoku golden template (RAG by tier). */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="beginner"],
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="easy"]     { color: #66BB6A; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="medium"]   { color: #FFB74D; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="hard"],
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="expert"]   { color: #EF5350; }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="beginner"],
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="easy"]     { color: #2E7D32; }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="medium"]   { color: #E65100; }
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="hard"],
  [data-theme="light"] body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-diff-pill[data-tier="expert"]   { color: #C62828; }
  /* 3-stat row tiles. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat[data-stat="time"]   { grid-area: time; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat[data-stat="errors"] { grid-area: errors; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat[data-stat="score"]  { grid-area: score; }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat[data-stat="level"]  { grid-area: level; }
  /* Stat tiles — 2-row stacked layout (label on top, value below)
     per user 2026-04-27 'Score tiles title and value could be 2 row'. */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat {
    flex-direction: column !important;
    justify-content: center;
    align-items: center;
    gap: 2px;
    padding: 6px 4px !important;
    min-width: 0;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat .board-lower-stat-label {
    font-size: 10px !important;
    text-align: center;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-stat .board-lower-stat-value {
    font-size: 16px !important;
    text-align: center;
  }

  /* ============================================================
     Desktop-only 2:1:2 layout rules per user 2026-04-28:
     - 3-container row (info / keys / numbers) → 2:1:2 flex ratio
     - Matchup row's 3 children (player / vs / opponent) → 2:1:2
       ratio (or 1:1 if only 2 children — no 'vs' separator).
     Mobile keeps its existing flex/grid layout untouched.
     ============================================================ */
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple > [class*="number-pad-numbers"] {
    flex: 2 1 0 !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple > .number-pad-info {
    flex: 2 1 0 !important;
  }
  body[data-moyako-v2] .page .moyako-board-center .number-pad--triple > .number-pad-keys {
    flex: 1 1 0 !important;
  }
}

/* Matchup row 1:1 ratio (all viewports) per user 2026-04-28
   'apply 1:1 ratio to player info row containers' + 'memory player 1
   vs player 2 row container ratio 1:1' — player and opponent get
   equal flex width; the small 'vs' separator sits between them at
   intrinsic width and doesn't claim a third slot. Applied to all
   games at desktop AND mobile (lifted out of the desktop @media
   block to also cover mobile portrait + landscape). */
body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup,
body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup {
  display: grid !important;
  grid-template-columns: 1fr auto 1fr !important;
  align-items: center !important;
  gap: 6px !important;
}
body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup > .matchup-side--player,
body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup > .matchup-side--player {
  justify-self: end !important;
  text-align: right;
}
body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup > .matchup-vs,
body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup > .matchup-vs {
  justify-self: center !important;
  text-align: center;
  opacity: 0.7;
  font-size: 10px;
  padding: 0 4px;
}
body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup > .matchup-side--opponent,
body[data-moyako-v2] .page .moyako-board-center .number-pad--triple .number-pad-info .board-lower-matchup > .matchup-side--opponent {
  justify-self: start !important;
  text-align: left;
}

/* ============================================================
   Daily-challenge nudge ring — proper CSS pulse, replaces the
   legacy 9px 🟢 emoji. Anchors to the Hint button (mid container)
   when available, fallback to diff pill, last resort
   .pane-left-head. Per user 2026-04-28 'improve the visual and
   locate may be under the hint button'.
   ============================================================ */
[data-moyako-v2] .page .moyako-daily-dot {
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #4CAF50;
  box-shadow:
    0 0 0 2px rgba(76, 175, 80, 0.30),
    0 0 6px rgba(76, 175, 80, 0.55);
  pointer-events: auto;
  z-index: 3;
  cursor: help;
  top: -3px;
  right: -3px;
}
/* When the dot lands on the matchup row, anchor at the LEFT start
   instead of top-right. Vertically centred. */
[data-moyako-v2] .page .board-lower-matchup > .moyako-daily-dot {
  top: 50%;
  right: auto;
  left: 8px;
  transform: translateY(-50%);
}
/* Pulsing ring animation — gentle 2.5s cycle so it nudges without
   becoming distracting. Respects prefers-reduced-motion. */
[data-moyako-v2] .page .moyako-daily-dot::before {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid rgba(76, 175, 80, 0.65);
  animation: moyako-daily-pulse 2.5s ease-out infinite;
  pointer-events: none;
}
/* Custom CSS tooltip on hover — uses data-tooltip attribute so it
   doesn't depend on the browser's native title delay. */
[data-moyako-v2] .page .moyako-daily-dot::after {
  content: attr(data-tooltip);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%);
  padding: 4px 8px;
  border-radius: 6px;
  background: rgba(15, 23, 42, 0.95);
  color: #ECEFF4;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.30);
  z-index: 10;
}
[data-moyako-v2] .page .moyako-daily-dot:hover::after,
[data-moyako-v2] .page .moyako-daily-dot:focus-visible::after {
  opacity: 1;
}
@keyframes moyako-daily-pulse {
  0%   { transform: scale(0.85); opacity: 0.85; }
  70%  { transform: scale(1.55); opacity: 0; }
  100% { transform: scale(1.55); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  [data-moyako-v2] .page .moyako-daily-dot::before {
    animation: none;
    opacity: 0.4;
    transform: none;
  }
}

/* ============================================================
 * Light-theme metallic surfaces (2026-05-07)
 * ------------------------------------------------------------
 * User feedback: "light display also should have our 3d metallic
 * shaded nice tile / container / input field style, not seeing
 * on the preview screen — and text contrast should be readable."
 *
 * The picker (.game-info-card / .difficulty-card) was already
 * metallic in light mode. This block extends the same silver-
 * gradient + inset-highlight aesthetic to every other primary
 * surface so light-mode reads as one cohesive design family:
 *
 *   - .form-container       (login / signup / reset-password forms)
 *   - .auth-secondary       (login col 2 — guest/create/legal card)
 *   - input[type=text|email|password]  (all auth inputs)
 *   - .ag-card              (age-gate modal injected by age-gate.js)
 *   - .scope-popup-card     (leaderboard scope tile popup)
 *   - .settings-panel       (settings modal panel)
 *   - .age-range-tile       (signup age tiles)
 *   - .scope-tile           (leaderboard scope tiles)
 *   - .moyako-card          (any future shared card surface)
 *
 * Selector strategy: target `html[data-theme="light"]` (no
 * data-moyako-v2 dependency) so the modals injected at <body>
 * level (age-gate, scope-popup) inherit the rule too.
 *
 * Dark mode untouched — these rules only apply when light theme
 * is active.
 * ============================================================ */
html[data-theme="light"] body .form-container,
html[data-theme="light"] body .auth-secondary,
html[data-theme="light"] body .ag-card,
html[data-theme="light"] body .scope-popup-card,
html[data-theme="light"] body .settings-panel,
html[data-theme="light"] body .moyako-card {
  background: linear-gradient(180deg, #F2F5FB 0%, #D8DFEC 100%) !important;
  border: 1px solid rgba(15, 23, 42, 0.10) !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 -1px 0 rgba(15, 23, 42, 0.04),
    0 2px 6px rgba(15, 23, 42, 0.08),
    0 8px 18px rgba(15, 23, 42, 0.06) !important;
  color: #0F172A !important;
}
/* Text-color overrides for the metallic-light surfaces — beats the
 * dark-mode rules that paint --text-primary (light grey) on these
 * containers. */
html[data-theme="light"] body .form-container,
html[data-theme="light"] body .form-container label,
html[data-theme="light"] body .form-container .input-hint,
html[data-theme="light"] body .auth-secondary,
html[data-theme="light"] body .auth-secondary .signup-link,
html[data-theme="light"] body .auth-secondary .auth-footer,
html[data-theme="light"] body .ag-card .ag-title,
html[data-theme="light"] body .ag-card .ag-desc,
html[data-theme="light"] body .ag-card .ag-brand,
html[data-theme="light"] body .scope-popup-card,
html[data-theme="light"] body .scope-popup-title,
html[data-theme="light"] body .settings-panel .settings-title,
html[data-theme="light"] body .settings-panel .settings-label,
html[data-theme="light"] body .settings-panel .sm-toggle-label {
  color: #0F172A !important;
}
html[data-theme="light"] body .form-container .input-hint {
  color: #475569 !important;
}
html[data-theme="light"] body .auth-secondary .signup-link span,
html[data-theme="light"] body .ag-desc,
html[data-theme="light"] body .ag-tile-sub,
html[data-theme="light"] body .age-range-tile .ag-sub,
html[data-theme="light"] body .sm-account-sub,
html[data-theme="light"] body .scope-popup-close {
  color: #475569 !important;
}

/* Inputs — silver-bevel surface with dark text. Matches the pickers'
 * pill aesthetic so the form reads as one tile-system family. */
html[data-theme="light"] body .form-container input[type="text"],
/* (v183, 2026-05-08) .settings-panel .settings-select dropped from
 * this selector list per user 'align settings dropdown as well to
 * create account - avatar dropdown style'. The shared v171/v181
 * light-theme select rule (silver gradient #FFFFFF → #F2F5FB,
 * inset 3D shadow) now applies to settings selects too. */
html[data-theme="light"] body .form-container input[type="email"],
html[data-theme="light"] body .form-container input[type="password"],
html[data-theme="light"] body .form-container select {
  background: linear-gradient(180deg, #FAFCFF 0%, #E5EAF3 100%) !important;
  border: 1px solid rgba(15, 23, 42, 0.18) !important;
  color: #0F172A !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(15, 23, 42, 0.04) !important;
}
html[data-theme="light"] body .form-container input::placeholder {
  color: #94A3B8 !important;
}
html[data-theme="light"] body .form-container input:focus {
  border-color: #2E7D32 !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    0 0 0 3px rgba(76, 175, 80, 0.18) !important;
  outline: none !important;
}

/* Tiles (age-range, scope, age-gate brackets) — silver bevel +
 * dark text + tier-friendly green highlight when checked. */
html[data-theme="light"] body .age-range-tile,
html[data-theme="light"] body .scope-tile,
html[data-theme="light"] body .ag-tile {
  background: linear-gradient(180deg, #F2F5FB 0%, #D8DFEC 100%) !important;
  border: 1px solid rgba(15, 23, 42, 0.14) !important;
  color: #0F172A !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    0 1px 3px rgba(15, 23, 42, 0.06) !important;
}
html[data-theme="light"] body .age-range-tile:hover,
html[data-theme="light"] body .age-range-tile:focus-visible,
html[data-theme="light"] body .scope-tile:hover,
html[data-theme="light"] body .scope-tile:focus-visible,
html[data-theme="light"] body .ag-tile:hover,
html[data-theme="light"] body .ag-tile:focus-visible {
  border-color: #2E7D32 !important;
  background: linear-gradient(180deg, #ECF6EE 0%, #D5E8DA 100%) !important;
}
html[data-theme="light"] body .age-range-tile[aria-checked="true"],
html[data-theme="light"] body .scope-tile[aria-checked="true"] {
  border-color: #2E7D32 !important;
  background: linear-gradient(180deg, #DDF0E2 0%, #B7DCC0 100%) !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    0 0 0 2px rgba(46, 125, 50, 0.30) !important;
}

/* Settings modal panel — slightly stronger shadow so it lifts off
 * the page bg in light mode. */
html[data-theme="light"] body .settings-panel {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    0 12px 36px rgba(15, 23, 42, 0.16) !important;
}

/* Toggle row dividers — beef up against the metallic bg. */
html[data-theme="light"] body .settings-panel .sm-toggle-row {
  border-bottom-color: rgba(15, 23, 42, 0.08) !important;
}

/* Scope chip on leaderboard — metallic bevel, dark text. */
html[data-theme="light"] body .scope-chip {
  background: linear-gradient(180deg, #F2F5FB 0%, #D8DFEC 100%) !important;
  border-color: rgba(15, 23, 42, 0.18) !important;
  color: #0F172A !important;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    0 1px 3px rgba(15, 23, 42, 0.06) !important;
}
html[data-theme="light"] body .scope-chip:hover { border-color: #2E7D32 !important; }
html[data-theme="light"] body .scope-chip [data-scope-label] { color: #0F172A !important; }
html[data-theme="light"] body .scope-chip-caret { color: #475569 !important; }

/* (Dropped 2026-05-08, v156) Sudoku guest auth CTA light-theme rules
 * removed — the bordered .guest-auth-cta card itself was replaced by
 * a compact .guest-actions-row of 2 inline buttons (no card, no msg)
 * per user "remove the container for signin button groups". The
 * inline cta-secondary button uses var(--action) which is already
 * theme-aware, and cta-primary stays green-on-green in both themes. */

/* ============================================================
 * Canonical cap-shell layout (2026-05-08, v150)
 * Source of truth: docs/CAP-SHELL-LAYOUT.md §4 (blocks 2-6).
 * Block 1 (tiered chrome heights) lives at line ~1065 above.
 * Companion rules: DESIGN-RULES.md §3.14 viewport discipline.
 * ------------------------------------------------------------
 *   ┌────────────────────────┐    .page (flex column 100dvh)
 *   │  topbar (--header-h)   │      ├── .topbar  (content-box;
 *   │  + sys-info padding    │      │   sys-info reserved as
 *   ├────────────────────────┤      │   padding-top env() / 15% L/R
 *   │  middle           1fr  │      │   based on orientation)
 *   │   ┌──────────────────┐ │      ├── .middle
 *   │   │ Container A      │ │      │     portrait: rows  1fr/1fr
 *   │   ├──────────────────┤ │      │     landscape: cols 1fr/1fr
 *   │   │ Container B      │ │      │     gap 8 · outer 8 16
 *   │   └──────────────────┘ │      │
 *   ├────────────────────────┤      │
 *   │  cap-nav (--footer-h)  │      └── #cap-nav (fixed bottom,
 *   │  + gesture inset       │          padding-bottom env())
 *   └────────────────────────┘
 *
 * Scope: in cap (html[data-moyako-native]) only — web pages keep
 * their original chrome and ad-slot footprint. Picker and play
 * states share the same .middle, so the rule applies uniformly.
 * ============================================================ */

/* (2) Sys-info reserve in chrome padding.
 *     v243 (2026-05-09): with v242's WindowCompat.setDecorFitsSystemWindows(true)
 *     the WebView is already sized inside the safe area (status bar
 *     above the WebView, not under it). env(safe-area-inset-top)
 *     still reports the bar height because viewport-fit=cover, but
 *     adding it as padding here would double-pad — the v237
 *     `.topbar { padding-top: env-top }` rule in moyako-components.css
 *     also kicks in. Reset cap-shell topbar padding-top to 0 here so
 *     the topbar's CONTENT height is just --header-h; the system bar
 *     sits visually above the WebView, not on top of it.
 *     Web (no data-moyako-native) keeps v237 env-top padding intact
 *     because there the WebView IS edge-to-edge in the rare case
 *     a browser exposes a notch inset. */
/* v247 (2026-05-09): v243's `padding-top: 0` override removed. With
 * the v242 Android setDecorFitsSystemWindows revert, the WebView is
 * edge-to-edge again — env(safe-area-inset-top) reports the real
 * status-bar height, and v237's `.topbar { padding-top: env-top }`
 * legitimately pushes topbar content below the system overlay. The
 * landscape-phone gameplay redesign moves the gear + sound icons OUT
 * of the topbar entirely (into the matchup row inside .number-pad-
 * info), so even when the status bar visually overlaps the topbar's
 * top region, no tappable target is in that overlap zone. */
html[data-moyako-native] header.topbar {
  box-sizing: content-box;
  padding-top: env(safe-area-inset-top, 0px);
}
@media (orientation: landscape) and (max-height: 500px) {
  html[data-moyako-native] header.topbar {
    /* v252 (2026-05-09): drop the 15% L/R sys-info padding. Pre-v252
     * the topbar reserved 15% on each side for camera-notch / status-
     * bar overlay clearance, because gear + sound icons used to live
     * in the topbar (and tap-routing was lossy in those zones). Since
     * v249 those icons moved to the matchup row + cap-nav respectively,
     * the topbar in landscape phone now contains ONLY logo + panda —
     * both display-only, neither tappable, both safe to overlap with
     * a system overlay. Removing the 15% padding lets logo + panda
     * each center within their respective viewport-half, which lines
     * them up with container 1 (board / game-info card) and container
     * 2 (chrome col / difficulty grid). Per user "header panda not
     * aligned center of the 2nd container". */
    box-sizing: border-box;
    width: 100%;
    margin-left: 0;
    margin-right: 0;
    padding-top: 0;
    padding-left: 0;
    padding-right: 0;
  }
}
/* v260 (2026-05-09): cap-nav sys-info inset — env() L/R/B everywhere
 * EXCEPT cramped landscape phone (which has its own 15 % rule below).
 *
 * Per user "same also for the navbar — put inset when bigger than
 * the threshold to avoid sys area". The threshold is the same height-
 * gated tier as the topbar (v258/v259): cramped landscape phone
 * `(orientation: landscape) AND (max-height: 500px)` gets the
 * aggressive 15 % L/R reserve; everywhere else (portrait phone,
 * tablet landscape, desktop) gets env(safe-area-inset-*) — non-zero
 * only when the OS reserves an edge zone (gesture nav, notch, side
 * status bar). On web previews env() evaluates to 0 so behaviour is
 * unchanged for the desktop reviewer. */
html[data-moyako-native] #cap-nav {
  box-sizing: border-box;
  padding-left:   env(safe-area-inset-left,   0px);
  padding-right:  env(safe-area-inset-right,  0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* v171 (2026-05-08, user request): phone-landscape cap-nav gets the
 * 15% L/R sys-info reserve. System status icons / camera notch sit
 * on the long edges in rotated mode and would otherwise overlap the
 * cap-nav button row. border-box + 15% padding keeps cap-nav full-
 * width visually but pushes the 4 tab buttons (Home / Play / Assess
 * / Ranks) inward into the clickable safe zone. Overrides the base
 * env() L/R from above for this tier. */
@media (orientation: landscape) and (max-height: 500px) {
  html[data-moyako-native] #cap-nav {
    padding-left: 15%;
    padding-right: 15%;
  }
}

/* v247 (2026-05-09): v243's gameplay-pane min-height + popover anchor
 * cap-shell overrides removed. With the v242 Android revert (cap-shell
 * back to edge-to-edge), env(safe-area-inset-*) report real values
 * inside the WebView and v237's CSS rules apply env-top padding to
 * .topbar — making the topbar's effective height match the design-
 * rule assumption of "var(--footer-h, 56px) + system insets ≈ 60 px"
 * for mobile portrait. The original `.gameplay-pane min-height:
 * calc(100dvh - 60px)` rule + v237 popover anchor work as written.
 *
 * For the landscape-phone gameplay redesign (v247), gear + sound move
 * to the matchup row inside .number-pad-info — popover anchor in that
 * specific scope is set in the landscape-phone block below, not here. */

/* (3) .middle outer margins — v245 (2026-05-09, symmetric 4 px gap rule).
 *     Cap-shell `.middle` reserves a minimum 4 px gap on both top and
 *     bottom edges between the content containers and the chrome
 *     (topbar / cap-nav). Asymmetric "eat space at one edge only" is
 *     forbidden — see docs/CAP-SHELL-LAYOUT.md §2.7.
 *
 *     Picker / non-game state (cap-nav visible at the bottom):
 *       top    = 4 px
 *       L/R    = 8 px (v239)
 *       bottom = footer-h + max(env-bottom, 4 px) so the visible gap
 *                between content and cap-nav is exactly 4 px (or the
 *                gesture inset, whichever is bigger).
 *     Playing / question state (cap-nav hidden) — see (4) block below. */
html[data-moyako-native] body[data-moyako-v2] .page .middle {
  /* v249 (2026-05-09): picker padding-bottom = footer-h + 4 (was
   * footer-h + max(env-bottom, 4)). The cap-nav itself already pads
   * its inner content by env(safe-area-inset-bottom) for the gesture
   * inset — .middle just needs to clear the cap-nav element height
   * + a 4 px gap so the bottom-edge gap matches the 4 px top-edge
   * gap. Per user (main / picker screen): "push nav bar up until
   * aligning the space to container to bottom bar and top bar". */
  padding: 4px 8px calc(var(--footer-h, 56px) + 4px) !important;
  box-sizing: border-box !important;
}
/* (3a) Landscape camera-hole reserve (v160, 2026-05-08, user rule).
 *      In phone landscape the camera punch-hole / status-bar icons
 *      sit on one of the long edges. Bump .middle's L/R padding to
 *      max(8, env(safe-area-inset-*)) so Container 1 / Container 2
 *      auto-shrink (their 1fr/1fr grid handles it) instead of the
 *      camera covering the container content. Documented in
 *      docs/CAP-SHELL-LAYOUT.md §2.1 + DESIGN-RULES §3.15.
 *      v239: floor lowered from 16 → 8 to match the new portrait
 *      reserve; env() overrides upward when notch demands. */
@media (orientation: landscape) and (max-height: 500px) {
  html[data-moyako-native] body[data-moyako-v2] .page .middle {
    padding-left: max(8px, env(safe-area-inset-left, 0px)) !important;
    padding-right: max(8px, env(safe-area-inset-right, 0px)) !important;
  }
}

/* (3a) Primary content containers (picker / board / auth-card)
 *      width-bounded and centered inside .middle (DESIGN-RULES
 *      §3.14.2). max-width 940 desktop, 100% phone. Belt-and-braces. */
html[data-moyako-native] body[data-moyako-v2] .page .middle > .difficulty-pane,
html[data-moyako-native] body[data-moyako-v2] .page .middle > .gameplay-pane,
html[data-moyako-native] body[data-moyako-v2] .page .middle > .auth-card,
html[data-moyako-native] body[data-moyako-v2] .page .middle > .container {
  margin-left: auto !important;
  margin-right: auto !important;
  max-width: 940px !important;
  width: 100% !important;
  box-sizing: border-box !important;
}

/* (4) Game / question state — cap-nav suppressed AND .middle
 *     releases its footer reserve. v245 (2026-05-09, symmetric gap
 *     rule): padding-bottom collapses to max(env-bottom, 4px) so the
 *     content has the same 4 px gap as the top, with env-bottom
 *     winning where the OS reserves the gesture inset. */
html[data-moyako-native] body[data-state="playing"]   .page .middle,
html[data-moyako-native] body[data-state="play"]      .page .middle,
html[data-moyako-native] body[data-cap-state="play"]  .page .middle,
html[data-moyako-native] body.verbal-active           .page .middle,
html[data-moyako-native] body.pattern-active          .page .middle,
html[data-moyako-native] body.reaction-active         .page .middle,
html[data-moyako-native] body.spatial-active          .page .middle,
html[data-moyako-native] body.memory-active           .page .middle,
html[data-moyako-native] body.number_seq-active       .page .middle,
html[data-moyako-native] body.attention-active        .page .middle {
  padding-bottom: max(env(safe-area-inset-bottom, 0px), 4px) !important;
}
html[data-moyako-native] body[data-state="playing"]   #cap-nav,
html[data-moyako-native] body[data-state="play"]      #cap-nav,
html[data-moyako-native] body[data-cap-state="play"]  #cap-nav,
html[data-moyako-native] body.verbal-active           #cap-nav,
html[data-moyako-native] body.pattern-active          #cap-nav,
html[data-moyako-native] body.reaction-active         #cap-nav,
html[data-moyako-native] body.spatial-active          #cap-nav,
html[data-moyako-native] body.memory-active           #cap-nav,
html[data-moyako-native] body.number_seq-active       #cap-nav,
html[data-moyako-native] body.attention-active        #cap-nav {
  display: none !important;
}

/* ============================================================
 * v247 (2026-05-09) — landscape-phone gameplay redesign
 * Scope: cap-shell (`html[data-moyako-native]`) + landscape phone
 *        (`(orientation: landscape) and (max-height: 500px)`) +
 *        playing state (`body[data-state="playing"]`).
 *
 * Goal: reclaim the topbar's vertical region for the board column.
 *
 *   ┌──────────────────┬────────────────────┐
 *   │                  │  TOPBAR (logo|panda)
 *   │  BOARD ~330×330  ├────────────────────┤
 *   │  (board col      │ ⚙ 🔊  You vs Sudoku
 *   │   gets full      │ ──────────────────
 *   │   vertical)      │ TIME ERR SCORE LVL
 *   │                  ├────────────────────┤
 *   │                  │ NUMPAD             │
 *   │                  ├────────────────────┤
 *   │                  │ KEYS               │
 *   │                  ├────────────────────┤
 *   │                  │ ACTIONS            │
 *   └──────────────────┴────────────────────┘
 *
 * Mechanism:
 *   - Topbar grid columns split: logo+title centered above board col,
 *     panda centered above chrome col. Sound + gear hidden.
 *   - Matchup row gains a left-edge icon stack (.matchup-actions)
 *     hosting `[data-action="settings"]` + `[data-action="sound"]`
 *     buttons. Existing capture-phase delegation in settings-modal.js
 *     fires for these — no new wiring needed beyond the buttons.
 *   - Turn dot (.moyako-turn-dot) hidden in this scope — function
 *     replaced by static "your turn" via .matchup-side--player.
 *   - Settings popover anchor: scoped to gear's new position.
 *
 * Per user "what about: push header back into sys area, align center
 * logo above 1st container and panda above 2nd container, move sound
 * icon next to settings, place setting on the matchup bar, replace
 * green dot there. proceed drawing is fine. add sound and settings
 * to the bar. this is only for game screen landscape view." */
@media (orientation: landscape) and (max-height: 500px) {

  /* v249/v253 (2026-05-09): topbar split layout — logo half-width
   * left, panda half-width right — only in landscape phone cap-shell.
   * Gear/sound hide is broader (applies to all cap-shell) — see the
   * top-level rule below this @media block. */

  /* Topbar layout: logo+title centered above container 1 (board col
   * in gameplay, game-info card in picker), panda centered above
   * container 2 (chrome col in gameplay, difficulty grid in picker).
   * The 15% sys-info reserve from the landscape-phone topbar block
   * (line ~4717) still applies for camera-notch clearance; the
   * children flex within. */
  html[data-moyako-native] .topbar {
    justify-content: space-between !important;
  }
  html[data-moyako-native] .topbar-left {
    flex: 1 1 50% !important;
    justify-content: center !important;
    display: flex !important;
  }
  html[data-moyako-native] .topbar-panda {
    flex: 1 1 50% !important;
    position: static !important;
    transform: none !important;
    justify-content: center !important;
    display: flex !important;
  }
  html[data-moyako-native] .topbar-right {
    /* Streak / sound / gear all hidden above; collapse the right
     * cluster so it doesn't reserve a flex slot. */
    display: none !important;
  }

  /* Hide the green turn dot, opponent side, vs separator, and player
   * name. Matchup row redesign per user (v249, 2026-05-09):
   *   "no need vs definition, remove green dot, make setting and
   *    sound bigger move to the right, put avatar full bar height
   *    in the middle, and difficulty level on the left".
   * Result: 3-cell layout — diff pill (LEFT) | avatar (CENTER, full
   * bar height) | gear+sound (RIGHT, 32x32 each). */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .moyako-turn-dot,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .moyako-turn-dot,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--opponent,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--opponent,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-vs,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-vs,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-name,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-name {
    display: none !important;
  }

  /* Matchup row layout — v255 (2026-05-09): switched from grid to
   * flex-nowrap for hard single-line guarantee. Grid was auto-
   * flowing children to row 2 when an oversized child (button
   * inheriting 44 px min from .btn class) exceeded the row's
   * 36 px cap. Flex with nowrap can never wrap regardless of
   * child size — overflow is clipped horizontally instead.
   *
   * Layout: matchup-actions on RIGHT (margin-left:auto); avatar
   * centered between left padding and actions via auto margins.
   * Diff pill (sibling of board-lower-matchup, grid-area: matchup
   * in the parent number-pad-info grid) floats to the LEFT via
   * justify-self: start, overlaying the matchup row's left zone. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .board-lower-matchup,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .board-lower-matchup {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center !important;
    justify-content: center !important;
    /* v288.4 (2026-05-10): reverted v288.1 bar-height bump (40 → 36)
     * + avatar bump per user "height increase and avatar bumps to be
     * cancelled". Back to v249 baseline 36 px bar with 32 px avatar. */
    min-height: 36px !important;
    height: 36px !important;
    max-height: 36px !important;
    padding: 2px 8px !important;
    gap: 0 !important;
    overflow: hidden !important;
    white-space: nowrap !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .board-lower-matchup *,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .board-lower-matchup *,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .board-lower-diff-pill,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .board-lower-diff-pill {
    white-space: nowrap !important;
  }
  /* Avatar centered. v255: in flex layout, the player wraps to a
   * `flex: 0 0 auto` size; auto margin on left/right centers it
   * between the diff-pill (which floats over the left zone via the
   * sibling grid) and matchup-actions (margin-left:auto pushes to
   * right). */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--player,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--player {
    display: inline-flex !important;
    align-items: center !important;
    flex: 0 0 auto !important;
    margin: 0 auto !important;
    height: 100% !important;
  }
  /* v333 (2026-05-11): apply the same 32×32 cap to BOTH sides so the
   * AI / opponent mascot can't overflow the 36 px matchup bar (was
   * inheriting an oversize default — head + feet were clipped by the
   * row's overflow:hidden in the user's landscape screenshot).
   *
   * v335 (2026-05-11): added overflow:hidden + line-height:32 on the
   * avatar itself + clipped the mascot bitmap with object-fit:contain
   * so emoji/SVG glyph excursions don't visually "bump" past the
   * 36 px bar. Per user: "no bump as in portrait" — landscape avatar
   * must be strictly contained within its 32×32 frame, matching
   * portrait's no-overflow behaviour. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--ai .matchup-avatar,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--ai .matchup-avatar,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--opponent .matchup-avatar,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--opponent .matchup-avatar {
    /* v288 (2026-05-10): avatar 120% bar height (10% bump top + 10% bottom)
     * to overflow padding and feel dominant. Green bottom border replaces
     * the (already-hidden) turn dot as the "you / online" accent. Per user:
     * "panda can bump 10% from top and bottom border to overlap the padding
     * area, to make it more dominant" + "green dot to be replaced with
     * green bottom border line". Same rule applies in portrait — see the
     * non-landscape block below. */
    /* v288.4 (2026-05-10): reverted avatar bumps. Back to 32×32 with
     * no border-bottom — the player marker is the box-shadow ring
     * applied universally at the end of this stylesheet. */
    width: 32px !important;
    height: 32px !important;
    max-width: 32px !important;
    max-height: 32px !important;
    /* v335: glyph contained — font-size matches frame height so emoji
     * + mascot SVG metrics don't visually push past the bar top/bottom
     * the way 28px-font-in-32px-box did in v334. line-height locked
     * to 32 so the line-box equals the frame. overflow:hidden clips
     * any residual descender. */
    font-size: 32px !important;
    line-height: 32px !important;
    overflow: hidden !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    box-sizing: border-box !important;
    flex: 0 0 32px !important;
  }
  /* v333/v335: also cap any inner <img> inside the AI mascot so an
   * SVG / bitmap mascot can't exceed the 32×32 frame and bump past
   * the bar top/bottom. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--ai .matchup-avatar > *,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--ai .matchup-avatar > *,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--opponent .matchup-avatar > *,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--opponent .matchup-avatar > * {
    width: 100% !important;
    height: 100% !important;
    max-width: 100% !important;
    max-height: 100% !important;
    object-fit: contain !important;
  }
  /* v288.5: matchup-actions buttons 36×36 — full bar height of 36 px
   * (per user "icons to be on the right and full height"; only the bar
   * height bump and avatar bumps were reverted, icon size adjustment
   * stays). */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-actions button,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-actions button {
    width: 36px !important;
    height: 36px !important;
    min-width: 36px !important;
    min-height: 36px !important;
    max-width: 36px !important;
    max-height: 36px !important;
    flex: 0 0 36px !important;
    font-size: 22px !important;
    line-height: 36px !important;
  }
  /* v288.4: difficulty pill back to 36 px / 18 px font / 22 px emoji. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill {
    height: 36px !important;
    font-size: 18px !important;
    line-height: 36px !important;
    display: inline-flex !important;
    align-items: center !important;
    gap: 6px !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji {
    font-size: 22px !important;
    line-height: 1 !important;
  }
  /* Diff pill — was justify-self: end; now LEFT. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill {
    justify-self: start !important;
    margin-left: 8px !important;
    margin-right: 0 !important;
  }

  /* v250 (2026-05-09): generic .matchup-actions styling — applies to
   * both gameplay (.moyako-board-center .board-lower-matchup) and
   * assessment (#testScreen > .test-header > .question-title) host
   * locations. Per user "apply the same to the assessment questions
   * page". Display + sizing universal; positioning per host below. */
  html[data-moyako-native] .matchup-actions {
    display: inline-flex !important;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
  }
  /* v250: button sizing universal — 32×32 with font 22 for both
   * gameplay matchup row and assessment title pill.
   * v255: bumped specificity (added body selector + !important on
   * sizing) to beat the global `.btn { min-width: 44px }` tap-target
   * rule that was inflating buttons to 44×44 on cap-shell. */
  html[data-moyako-native] body .matchup-actions button {
    width: 32px !important;
    height: 32px !important;
    min-width: 32px !important;
    min-height: 32px !important;
    max-width: 32px !important;
    max-height: 32px !important;
    padding: 0 !important;
    background: transparent !important;
    border: 0 !important;
    color: var(--text-secondary, #9aa0a6);
    /* v256 (2026-05-09): font-size 22 → 18, line-height set to the
     * button's exact height (32) instead of `1`. Emojis (🔊) use
     * different intrinsic vertical metrics than glyph characters
     * (⚙) — line-height: 1 + flex align-items: center centers the
     * BASELINE, leaving the descender rendering visibly low. With
     * line-height: 32 the emoji's line-box is the same height as
     * the button, and align-items: center then centers a single
     * line box in the flex container — visually the emoji sits at
     * the geometric center of the button. Per user "sound icon on
     * the bar should be vertically aligned". */
    font-size: 18px !important;
    line-height: 32px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    flex: 0 0 32px !important;
    text-align: center !important;
    vertical-align: middle !important;
  }
  html[data-moyako-native] .matchup-actions button:active {
    color: var(--text, #eceff4);
  }
  /* v310 — landscape gameplay icon contrast. User feedback 2026-05-11:
   * "souind / setting icons not correct" in landscape. Default color
   * var(--text-secondary, #9aa0a6) was too dim against the dark blue
   * matchup-bar background, particularly for the settings gear SVG
   * whose thin paths fade into the bar. Bump to high-contrast white
   * during gameplay state in cap-shell.
   *
   * Sound + settings buttons both reach 92% white — distinct from the
   * matchup bar background, readable at 32×32 size. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-actions button,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-actions button {
    color: rgba(255, 255, 255, 0.92) !important;
  }
  /* Force the SVG inside to inherit the bumped color (some themes
   * scope stroke separately from button color). */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-actions button svg,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-actions button svg {
    stroke: currentColor !important;
  }
  /* Gameplay positioning — push to right end of the matchup row.
   *
   * v332 (2026-05-11): added `order: 99` so the actions rail paints
   * LAST in the flex row regardless of DOM insertion point. The shared
   * top-bar.js inserts the rail as the FIRST child of
   * .board-lower-matchup (so it can be the natural recipient of the
   * left-anchored difficulty pill on screens that show one). Without
   * `order`, `margin-left:auto` fights `.matchup-side--player`'s
   * `margin: 0 auto` for free space and the icons land near the
   * centre instead of the right edge. With `order: 99` the icons
   * are guaranteed to be the rightmost flex child, then
   * `margin-left:auto` consumes all the slack to the left of them.
   *
   * v255 dropped the grid-column placement after switching to
   * flex-nowrap; this rule supersedes that mechanism. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-actions,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-actions {
    /* v333 (2026-05-11): v332's order:99 + margin-left:auto fix did not
     * win against the parent `.board-lower-matchup`'s `justify-content:
     * center` + sibling `.matchup-side--player { margin: 0 auto }` in
     * production. Switched to absolute positioning relative to the
     * matchup row. The parent gets position:relative below, then this
     * rail anchors flush to the right edge with 8px padding to match
     * the row's `padding: 2px 8px`. Decouples icon placement from the
     * flex layout entirely. */
    position: absolute !important;
    right: 8px !important;
    top: 50% !important;
    transform: translateY(-50%) !important;
    margin: 0 !important;
    flex: 0 0 auto !important;
    z-index: 2 !important;
  }
  /* v333: anchor parent for the absolute-positioned matchup-actions. */
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .board-lower-matchup,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .board-lower-matchup {
    position: relative !important;
  }
  /* v250: assessment positioning — inside the title pill, pushed to
   * the right via margin-left:auto. Hide the pill's leading green
   * dot pseudo-element in this scope so the icons take its visual
   * slot at the start of the row's RIGHT side. */
  html[data-moyako-native] body[class*="-active"] #testScreen > .test-header > .question-title {
    justify-content: space-between !important;
  }
  html[data-moyako-native] body[class*="-active"] #testScreen > .test-header > .question-title::before {
    display: none !important;
  }
  html[data-moyako-native] body[class*="-active"] #testScreen .matchup-actions {
    margin-left: auto !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-actions button:active,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-actions button:active {
    color: var(--text, #eceff4);
  }

  /* Settings popover re-anchored to the matchup row's gear position
   * (left side of the chrome column, just below the topbar). */
  html[data-moyako-native] body[data-state="playing"] .settings-modal.settings-popover,
  html[data-moyako-native] body[data-state="play"]    .settings-modal.settings-popover {
    /* matchup row sits ~6 px below the topbar; gear is inside it.
     * Anchor popover just under the row, left side of chrome col. */
    top: calc(env(safe-area-inset-top, 0px) + var(--header-h, 56px) + 30px) !important;
    left: 50% !important;        /* chrome col starts at viewport ~50% */
    right: auto !important;
  }
}

/* v288 (2026-05-10) — portrait matchup row alignment. Per user
 * "screenshot from gameplay, both portrait and landscape to be checked
 * and aligned" + "panda can bump 10% from top and bottom border to
 * overlap the padding area" + "green dot to be replaced with green
 * bottom border line" + "difficulty icon at left with full height".
 *
 * In portrait, the topbar already hosts sound + settings (per user
 * "no icons won't be in portrait, space would be empty" — keep topbar
 * unchanged). The matchup row stays as a clean diff-pill (left) +
 * avatar (right) bar, no actions.
 *
 * Hide v249-vintage clutter (turn dot, opponent side, vs separator,
 * name, matchup-actions) so only the diff pill + player avatar remain.
 * Match landscape's avatar 120% bump + green bottom border + full-
 * height diff pill so the bar reads identically in both orientations. */
@media (orientation: portrait) {
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .moyako-turn-dot,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .moyako-turn-dot,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--opponent,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--opponent,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-vs,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-vs,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-name,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-name,
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-actions,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-actions {
    display: none !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .board-lower-matchup,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .board-lower-matchup {
    display: flex !important;
    flex-direction: row !important;
    align-items: center !important;
    justify-content: flex-end !important;
    min-height: 36px !important;
    height: 36px !important;
    padding: 2px 8px !important;
    overflow: hidden !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--player,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--player {
    display: inline-flex !important;
    align-items: center !important;
    flex: 0 0 auto !important;
    margin-left: auto !important;
    margin-right: 0 !important;
    height: 100% !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar {
    /* v288.4: reverted bumps. 32×32, no border-bottom. */
    width: 32px !important;
    height: 32px !important;
    font-size: 28px !important;
    line-height: 1 !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    box-sizing: border-box !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill {
    justify-self: start !important;
    margin-left: 8px !important;
    height: 36px !important;
    font-size: 18px !important;
    line-height: 36px !important;
    display: inline-flex !important;
    align-items: center !important;
    gap: 6px !important;
  }
  html[data-moyako-native] body[data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
  html[data-moyako-native] body[data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji {
    font-size: 22px !important;
    line-height: 1 !important;
  }
}

/* v258/v259 (2026-05-09): cap-shell topbar visibility — height-gated.
 *
 * Threshold: `(orientation: landscape) AND (max-height: 500px)` — the
 * cramped landscape-phone tier where chrome height eats into game
 * content. Outside that tier (portrait phone, landscape tablet,
 * desktop), the topbar behaves like portrait: sound + settings live
 * on the topbar right side.
 *
 * Portrait + bigger landscape (≥ 501px tall): topbar shows the sound
 * icon AND the gear at the right end. Streak stays hidden in cap-shell
 * across all orientations (surfaced via the dashboard hero card).
 *
 * Landscape phone (≤ 500px tall): topbar is logo + panda only (the
 * v249/v253 split layout). Sound + settings move:
 *   · sound    → .matchup-actions rail (board-lower-matchup or
 *                 #testScreen .question-title)
 *   · settings → per-page anchor stamped by ensureSettingsAnchor():
 *      - dashboard      .daily-mix-strip__title
 *      - leaderboard    .lb-header-card > .page-title
 *      - assessment     #testSelectionScreen .selection-title
 *      - game picker    .difficulty-pane .difficulty-card  (top-right)
 *      - game gameplay  .sudoku-group-2.board-lower        (top-right)
 *
 * Per user "this should be applied to only the screen height smaller
 * than a size which causing our game content not fitting in properly,
 * for the bigger size [...] we can apply the same approach as in
 * portrait — header bar could have the sound/settings icons on the
 * header bar". */
html[data-moyako-native] .topbar-streak {
  display: none !important;
}
@media (orientation: landscape) and (max-height: 500px) {
  html[data-moyako-native] .topbar-sound,
  html[data-moyako-native] .topbar-settings {
    display: none !important;
  }
}

/* v258 — per-page settings anchor (landscape phone, cap-shell).
 *
 * `.cap-settings-anchor` is a flex-row stamped onto a page-specific
 * title element (or a top-right corner on game panes) by top-bar.js
 * `ensureSettingsAnchor()`. The anchor always contains a single
 * `<button data-action="settings">` rendered as a 32×32 gear.
 *
 * Only visible on landscape phone in cap-shell. On portrait + web,
 * the topbar gear is the entry point. */
html[data-moyako-native] .cap-settings-anchor { display: none; }
@media (orientation: landscape) and (max-height: 500px) {
  html[data-moyako-native] .cap-settings-anchor {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    margin-left: auto;
  }
  html[data-moyako-native] .cap-settings-anchor button[data-action="settings"] {
    width: 32px;
    height: 32px;
    min-width: 32px;
    max-width: 32px;
    min-height: 32px;
    max-height: 32px;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    font-size: 18px;
    line-height: 32px;
    text-align: center;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    vertical-align: middle;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* When the anchor is stamped onto a heading/title element, switch
   * the heading to flex so the gear sits at the right end of the
   * title row (per user "title row right end"). */
  html[data-moyako-native] .daily-mix-strip__title.cap-settings-host,
  html[data-moyako-native] h1.page-title.cap-settings-host,
  html[data-moyako-native] .selection-title.cap-settings-host {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }
  /* Game pane second-container variant — absolute-positioned in the
   * top-right corner of the difficulty-card (picker) and the
   * sudoku-group-2.board-lower (gameplay). The host gets relative
   * positioning so the gear floats above the content. */
  html[data-moyako-native] .difficulty-card.cap-settings-host,
  html[data-moyako-native] .sudoku-group.sudoku-group-2.cap-settings-host,
  html[data-moyako-native] .board-lower.cap-settings-host {
    position: relative;
  }
  html[data-moyako-native] .difficulty-card.cap-settings-host > .cap-settings-anchor,
  html[data-moyako-native] .sudoku-group-2.cap-settings-host > .cap-settings-anchor,
  html[data-moyako-native] .board-lower.cap-settings-host > .cap-settings-anchor {
    position: absolute;
    top: 4px;
    right: 4px;
    margin-left: 0;
    z-index: 5;
  }
}
/* ============================================================ */


/* (5) Containers locked equal via minmax(0, 1fr), gap 8 (matches
 *     top/bottom inner padding for visual rhythm). Portrait stacks
 *     rows; landscape splits cols. v187 (2026-05-08) — switched
 *     from 1fr to minmax(0, 1fr) per DESIGN-RULES §3.19 so neither
 *     tile can blow the fair-share track when content is wider/taller. */
@media (orientation: portrait) {
  html[data-moyako-native] body[data-moyako-v2] .page .difficulty-pane,
  html[data-moyako-native] body[data-moyako-v2] .page .gameplay-pane {
    display: grid;
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr);
    align-items: stretch;
    align-content: stretch;
    justify-items: stretch;
    row-gap: 8px;
  }
  /* Children stretch explicitly — overrides per-game min-content
   * sizing that would otherwise fight the grid's stretch default. */
  html[data-moyako-native] body[data-moyako-v2] .page .difficulty-pane > *,
  html[data-moyako-native] body[data-moyako-v2] .page .gameplay-pane > * {
    align-self: stretch;
    justify-self: stretch;
    width: 100%;
    height: 100%;
    min-height: 0;
    min-width: 0;
    box-sizing: border-box;
  }
}
@media (orientation: landscape) {
  html[data-moyako-native] body[data-moyako-v2] .page .difficulty-pane,
  html[data-moyako-native] body[data-moyako-v2] .page .gameplay-pane {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
    align-content: stretch;
    justify-items: stretch;
    column-gap: 8px;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .difficulty-pane > *,
  html[data-moyako-native] body[data-moyako-v2] .page .gameplay-pane > * {
    align-self: stretch;
    justify-self: stretch;
    width: 100%;
    height: 100%;
    min-height: 0;
    min-width: 0;
    box-sizing: border-box;
  }
}

/* (6) Swap button — phone landscape only, inside Container 2 on
 *     the outer edge, 24px × full container height. data-swap on
 *     .page mirrors C1↔C2 so the button stays on the new outer edge. */
/* (Scoped to .gameplay-pane only — 2026-05-08 v151 fix.)
 * Earlier v150 paste also targeted .difficulty-pane > :nth-child(2)
 * (the picker's .difficulty-card), which has no swap button and
 * already stacks its 3 children (difficulty-list / difficulty-actions
 * / guest-auth-cta) vertically. Applying flex-row there made those
 * 3 children render horizontally in phone landscape — bug surfaced
 * in user screenshot 2026-05-08. Picker stays untouched here; only
 * the gameplay tile (Container 2 in play state) gets flex-rowed so
 * the swap button can sit on the outer edge. */
@media (orientation: landscape) and (max-height: 500px) {
  html[data-moyako-native] body[data-moyako-v2]
    .page .gameplay-pane > :nth-child(2) {
    display: flex;
    flex-direction: row;
  }
  html[data-moyako-native] body[data-moyako-v2] .page[data-swap="left"]
    .gameplay-pane > :nth-child(2) {
    flex-direction: row-reverse;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-swap-btn {
    display: flex !important;
    align-self: stretch;
    width: 24px;
    align-items: center;
    justify-content: center;
    order: 99;
    cursor: pointer;
  }
  html[data-moyako-native] body[data-moyako-v2] .page
    .moyako-swap-btn::before { content: "<"; }
  html[data-moyako-native] body[data-moyako-v2] .page[data-swap="left"]
    .moyako-swap-btn::before { content: ">"; }
}
@media not all and (orientation: landscape) and (max-height: 500px) {
  html[data-moyako-native] body[data-moyako-v2]
    .page .moyako-swap-btn { display: none !important; }
}

/* v266 (2026-05-09) — cap-shell PORTRAIT viewport-fit fixes.
 *
 * Per user "the problem is viewport overflowing the screen size,
 * containers should have 1:1 aspect ratio after reducing the topbar
 * and bottom margin".
 *
 * Two issues compounded the v265 result:
 *  1. .gameplay-pane min-height: calc(100dvh - 60px) (line ~317)
 *     assumes only 60 px chrome. Cap-shell has ~144 px (topbar 108
 *     + .middle 28 + gameplay-pane 8) so .gameplay-pane was forced
 *     to overflow viewport by ~52 px and .moyako-board-center
 *     stretched into the system gesture area.
 *  2. Topbar h was 108 px on the Pixel 9a emulator — the
 *     env(safe-area-inset-top) reported 55 px under Android edge-
 *     to-edge, much larger than typical phone status bars (24 px).
 *
 * Fix:
 *  · Cap topbar padding-top to min(env-top, 24 px) so the topbar
 *    shrinks from ~108 → ~80 px on devices with inflated env values.
 *  · Reduce .middle padding-bottom from max(env-bottom, 4 px) to a
 *    fixed 4 px in playing state — cap-nav is hidden so env-bottom
 *    isn't needed for cap-nav clearance.
 *  · Remove .gameplay-pane min-height (set to 0) and use height:100%
 *    so it fills the parent's content area, not 100dvh.
 *  · Force each container to a true 1:1 square via aspect-ratio:1 +
 *    grid-template-rows: auto auto. Centred vertically when there's
 *    leftover space. */
@media (orientation: portrait) {
  /* v268: restore full env-top inset on the topbar — v266's
   * min(env, 24px) cap was eating the status-bar reserve and the
   * topbar's inner content ended up under the system status icons.
   * The topbar will be tall on devices with inflated env-top values
   * (e.g. some emulators report 55 px) but content is readable. */
  html[data-moyako-native] [data-moyako-v2] .topbar {
    padding-top: env(safe-area-inset-top, 0px) !important;
  }
  html[data-moyako-native] body[data-state="playing"] .page .middle,
  html[data-moyako-native] body[data-state="play"] .page .middle {
    padding-bottom: 4px !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .gameplay-pane {
    min-height: 0 !important;
    height: 100% !important;
  }
  /* v267 (2026-05-09) — content-fit containers with 2 px padding.
   *
   * Per user "container sizes should align with content size with
   * 2px padding". v266 forced both containers into a fixed 1:2
   * aspect-ratio (each square at column-width). User now wants each
   * container to size to its OWN content (board = square because
   * sudoku-grid is square; controls = sum of stacked rows) with
   * 2 px padding around the content.
   *
   * Implementation:
   *   · Drop the .moyako-board-center aspect-ratio lock
   *   · grid-template-rows: auto auto — each row sizes to its child
   *   · Each container has padding: 2 px (overrides the existing
   *     12 px on .board-lower and 0 on .board-upper)
   *   · Items use height: auto + max-height: none + align-self: start
   *     so the natural content height drives the row height
   *   · align-content: start in the bc grid so containers stack at
   *     top with remaining vertical space below */
  /* v269: equal-share 1:1 between the two containers per DESIGN-RULES
   * §3.19 + §3.20. The bigger one drives the size — board is a
   * natural square at column-width (387 px) so both rows = 387 px.
   * Container 2's inner content (matchup + numpad + keys + actions
   * ≈ 301 px) is shorter than 387, so we centre it vertically inside
   * the 387 px container per §3.17 (no flex-start when slack visible).
   *
   * Mechanics:
   *   · .moyako-board-center: aspect-ratio 1/2 (locks height to 2×
   *     column width + gap); grid-template-rows: 1fr 1fr (equal share)
   *   · .board-upper: stretch + aspect-ratio:1 (board fills row)
   *   · .board-lower: stretch (fills row at 387 px); justify-content:
   *     center on its flex column so the natural-height content
   *     centres vertically when slack exists. */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center {
    aspect-ratio: 1 / 2 !important;
    height: auto !important;
    max-height: 100% !important;
    grid-template-rows: 1fr 1fr !important;
    align-content: center !important;
    align-self: center !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center > .board-upper {
    height: 100% !important;
    max-height: 100% !important;
    aspect-ratio: 1 !important;
    padding: 2px !important;
    align-self: stretch !important;
    flex: 1 1 auto !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center > .board-lower {
    height: 100% !important;
    max-height: 100% !important;
    aspect-ratio: auto !important;
    padding: 2px !important;
    align-self: stretch !important;
    flex: 1 1 auto !important;
    /* v270: don't override sudoku.html's internal flex layout
     * (justify-content: space-between + flex: 2/1/3/0 grow ratios on
     * children at lines 317-409). Those distribute the controls'
     * inner cluster naturally inside whatever height the container
     * has. The 1:1 main-container rule is at the .moyako-board-center
     * grid level only. */
  }
  /* Beat the brand-label rule that sets `height: 50dvh !important`
   * + `max-height: 50dvh !important` + `flex: 0 0 50dvh !important`
   * on .board-upper for Sudoku/Chess/Memory at line ~691. */
  html[data-moyako-native] body[data-page-brand-label="Sudoku"][data-moyako-v2] .page .moyako-board-center > .board-upper,
  html[data-moyako-native] body[data-page-brand-label="Chess"][data-moyako-v2] .page .moyako-board-center > .board-upper,
  html[data-moyako-native] body[data-page-brand-label="Memory Game"][data-moyako-v2] .page .moyako-board-center > .board-upper {
    height: auto !important;
    max-height: none !important;
    flex: 0 0 auto !important;
  }
  /* v270: leave sudoku.html's inner flex distribution alone — the
   * `flex: 2 1 0` / `flex: 1 1 0` / `flex: 3 1 0` ratios on
   * .number-pad-info / -keys / -numbers (lines 317-409) correctly
   * distribute the controls cluster across the .board-lower
   * container's height. v268's flex-shrink:0 override was needed
   * only when min-content tracks were collapsing the container; now
   * that the .moyako-board-center grid uses 1fr 1fr (equal share)
   * the container has a fixed 385 px and the natural grow ratios do
   * the right thing. Per user "not like that, I meant the main 2
   * container and viewport, not inside objects". */
}

/* v265 (2026-05-09) — cap-shell PORTRAIT 2-container 1:1 layout.
 *
 * Per user "check portrait view, containers 1:1 with 4px min padding
 * from top/bottom and between the containers".
 *
 * The shared portrait rule for .moyako-board-center (line ~595) was
 * a 5-row grid: `board / info / numpad / keys / actions` with the
 * board claiming `1fr` and the four control rows sized `auto`. The
 * intermediate wrappers (.board-lower, .board-lower-slot[controls],
 * .board-lower-slot[actions], .number-pad--triple) used
 * `display: contents` so the leaf control elements became direct
 * grid items of .moyako-board-center.
 *
 * In cap-shell that produced a 1.6:1 board:controls visual ratio,
 * not the canonical 2-container 1:1 the rest of cap-shell uses.
 *
 * Fix: un-flatten ONLY .board-lower (sudoku-group-2) so it becomes
 * a real flex container. Its internal slots stay `display: contents`
 * so the leaf control items become direct flex children of
 * .board-lower (matching the shape sudoku.html line 986 already
 * defines). .moyako-board-center collapses to a 2-row 1:1 grid with
 * a 4 px gap. .gameplay-pane padding tightens to 4 px on every side
 * (was 4/4/6) so the outer chrome matches the inner gap. */
@media (orientation: portrait) {
  html[data-moyako-native] body[data-moyako-v2] .page .gameplay-pane {
    padding: 4px !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center {
    /* v267: grid-template-rows lives in the v267 block (line ~5438) —
     * `min-content min-content` so each track sizes to its child's
     * content, not the parent's leftover height. */
    grid-template-areas:
      "board"
      "controls" !important;
    row-gap: 4px !important;
    padding: 0 !important;
  }
  /* Un-flatten sudoku-group-2 so it's a visible container in row 2.
   * Its inner children (number-pad-info / numpad / keys / actions)
   * flow up via the slot/.number-pad--triple display:contents that's
   * already in place — they become direct flex children of
   * sudoku-group-2 thanks to its `display:flex; flex-direction:column`
   * (sudoku.html line 986). */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center > .board-lower {
    display: flex !important;
    grid-area: controls !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center > .board-upper {
    grid-area: board !important;
    overflow: hidden !important;
    /* v267: height/max-height/align-self set in the v267 block above
     * (auto / none / start) for content-fit sizing. */
  }
  /* Hide the portrait divider — the 4 px row-gap creates the visual
   * separation between the two containers. */
  html[data-moyako-native] body[data-moyako-v2] .page .sudoku-portrait-divider {
    display: none !important;
  }
  /* The leaf control items are now flex children of sudoku-group-2,
   * not grid items of .moyako-board-center. Clear their grid-area
   * assignments so they don't try to place into a grid track that
   * no longer holds them. */
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-info,
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-numbers,
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .number-pad-keys,
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center .board-lower-actions {
    grid-area: auto !important;
  }
}

/* v271 (2026-05-10) — Tablet + desktop tier override per
 * CAP-SHELL-LAYOUT.md §2.3. Phone-tier rules above use compact
 * spacing (4 px top, 8 px L/R, 4 px gaps, 2 px container padding).
 * Tablet + desktop have pixel budget — bump to comfortable values
 * (8 px top, 16 px L/R, 8 px gaps, 4 px container padding).
 *
 * Source-order matters: this block must come AFTER all phone-tier
 * portrait rules (line 5435+ and 5593+) so it wins on tablet portrait
 * viewports where both media queries match. */
@media (min-width: 768px) and (min-height: 800px) {
  html[data-moyako-native] body[data-moyako-v2] .page .middle {
    padding: 8px 16px calc(var(--footer-h, 56px) + max(8px, env(safe-area-inset-bottom, 0px))) !important;
  }
  html[data-moyako-native] body[data-state="playing"]   .page .middle,
  html[data-moyako-native] body[data-state="play"]      .page .middle,
  html[data-moyako-native] body[data-cap-state="play"]  .page .middle,
  html[data-moyako-native] body.verbal-active           .page .middle,
  html[data-moyako-native] body.pattern-active          .page .middle,
  html[data-moyako-native] body.reaction-active         .page .middle,
  html[data-moyako-native] body.spatial-active          .page .middle,
  html[data-moyako-native] body.memory-active           .page .middle,
  html[data-moyako-native] body.number_seq-active       .page .middle,
  html[data-moyako-native] body.attention-active        .page .middle {
    padding-bottom: max(8px, env(safe-area-inset-bottom, 0px)) !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .difficulty-pane,
  html[data-moyako-native] body[data-moyako-v2] .page .gameplay-pane {
    row-gap: 8px !important;
    column-gap: 8px !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center {
    row-gap: 8px !important;
    column-gap: 8px !important;
  }
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center > .board-upper,
  html[data-moyako-native] body[data-moyako-v2] .page .moyako-board-center > .board-lower {
    padding: 4px !important;
  }
}

/* Cap-nav anchor — re-asserts position so no ancestor rule can
 * shift it (DESIGN-RULES §3.14.1). Belt-and-braces: kept from v148. */
html[data-moyako-native] body #cap-nav {
  position: fixed !important;
  left: 0 !important;
  right: 0 !important;
  top: auto !important;
  bottom: 0 !important;
  height: var(--footer-h, 56px) !important;
  z-index: 9999 !important;
  margin: 0 !important;
  transform: none !important;
}

/* ============================================================
 * v288_2 (2026-05-10) — universal matchup-row redesign.
 *
 * Applies on web + cap-shell, portrait + landscape, all orientations.
 * Per user "A — for the games with 2 players, would have vs (Player 2
 * avatar)": cap-shell-scoped v288 rules generalised to every gameplay
 * surface; opponent avatar kept visible on 2-player games (chess,
 * backgammon) so the matchup row reads "you vs them" via avatars
 * alone — no separate vs/name/turn-dot text.
 *
 * Visual contract:
 *   · Bar height 40 px (10% taller than the 36 px v249 baseline)
 *   · Player avatar 48×48 (120% — 10% above + 10% below bar padding)
 *     with 2 px green bottom border (#42DC82) as the "you / online"
 *     accent that replaces the v249-hidden turn dot.
 *   · Opponent avatar 48×48 (matching size) with NO border — the
 *     border is the player marker, not the matchup marker.
 *   · Diff pill 40 px tall, full bar height, left edge.
 *   · turn-dot, vs separator, player/opponent name HIDDEN universally.
 *   · Opponent SIDE hidden on solo games (sudoku, maze, word-puzzle,
 *     block-puzzle, memory). Visible on 2-player (chess, backgammon).
 * ============================================================ */
body[data-moyako-v2][data-state="playing"] .moyako-board-center .moyako-turn-dot,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .moyako-turn-dot,
body[data-moyako-v2][data-state="playing"] .moyako-board-center .matchup-vs,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .matchup-vs,
body[data-moyako-v2][data-state="playing"] .moyako-board-center .matchup-name,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .matchup-name {
  display: none !important;
}

/* Solo games — opponent side hidden entirely (no AI to show). */
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .matchup-side--opponent {
  display: none !important;
}

/* v288.4: bar layout — 36 px (v249 baseline). Reverted v288_1 height
 * bump per user "height increase and avatar bumps to be cancelled". */
body[data-moyako-v2][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .board-lower-matchup {
  display: flex !important;
  flex-direction: row !important;
  align-items: center !important;
  justify-content: space-between !important;
  min-height: 36px !important;
  height: 36px !important;
  padding: 2px 8px !important;
  overflow: hidden !important;
  white-space: nowrap !important;
}

/* v288.4: avatar 32×32, circular, ring marks the turn (no size bump). */
body[data-moyako-v2][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-moyako-v2][data-state="playing"] .moyako-board-center .matchup-side--opponent .matchup-avatar,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .matchup-side--opponent .matchup-avatar {
  width: 32px !important;
  height: 32px !important;
  font-size: 28px !important;
  line-height: 1 !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  box-sizing: border-box !important;
  border-radius: 50% !important;
  /* Smooth ring on/off when turn flips between sides. */
  transition: box-shadow 200ms ease !important;
}

/* v288_3: circle ring defines the turn. Per user "circle ring would
 * define the turn" — replaces the v288_2 green bottom border (which
 * read as a static "you online" marker, not a turn marker).
 *
 * Solo games (sudoku, maze, …): opponent side is hidden, so the
 * player-side ring is always lit — reads as "you're playing".
 *
 * 2-player games (chess, backgammon): the ring lights up on
 * whichever side has the `is-turn` class. Falls back to player
 * side when no class is present (initial state, before first move).
 *
 * Implementation: 2 px box-shadow ring (no layout cost). Uses
 * theme-aware --moyako-green-bright with #42DC82 fallback. */
body[data-moyako-v2][data-state="playing"] .moyako-board-center .matchup-side--player.is-turn .matchup-avatar,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .matchup-side--player.is-turn .matchup-avatar,
body[data-moyako-v2][data-state="playing"] .moyako-board-center .matchup-side--opponent.is-turn .matchup-avatar,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .matchup-side--opponent.is-turn .matchup-avatar {
  box-shadow: 0 0 0 2px var(--moyako-green-bright, #42DC82) !important;
}
/* v288_6 (2026-05-10): solo-game matchup row simplified per user
 * "no opponent on sudoku, just we need the avatar, in the middle
 * full height, only difficulty icon on the left and your turn not
 * needed here":
 *
 *  - No ring on player avatar (your-turn doesn't apply in solo).
 *  - Avatar 36×36 (fills the 36 px bar — full height, no overflow).
 *  - Avatar centered horizontally (margin: 0 auto on the side).
 *  - Diff pill on the LEFT (already via .board-lower-diff-pill grid
 *    placement — sibling to .board-lower-matchup at grid-area:matchup;
 *    justify-self: start kicks in for cap-shell landscape via the
 *    existing rule at line ~5104). For web/portrait the pill renders
 *    above/below the matchup row depending on game; the avatar
 *    centering applies regardless.
 *
 * Applied via per-game brand-label selectors so 2-player games
 * (chess, backgammon) keep the v288_3 universal rules (right-edge
 * player, opponent visible, ring marks the active turn). */
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar {
  /* v288_6: avatar fills bar height (36×36, no padding above/below). */
  width: 36px !important;
  height: 36px !important;
  font-size: 32px !important;
  /* No box-shadow ring — your-turn doesn't apply in solo games. */
  box-shadow: none !important;
}
/* v288_8 (2026-05-10): solo-game player side absolute-centred inside
 * the matchup row. The diff pill and matchup row share grid-area:
 * matchup so they stack — letting the avatar flex-centre would put
 * it ON TOP of the pill (geometric overlap, observed at width=375).
 * Absolute centring keeps the avatar at the true visual middle of
 * the bar regardless of pill width.
 *
 * Per user "in the middle full height". */
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .board-lower-matchup {
  position: relative !important;
}
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .matchup-side--player {
  position: absolute !important;
  left: 50% !important;
  top: 50% !important;
  transform: translate(-50%, -50%) !important;
  margin: 0 !important;
  display: inline-flex !important;
  align-items: center !important;
  pointer-events: none !important;
}

/* v288.4: diff pill 36 px / 18 px font / 22 px emoji (reverted bumps). */
body[data-moyako-v2][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill {
  height: 36px !important;
  font-size: 18px !important;
  line-height: 36px !important;
  display: inline-flex !important;
  align-items: center !important;
  gap: 6px !important;
}

/* v288_7 (2026-05-10) — solo games: move diff pill to LEFT, hide
 * the .moyako-daily-dot inside the matchup row (it's a separate
 * "daily challenge available" indicator that visually duplicates
 * the v249 turn dot). Per user "only difficulty icon on the left
 * and your turn not needed here". */
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill {
  justify-self: start !important;
  margin-left: 8px !important;
  margin-right: 0 !important;
}
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .board-lower-matchup .moyako-daily-dot,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .board-lower-matchup .moyako-daily-dot {
  display: none !important;
}
body[data-moyako-v2][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-moyako-v2][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji {
  font-size: 22px !important;
  line-height: 1 !important;
}

/* v288_9 (2026-05-10): solo gameplay refinements per user feedback —
 *  - "difficulty icon is enough, text not needed" → hide pill label
 *  - "panda full height" → emoji avatar fills bar (font-size = bar h)
 *  - "align the row heights of avatar and score rows" → matchup row
 *    grows to 54 px (matches the stats row's intrinsic 54 px); avatar
 *    fills new height (54×54), pill matches.
 */
/* v288_9.2 (2026-05-10) — solo games matchup + stats rows share the
 * container height EQUALLY with a 64 px max cap. Per user "avatar
 * row and time row should share the container height equally" +
 * "there should be a max cap as other not to overstretch on bigger
 * screens".
 *
 * grid-template-rows on .number-pad-info: 1fr 1fr (equal share),
 * each capped via minmax(36px, 64px). Min 36 prevents collapse on
 * tiny phones; max 64 prevents desktop over-stretch.
 *
 * Children stretch to 100% of their row. Avatar size derives from
 * the row height via height:100% + aspect-ratio:1. */
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .number-pad-info,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .number-pad-info,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .number-pad-info,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .number-pad-info,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .number-pad-info,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .number-pad-info {
  grid-template-rows: repeat(2, minmax(36px, 64px)) !important;
}
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .board-lower-matchup {
  min-height: 36px !important;
  height: 100% !important;
  max-height: 64px !important;
  /* v288_9.4: overflow visible so the absolute-centered avatar circle
   * isn't cropped top/bottom when the row is shorter than 64 px on
   * cramped landscape (observed: row 47 px, avatar 64 px). Per user
   * "panda circle cropped from bottom and top" + earlier "avatar
   * circle will be displayed even overlay the others". */
  overflow: visible !important;
}
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .matchup-side--player .matchup-avatar {
  /* v290.10 (2026-05-11): fixed 72; landscape + desktop sizes via
   * media-query overrides in the universal block below. */
  width: 72px !important;
  height: 72px !important;
  aspect-ratio: 1 / 1 !important;
  font-size: 64px !important;
}
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill {
  height: 100% !important;
  max-height: 64px !important;
  line-height: 1 !important;
  padding: 0 12px !important;
}
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji {
  /* v288_9.1: emoji ~50% smaller per user "difficulty icon smaller
   * like 50%". 36 → 18 px. */
  font-size: 18px !important;
}
/* v288_9.3 (2026-05-10) — solo gameplay landscape: un-hide the diff
 * pill. A landscape MQ rule earlier (`@media (orientation: landscape)
 * and (max-height: 500px) body[data-moyako-v2] .page #boardLowerDiff
 * Pill { display: none }`) hides the pill on cramped landscape phones;
 * solo gameplay still wants the difficulty icon visible on the left.
 * Higher specificity (id + brand + state attrs) + same media query
 * override + display: inline-flex restores it. */
@media (orientation: landscape) and (max-height: 500px) {
  body[data-page-brand-label="Sudoku"][data-state="playing"] .page #boardLowerDiffPill,
  body[data-page-brand-label="Sudoku"][data-state="play"]    .page #boardLowerDiffPill,
  body[data-page-brand-label="Maze"][data-state="playing"] .page #boardLowerDiffPill,
  body[data-page-brand-label="Maze"][data-state="play"]    .page #boardLowerDiffPill,
  body[data-page-brand-label="Word Puzzle"][data-state="playing"] .page #boardLowerDiffPill,
  body[data-page-brand-label="Word Puzzle"][data-state="play"]    .page #boardLowerDiffPill,
  body[data-page-brand-label="Block Puzzle"][data-state="playing"] .page #boardLowerDiffPill,
  body[data-page-brand-label="Block Puzzle"][data-state="play"]    .page #boardLowerDiffPill,
  body[data-page-brand-label="Memory"][data-state="playing"] .page #boardLowerDiffPill,
  body[data-page-brand-label="Memory"][data-state="play"]    .page #boardLowerDiffPill {
    display: inline-flex !important;
  }
}

/* Hide difficulty label (text) on solo gameplay — emoji communicates
 * the tier alone. */
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel {
  display: none !important;
}

/* v288_9.5 (2026-05-10) — sudoku grid: right + bottom borders of the
 * outer colored 3×3 boxes were being clipped. Per user "board borders
 * right/bottom cropped".
 *
 * Cause: .sudoku-grid had overflow: hidden + padding 2 px + border
 * 1.6 px (≈7 px chrome). With box-sizing: border-box the inner
 * content area is 310 - 2*(2+1.6) = 302.8 px, but 9 cells ×
 * 34.6 px = 311.4 px → the rightmost / bottommost cell bled past
 * the grid's content edge by ~9 px and got clipped, taking the
 * box-border accents with it.
 *
 * Fix: overflow: visible on .sudoku-grid lets the cells render
 * fully. The grid's parent .sudoku-group-1.board-upper has
 * box-sizing room (312 vs 311.4) so no further bleed. */
body[data-page-brand-label="Sudoku"] .sudoku-grid,
body[data-page-brand-label="Sudoku"] #sudokuGrid {
  overflow: visible !important;
}

/* v288_9.7 (2026-05-10) — solo-game matchup row background:
 * align portrait to landscape's softer translucent blue gradient
 * (was an opaque indigo `rgb(92,107,192) → rgb(40,53,147)` in
 * portrait, vs translucent `rgba(33,150,243,.22) → rgba(13,71,161,
 * .28)` in landscape). Per user "background color of the avatar
 * bar, landscape one seems better, align to that". */
body[data-page-brand-label="Sudoku"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Sudoku"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Maze"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Maze"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Word Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Word Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Block Puzzle"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Block Puzzle"][data-state="play"]    .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Memory"][data-state="playing"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Memory"][data-state="play"]    .moyako-board-center .board-lower-matchup {
  background: linear-gradient(135deg, rgba(33, 150, 243, 0.22) 0%, rgba(13, 71, 161, 0.28) 100%) !important;
}
/* v288_9.6: parent containers also need overflow: visible so the
 * grid's bleed (cells extend ~1-2 px past the grid's inner content
 * edge) renders rather than getting re-clipped at the next level
 * up. Observed in landscape — portrait worked because the grid
 * fits without bleed there. */
body[data-page-brand-label="Sudoku"] .sudoku-group-1.board-upper,
body[data-page-brand-label="Sudoku"] .moyako-board-center {
  overflow: visible !important;
}

/* ============================================================
 * v290 (2026-05-10) — UNIVERSAL matchup-row styling.
 *
 * Per user "align this avatar bar in all games" + "could they be a
 * shared object". The visual contract is now identical across all
 * 7 games (chess, backgammon, sudoku, maze, word-puzzle, block-
 * puzzle, memory). Solo-specific behaviours (opponent hide, player
 * absolute-center) stay scoped via per-brand-label selectors
 * earlier in this file; everything else generalises here.
 *
 * Cascade order: this block comes AFTER the brand-scoped v288_9.x
 * rules. Equal specificity + later position means these properties
 * win for shared values; solo-only properties (display: none on
 * opponent, position: absolute on player) only exist in the solo
 * block so they're unaffected.
 * ============================================================ */

/* v290.1: dropped the [data-state="playing"] gate — chess uses
 * data-focus-mode="on", sudoku uses data-state="playing", others vary.
 * The matchup row only exists in gameplay screens (it's inside
 * .moyako-board-center .number-pad-info which the picker overlay
 * hides), so scoping by element presence is enough. */

/* Bar grid: matchup row + stats row share container height equally,
 * capped 64 px to prevent over-stretch on big screens. */
body[data-moyako-v2] .moyako-board-center .number-pad-info {
  grid-template-rows: repeat(2, minmax(36px, 64px)) !important;
}

/* Bar itself — overflow visible so the 80-px avatar can bump out
 * top/bottom; min 36, max 64. */
body[data-moyako-v2] .moyako-board-center .board-lower-matchup {
  min-height: 36px !important;
  height: 100% !important;
  max-height: 64px !important;
  overflow: visible !important;
  background: linear-gradient(135deg, rgba(33, 150, 243, 0.22) 0%, rgba(13, 71, 161, 0.28) 100%) !important;
}

/* Hide v249 chrome (turn dot, vs separator, names) universally. */
body[data-moyako-v2] .moyako-board-center .moyako-turn-dot,
body[data-moyako-v2] .moyako-board-center .matchup-vs,
body[data-moyako-v2] .moyako-board-center .matchup-name {
  display: none !important;
}

/* Avatar — bumps 4 px past the bar top + bottom in the dominant
 * portrait viewport (row 64 → avatar 72). On landscape phone the bar
 * shrinks (~47) and the avatar correspondingly shrinks via the media
 * override below, keeping bump consistent at 4 px each side.
 * v290.10 (2026-05-11): per-viewport sizing instead of a single fixed
 * value. `calc(100% + 8px)` doesn't work here — the absolute-positioned
 * player slot has no fixed height so the percentage is unresolved. */
body[data-moyako-v2] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-moyako-v2] .moyako-board-center .matchup-side--opponent .matchup-avatar {
  width: 72px !important;
  height: 72px !important;
  aspect-ratio: 1 / 1 !important;
  font-size: 64px !important;
  border-radius: 50% !important;
  overflow: visible !important;
}
/* Landscape phone — row shrinks to ~47, avatar to 55 (4 px bump each).
 * Selector needs to beat the brand-scoped state-attr rule
 * (specificity 0,5,1) — add a data-page-brand-label match wildcard
 * via [data-page-brand-label] presence. */
@media (orientation: landscape) and (max-height: 500px) {
  body[data-moyako-v2][data-page-brand-label] .page .moyako-board-center .matchup-side--player .matchup-avatar,
  body[data-moyako-v2][data-page-brand-label] .page .moyako-board-center .matchup-side--opponent .matchup-avatar {
    width: 55px !important;
    height: 55px !important;
    font-size: 48px !important;
  }
}
/* Desktop — row at 1280w is ~52, avatar 60 (4 px bump each). */
@media (min-width: 1025px) {
  body[data-moyako-v2][data-page-brand-label] .page .moyako-board-center .matchup-side--player .matchup-avatar,
  body[data-moyako-v2][data-page-brand-label] .page .moyako-board-center .matchup-side--opponent .matchup-avatar {
    width: 60px !important;
    height: 60px !important;
    font-size: 52px !important;
  }
}

/* Diff pill — left edge, full bar height, emoji-only (label hidden).
 * Selector matches the existing `.page` chain at line 461 to beat
 * its `justify-self: end` (specificity (0,6,1) vs (0,5,1)). */
body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill {
  justify-self: start !important;
  margin-left: 8px !important;
  margin-right: 0 !important;
  height: 100% !important;
  max-height: 64px !important;
  line-height: 1 !important;
  padding: 0 12px !important;
}
body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffLabel {
  display: none !important;
}
body[data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-diff-pill #boardLowerDiffEmoji {
  font-size: 18px !important;
  line-height: 1 !important;
}
/* Landscape phone: existing rule hides #boardLowerDiffPill — un-hide. */
@media (orientation: landscape) and (max-height: 500px) {
  body[data-moyako-v2] .page #boardLowerDiffPill {
    display: inline-flex !important;
  }
}

/* Daily-dot inside matchup row — hidden universally. */
body[data-moyako-v2] .moyako-board-center .board-lower-matchup .moyako-daily-dot {
  display: none !important;
}

/* v290.3 (2026-05-11): SOLO layout is the DEFAULT for all games.
 * Per user "when it is AI no need to display 2nd avatar and text,
 * only available when 2 users are playing". Chess + backgammon
 * playing vs AI (the common case) get the solo single-avatar layout;
 * the 2-player override below kicks in only when body has
 * data-multiplayer="true" (set by the multiplayer code path). */
body[data-page-brand-label="Sudoku"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Maze"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Word Puzzle"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Block Puzzle"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Memory Game"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Chess"]:not([data-multiplayer="true"]) .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Backgammon"]:not([data-multiplayer="true"]) .moyako-board-center .matchup-side--opponent {
  display: none !important;
}
body[data-page-brand-label="Sudoku"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Maze"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Word Puzzle"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Block Puzzle"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Memory Game"] .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Chess"]:not([data-multiplayer="true"]) .moyako-board-center .board-lower-matchup,
body[data-page-brand-label="Backgammon"]:not([data-multiplayer="true"]) .moyako-board-center .board-lower-matchup {
  position: relative !important;
}
body[data-page-brand-label="Sudoku"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Maze"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Word Puzzle"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Block Puzzle"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Memory Game"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Chess"]:not([data-multiplayer="true"]) .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Backgammon"]:not([data-multiplayer="true"]) .moyako-board-center .matchup-side--player {
  position: absolute !important;
  left: 50% !important;
  top: 50% !important;
  transform: translate(-50%, -50%) !important;
  margin: 0 !important;
  display: inline-flex !important;
  align-items: center !important;
  pointer-events: none !important;
}

/* 2-player games: 3-column grid — [player avatar | center labels |
 * opponent avatar]. Center column stacks "You / vs / AI" in 3 rows.
 * Sides use display: contents so the avatar + name (children of the
 * .matchup-side) participate directly in the parent grid placement. */
body[data-page-brand-label="Chess"][data-multiplayer="true"][data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"][data-moyako-v2] .page .moyako-board-center .number-pad-info .board-lower-matchup {
  display: grid !important;
  /* v290.2: 5-col grid with 3 fixed-height rows for the label stack.
   * Avatars span all 3 rows so they vertically centre against the
   * stack. align-items: center keeps each cell centred. */
  grid-template-columns: 1fr auto auto auto 1fr !important;
  grid-template-rows: 1fr 1fr 1fr !important;
  align-items: center !important;
  align-content: center !important;
  justify-items: center !important;
  column-gap: 6px !important;
  row-gap: 0 !important;
  padding: 2px 16px !important;
}
body[data-page-brand-label="Chess"][data-multiplayer="true"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"] .moyako-board-center .matchup-side--player,
body[data-page-brand-label="Chess"][data-multiplayer="true"] .moyako-board-center .matchup-side--opponent,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"] .moyako-board-center .matchup-side--opponent {
  display: contents !important;
}
body[data-page-brand-label="Chess"][data-multiplayer="true"] .moyako-board-center .matchup-side--player .matchup-avatar,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"] .moyako-board-center .matchup-side--player .matchup-avatar {
  grid-column: 2 !important;
  grid-row: 1 / span 3 !important;
}
body[data-page-brand-label="Chess"][data-multiplayer="true"] .moyako-board-center .matchup-side--opponent .matchup-avatar,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"] .moyako-board-center .matchup-side--opponent .matchup-avatar {
  grid-column: 4 !important;
  grid-row: 1 / span 3 !important;
}
/* Center column 3: row 1 = "You", row 2 = "vs", row 3 = "AI".
 * v290.2: line-height 1 + margin 0 + zero padding so the rows
 * stack flush — minimal vertical gap between lines. */
body[data-page-brand-label="Chess"][data-multiplayer="true"] .moyako-board-center .matchup-side--player .matchup-name,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"] .moyako-board-center .matchup-side--player .matchup-name {
  display: block !important;
  grid-column: 3 !important;
  grid-row: 1 !important;
  font-size: 10px !important;
  font-weight: 600 !important;
  line-height: 1 !important;
  margin: 0 !important;
  padding: 0 !important;
  color: var(--text-primary, #ECEFF4) !important;
  white-space: nowrap !important;
  align-self: end !important;
}
body[data-page-brand-label="Chess"][data-multiplayer="true"] .moyako-board-center .matchup-vs,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"] .moyako-board-center .matchup-vs {
  display: block !important;
  grid-column: 3 !important;
  grid-row: 2 !important;
  font-size: 9px !important;
  font-weight: 400 !important;
  line-height: 1 !important;
  margin: 0 !important;
  padding: 0 !important;
  color: var(--text-secondary, #9aa0a6) !important;
  text-transform: lowercase !important;
  white-space: nowrap !important;
  align-self: center !important;
}
body[data-page-brand-label="Chess"][data-multiplayer="true"] .moyako-board-center .matchup-side--opponent .matchup-name,
body[data-page-brand-label="Backgammon"][data-multiplayer="true"] .moyako-board-center .matchup-side--opponent .matchup-name {
  display: block !important;
  grid-column: 3 !important;
  grid-row: 3 !important;
  font-size: 10px !important;
  font-weight: 600 !important;
  line-height: 1 !important;
  margin: 0 !important;
  padding: 0 !important;
  color: var(--text-primary, #ECEFF4) !important;
  white-space: nowrap !important;
  align-self: start !important;
}
