/* Brateng Games — the shell.

   Same vocabulary as Brateng Music (.logo / .chip / .btn / .toast / .home-btn /
   .theme-overlay / .pcard) so the two apps feel like one house. One radius, one
   press, one fade; kid layouts scale up via body.kid. */

* { box-sizing: border-box; margin: 0; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
  background: var(--bg); color: var(--text); font-family: var(--font);
  font-size: 15px; line-height: 1.45; overflow: hidden;
  transition: background 400ms ease;
  -webkit-user-select: none; user-select: none;
}
button, input, select { font-family: var(--font); color: var(--text); }
button { background: none; border: 0; cursor: pointer; font-size: inherit; touch-action: manipulation; }

#app { height: 100%; }
#app.fade-in > * { animation: fadein var(--fade); }
@keyframes fadein { from { opacity: 0 } to { opacity: 1 } }
.screen > * { height: 100%; display: flex; flex-direction: column; }

/* Safe-area helpers — the status bar is translucent in standalone mode, so the
   top inset is ours to pay for (iPhone 12 Pro Max: 47px portrait). */
:root {
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  /* Sideways on a notched phone iOS reserves a strip down each side as well —
     roughly 47px for the sensor housing, plus the rounded corners. Only the top
     and bottom insets were paid for, so in landscape the leftmost topbar item
     and the home button sat under the notch. */
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
}

/* ---------- lockup: 3×3 block mark + Brateng over the GAMES pill ---------- */
.logo { display: flex; align-items: center; gap: 16px; }
.logo-mark { display: grid; grid-template-columns: repeat(3, 1fr); gap: 11%; flex-shrink: 0; }
.logo-mark i {
  border-radius: 22%; opacity: .28;
  background: linear-gradient(150deg, var(--accent-2), var(--accent));
}
.logo.live .logo-mark i { animation: blockpop 2.6s ease-in-out infinite; }
@keyframes blockpop {
  0%, 100% { opacity: .26; transform: scale(.84); }
  8%       { opacity: 1;   transform: scale(1); filter: drop-shadow(0 0 7px var(--glow)); }
  26%      { opacity: .26; transform: scale(.84); }
}
.logo-stack { text-align: center; }
.logo-name { font-weight: 650; letter-spacing: -.01em; }
.logo-pill {
  display: inline-block; margin-top: 7px; border: 1.5px solid var(--accent);
  border-radius: 999px; padding: 3px 15px; font-size: 11px; letter-spacing: .5em;
  text-indent: .5em; color: var(--accent-2); box-shadow: 0 0 16px var(--glow);
  white-space: nowrap;
}

/* ---------- inline svg icons (never unicode glyphs — iOS emoji-ises them) --- */
.ic-svg { display: inline-block; vertical-align: -3px; fill: currentColor; flex-shrink: 0; }
.iconbtn .ic-svg, .home-btn .ic-svg { vertical-align: 0; }

/* ---------- shared chrome ---------- */
.topbar {
  display: flex; align-items: center; gap: 12px; flex-shrink: 0;
  padding: calc(12px + var(--safe-t)) calc(var(--gutter) + var(--safe-r))
           10px calc(var(--gutter) + var(--safe-l));
}
.topbar .spacer { flex: 1; }
.topbar .logo { gap: 9px; flex-shrink: 0; }
.topbar .logo-pill {
  font-size: 7.5px; padding: 1px 8px; margin-top: 3px; border-width: 1px;
  letter-spacing: .4em; text-indent: .4em; box-shadow: 0 0 10px var(--glow);
}
@media (max-width: 560px) { .chip-name { display: none; } }
@media (max-width: 430px) { .topbar .logo-stack { display: none; } }

.chip {
  display: flex; align-items: center; gap: 9px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 999px; padding: 6px 14px 6px 7px;
  font-weight: 600; min-height: var(--touch); flex-shrink: 0;
}
.chip:active { transform: var(--press); }
.chip-ava { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; font-size: 17px; }
.iconbtn {
  width: var(--touch); height: var(--touch); border-radius: 50%; display: grid;
  place-items: center; color: var(--text-dim); flex-shrink: 0;
}
.iconbtn:active { transform: var(--press); }
.iconbtn.off { color: var(--text-dim); opacity: .55; }

.home-btn {
  position: absolute; right: calc(var(--gutter) + var(--safe-r)); bottom: calc(20px + var(--safe-b));
  width: 52px; height: 52px; border-radius: var(--radius-sm); display: grid;
  place-items: center; background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text); z-index: 5; box-shadow: var(--shadow);
}
.home-btn:active { transform: var(--press); }

.sect {
  padding: 10px var(--gutter) 2px; color: var(--text-dim); font-size: 12.5px;
  letter-spacing: .12em; text-transform: uppercase; font-weight: 700;
}
.btn {
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 18px; font-weight: 650; color: var(--accent-2); min-height: var(--touch);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.btn.primary { background: var(--accent); color: var(--on-accent); border: 0; }
.btn:active { transform: var(--press); }
/* A button you can't press has to look like one. Without this, Memory's "Deal",
   Sudoku's "Deal a puzzle" and Hangman's "Ready" render identically whether they
   are live or inert — so the biggest, brightest thing on a child's first screen
   is a button that does nothing when tapped, with no hint why. */
.btn:disabled, .btn.disabled {
  opacity: .4;
  cursor: default;
  box-shadow: none;
  filter: saturate(.6);
}
.btn:disabled:active, .btn.disabled:active { transform: none; }
.toast {
  position: fixed; left: 50%; bottom: calc(40px + var(--safe-b)); transform: translateX(-50%);
  background: var(--surface-3); border: 1px solid var(--line); color: var(--text);
  padding: 11px 20px; border-radius: 999px; box-shadow: var(--shadow); z-index: 100;
  animation: fadein 150ms ease; max-width: 80vw; text-align: center;
}

/* ---------- the front gate (once per device) ---------- */
.gate {
  flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center;
  padding: calc(12px + var(--safe-t)) calc(var(--gutter) + var(--safe-r))
           calc(12px + var(--safe-b)) calc(var(--gutter) + var(--safe-l));
  overflow-y: auto;
}
/* An arcade cabinet: bezel, inset glow, the lockup as the marquee. */
.gate-cab {
  width: min(340px, 100%); display: flex; flex-direction: column; align-items: center;
  gap: 14px; padding: 24px 22px 20px;
  background: linear-gradient(160deg, var(--surface-2), var(--surface));
  border: 1px solid var(--line); border-radius: 26px;
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, .06);
}
.gate-slot {
  font-size: 10.5px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 800;
}

.gate-dots { display: flex; gap: 14px; padding: 4px 0 2px; }
.gate-dot {
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface-3); border: 2px solid var(--line);
  transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
}
.gate-dot.on {
  background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 12px var(--glow); transform: scale(1.14);
}
.gate-dots.win .gate-dot { background: var(--ok); border-color: var(--ok); box-shadow: 0 0 16px var(--ok); }
.gate-dots.shake .gate-dot { background: var(--danger); border-color: var(--danger); }
.gate-dots.shake { animation: gateshake 400ms ease; }
@keyframes gateshake {
  10%, 90% { transform: translateX(-4px) } 30%, 70% { transform: translateX(6px) }
  50% { transform: translateX(-6px) }
}
.gate-msg { min-height: 17px; font-size: 12.5px; font-weight: 650; color: var(--danger); }

.gate-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; width: 100%; }
.gate-key {
  height: 58px; border-radius: 16px; font-size: 24px; font-weight: 800;
  font-variant-numeric: tabular-nums; color: var(--text);
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  border: 1px solid var(--line);
  box-shadow: 0 3px 0 var(--line), inset 0 1px 0 rgba(255, 255, 255, .07);
  display: grid; place-items: center;
  transition: transform 70ms ease, box-shadow 70ms ease;
}
/* Chunky travel — the button physically sinks onto its own shadow. */
.gate-key:active { transform: translateY(3px); box-shadow: 0 0 0 var(--line); }
.gate-fn { color: var(--text-dim); }
.gate-go { color: var(--on-accent); background: linear-gradient(180deg, var(--accent), var(--accent)); border-color: var(--accent); }
.gate-foot { font-size: 11px; color: var(--text-dim); }

body.kid .gate-key { height: 64px; font-size: 27px; }

@media (orientation: landscape) and (max-height: 600px) {
  .gate-cab { width: min(560px, 100%); display: grid; gap: 10px 20px;
              grid-template-columns: 1fr 1fr; align-items: center; padding: 18px; }
  .gate-cab > .logo, .gate-slot, .gate-dots, .gate-msg, .gate-foot { grid-column: 1; justify-self: center; }
  .gate-pad { grid-column: 2; grid-row: 1 / span 5; }
  .gate-key { height: 46px; font-size: 20px; }
}

/* ---------- front door ---------- */
.picker { align-items: center; justify-content: center; gap: 46px; padding: calc(var(--safe-t)) 12px calc(var(--safe-b)); }
.picker-cards { display: flex; gap: 22px; flex-wrap: wrap; justify-content: center; max-width: 720px; }
.pcard {
  width: 148px; padding: 24px 0 18px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--line);
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  transition: transform 120ms ease; position: relative;
}
.pcard:active { transform: var(--press); }
.pcard-ava { width: 76px; height: 76px; border-radius: 50%; display: grid; place-items: center; font-size: 37px; }
.pcard-name { font-size: 17px; font-weight: 650; }
.pcard-last {
  position: absolute; top: 10px; right: 12px; font-size: 9.5px; font-weight: 700;
  letter-spacing: .1em; color: var(--accent-2); text-transform: uppercase;
}
.picker-hint { color: var(--text-dim); font-size: 12.5px; }

/* ---------- overlays (on <body>, so a repaint can never kill them) ---------- */
.theme-overlay {
  position: fixed; inset: 0; z-index: 50; display: grid; place-items: center;
  background: rgba(0, 0, 0, .5); padding: 16px;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.theme-overlay .panel {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 22px 24px; box-shadow: var(--shadow); width: min(430px, 92vw);
  max-height: 86vh; overflow-y: auto;
}
.theme-overlay h2 { font-size: 17px; margin-bottom: 16px; }
.theme-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.theme-opt {
  display: flex; align-items: center; gap: 10px; padding: 11px 13px; font-weight: 600;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.theme-opt.current { border-color: var(--accent); }
.theme-opt .swatch {
  width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--line);
  background: linear-gradient(135deg, var(--accent), var(--bg));
}
.emoji-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
.emoji-grid button {
  font-size: 26px; padding: 9px 0; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid transparent;
}
.emoji-grid button.current { border-color: var(--accent); }

/* ---------- the shelf ---------- */
.hub-scroll { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding-bottom: calc(24px + var(--safe-b)); }
.hub-hello { padding: 4px var(--gutter) 0; }
.hub-hello h1 { font-size: 25px; font-weight: 700; letter-spacing: -.02em; }
.hub-hello p { color: var(--text-dim); font-size: 13.5px; margin-top: 2px; }
.game-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(156px, 1fr));
  gap: 16px; padding: 8px var(--gutter) 10px;
}
.gtile {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; text-align: left; display: flex; flex-direction: column;
  transition: transform 120ms ease;
}
.gtile:active { transform: var(--press); }
.gtile-art { aspect-ratio: 4 / 3; display: grid; place-items: center; position: relative; color: #fff; }
.gtile-art::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(135deg, rgba(255,255,255,.16), rgba(255,255,255,0) 55%);
}
.gtile-art svg { width: 56%; height: 56%; filter: drop-shadow(0 3px 10px rgba(0,0,0,.35)); }
.gtile-meta { padding: 11px 13px 13px; }
.gtile-meta b { display: block; font-size: 15px; }
.gtile-meta small { display: block; color: var(--text-dim); font-size: 12px; margin-top: 1px; }
.gtile-rec { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.rec-pill {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; border-radius: 999px;
  padding: 2px 8px; border: 1px solid var(--line); color: var(--text-dim);
}
.rec-pill.w { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.rec-pill.new { color: var(--accent-2); border-color: var(--accent); }
.gtile.soon {
  border-style: dashed; background: none; align-items: center; justify-content: center;
  min-height: 150px; color: var(--text-dim); gap: 8px; padding: 20px; text-align: center;
}
.gtile.soon svg { opacity: .5; }
.gtile.soon:active { transform: none; }

/* ---------- the game frame ---------- */
.play { position: relative; }
.play-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.play-title { font-weight: 700; font-size: 16px; }
.back-btn { display: flex; align-items: center; gap: 4px; color: var(--accent-2); font-weight: 650; min-height: var(--touch); }

/* ---------- tic-tac-toe ---------- */
.ttt {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 16px; padding: 4px var(--gutter) calc(12px + var(--safe-b));
}
.scorebar {
  display: flex; align-items: stretch; gap: 10px; width: var(--board); max-width: 100%;
}
.sc {
  flex: 1; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 8px 10px; display: flex; align-items: center; gap: 9px; min-width: 0;
}
.sc.turn { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 4px 18px var(--glow); }
.sc-mark { width: 22px; height: 22px; flex-shrink: 0; }
.sc-mark.p1 { color: var(--p1); }
.sc-mark.p2 { color: var(--p2); }
.sc-mark svg { width: 100%; height: 100%; overflow: visible; }
/* Same mark component as the board, but static — the draw-in animation is
   scoped to .cell so the scoreboard never replays it. */
.sc-mark .mark {
  fill: none; stroke: currentColor; stroke-width: 14; stroke-linecap: round;
  stroke-dasharray: none; stroke-dashoffset: 0;
}
.sc-who { min-width: 0; margin-right: auto; }
.sc-who b { display: block; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sc-who small { color: var(--text-dim); font-size: 10.5px; }
.sc-n { font-size: 21px; font-weight: 800; font-variant-numeric: tabular-nums; }
.sc-draws {
  flex: 0 0 auto; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 8px 12px; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
}
.sc-draws b { font-size: 19px; font-weight: 800; font-variant-numeric: tabular-nums; }
.sc-draws small { color: var(--text-dim); font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; }

.board-wrap { position: relative; width: var(--board); height: var(--board); }
.board {
  display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr);
  gap: 3.2%; width: 100%; height: 100%;
}
.cell {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  display: grid; place-items: center; padding: 12%; transition: background 140ms ease, opacity 260ms ease;
}
.cell:active { background: var(--surface-2); }
.cell.filled { cursor: default; }
.cell.dim { opacity: .34; }
.cell.win { border-color: currentColor; box-shadow: 0 0 0 1px currentColor, 0 6px 26px var(--glow); }
.cell svg { width: 100%; height: 100%; overflow: visible; }
.cell .mark { fill: none; stroke: currentColor; stroke-width: 11; stroke-linecap: round; }
.cell.p1 { color: var(--p1); }
.cell.p2 { color: var(--p2); }
/* Same rule as the hangman strokes: the base state is the finished mark and the
   animation reveals it, so nothing that suppresses animation can erase it. */
.cell .mark { stroke-dasharray: var(--len); stroke-dashoffset: 0; animation: drawin 260ms ease backwards; }
.cell .mark.d2 { animation-delay: 130ms; }
.cell.ghost svg { opacity: .16; }
.cell.ghost .mark { animation: none; stroke-dashoffset: 0; }

.winline { position: absolute; inset: 0; pointer-events: none; }
.winline line {
  stroke: currentColor; stroke-width: 3.4; stroke-linecap: round;
  stroke-dasharray: 120; stroke-dashoffset: 0;
  animation: drawline 420ms ease 160ms backwards;
  filter: drop-shadow(0 0 6px currentColor);
}
@keyframes drawline { from { stroke-dashoffset: 120; } to { stroke-dashoffset: 0; } }
.board-wrap.shake { animation: shake 380ms ease; }
@keyframes shake {
  10%, 90% { transform: translateX(-2px) } 30%, 70% { transform: translateX(4px) }
  50% { transform: translateX(-4px) }
}

.status { font-size: 16px; font-weight: 650; min-height: 24px; text-align: center; }
.status .em { color: var(--accent-2); }
.ttt-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

/* Portrait: the side panel is transparent and its children lay out in the
   column with the board. Landscape re-materialises it as a real column beside
   the board (rule lives in the orientation query below — source order matters). */
.ttt-side { display: contents; }

/* opponent setup */
.setup { width: min(420px, 92vw); display: flex; flex-direction: column; gap: 18px; }
.setup h2 { font-size: 19px; text-align: center; }
.setup .sect { padding: 0 2px 8px; }
.opt-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px; }
.opt {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 12px 6px; font-weight: 650; font-size: 13.5px; min-height: 52px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
}
.opt small { color: var(--text-dim); font-size: 10.5px; font-weight: 500; }
.opt:active { transform: var(--press); }
.opt.current {
  border-color: var(--accent); background: linear-gradient(135deg, var(--surface-3), var(--surface-2));
  box-shadow: 0 0 0 1px var(--accent), 0 6px 22px var(--glow);
}
.opt-people { grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); }
.opt-row.two { grid-template-columns: 1fr 1fr; }
.opt-ava { font-size: 22px; }

/* ---------- taberinos ---------- */
.tab {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  gap: 10px; padding: 2px var(--gutter) calc(10px + var(--safe-b));
}
.tab.tab-center { justify-content: center; }
.tab-stats { display: flex; gap: 8px; width: 100%; max-width: 900px; flex-shrink: 0; }
.tab-stat {
  flex: 1; min-width: 0; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 5px 8px; text-align: center;
}
.tab-stat span {
  display: block; font-size: 9px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700;
}
.tab-stat b { font-size: 19px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--accent-2); }
.tab-stage { flex: 1; min-height: 0; width: 100%; display: grid; place-items: center; }
/* The wrapper's pixel size is set by the game to fit the board's aspect inside
   the stage — CSS alone can't clamp both axes of a fixed-ratio box reliably. */
.tab-wrap { position: relative; }
.tab-wrap canvas.board {
  display: block; width: 100%; height: 100%; touch-action: none;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius);
}
.tab-wrap canvas.fx {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 5;        /* fireworks burst over the card */
}
.tab-hint { color: var(--text-dim); font-size: 11.5px; text-align: center; line-height: 1.35; flex-shrink: 0; }
.tab-actions { display: flex; gap: 12px; flex-shrink: 0; flex-wrap: wrap; justify-content: center; }
.tab-over { position: absolute; inset: 0; display: none; place-items: center; padding: 14px; z-index: 4; }
.tab-over.show { display: grid; }
.tab-over-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 20px 24px; text-align: center; max-width: min(360px, 90%);
}
.tab-over-card h2 { font-size: 20px; margin-bottom: 8px; }
.tab-over-card p { font-size: 14px; color: var(--text-dim); line-height: 1.5; }
.tab-over-card b { color: var(--text); }
.tab-shame { font-style: italic; opacity: .85; }
.tab-over-actions { display: flex; gap: 10px; justify-content: center; margin-top: 16px; flex-wrap: wrap; }
body.kid .tab-stat b { font-size: 21px; }
body.kid .tab-hint { font-size: 13px; }

/* ---------- sudoku ---------- */
.sd {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 10px; padding: 4px var(--gutter) calc(10px + var(--safe-b));
  /* Definite width from the viewport, height from aspect-ratio. Nothing here
     resolves against a content-derived box (D20). */
  --gw: min(calc(100vw - 2 * var(--gutter)), 46vh, 460px);
}
.sd.sd-center { justify-content: center; }
.sd-side { display: contents; }
.sd-bar { display: flex; gap: 8px; width: var(--gw); max-width: 100%; flex-shrink: 0; }
.sd-stat {
  flex: 1; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 4px 8px; text-align: center;
}
.sd-stat span {
  display: block; font-size: 9px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700;
}
.sd-stat b { font-size: 17px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--accent-2); }
.sd-stage { flex: 0 0 auto; display: grid; place-items: center; }
.sd-grid {
  width: var(--gw); aspect-ratio: 1;
  display: grid; grid-template-columns: repeat(var(--n), 1fr);
  background: var(--surface); border: 2px solid var(--text-dim);
  border-radius: var(--radius-sm); overflow: hidden;
}
.sd-cell {
  position: relative; padding: 0; border: 0; background: var(--surface);
  border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
  color: var(--accent-2); font-weight: 700; display: grid; place-items: center;
  font-size: calc(var(--gw) / var(--n) * 0.56); line-height: 1;
  transition: background 120ms ease;
}
.sd-cell.bx-r { border-right: 2px solid var(--text-dim); }
.sd-cell.bx-b { border-bottom: 2px solid var(--text-dim); }
.sd-cell.given { color: var(--text); background: var(--surface-2); }
.sd-cell.peer { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
.sd-cell.given.peer { background: color-mix(in srgb, var(--accent) 12%, var(--surface-2)); }
.sd-cell.same { background: color-mix(in srgb, var(--accent) 26%, var(--surface)); }
.sd-cell.sel { background: color-mix(in srgb, var(--accent) 42%, var(--surface)); box-shadow: inset 0 0 0 2px var(--accent); }
.sd-cell.conflict { color: var(--danger); }
.sd-cell.bad { background: color-mix(in srgb, var(--danger) 30%, var(--surface)); color: var(--danger); }
.sd-notes {
  display: grid; grid-template-columns: repeat(var(--nw), 1fr); width: 100%; height: 100%;
  align-items: center; justify-items: center;
}
.sd-notes i {
  font-style: normal; font-size: calc(var(--gw) / var(--n) * 0.24);
  color: var(--text-dim); font-weight: 600; line-height: 1;
}
.sd-status { font-size: 13px; color: var(--text-dim); text-align: center; flex-shrink: 0; }
.sd-status .em { color: var(--accent-2); font-weight: 700; font-size: 15px; }
.sd-pad { display: grid; grid-template-columns: repeat(var(--nk), 1fr); gap: 5px; width: var(--gw); max-width: 100%; flex-shrink: 0; }
.sd-key {
  position: relative; height: 48px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  font-size: 19px; font-weight: 700; display: grid; place-items: center;
}
.sd-key:active { transform: var(--press); }
.sd-key i {
  position: absolute; right: 3px; bottom: 1px; font-style: normal;
  font-size: 9px; color: var(--text-dim); font-weight: 600;
}
.sd-key.done { opacity: .35; }
.sd-tools { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; flex-shrink: 0; }
.sd-tools .tool { font-size: 12.5px; padding: 7px 11px; min-height: 38px; }
.sd-tools .tool.on { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.sd-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; flex-shrink: 0; }
.sd-actions .btn { font-size: 13px; padding: 8px 13px; }
body.kid .sd-key { height: 52px; font-size: 21px; }
body.kid .sd-stat b { font-size: 19px; }

@media (orientation: landscape) and (max-height: 600px) {
  .sd { flex-direction: row; align-items: center; gap: 14px; --gw: min(44vw, 80vh, 460px); }
  .sd-side {
    display: flex; flex-direction: column; justify-content: center; gap: 8px;
    flex: 1 1 0; min-width: 0; max-width: 300px;
  }
  .sd-bar { width: 100%; }
  .sd-pad { width: 100%; grid-template-columns: repeat(5, 1fr); }
  .sd-key { height: 40px; font-size: 16px; }
  .sd.sd-center { flex-direction: column; }
}

/* ---------- connect four ---------- */
.c4 {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 14px; padding: 4px var(--gutter) calc(12px + var(--safe-b));
  /* Board width drives everything and the cell size falls out of it in real
     pixels. Percentage gaps were tried first and are a trap: a row-gap
     percentage inside a column flex box resolves against a height that is
     itself content-derived, so the columns grew taller than the board and the
     bottom row hung off a sideways phone. Nothing here is a percentage. */
  --pad: 7px;
  --gap: 6px;
  --bw: min(calc(100vw - 2 * var(--gutter)), 52vh, 520px);
  --cell: calc((var(--bw) - 2 * var(--pad) - 6 * var(--gap)) / 7);
}
.c4.c4-center { justify-content: center; }
.c4-side { display: contents; }
.c4-stage { flex: 0 0 auto; display: grid; place-items: center; }
.c4-board {
  width: var(--bw); display: flex; gap: var(--gap); padding: var(--pad);
  background: linear-gradient(160deg, var(--surface-3), var(--surface-2));
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.c4 .scorebar { width: var(--bw); max-width: 100%; }
.c4-col {
  flex: 0 0 var(--cell); display: flex; flex-direction: column; gap: var(--gap);
  padding: 0; background: none; border: 0; border-radius: var(--radius-sm);
  transition: background 140ms ease;
}
.c4-col:not(.full):hover { background: rgba(255, 255, 255, .07); }
.c4-col:not(.full):active { background: rgba(255, 255, 255, .12); }
.c4-cell {
  position: relative; width: var(--cell); height: var(--cell); border-radius: 50%;
  background: var(--bg); box-shadow: inset 0 2px 5px rgba(0, 0, 0, .5);
}
.c4-disc {
  position: absolute; inset: 6%; border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.42), rgba(255,255,255,0) 58%), currentColor;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .45);
}
.c4-cell.p1 { color: var(--p1); }
.c4-cell.p2 { color: var(--p2); }
/* The disc's resting place is its base state; the drop animates in from above,
   so nothing that suppresses animation can hide a played piece (D16). */
.c4-disc.fresh { animation: c4drop 340ms cubic-bezier(.4, 0, .65, 1.25); }
@keyframes c4drop { from { transform: translateY(var(--from)); } to { transform: none; } }
.c4-cell.dim { opacity: .38; }
.c4-cell.win .c4-disc {
  box-shadow: 0 0 0 3px var(--text), 0 0 22px currentColor;
  /* 55 x 1.1s = ~1 minute, then it settles. It used to be `infinite`, so the
     winning discs pulsed until somebody tapped Rematch. */
  animation: c4pulse 1.1s ease-in-out 55;
}
@keyframes c4pulse { 0%, 100% { transform: scale(1) } 50% { transform: scale(1.07) } }
/* The next disc's colour, shown as a hint on the column you're about to use. */
.c4-board[data-turn="1"] .c4-col:not(.full):hover { box-shadow: inset 0 0 0 2px var(--p1); }
.c4-board[data-turn="2"] .c4-col:not(.full):hover { box-shadow: inset 0 0 0 2px var(--p2); }
.c4-chip {
  width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.42), rgba(255,255,255,0) 58%), currentColor;
}
.c4-chip.p1 { color: var(--p1); }
.c4-chip.p2 { color: var(--p2); }
body.kid .c4 { --bw: min(calc(100vw - 2 * var(--gutter)), 55vh, 560px); }

@media (orientation: landscape) and (max-height: 600px) {
  .c4 { flex-direction: row; align-items: center; gap: 16px; --bw: min(46vw, 76vh, 520px); }
  .c4-side {
    display: flex; flex-direction: column; justify-content: center; gap: 12px;
    flex: 1 1 0; min-width: 0; max-width: 250px;
  }
  .c4 .scorebar { width: 100%; flex-direction: column; }
  .c4 .sc-draws { flex-direction: row; gap: 8px; padding: 6px 12px; }
  .c4.c4-center { flex-direction: column; }
}

/* ---------- memory match ---------- */
.mm {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  gap: 10px; padding: 4px var(--gutter) calc(10px + var(--safe-b));
}
.mm.mm-center { justify-content: center; }
.mm-side { display: contents; }
.mm-setup { max-height: 100%; overflow-y: auto; overscroll-behavior: contain; }
.mm-bar { display: flex; gap: 8px; width: 100%; max-width: 560px; flex-shrink: 0; }
.mm-stat {
  flex: 1; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 5px 8px; text-align: center;
}
.mm-stat span {
  display: block; font-size: 9px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700;
}
.mm-stat b { font-size: 18px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--accent-2); }
.mm-who {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 6px 10px;
}
.mm-who.turn { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 4px 18px var(--glow); }
.mm-ava { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; font-size: 16px; flex-shrink: 0; }
.mm-who-meta { min-width: 0; margin-right: auto; }
.mm-who-meta b { display: block; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mm-who-meta small { color: var(--text-dim); font-size: 10px; }
.mm-pairs { font-size: 21px; font-weight: 800; font-variant-numeric: tabular-nums; }

.mm-stage { flex: 1; min-height: 0; width: 100%; display: grid; place-items: center; }
.mm-grid { display: grid; justify-content: center; }
.mm-card {
  width: var(--card); height: var(--card); padding: 0; border: 0; background: none;
  perspective: 640px; touch-action: manipulation;
}
.mm-inner {
  position: relative; display: block; width: 100%; height: 100%;
  transform-style: preserve-3d; transition: transform 320ms cubic-bezier(.2, .7, .3, 1);
}
.mm-card.up .mm-inner, .mm-card.done .mm-inner { transform: rotateY(180deg); }
.mm-face {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: var(--radius-sm); backface-visibility: hidden; -webkit-backface-visibility: hidden;
}
.mm-back {
  background: linear-gradient(145deg, var(--surface-3), var(--surface-2));
  border: 1px solid var(--line); color: var(--accent);
}
.mm-mark { width: 46%; height: 46%; fill: currentColor; opacity: .32; }
.mm-front {
  transform: rotateY(180deg);
  background: var(--surface); border: 1px solid var(--accent);
  font-size: calc(var(--card) * 0.52); line-height: 1;
}
.mm-card:active .mm-inner { transform: scale(.95); }
.mm-card.up:active .mm-inner, .mm-card.done:active .mm-inner { transform: rotateY(180deg) scale(.95); }
.mm-card.done { cursor: default; }
.mm-card.done .mm-front { opacity: .58; border-color: var(--ok); box-shadow: 0 0 0 1px var(--ok); }
.mm-status { font-size: 15px; font-weight: 650; min-height: 22px; text-align: center; flex-shrink: 0; }
.mm-status .em { color: var(--accent-2); }
.mm-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; flex-shrink: 0; }

body.kid .mm-stat b { font-size: 20px; }
body.kid .mm-status { font-size: 17px; }

@media (orientation: landscape) and (max-height: 600px) {
  /* align-items must be stretch, not center: with center the stage's height
     becomes content-driven, so the board can never be told to shrink and it
     runs straight off the bottom of the screen. */
  .mm { flex-direction: row; align-items: stretch; gap: 14px; }
  .mm-side {
    display: flex; flex-direction: column; justify-content: center; gap: 10px;
    flex: 0 0 auto; width: clamp(120px, 21vw, 190px);
  }
  .mm-stage { min-height: 0; }
  .mm-bar { flex-direction: column; }
  .mm.mm-center { flex-direction: column; align-items: center; }
}

/* ---------- hangman ---------- */
.hm {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 12px; padding: 4px var(--gutter) calc(10px + var(--safe-b));
}
.hm.hm-center { justify-content: center; }
/* Portrait: the wrapper is transparent, so the drawing, the word, the keyboard
   and the buttons stack in one column. Landscape re-materialises it as a real
   column beside the drawing (same trick as the other two games). */
.hm-right { display: contents; }
/* The drawing gets a fixed share of the height rather than flexing: given the
   spare space it stretched to 500px of mostly-empty box and pushed the word
   and keyboard down the screen. */
.hm-figure { flex: 0 0 auto; display: grid; place-items: center; padding: 2px 0; }
.hm-draw { height: min(240px, 26vh); width: auto; overflow: visible; }
@media (min-width: 760px) { .hm-draw { height: min(320px, 27vh); } }
.hm-info { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; flex-shrink: 0; }
.hm-gallows, .hm-part {
  fill: none; stroke-linecap: round; stroke-width: 5;
  stroke-dasharray: var(--len); stroke-dashoffset: 0;
}
.hm-gallows { stroke: var(--text-dim); opacity: .7; }
.hm-part { stroke: var(--danger); }
/* The base state is the FINISHED stroke and the animation reveals it from
   hidden — not the other way round. Written the other way, anything that
   suppresses animation (prefers-reduced-motion, a stalled animation clock)
   leaves the newest body part permanently invisible. */
.hm-part.fresh { animation: drawin 300ms ease; }
@keyframes drawin { from { stroke-dashoffset: var(--len); } to { stroke-dashoffset: 0; } }
.hm-figure.shake { animation: shake 380ms ease; }

.hm-cat {
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700;
}
.hm-word { display: flex; flex-wrap: wrap; gap: 6px; }
.hm-slot {
  min-width: 26px; height: 38px; border-bottom: 3px solid var(--line);
  display: grid; place-items: center; font-size: 24px; font-weight: 800;
  letter-spacing: .02em; color: transparent;
}
.hm-slot.filled { color: var(--text); border-bottom-color: var(--accent); animation: pop 220ms ease; }
.hm-slot.missed { color: var(--danger); border-bottom-color: var(--danger); }
@keyframes pop { from { transform: translateY(6px) scale(.8); opacity: 0 } to { transform: none; opacity: 1 } }
.hm-status { font-size: 15px; font-weight: 650; min-height: 22px; }
.hm-status .em { color: var(--accent-2); }
.hm-series { font-size: 11.5px; color: var(--text-dim); }

.hm-keys { display: flex; flex-direction: column; gap: 7px; width: 100%; max-width: 560px; flex-shrink: 0; }
.hm-row { display: flex; gap: 6px; justify-content: center; }
.hm-key {
  flex: 1 1 0; min-width: 0; max-width: 58px; height: 46px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  font-size: 16px; font-weight: 700; display: grid; place-items: center;
  transition: background 140ms ease, opacity 140ms ease;
}
.hm-key:active { transform: var(--press); }
.hm-key.wide { flex: 0 0 auto; max-width: none; padding: 0 18px; gap: 8px; display: flex; align-items: center; font-size: 14px; }
.hm-key.hit { background: color-mix(in srgb, var(--ok) 24%, var(--surface-2)); border-color: var(--ok); color: var(--text); }
.hm-key.miss { opacity: .32; }
.hm-key.disabled { pointer-events: none; }
.hm-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; flex-shrink: 0; }
.hm-actions .btn { font-size: 13.5px; padding: 9px 14px; }

/* secret-word entry */
.hm-entry { width: 100%; max-width: 560px; display: flex; flex-direction: column; align-items: center; gap: 12px; }
.hm-entry h2 { font-size: 19px; text-align: center; }
.hm-sub { color: var(--text-dim); font-size: 13px; text-align: center; }
.hm-secret {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center;
  min-height: 52px; width: 100%; padding: 10px 16px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.hm-dot { width: 13px; height: 13px; border-radius: 50%; background: var(--accent); }
.hm-count { margin-left: 6px; font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.hm-placeholder { color: var(--text-dim); font-size: 13px; }
.hm-entry-msg { color: var(--danger); font-size: 12.5px; min-height: 16px; }

.hm-handoff { display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; }
.hm-hand-ava { width: 92px; height: 92px; border-radius: 50%; display: grid; place-items: center; font-size: 44px; }
.hm-handoff h2 { font-size: 21px; }

body.kid .hm-key { height: 52px; font-size: 18px; }
body.kid .hm-slot { font-size: 27px; height: 42px; min-width: 30px; }
body.kid .hm-status { font-size: 17px; }

@media (max-width: 480px) {
  .hm-draw { height: min(196px, 23vh); }
  .hm-key { height: 42px; font-size: 15px; }
}

/* Phone held sideways: drawing left, word and keyboard right. */
@media (orientation: landscape) and (max-height: 600px) {
  .hm { flex-direction: row; align-items: center; gap: 16px; }
  .hm-draw { height: min(230px, 68vh); }
  .hm-right { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 0; }
  .hm-keys { max-width: none; }
  .hm-key { height: 38px; font-size: 14px; }
  .hm.hm-center { flex-direction: column; }
}

/* Portrait: the side wrappers are transparent, so stats sit above the board and
   the hint and buttons below it. Landscape re-materialises them as columns
   either side, which is the only way the board gets any real size on a phone
   held sideways (same trick as the tic-tac-toe layout). */
.tab-side { display: contents; }
@media (orientation: landscape) and (max-height: 600px) {
  .tab { flex-direction: row; gap: 16px; align-items: stretch; }
  .tab-side {
    display: flex; flex-direction: column; justify-content: center; gap: 12px;
    flex: 0 0 auto; width: clamp(120px, 20vw, 190px);
  }
  .tab-stats { flex-direction: column; }
  .tab-stat { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; text-align: left; }
  .tab-actions { flex-direction: column; align-items: stretch; gap: 8px; }
  .tab-hint { font-size: 10.5px; }
  .tab.tab-center { flex-direction: column; }
  .tab.tab-center .setup { width: min(520px, 92vw); }
}

/* ---------- battleship ---------- */
.bs {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  gap: 8px; padding: 4px var(--gutter) calc(10px + var(--safe-b));
}
.bs.bs-center { justify-content: center; }
/* Transparent in portrait so the panels stack with the boards; landscape
   re-materialises them as real columns either side (same trick as .ttt-side). */
.bs-side { display: contents; }
.bs-head { width: 100%; max-width: 540px; display: flex; flex-direction: column; gap: 3px; }
.bs-sub { font-size: 11.5px; color: var(--text-dim); text-align: center; }
.bs-sub b { color: var(--accent-2); }

/* The stage is measured and the cells are told their size in px — nothing here
   is a fraction of a box whose own size comes from its contents (D18, D20). */
.bs-stage {
  flex: 1; min-height: 0; width: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
}

.bs-plot {
  display: grid;
  grid-template-columns: var(--hd, 19px) repeat(10, var(--bc, 30px));
  grid-template-rows: var(--hd, 19px) repeat(10, var(--bc, 30px));
  gap: 2px; flex-shrink: 0;
}
.bs-hd {
  display: grid; place-items: center; font-style: normal; line-height: 1;
  font-size: calc(var(--hd, 19px) * 0.6); font-weight: 700; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.bs-c {
  display: grid; place-items: center; padding: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: 3px;
  transition: background 130ms ease;
}
.bs-c[data-i]:not(:disabled):active { transform: var(--press); background: var(--surface-3); }

/* Splashes and strikes. Both are pseudo-elements so they scale with the cell
   and stay legible at 14 px on a phone. */
.bs-c.miss::after {
  content: ""; width: 26%; height: 26%; border-radius: 50%;
  background: var(--text-dim); opacity: .55;
}
.bs-c.hit { background: var(--danger); border-color: var(--danger); }
.bs-c.hit::after { content: ""; width: 34%; height: 34%; border-radius: 50%; background: var(--bg); opacity: .5; }
.bs-c.sunk { background: var(--danger); border-color: var(--danger); filter: saturate(.65) brightness(.66); }
.bs-c.sunk::after { content: ""; width: 52%; height: 2px; border-radius: 2px; background: var(--bg); opacity: .7; }

/* Your own fleet: hulls read as continuous bars, rounded at bow and stern. */
.bs-c.own.sh { background: var(--accent); border-color: var(--accent); }
.bs-c.sh-h.sh-a { border-top-left-radius: 999px; border-bottom-left-radius: 999px; }
.bs-c.sh-h.sh-z { border-top-right-radius: 999px; border-bottom-right-radius: 999px; }
.bs-c.sh-v.sh-a { border-top-left-radius: 999px; border-top-right-radius: 999px; }
.bs-c.sh-v.sh-z { border-bottom-left-radius: 999px; border-bottom-right-radius: 999px; }
.bs-c.own.sh.sh-gone { background: var(--danger); border-color: var(--danger); filter: saturate(.65) brightness(.66); }
.bs-c.own.hit { background: var(--danger); border-color: var(--danger); }

/* Placement preview. Touch has no hover, so this is a desktop nicety only —
   placement never depends on it. */
.bs-c.place.ok { box-shadow: inset 0 0 0 2px var(--ok); }
.bs-c.place.no { box-shadow: inset 0 0 0 2px var(--danger); }
.bs-c.own.place.bad { background: var(--danger); border-color: var(--danger); }

.bs-minibox { display: flex; flex-direction: column; align-items: center; gap: 3px; flex-shrink: 0; }
.bs-cap {
  font-size: 9px; letter-spacing: .13em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700;
}
.bs-mini {
  display: grid; grid-template-columns: repeat(10, var(--sc, 13px));
  grid-auto-rows: var(--sc, 13px); gap: 1px;
}
/* At 13 px a 1 px border eats the cell, so the gap is the grid line instead. */
.bs-c.mini { border: 0; border-radius: 2px; background: var(--surface-2); }
.bs-c.mini.miss::after { width: 32%; height: 32%; }
.bs-c.mini.hit::after { width: 40%; height: 40%; }

/* Fleet strips: one block per ship, so "three left" is readable without words. */
.bs-fleets { display: flex; gap: 8px; width: 100%; max-width: 540px; }
.bs-fl {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 7px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 4px 8px;
}
.bs-fl-t {
  font-size: 8.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-dim); font-weight: 700; flex-shrink: 0;
}
.bs-pips { display: flex; gap: 5px; margin-right: auto; }
.bs-pip i { display: flex; gap: 1px; }
.bs-pip u { display: block; width: 4px; height: 10px; border-radius: 1.5px; background: var(--accent-2); text-decoration: none; }
.bs-pip u.x { background: var(--danger); }
.bs-pip.gone u { background: var(--danger); opacity: .4; }
.bs-fl b { font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; }
.bs-fl.mine .bs-pip u { background: var(--accent); }
.bs-fl.mine .bs-pip u.x, .bs-fl.mine .bs-pip.gone u { background: var(--danger); }

/* The roster during placement. */
.bs-roster { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; width: 100%; max-width: 540px; }
.bs-ship {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 5px 8px; display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.bs-ship em { font-style: normal; font-size: 10px; font-weight: 700; color: var(--text-dim); }
.bs-ship i { display: flex; gap: 1.5px; }
.bs-ship u { display: block; width: 8px; height: 8px; border-radius: 2px; background: var(--accent); text-decoration: none; }
.bs-ship:active { transform: var(--press); }
.bs-ship.sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 6px 22px var(--glow); }
.bs-ship.off u { background: var(--line); }
.bs-ship.off em { opacity: .55; }

.bs-status { font-size: 14.5px; font-weight: 650; min-height: 22px; text-align: center; }
.bs-status .em { color: var(--accent-2); }
.bs-status b { color: var(--accent-2); font-variant-numeric: tabular-nums; }
.bs-tally { font-size: 10.5px; color: var(--text-dim); text-align: center; font-variant-numeric: tabular-nums; }
.bs-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }

.bs-pass { display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; }
.bs-pass-ava { width: 84px; height: 84px; border-radius: 50%; display: grid; place-items: center; font-size: 40px; }
.bs-pass h2 { font-size: 21px; }

/* Base state is the landed shot; the animation only reveals it (D16). */
.bs-c.fresh::after { animation: bspop 320ms cubic-bezier(.2, .8, .3, 1.35) backwards; }
@keyframes bspop { from { transform: scale(0); } to { transform: scale(1); } }
.bs-c.fresh { animation: bsflash 700ms ease; }
@keyframes bsflash {
  0%, 100% { box-shadow: none; }
  30% { box-shadow: 0 0 0 2px var(--accent-2), 0 0 16px var(--glow); }
}
.bs-c.own.place.bad { animation: bsbad 360ms ease; }
@keyframes bsbad { 0%, 100% { opacity: 1 } 40% { opacity: .35 } }

body.kid .bs-status { font-size: 16.5px; }
body.kid .bs-ship em { font-size: 11.5px; }
body.kid .bs-fl b { font-size: 17px; }

/* Any landscape at all — phone or iPad — puts the two grids side by side.
   Stacked, the board is limited by the one dimension that is scarce while a few
   hundred pixels of width go unused: an iPad held sideways got 36 px cells
   stacked and 53 px side by side. */
@media (orientation: landscape) {
  .bs-stage { flex-direction: row; min-height: 0; gap: 12px; }
}

/* A phone held sideways has no vertical room for the panels either, so they
   become columns on each side of the boards (same trick as .ttt-side). */
@media (orientation: landscape) and (max-height: 600px) {
  /* stretch, not center: with center the stage's height becomes content-driven
     and the JS sizing the boards measures a box the boards themselves define. */
  .bs { flex-direction: row; align-items: stretch; gap: 12px; }
  .bs-side {
    display: flex; flex-direction: column; justify-content: center; gap: 8px;
    flex: 0 0 auto; width: clamp(132px, 23vw, 196px);
  }
  .bs-fleets { flex-direction: column; gap: 6px; }
  .bs-actions { flex-direction: column; align-items: stretch; }
  .bs-roster { gap: 5px; }
  .bs.bs-center { flex-direction: column; align-items: center; }
}

/* ---------- checkers ---------- */
.ck {
  flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 12px; padding: 4px var(--gutter) calc(10px + var(--safe-b));
}
.ck.ck-center { justify-content: center; }
.ck-side { display: contents; }
.ck-bar { width: 100%; max-width: 520px; display: flex; justify-content: center; }
.ck-stage { flex: 1; min-height: 0; width: 100%; display: grid; place-items: center; }

.ck-board {
  display: grid; grid-template-columns: repeat(8, var(--cell, 44px));
  grid-auto-rows: var(--cell, 44px); padding: var(--pad, 5px);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.ck-sq { position: relative; display: grid; place-items: center; padding: 0; }
/* Playable squares are the dark ones, as on a real board. */
.ck-dk { background: var(--surface); }
.ck-lt { background: var(--surface-3); }
.ck-sq.live { cursor: pointer; }
.ck-sq.live:active { transform: var(--press); }
.ck-sq.trail { box-shadow: inset 0 0 0 2px var(--accent-2); }
.ck-sq.sel { box-shadow: inset 0 0 0 3px var(--accent), 0 0 18px var(--glow); }

.ck-piece { width: 84%; height: 84%; }
.ck-piece.p1 { color: var(--p1); }
.ck-piece.p2 { color: var(--p2); }
.ck-disc { fill: currentColor; }
.ck-ring { fill: none; stroke: var(--bg); stroke-width: 4.5; opacity: .3; }
.ck-crown { fill: var(--bg); opacity: .62; }

/* Where you may go. A jump target is bigger and in the danger colour, so
   "this one takes a piece" reads before you've worked out why. */
.ck-dot {
  position: absolute; width: 28%; height: 28%; border-radius: 50%;
  background: var(--ok); opacity: .8; pointer-events: none;
}
.ck-dot.jump { width: 44%; height: 44%; background: var(--danger); opacity: .88; }
.ck-sq.tgt:active .ck-dot { opacity: 1; }

.ck-chip { width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0; }
.ck-chip.p1 { background: var(--p1); }
.ck-chip.p2 { background: var(--p2); }
.ck-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

body.kid .ck-dot { width: 34%; height: 34%; }
body.kid .ck-dot.jump { width: 50%; height: 50%; }

@media (orientation: landscape) and (max-height: 600px) {
  .ck { flex-direction: row; align-items: stretch; gap: 16px; }
  .ck-side {
    display: flex; flex-direction: column; justify-content: center; gap: 12px;
    flex: 1 1 0; min-width: 0; max-width: 260px;
  }
  .ck-stage { min-height: 0; }
  .ck-bar { max-width: none; }
  .scorebar:has(.ck-chip) { width: 100%; flex-direction: column; }
  .ck-actions { flex-direction: column; align-items: stretch; }
  .ck.ck-center { flex-direction: column; align-items: center; }
}

/* ---------- confetti ---------- */
#confetti { position: fixed; inset: 0; pointer-events: none; z-index: 60; }

/* ---------- kid scaling (driven by the player, not the theme) ---------- */
body.kid { font-size: 17px; }
body.kid .gtile-meta b { font-size: 17px; }
body.kid .gtile-meta small { font-size: 13px; }
body.kid .game-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 18px; }
body.kid .hub-hello h1 { font-size: 28px; }
body.kid .status { font-size: 18px; }
body.kid .btn { font-size: 16px; padding: 12px 22px; }
body.kid .sc-who b { font-size: 14.5px; }
body.kid .sc-n { font-size: 23px; }
body.kid .opt { font-size: 15px; min-height: 60px; }

/* ---------- board sizing ---------- */
.ttt { --board: min(86vw, 48vh, 440px); }
body.kid .ttt { --board: min(90vw, 50vh, 470px); }

/* Phone held sideways: board in the middle, panels either side of it.
   body.kid must be restated here — it is the more specific selector, so the
   portrait kid rule would otherwise keep winning and squash the board. */
@media (orientation: landscape) and (max-height: 600px) {
  .ttt, body.kid .ttt { --board: min(78vh, 40vw); }
  .ttt { flex-direction: row; gap: 18px; }
  .ttt-side {
    display: flex; flex-direction: column; gap: 14px; justify-content: center;
    flex: 1 1 0; min-width: 0; max-width: 270px;
  }
  .scorebar { width: 100%; flex-direction: column; }
  .sc-draws { flex-direction: row; gap: 8px; padding: 6px 12px; }
  .setup { width: 100%; gap: 12px; }
  .ttt:has(.setup) { flex-direction: column; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .logo.live .logo-mark i { animation: none; opacity: .85; transform: none; }
  .cell .mark, .winline line, .hm-part.fresh { animation: none; stroke-dashoffset: 0; }
  .hm-slot.filled { animation: none; }
  .board-wrap.shake { animation: none; }
  .bs-c.fresh, .bs-c.own.place.bad { animation: none; }
  .bs-c.fresh::after { animation: none; transform: none; }
  /* These four were missing. The Connect Four win pulse matters most — it is
     the only looping animation in the app, and a repeating movement is the
     thing this setting exists to switch off. The hangman shake is a vestibular
     trigger whose identical twin .board-wrap.shake was already suppressed two
     lines up, and the memory card flip is a *transition*, which an
     `animation: none` block does not reach. */
  .c4-cell.win .c4-disc { animation: none; }
  .c4-disc.fresh { animation: none; }
  .hm-figure.shake { animation: none; }
  .mm-inner { transition: none; }
  #app.fade-in > * { animation: none; }
}
