:root {
  --tile-size: 64px;
  --gap: 6px;
  --grid-size: 70px; /* tile + gap */

  /* Brand colors */
  --sujido-blue: #10a5e5; /* Sujidó Blue (candidate, source-derived) — docs/brand-colors.md */
  --sujido-orange: #fc721d; /* Sujidó Orange (candidate, source-derived) — docs/brand-colors.md */
  /* Two semantic colors ONLY (design 2026-06-16): blue = progress/solved,
     orange = carry letter. The gold placeholder was removed — not in the Sujido
     aesthetic. Do not reintroduce a third accent. */

  /* Other colors */
  --white: #ffffff;
  --red: #c53232;

  /* Light mode colors (default) */
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --text-primary: #1a1a1a;
  --text-secondary: #666666;

  /* Quiet light-grey edge shared by outlined chrome (win-modal buttons, rating
     segments, notification border) — owner 2026-06-15. */
  --tile-border: #c0c5cc;

  /* Immovable-wall tone — also the intro modal's "black" swatch */
  --tile-immovable: #2c3e50;

  /* Grid and UI */
  /* Soft grey to match --tile-border: the grid-cell slot exposed under a lifted
     tile shouldn't read as a stark black outline (owner 2026-06-15). */
  --grid-line: #c0c5cc;
  --menu-bg: #ffffff;

  /* Board tonal-lane tokens (docs/archive/phase2-spec.md; un-scoped from body.proto in
     remediation M4 — the proto model IS the game). Every type gets its own
     range: carry=orange hero · movable=brick · immovable=dark extreme ·
     open=light extreme. Flat fills only — no bevels/shadows (not-Sujido).
     Light: board = page-white; open cells read by their grey outline. */
  --board-open: #ffffff;
  --board-open-border: var(--grid-line); /* grey outline so white reads on white */
  --board-immov: #000000; /* full black block (owner 2026-07-01: both modes black) */
  --board-immov-border: transparent; /* stands out on white; no edge needed */
  --board-carry: var(--sujido-orange);
  --board-carry-text: #ffffff;
  --board-move: #a8322a; /* brick red */

  /* Menu popover (owner 2026-07-01, from docs/archive/sujido-menu-prototype.html).
     Text/hover/accent map to existing tokens (--text-primary/-secondary,
     --bg-secondary, --sujido-orange); these are the values with no existing
     counterpart. The callout trio is the mockup's orange-tint family (a tint
     of --sujido-orange, not a new accent). */
  --menu-line: #ececec; /* hairline dividers + field borders in the menu */
  --menu-track: #e0e0e0; /* dark-toggle off track */
  --menu-faint: #b0b0b0; /* chevrons, placeholder, version text */

  /* Win modal (owner 2026-06-22 zen redesign): a lifted card tone + a strong
     near-monochrome hero so the score number dominates (NOT brand blue — the
     mockup hero is ink/white). */
  --win-card-bg: #ffffff;
  --win-hero: #16181b;

  /* Animation timings */
  --anim-tile-swap: 200ms;

  /* Z-indexes */
  --z-tile: 20;
  --z-dragging: 100;
  --z-overlay: 2000;
  --z-menu: 2100;

  font-family: "Clear Sans", system-ui, Helvetica, Arial, sans-serif;
}

/* Prevent iOS double-tap zoom */
* {
  -webkit-tap-highlight-color: transparent;
}

/* Smooth theme transitions */
body,
.container,
.tile,
.grid-cell,
.hamburger-line {
  transition: background-color 0.3s ease, color 0.3s ease,
    border-color 0.3s ease;
}

/* Prevent logo from transitioning (it swaps instantly) */
.logo {
  transition: none;
}

/* Dark mode overrides */
[data-theme="dark"] {
  /* Dark mode colors */
  --bg-primary: #272b31; /* dark bg = the open-cell grey (--board-open) so the board's
                            empty field and the page read as one tone (owner 2026-07-01) */
  --bg-secondary: #2d2d2d;
  --text-primary: #e8eaed; /* off-white, not pure #fff (2026-06-17, per spec) */
  --text-secondary: #999999;

  /* Tile colors */
  --tile-border: #565d68; /* dimmed from #999 (2026-06-17) so outlined chrome
     recedes; matches the spec's dark border value. */

  /* Immovable-wall tone (dark) — see the light-theme note */
  --tile-immovable: #666666;

  /* Grid and UI */
  --grid-line: #565d68; /* dimmed from #999 to match --tile-border (kept equal) */
  --menu-bg: #26292e; /* card surface — matches --win-card-bg so every lifted
     card in dark mode shares one tone (was #2d2d2d, a near-duplicate) */

  /* Board tonal-lane tokens — DARK (archive/phase2-spec.md §3, LOCKED 2026-06-20).
     NO board panel: the board background IS the page, so tiles float on the
     app surface and can't read as a card. Open cells are the page charcoal
     with a soft grey outline; immovable is pure black with no edge (reads as
     the dark extreme on the lighter page). Carry bound to the SAME brand
     token as light, so the "unified orange" intent can't drift. */
  --board-open: #272b31;
  --board-open-border: #474d56; /* between #3a3f47 and --grid-line #565d68, tuned by eye */
  --board-immov: #000000;
  --board-immov-border: #000000; /* no edge — reads on the lighter page */
  --board-carry: var(--sujido-orange);
  --board-carry-text: #ffffff;
  --board-move: #a8322a;

  /* Menu dark values (mockup dark variant, mapped to our surfaces) */
  --menu-line: #343a41;
  --menu-track: #3d444c;
  --menu-faint: #6b7178;

  /* Keep brand colors unchanged for logo/branding */
  --sujido-blue: #10a5e5; /* Sujidó Blue (candidate, source-derived) — docs/brand-colors.md */
  --sujido-orange: #fc721d; /* Sujidó Orange (candidate, source-derived) — docs/brand-colors.md */

  /* Win modal: lifted card off the #1a1a1a board bg; white hero number. */
  --win-card-bg: #26292e;
  --win-hero: #ffffff;
}

* {
  box-sizing: border-box;
}

html {
  height: 100%;
  height: -webkit-fill-available;
}

body {
  margin: 0;
  padding: 2rem 1rem;
  display: flex;
  justify-content: center;
  /* Stretch the container to full height so the board can center in the space
     BELOW the logo (owner 2026-06-29): the logo stays pinned at the top, the
     board gets auto top/bottom margins (see .game-board) and drops to mid-screen
     for easier thumb reach. (Centering the whole stack pulled the logo down.) */
  align-items: stretch;
  height: 100%;
  min-height: 100vh;
  min-height: -webkit-fill-available;
  background: var(--bg-primary);
  color: var(--text-primary);
  user-select: none;
  -webkit-user-select: none;
  touch-action: pan-x pan-y;
  overscroll-behavior: contain;
  overflow: hidden;
  position: fixed;
  width: 100%;
  -webkit-text-size-adjust: 100%;
}

.container {
  max-width: 420px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Three children now: .top-region (flex:1, holds the logo+hamburger centered),
     the board, and .board-spacer (flex:1). The two flex:1 regions center the
     board between them, while the logo sits centered in the top whitespace. No
     container gap — the flex regions own the spacing. */
  gap: 0;
  touch-action: manipulation;
}

/* Top region — centers the logo (and the hamburger, absolute) in the whitespace
   above the board (owner 2026-06-29). Weighted heavier than .board-spacer (1.5
   vs 1) so the board sits a touch BELOW dead-center (where the owner likes it);
   the logo stays centered in this larger top gap. Tune this ratio to raise/lower
   the board. */
.top-region {
  flex: 1.5;
  width: 100%;
  position: relative; /* anchors the absolutely-positioned hamburger */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}

/* Balances .top-region so the board lands centered between them. */
.board-spacer {
  flex: 1;
}

/* Hamburger Menu Button — vertically centered in .top-region (right side),
   aligned with the logo (owner 2026-06-29; was fixed to the screen top). */
.hamburger-button {
  position: absolute;
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  background: transparent; /* Changed from white */
  border: none; /* Changed from 1px solid #8d8d8d */
  cursor: pointer;
  padding: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  border-radius: 8px;
  box-shadow: none; /* Changed from shadow */
  z-index: 1000;
  transition: all 0.2s ease;
}

.hamburger-button:hover {
  background: transparent; /* Changed from #f5f5f5 */
  /* keep the -50% centering offset (see .hamburger-button) + the 1px lift */
  transform: translateY(calc(-50% - 1px));
  box-shadow: none; /* Removed shadow */
}

.hamburger-button:active {
  transform: translateY(-50%);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

.hamburger-line {
  width: 24px;
  height: 2px;
  background: var(--text-secondary);
  border-radius: 2px;
  transition: all 0.3s ease;
}

/* Hamburger animation when menu is open */
.hamburger-button.active .hamburger-line:nth-child(1) {
  transform: rotate(45deg) translate(6px, 6px);
}

.hamburger-button.active .hamburger-line:nth-child(2) {
  opacity: 0;
}

.hamburger-button.active .hamburger-line:nth-child(3) {
  transform: rotate(-45deg) translate(6px, -6px);
}

/* ============================================================
   Menu page (owner 2026-07-01) — adopted from
   docs/archive/sujido-menu-prototype.html, re-containered same day from
   a compact popover to a FULL-SCREEN opaque takeover on the
   win-modal pattern (the popover read too small against the
   busy board). The real logo + hamburger-X stay on top
   (body.menu-open raises .top-region); content starts at the
   measured bottom of that region (menu.js sets padding-top on
   open — measure, don't model). Hub swaps to in-place
   Challenge / About / How-to views. Colors map to OUR tokens
   (docs/brand-colors.md), not the mockup's hexes; type is the
   game's system font stack, not the mockup's Nunito.
   ============================================================ */

.menu-page {
  position: fixed;
  inset: 0;
  background: var(--menu-bg);
  z-index: var(--z-menu);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* padding-top is set by menu.js on open (the measured bottom of
     .top-region), so content begins where the board area begins. */
  padding: 0 1rem 1.25rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease-out, background-color 0.3s ease;
}

.menu-page.visible {
  opacity: 1;
  pointer-events: auto;
}

/* The game's column width, so the menu reads as the same app on desktop. */
.menu-page-inner {
  width: 100%;
  max-width: 420px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  transform: translateY(8px);
  transition: transform 0.2s ease-out;
}

.menu-page.visible .menu-page-inner {
  transform: none;
}

/* While the menu page is up, the REAL logo + hamburger stay on top — same
   DOM nodes, so the logo anchor and the hamburger→X morph need no
   replication. The dev challenge badge (also in .top-region) hides. */
body.menu-open .top-region {
  z-index: calc(var(--z-menu) + 1);
}

body.menu-open .challenge-badge {
  visibility: hidden;
}

/* In-place views (hub / challenge / about / how-to). The active view fills
   the page column so the hub's version line can pin to the bottom. */
.menu-view {
  display: none;
}

.menu-view.active {
  display: flex;
  flex-direction: column;
  flex: 1 0 auto;
}

/* Hub rows */
.menu-row {
  display: flex;
  align-items: center;
  gap: 15px;
  width: 100%;
  padding: 15px 12px;
  background: none;
  border: none;
  border-radius: 13px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease;
}

.menu-row:hover {
  background: var(--bg-secondary);
}

/* The dark-mode row is a setting, not a navigation target */
.menu-setting,
.menu-setting:hover {
  cursor: default;
  background: none;
}

.menu-ico {
  width: 24px;
  height: 24px;
  flex: none;
  color: var(--text-primary);
}

.menu-ico svg {
  width: 100%;
  height: 100%;
  display: block;
}

.menu-lbl {
  flex: 1;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-primary);
}

.menu-chev {
  width: 18px;
  height: 18px;
  flex: none;
  margin-left: auto;
  color: var(--menu-faint);
}

.menu-chev svg {
  width: 100%;
  height: 100%;
  display: block;
}

.menu-divider {
  height: 1px;
  background: var(--menu-line);
  margin: 8px 6px;
}

/* Pinned to the bottom of the full-page hub (margin-top: auto in the
   flex-column view). */
.menu-version {
  text-align: center;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--menu-faint);
  margin-top: auto;
  padding: 16px 0 4px;
}

/* Dark-mode toggle */
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 44px;
  height: 26px;
  flex: none;
}

.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-slider {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--menu-track);
  cursor: pointer;
  transition: background 0.15s ease;
}

.toggle-slider::before {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--white);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transition: left 0.15s ease;
}

input:checked + .toggle-slider {
  background: var(--sujido-orange);
}

input:checked + .toggle-slider::before {
  left: 21px;
}

/* Sub-view header (back + title) */
.menu-subhead {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 6px 6px 11px;
  border-bottom: 1px solid var(--menu-line);
  margin-bottom: 9px;
}

.menu-back {
  width: 28px;
  height: 28px;
  flex: none;
  border: none;
  border-radius: 50%;
  background: var(--bg-secondary);
  color: var(--text-secondary);
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.menu-back svg {
  width: 15px;
  height: 15px;
}

.menu-subttl {
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--text-primary);
}

.menu-subbody {
  padding: 6px 8px 10px;
}

.menu-subbody p {
  font-size: 0.95rem;
  color: var(--text-secondary);
  line-height: 1.55;
  margin: 0 0 10px;
}

.menu-subhd {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-primary);
  margin: 16px 0 6px;
}

/* Scoped under .menu-subbody so it outranks the `.menu-subbody p` base
   style (a bare .menu-meta loses that specificity contest). */
.menu-subbody .menu-meta {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--menu-faint);
  margin-top: 14px;
}

/* (The About test-mode note is a plain .menu-subhd + paragraph section —
   the boxed callout treatments read tacked-on against the typographic
   About page and were removed, owner 2026-07-01.) */

/* Challenge sub-view: code field + CTA (ids wired in challengeMode.js) */
.challenge-code-input {
  width: 100%;
  text-align: center;
  font-family: inherit;
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: 0.18em;
  color: var(--text-primary);
  background: var(--bg-primary);
  padding: 14px 10px;
  border: 2px solid var(--menu-line);
  border-radius: 14px;
  margin: 4px 0 12px;
  outline: none;
  transition: border-color 0.14s ease;
}

.challenge-code-input::placeholder {
  color: var(--menu-faint);
  letter-spacing: 0.14em;
  font-weight: 700;
}

.challenge-code-input:focus {
  border-color: var(--sujido-orange);
}

.challenge-start-button {
  width: 100%;
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 700;
  cursor: pointer;
  padding: 14px;
  border-radius: 14px;
  border: none;
  background: var(--sujido-orange);
  color: var(--white);
  transition: filter 0.14s ease, transform 0.14s ease;
}

.challenge-start-button:hover {
  filter: brightness(1.03);
  transform: translateY(-1px);
}

.challenge-start-button:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
}

.challenge-error {
  color: var(--red);
  font-size: 0.85rem;
  text-align: center;
  margin-top: 10px;
}

/* How-to steps (an in-place menu view, same surface as the others) */
.howto-steps {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.howto-steps li {
  display: flex;
  gap: 13px;
  font-size: 0.95rem;
  color: var(--text-primary);
  line-height: 1.45;
}

.howto-n {
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: 50%;
  background: var(--sujido-orange);
  color: var(--white);
  font-size: 0.8rem;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Mobile optimizations */
@media (max-width: 480px) {
  body {
    /* Trimmed from 2.5rem: too much top padding clipped content below the fold
       (body is position:fixed/overflow:hidden, so it gets clipped). */
    padding: 1.5rem 1rem;
  }

  .container {
    /* Tightened from 3.5rem so everything fits within the visible area on
       short iPhones (incl. Safari's toolbars). */
    gap: 1.5rem;
  }

  .logo {
    height: 64px;
  }

  /* Reduce tile size slightly on very small screens */
  @media (max-width: 360px) {
    :root {
      --tile-size: 56px;
      --gap: 5px;
      --grid-size: 60px;
    }
  }
}

/* Logo */
.logo {
  height: 72px;
  width: auto;
  cursor: default;
  transition: transform 0.2s ease;
  /* Inline-SVG logo: currentColor (the wordmark) follows this -> themes light/dark.
     Dots keep their canonical brand hex. */
  color: var(--text-primary);
}

/* Hide the screen chrome (logo + hamburger + challenge badge) while the win modal
   is up — the card carries its own logo, and the duplicates showing through the
   dim looked off (owner 2026-06-29). visibility keeps the layout so nothing
   shifts (and the board area below is already cleared by the outro). */
body.win-open .top-region {
  visibility: hidden;
}

/* Completion logo */
.completion-logo {
  height: 60px;
  width: auto;
  margin: 0 auto 1.5rem;
  display: block;
  color: var(--text-primary); /* inline-SVG wordmark themes via currentColor */
}

/* Dev playtest aid (remove after difficulty playtest): loaded challenge id,
   above the grid, muted so it doesn't fight the zen UI. */
.challenge-badge {
  text-align: center;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  opacity: 0.6;
  user-select: text;
  margin-bottom: 4px;
}

.challenge-badge.hidden {
  display: none;
}

/* Game board */
.game-board {
  position: relative;
  width: calc(5 * var(--tile-size) + 4 * var(--gap));
  height: calc(5 * var(--tile-size) + 4 * var(--gap));
  background: transparent;
  -webkit-touch-callout: none;
  touch-action: manipulation;
  /* Centered by the flex:1 .top-region / .board-spacer pair (owner 2026-06-29). */
  flex: 0 0 auto;
}

/* Grid background */
.grid-background {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: grid;
  grid-template-columns: repeat(5, var(--tile-size));
  grid-template-rows: repeat(5, var(--tile-size));
  gap: var(--gap);
  pointer-events: none;
}

.grid-cell {
  background: var(--bg-primary);
  border: 2px solid var(--grid-line);
  border-radius: 8px;
  transition: border-color 0s ease;
}

.grid-cell.no-border {
  border-color: transparent;
}

/* All tiles are absolutely positioned */
.tile {
  position: absolute;
  width: var(--tile-size);
  height: var(--tile-size);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  font-weight: 700;
  border-radius: 8px;
  cursor: grab;
  transition: background-color 0.3s ease, color 0.3s ease,
    border-color 0.3s ease, opacity 0.6s ease-in-out, transform 0.25s ease;
  touch-action: none;
  -webkit-touch-callout: none;
  box-sizing: border-box; /* CRITICAL FIX: Include borders in dimensions */
}

@keyframes popOut {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  100% {
    transform: scale(0);
    opacity: 0;
  }
}

/* Typewriter animation */
@keyframes typewriter {
  0% {
    transform: translateY(0) scale(1);
  }
  50% {
    transform: translateY(-8px) scale(1.1);
  }
  100% {
    transform: translateY(0) scale(1);
  }
}

.tile.typewriter-effect {
  animation: typewriter 0.3s ease-out;
  z-index: 50; /* Ensure it's above other tiles during animation */
}

/* ======================================================================
   Board styles (docs/archive/phase2-spec.md tonal-lane system — un-scoped from
   body.proto in remediation M4: the proto model IS the game). Tokens
   (--board-*) live in :root / [data-theme="dark"]. Flat fills, no shadows.
   ====================================================================== */
.tile {
  z-index: var(--z-tile);
  opacity: 0;
  pointer-events: none;
}

/* Open / fillable cells — restyles the shared grid-cells only once the board
   is live (body[data-phase] is set by GameState.setPhase). */
body[data-phase="transition"] .grid-cell,
body[data-phase="slide"] .grid-cell {
  background: var(--board-open);
  border-color: var(--board-open-border);
}

/* Carry (hero orange) */
.tile.letter {
  background: var(--board-carry);
  border: 2px solid var(--board-carry);
  color: var(--board-carry-text);
  box-shadow: none;
}

/* Hint subscript — spec §6 (docs/archive/p2-subscript.html option B @ 80%): the pinned
   carry's position number reads as a quiet annotation, bottom-right, weight
   800, ~80% opacity so the letter stays the hero. No chip. White, both modes. */
.tile.letter::after {
  content: attr(data-hint);
  position: absolute;
  bottom: 4px;
  right: 6px;
  font-size: 0.85rem;
  font-weight: 800;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.tile.letter.hint-shown::after {
  opacity: 0.8;
}

/* Solved (blue). Declared AFTER the carry rule: animateRowSolve adds .solved
   but leaves .letter on for ~50ms, so blue must win the moment .solved lands,
   flipping cleanly WITH the typewriter bounce. --sujido-blue is identical in
   both themes, so this covers light + dark. */
.tile.solved {
  background: var(--sujido-blue);
  border: 2px solid var(--sujido-blue);
  color: var(--white);
  box-shadow: none;
  /* QUICK colour flip (not the base 0.3s fade). The base .tile transition fades
     background over 0.3s, so orange→blue smeared across the whole bounce +
     landing ("changes colour as it descends"). The feel the owner likes is a
     fast snap (~0.12s). Keep transform on its base 0.25s so the descent stays
     gentle — only the colour is sped up. */
  transition: background-color 0.12s ease-out, border-color 0.12s ease-out,
    color 0.12s ease-out, transform 0.25s ease;
}

/* Movable blocker (brick) */
.tile.obstacle {
  background: var(--board-move);
  border: none;
  box-shadow: none;
}

/* Immovable wall (dark extreme) */
.tile.immovable {
  background: var(--board-immov);
  border: 2px solid var(--board-immov-border);
  cursor: not-allowed;
  box-shadow: none;
}

.tile.visible {
  opacity: 1;
  pointer-events: auto;
}

.tile.pop-in {
  animation: popIn 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

@keyframes popIn {
  0% {
    transform: scale(0);
    opacity: 0;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

/* Board build-in: the orange carries pop IN exactly like the blockers
   (popIn — grow + snap). */
.tile.letter.visible {
  animation: popIn 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

/* The typewriter SOLVE bounce must win the `animation` property on letter
   tiles over the popIn entrance rule above (same specificity — source order
   decides), so the full lift-and-drop plays from the start of the win wave. */
.tile.letter.typewriter-effect {
  animation: typewriter 0.3s ease-out;
}

/* Solve outro popcorn: solved tiles / obstacles / walls all pop out. (The
   winning tiles have had .letter removed by then, so the popIn entrance rule
   above never outranks this.) */
.tile.pop-out {
  animation: popOut 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
  pointer-events: none;
}

.tile.wide-2 {
  width: calc(2 * var(--tile-size) + var(--gap));
}

.tile.tall-2 {
  height: calc(2 * var(--tile-size) + var(--gap));
}

/* Drag states */
.dragging {
  cursor: grabbing;
  z-index: var(--z-dragging);
  transform: scale(1.05);
}

.sliding {
  transition: left var(--anim-tile-swap) cubic-bezier(0.4, 0, 0.2, 1),
    top var(--anim-tile-swap) cubic-bezier(0.4, 0, 0.2, 1);
}

/* Completion card */
/* FULL-SCREEN takeover (owner 2026-06-30): the win screen is a solid full-bleed
   surface, not a floating card — the content-heavy result (stats + 5-point scale +
   two CTAs) reads far better with the whole viewport than a 300px box, and the
   rating labels stop cramping. The overlay IS the surface now. */
.completion-overlay {
  position: fixed;
  inset: 0;
  background: var(--win-card-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-overlay);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
  padding: 0; /* the card carries the same 2rem inset as body, so the flex math
                 (and the logo position) matches gameplay exactly */
}

.completion-overlay.visible {
  opacity: 1;
  pointer-events: auto;
}

.completion-card {
  position: relative;
  background: transparent; /* the overlay provides the surface */
  width: 100%;
  max-width: 420px; /* match the game container so the logo aligns horizontally too */
  height: 100%;
  max-height: 100%;
  overflow-y: auto;
  /* Three regions mirroring the game container (.top-region 1.5 / board / spacer 1)
     so the logo holds its exact gameplay position (owner 2026-07-01). */
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2rem 1rem; /* = body's padding, so the card's content box == the game
                         container's box → identical flex regions → same logo pos */
  text-align: center;
  transform: translateY(8px);
  transition: transform 0.3s cubic-bezier(0.34, 1.1, 0.64, 1);
}
/* .win-top mirrors .top-region (flex 1.5, logo centered); .win-mid reserves the
   board's exact height so the flex math puts the logo where it sits in-game; the
   content flows from the board-top downward. .win-spacer mirrors .board-spacer. */
.win-top {
  flex: 1.5;
  min-height: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.win-mid {
  flex: 0 0 calc(5 * var(--tile-size) + 4 * var(--gap)); /* = board height */
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 0;
}
.win-spacer {
  flex: 1;
  min-height: 0;
}

.completion-overlay.visible .completion-card {
  transform: none;
}

.win-stack {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 400px; /* content column on the full-screen surface (desktop-safe) */
  margin: 0; /* top-aligned within .win-mid (starts at the board-top position) */
  padding-top: 0.2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.3rem;
}

.completion-card .completion-logo {
  height: 72px; /* same size as the in-game logo (.logo) */
  width: auto;
  max-width: 100%;
  margin: 0;
}

.win-meta {
  font-size: 0.68rem;
  color: var(--text-secondary);
  font-weight: 600;
  letter-spacing: 0.04em;
  margin-top: -0.55rem;
}

/* Moves + time as boxed stat cards (owner 2026-06-30 modal refresh). Full-width
   so the two cards spread across the card, not pinched in the center. */
.win-stats {
  width: 100%;
  display: flex;
  gap: 0.75rem;
  margin: 1.1rem 0 0;
  animation: winStatsIn 0.5s ease-out both;
}
@keyframes winStatsIn {
  from { opacity: 0; transform: translateY(6px) scale(0.97); }
  to { opacity: 1; transform: none; }
}
.win-stat {
  flex: 1;
  background: var(--bg-secondary);
  border-radius: 18px;
  padding: 1.1rem 0.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
}
.win-stat-num {
  font-size: 2.3rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--win-hero);
}
.win-stat-label {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
/* Dark mode: --bg-secondary (#2d2d2d) is nearly the win-card colour (#26292e), so
   the cards became invisible. Give them a distinctly lighter raised surface. */
[data-theme="dark"] .win-stat {
  background: #33383f;
}

/* Challenge a friend — now the SECONDARY win-card action (owner 2026-06-30):
   white/outline pill at the bottom, ceding the orange to "Play another". Overrides
   the base .challenge-friend-button orange fill. Fills in on hover. */
.challenge-friend-button.win-challenge {
  width: 100%;
  background: none;
  border: 1.5px solid var(--tile-border);
  border-radius: 999px;
  padding: 12.5px 26px;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text-primary);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.challenge-friend-button.win-challenge:hover {
  background: var(--bg-secondary);
  border-color: var(--text-secondary);
  box-shadow: none;
}

/* Difficulty rating (owner 2026-06-30) — gates "Play another" in test mode.
   Three plain pill buttons; the chosen one fills. */
.win-rating[hidden] {
  display: none;
}
/* Spacing above the rating (no separator line — owner 2026-06-30). Full-width so
   the segmented scale spans the card, aligned with the stats + buttons. */
.win-rating {
  width: 100%;
  margin-top: 1.3rem;
}
.win-rating-prompt {
  margin: 0 0 0.9rem;
  font-size: 1rem;
  font-weight: 800;
  color: var(--text-primary);
  transition: color 0.2s ease;
}
/* Nudge: when a player taps "Play another" before rating, draw the eye to the
   ask (owner 2026-06-30). Orange prompt + a quick shake; clears once they rate. */
.win-rating.nudge .win-rating-prompt {
  color: var(--sujido-orange);
  animation: ratingNudge 0.45s ease;
}
@keyframes ratingNudge {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}
/* Segmented felt-difficulty SCALE — one connected left→right control. Selected
   segment fills Sujido BLUE (orange stays exclusive to "Play another"). */
.win-rating-buttons {
  display: flex;
  border: 1px solid var(--tile-border);
  border-radius: 14px;
  overflow: hidden;
}
.win-rate-btn {
  flex: 1;
  min-width: 0;
  background: var(--win-card-bg);
  border: none;
  border-right: 1px solid var(--tile-border);
  color: var(--text-secondary);
  font-family: inherit;
  cursor: pointer;
  padding: 0.75rem 0.15rem 0.65rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  transition: background 0.14s ease;
}
.win-rate-btn:last-child {
  border-right: none;
}
.win-rate-btn:hover {
  background: var(--bg-secondary);
}
.win-rate-ic {
  font-size: 1.2rem;
  line-height: 1;
}
.win-rate-t {
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  color: var(--text-secondary);
}
.win-rate-btn.selected {
  background: var(--sujido-blue);
}
.win-rate-btn.selected .win-rate-t {
  color: #ffffff;
}
/* Axis caption under the scale. */
.win-rating-caps {
  display: flex;
  justify-content: space-between;
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--text-secondary);
  margin-top: 0.5rem;
  padding: 0 0.15rem;
}

/* Play another — now the PRIMARY win-card action (owner 2026-06-30): orange pill,
   top of the button stack. Drives the multi-board playtest loop; the rating sits
   just below and gateAdvance() keeps it effectively mandatory. */
.win-playtest {
  width: 100%;
  background: var(--sujido-orange);
  border: none;
  color: #ffffff;
  border-radius: 999px;
  padding: 14px 30px;
  font-size: 0.9rem;
  font-weight: 800;
  font-family: inherit;
  cursor: pointer;
  opacity: 1;
  transition: background 0.15s ease, box-shadow 0.15s ease;
}
.win-playtest[hidden] {
  display: none;
}
.win-playtest:hover {
  background: #fd863c; /* Play-another hover — lighter tint of #FC721D (re-derived) */
  box-shadow: 0 4px 14px rgba(252, 114, 29, 0.28);
}

/* Error state */
.error-message {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--white);
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
  text-align: center;
  max-width: 300px;
  display: none;
}

.error-message.visible {
  display: block;
}

/* Orientation message */
.orientation-message {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--white);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 3000;
}

.orientation-content {
  text-align: center;
  padding: 2rem;
  max-width: 300px;
}

.rotate-icon {
  font-size: 64px;
  margin-bottom: 1.5rem;
  display: block;
  animation: rotate-phone 2s ease-in-out infinite;
}

@keyframes rotate-phone {
  0%,
  100% {
    transform: rotate(90deg);
  }
  50% {
    transform: rotate(0deg);
  }
}

.orientation-content h2 {
  font-size: 1.8rem;
  margin: 0 0 1rem;
  color: var(--sujido-blue);
}

.orientation-content p {
  font-size: 1.1rem;
  color: #666;
  margin: 0;
}

/* Show orientation message in landscape */
@media (orientation: landscape) and (max-width: 896px) {
  .orientation-message {
    display: flex;
  }

  body > *:not(.orientation-message) {
    display: none !important;
  }
}

/* Ensure game is optimized for portrait */
@media (orientation: portrait) {
  .orientation-message {
    display: none !important;
  }
}

/* Intro Modals */
.intro-modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-overlay);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.intro-modal-overlay.visible {
  opacity: 1;
  pointer-events: auto;
}

.intro-modal-card {
  background: var(--bg-primary);
  border-radius: 16px;
  padding: 2rem 1.75rem;
  max-width: 320px;
  width: 90%;
  text-align: center;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  transform: scale(0.9);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.intro-modal-overlay.visible .intro-modal-card {
  transform: scale(1);
}

.intro-modal-heading {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-primary);
  margin: 0 0 1rem;
}

.intro-modal-body {
  font-size: 1rem;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0 0 1.5rem;
}

.intro-orange-color {
  color: var(--sujido-orange);
  font-weight: 600;
}

.intro-red-color {
  color: var(--red);
  font-weight: 600;
}

.intro-black-color {
  color: var(--tile-immovable);
  font-weight: 600;
}

.intro-modal-btn {
  background: var(--sujido-orange);
  color: var(--white);
  border: none;
  border-radius: 30px;
  padding: 0.5rem 2rem;
  font-size: 0.95rem;
  font-weight: 600;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(252, 114, 29, 0.3);
  transition: background 0.2s ease, transform 0.2s ease;
}

.intro-modal-btn:hover {
  background: #e07830;
  transform: translateY(-2px);
}

.intro-modal-btn:active {
  transform: translateY(0);
}

[data-theme="dark"] .intro-black-color {
  color: #8899aa;
}

.challenge-friend-button {
  background: var(--sujido-orange);
  color: var(--white);
  border: none;
  border-radius: 8px;
  padding: 0.75rem 2rem;
  font-size: 1.1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
}

.challenge-friend-button:hover {
  background: #e67e22;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(252, 114, 29, 0.3);
}

.challenge-friend-button:active {
  transform: translateY(0);
}

[data-theme="dark"] .completion-card {
  /* Use the token named for this card (#26292e), not --bg-secondary (#2d2d2d),
     so the card matches everything that references --win-card-bg — e.g. the
     rating segments blend into it exactly like white-on-white in light mode. */
  background: var(--win-card-bg);
  color: var(--text-primary);
}

/* Game Notification */
.game-notification {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) translateY(-20px);
  background: var(--sujido-orange);
  color: var(--white);
  border: 2px solid var(--tile-border);
  padding: 1rem 2rem;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 500;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  opacity: 0;
  transition: all 0.3s ease;
  z-index: 1000;
  pointer-events: none;
  text-align: center;
  min-width: 250px;
}

.game-notification.visible {
  opacity: 1;
  transform: translate(-50%, -50%) translateY(0);
}

/* ============================================================
   Dev placement aid — measurement grid overlay
   Toggle with the "g" key (localhost) or ?grid=1 anywhere.
   16px fine grid + 80px major grid + orange centre crosshair,
   so elements can be placed/described against fixed references.
   Non-interactive; never shown unless explicitly toggled.
   ============================================================ */
.grid-overlay {
  position: fixed;
  inset: 0;
  z-index: 99998;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(
      to right,
      rgba(8, 141, 228, 0.16) 0 1px,
      transparent 1px 16px
    ),
    repeating-linear-gradient(
      to bottom,
      rgba(8, 141, 228, 0.16) 0 1px,
      transparent 1px 16px
    ),
    repeating-linear-gradient(
      to right,
      rgba(8, 141, 228, 0.4) 0 1px,
      transparent 1px 80px
    ),
    repeating-linear-gradient(
      to bottom,
      rgba(8, 141, 228, 0.4) 0 1px,
      transparent 1px 80px
    );
}

.grid-overlay[hidden] {
  display: none;
}

/* Centre crosshair (orange) for aligning centred elements. */
.grid-overlay::before,
.grid-overlay::after {
  content: "";
  position: absolute;
  background: rgba(252, 114, 29, 0.85);
}

.grid-overlay::before {
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  transform: translateX(-0.5px);
}

.grid-overlay::after {
  top: 50%;
  left: 0;
  right: 0;
  height: 1px;
  transform: translateY(-0.5px);
}
