/* GridBloc — "Kraft & Marker" restyle (design/ handoff, 2026-07).
   Two themes (Kraft default, Slate) via data-theme on <html>; shared tokens below. */

:root {
  /* Kraft (default theme) */
  --desk: #d1bc96;
  --field: #b69c6e;
  --paper: #f3ead6;
  --ink: #20263a;
  --grid: #fbf7ec;
  --orig: #ffffff;
  --placed: #722b4e;
  --star: #efefef;
  --accent: #ffaa33;

  /* shared (both themes) */
  --grade1: #93321f;
  --grade2: #ce7a62;
  --grade3: #a8c68f;
  --grade4: #2e6b3f;
  --scored: #fbe4b0;
  --reach: #d0dff0;
  --slider: #a4f0fb;
  --mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;

  /* marker-highlight palette (wireframe voice — same pop colors in both themes) */
  --mk-pink: #e37fd4;
  --mk-orange: #f6a83c;
  --mk-green: #7ade4e;
  --mk-blue: #57b1e8;
  --mk-teal: #93d1c6;
  --badge: #7ade4e;
}
[data-theme="slate"] {
  --desk: #b3c1d1;
  --field: #97a8ba;
  --paper: #e4eaf0;
  --ink: #1e2436;
  --grid: #f4f7fa;
  --orig: #ffffff;
  --placed: #b83f1e;
  --star: #efefef;
  --accent: #90d2e0;
}

* {
  box-sizing: border-box;
}

/* ---- views (hash-routed screens; src/router.js toggles .active) ---- */
.view {
  display: none;
}
.view.active {
  /* replicate the body's centered column so the game view lays out exactly as before */
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}
.view .placeholder {
  font-size: 14px;
  opacity: 0.7;
}
.view .placeholder a {
  color: var(--ink);
}
body {
  font-family: var(--mono);
  background: var(--desk);
  color: var(--ink);
  margin: 0;
  padding: 30px 16px 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  box-shadow: inset 0 0 200px rgba(60, 45, 20, 0.24);
  -webkit-font-smoothing: antialiased;
}

/* ---- screen headers (per-view: logo box + titles) ---- */
.screenhead {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
  width: min(400px, 92vw);
}
.screenhead.home {
  justify-content: center;
  margin-bottom: 4px;
}
.logolink {
  flex: none;
  display: block;
  line-height: 0;
  /* transform: rotate(-1deg); */
  /* plain link to home — deliberately no hover/rollover treatment */
}
.logolink img {
  display: block;
  image-rendering: pixelated; /* keep the pixel logo crisp if it ever scales */
}
.titles {
  min-width: 0;
}
h1 {
  margin: 0;
  font-weight: 700;
  font-size: 44px;
  line-height: 0.9;
  letter-spacing: 1px;
  transform: rotate(-1.2deg);
}
.sub {
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0.55;
  margin-top: 7px;
  margin-left: 5px;  
}
.kicker {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.6;
}
.gamename {
  font-size: 26px;
  font-weight: 800;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.namein {
  font: inherit;
  font-size: 16px;
  font-weight: 800;
  color: var(--ink);
  background: transparent;
  border: none;
  border-bottom: 2px dashed var(--ink);
  width: 100%;
  padding: 2px 0;
  outline: none;
}
.namein::placeholder {
  color: var(--ink);
  opacity: 0.4;
}

/* ---- Home screen ---- */
.bigplay {
  font-size: 52px;
  font-weight: 800;
  margin: 26px 0 8px;
  transform: rotate(-1deg);
}
.homebtns {
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: center;
  margin: 10px 0 30px;
}
/* Marker buttons: outlined pill riding on a highlighter swipe (wireframe treatment).
   The swipe (::before) pokes out past the pill ends, slightly rotated, per button. */
.btn.marker {
  position: relative;
  z-index: 0;
  background: var(--paper);
  border-radius: 999px;
  border-width: 2px;
  isolation: isolate;
}
.homebtns .btn.marker,
#whatTheWhat {
  min-width: 220px;
  padding: 11px 22px;
  font-size: 16px;
}
.btn.marker::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: -14px;
  right: -18px;
  top: 15%;
  bottom: 8%;
  background: var(--mk, var(--accent));
  border-radius: 2px 4px 3px 2px;
  transform: rotate(var(--mk-rot, -1.2deg));
}
.m-pink { --mk: var(--mk-pink); --mk-rot: -1.4deg; }
.m-orange { --mk: var(--mk-orange); --mk-rot: 0.8deg; }
.m-green { --mk: var(--mk-green); --mk-rot: -0.7deg; }
.m-blue { --mk: var(--mk-blue); --mk-rot: 0.9deg; }
.m-teal { --mk: var(--mk-teal); --mk-rot: -0.8deg; }
.btn.marker:disabled::before {
  opacity: 0.35;
}
/* small marker buttons (e.g. Setup's wall pair): shorter swipe so neighbors don't touch */
.btn.small.marker::before {
  left: -5px;
  right: -7px;
}
/* setup action row (SAVE & PLAY): tightest swipe — barely past the edges, clear of RESET */
.setup-actions .btn.marker::before {
  left: -3px;
  right: -4px;
}
.learnhead {
  font-size: 26px;
  font-weight: 800;
  margin: 6px 0 12px;
}
/* Now a <button> (2026-08-01): it opens the AI "coming soon" dialog, so it must be
   keyboard-reachable and announce itself as a control — but it still LOOKS like the
   quiet line of text the wireframe called for. */
.contrib {
  display: block;
  margin: 30px auto 0;
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: none;
  border: 0;
  padding: 0;
  opacity: 0.75;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.contrib:hover {
  opacity: 1;
}
/* Inline export status in the ≡ drawer (2026-08-15: replaced the retired #exportDialog).
   Empty <p>s must collapse to nothing so the drawer doesn't reserve blank rows. */
.expstatus {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: min(340px, 90vw);
  margin-top: -4px;
}
.expstatus p {
  margin: 0;
}
.expstatus p:empty {
  display: none;
}
.drawnote {
  opacity: 0.6;
  font-size: 11px;
  line-height: 1.45;
  text-align: center;
}

/* inline text link inside dialogs (export dialog's "Want to build an AI?") */
.textlink {
  color: var(--ink);
  font-size: 12px;
  text-underline-offset: 3px;
}
/* the export/send button greys out while a send is in flight or cooling down */
.gbdialog .btn[disabled],
.actions .btn[disabled] {
  opacity: 0.45;
  cursor: default;
  filter: none;
}
.blurb {
  margin-top: 22px;
  max-width: 340px;
  border: 2px solid var(--ink);
  border-radius: 3px;
  background: var(--paper);
  padding: 12px 14px;
  font-size: 13px;
  line-height: 1.5;
  text-align: left;
}

/* ---- Setup screen ---- */
.bluebanner {
  width: min(400px, 92vw);
  background: var(--slider);
  text-align: center;
  font-size: 21px;
  font-weight: 800;
  padding: 8px 0;
  margin-bottom: 14px;
}
.setup-sliders {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(360px, 88vw);
  margin-bottom: 12px;
}
.setup-sliders .ctl {
  display: grid;
  grid-template-columns: 52px 1fr 84px;
  align-items: center;
  gap: 10px;
  opacity: 0.85;
}
.setup-sliders .ctl input[type="range"] {
  width: 100%;
}
.wallbtns {
  display: flex;
  gap: 14px;
  margin-bottom: 14px;
}
.btn.active {
  box-shadow: inset 0 0 0 3px var(--ink);
}
.manualhint {
  font-size: 12px;
  opacity: 0.7;
  margin: -6px 0 12px;
}
.ctl input[type="range"]:disabled {
  opacity: 0.35;
  cursor: default;
}
.field.preview {
  transform: rotate(0.5deg);
}
.setup-actions {
  display: flex;
  gap: 12px;
  margin-top: 18px;
  width: min(400px, 92vw);
}
.setup-actions .btn {
  flex: 1;
  padding: 10px 0;
}

/* ---- Game screen: round badge + running banner ---- */
.turnrow {
  display: flex;
  align-items: stretch;
  gap: 10px;
  width: min(400px, 92vw);
  margin-bottom: 6px;
}
.roundbadge {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--badge);
  border: 2px solid var(--ink);
  border-radius: 6px 4px 7px 5px;
  padding: 3px 10px;
  line-height: 1;
}
.roundbadge .rb-label {
  font-size: 9px;
  letter-spacing: 0.12em;
  font-weight: 700;
}
#roundNum {
  font-size: 24px;
  font-weight: 800;
}
.runbanner {
  flex: 1;
  display: flex;
  align-items: center;
  background: var(--slider);
  border-bottom: 2px solid var(--ink);
  padding: 4px 12px;
  font-size: 19px;
  font-weight: 800;
  min-width: 0;
}
.runbanner .role {
  font-weight: 400;
  font-size: 13px;
  margin-right: 8px;
  opacity: 0.75;
}
.runbanner .pts {
  margin-left: auto; /* flush right in the banner */
  display: flex;
  align-items: baseline;
  gap: 5px;
  white-space: nowrap;
  padding-left: 10px;
}
.runbanner #ptsNum {
  font-size: 24px;
  font-weight: 800;
}
.runbanner .ptslabel {
  font-size: 11px;
  font-weight: 700;
  opacity: 0.75;
}

/* ---- Game screen: tips strip + bottom bar + drawer ---- */
.tipstrip {
  display: grid;
  grid-template-columns: 34px 1fr 34px;
  align-items: center;
  gap: 6px;
  width: min(400px, 92vw);
  border: 2px solid var(--ink);
  border-radius: 4px;
  background: var(--paper);
  min-height: 68px; /* matches .rateblock so RATE toggling doesn't shift the bar below */
  margin-top: 14px;
  padding: 4px 6px;
}
.tiparrow {
  border: none;
  background: none;
  font-size: 15px;
  color: var(--ink);
  cursor: pointer;
  opacity: 0.6;
  padding: 8px 4px;
  min-height: 44px;
}
.tiparrow:hover {
  opacity: 1;
}
.tiptext {
  text-align: center;
  font-size: 12px;
  line-height: 1.45;
  opacity: 0.85;
}
.gamebar {
  display: flex;
  gap: 12px;
  width: min(400px, 92vw);
  margin-top: 14px;
}
.gamebar .btn {
  flex: 1;
  padding: 9px 0;
}
.gamebar #menuBtn {
  flex: 0 0 56px;
  font-size: 17px;
}
.drawer {
  display: none;
  width: min(520px, 94vw);
  margin-top: 16px;
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 14px 11px 15px 12px;
  padding: 16px 14px 10px;
}
.drawer.open {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* ---- dialogs ---- */
.gbdialog {
  font-family: var(--mono);
  color: var(--ink);
  background: var(--paper);
  border: 3px solid var(--ink);
  border-radius: 14px 11px 15px 12px;
  padding: 20px 22px;
  max-width: min(400px, 88vw);
  text-align: center;
}
.gbdialog::backdrop {
  background: rgba(30, 25, 15, 0.45);
}
.gbdialog h3 {
  margin-top: 0;
}
.gbdialog p {
  font-size: 13px;
  line-height: 1.5;
  text-align: left;
}
.gbdialog .dialognote {
  opacity: 0.55;
  font-size: 11px;
  text-align: center;
}
.gbdialog .btn {
  min-width: 100px;
}

/* ---- resume offer (lives in the strip slot, same footprint as .tipstrip) ---- */
.banner {
  display: flex;
  flex-direction: column; /* message on top, both buttons side by side below */
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: min(400px, 92vw);
  min-height: 68px;
  margin-top: 14px;
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 14px 11px 16px 12px;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 700;
}
.banner-btns {
  display: flex;
  gap: 12px;
}


/* ---- tip / AI-reason line ---- */
/* Same 3-column grid as #stepper, so the round-jump links land directly under the
   prev/next arrows and the reason text stays centred between them. */
#reasonbar {
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  gap: 14px;
  align-items: center;
  width: 100%;
  min-height: 18px;
  margin-top: 4px;
  padding: 0 6px;
}
/* Round jumps: quiet text, not buttons — the arrows above are the loud controls. */
.roundjump {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--ink);
  opacity: 0.72;
  cursor: pointer;
  white-space: nowrap;
}
#jumpR1 {
  text-align: left;
}
#jumpR2 {
  text-align: right;
}
.roundjump:disabled {
  opacity: 0.28;
  cursor: default;
}
.reason {
  font-size: 12px;
  opacity: 0.62;
  line-height: 1.5;
  text-align: center;
  /* one line, ellipsized — keeps the rate block a constant height (full text on hover) */
  display: block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- rate mode (stepper + grade bar + reason, swaps in for the tips strip) ----
   Same footprint as .tipstrip (shared min-height + margin) so toggling RATE never
   shifts the INVITE/FORK/RATE bar. */
.rateblock {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: min(400px, 92vw);
  min-height: 68px;
  margin-top: 14px;
}
#stepper {
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
  gap: 14px;
  width: 100%;
}
button.step {
  width: 44px;
  height: 44px;
  border-radius: 50% 48% 52% 47%;
  border: 2px solid var(--ink);
  background: var(--paper);
  font: inherit;
  font-size: 36px;
  line-height: 1;
  color: var(--ink);
  cursor: pointer;
  padding: 0;
}
#nextMove {
  border-radius: 48% 52% 47% 50%;
}
button.step:disabled {
  opacity: 0.45;
  cursor: default;
}
.gradebar {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  border: 2px solid var(--ink);
  border-radius: 12px 10px 13px 11px;
  overflow: hidden;
  height: 44px;
}
button.grade {
  border: none;
  border-right: 2px solid var(--ink);
  line-height: 1;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
}
button.grade:last-child {
  border-right: none;
}
button.grade span {
  display: inline-block;
}
button.grade.active {
  box-shadow: inset 0 0 0 3px var(--ink);
}
button.grade:disabled {
  opacity: 0.55;
  cursor: default;
}

/* ---- board ---- */
.field {
  background: var(--field);
  padding: 20px 20px 16px;
  transform: rotate(-0.6deg);
}
.boardwrap {
  width: min(360px, 80vw);
  position: relative; /* anchors the stacked star/label svgs (.gbstar/.gbover) */
}
/* Overlay svgs stacked on the board (2026-09-12): same viewBox as the board, so their
   coordinates line up 1:1; never interactive (all taps go to the board underneath). */
.gbboard.gbstar,
.gbboard.gbover,
.gbboard.gbplay {
  position: absolute;
  inset: 0;
  height: 100%;
  pointer-events: none;
  cursor: default;
  touch-action: auto;
}
/* Autoplay overlay (2026-09-21): fades on the HTML-level svg element (compositor only) —
   never animate inside the filter group (see the star note below: that heated phones). */
.gbboard.gbplay {
  opacity: 0;
  transition: opacity 180ms ease;
}
.gbboard.gbplay.show {
  opacity: 1;
}
.gbplay .disc {
  fill: var(--ink);
  fill-opacity: 0.55;
}
.gbplay .glyph {
  fill: var(--paper);
}
@media (prefers-reduced-motion: reduce) {
  .gbboard.gbplay {
    transition: none;
  }
}
svg.gbboard {
  display: block;
  width: 100%;
  height: auto;
  touch-action: none;
  cursor: pointer;
}
.gbboard .gl {
  stroke: var(--grid);
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0.9;
}
.gbboard .scored {
  fill: var(--scored);
}
.gbboard .reach {
  fill: var(--reach);
}
.gbboard .ow {
  /* Original walls use the PLACED color too (2026-07-24): testers were tripped up by
     original (was white) vs placed being different colors — a wall is a wall. */
  stroke: var(--placed);
  stroke-width: 6.5;
  stroke-linecap: round;
}
.gbboard .bd {
  /* Board border — stays the light --orig color: it's the non-crossable edge, not a
     clickable wall, so it should read differently from interior walls. */
  stroke: var(--orig);
  stroke-width: 6.5;
  stroke-linecap: round;
}
.gbboard .pw {
  stroke: var(--placed);
  stroke-linecap: round;
}
.gbboard .star polygon {
  fill: none;
  stroke: var(--star);
  stroke-width: 3.2;
  stroke-linejoin: round;
}
/* The star's breathing is animated on the OVERLAY SVG ELEMENT (.gbstar), not on the
   inner <g class="breathe"> (2026-09-12). Why: a CSS transform on an element inside an
   SVG filter group re-runs feTurbulence/feDisplacementMap over the whole group every
   frame (60×/s, forever) — measured as the cause of phones heating up during play. An
   HTML-level element's transform animation runs on the compositor: the star (filter
   included) is rasterized once and only a tiny textured quad moves. The star sits at the
   board's geometric center, which is also the overlay's center, so scaling the overlay
   about its own center is the same motion as before. `.breathe` stays on the inner <g>
   as a marker class (starSVG emits it) but no longer animates. */
.gbboard.gbstar {
  transform-origin: center;
  /*animation: gb-breathe 3.2s ease-in-out infinite;*/
  animation: none;
  will-change: transform;
}
.gbboard.gbstar[hidden] {
  display: none; /* .gbboard's display:block would otherwise beat [hidden] */
}
@media (prefers-reduced-motion: reduce) {
  .gbboard.gbstar {
    animation: none;
  }
}
@keyframes gb-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.09); }
}
.gbboard .runner {
  fill: rgba(255, 255, 255, 0.16);
  stroke: var(--ink);
  stroke-width: 3.2;
}
.gbboard .num {
  font-family: var(--mono);
  font-weight: 800;
  font-size: 30px;
  fill: var(--ink);
  text-anchor: middle;
  dominant-baseline: central;
}
.gbboard .runnum {
  font-weight: 900;
  font-size: 32px;
}

/* ---- mode / sliders ---- */
.controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  max-width: 520px;
}
/* Mode + Playing as: stacked, labels right-aligned so the selects line up */
.controls.modes {
  flex-direction: column;
  gap: 12px;
  margin-top: 20px;
}
.controls.modes .ctl {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  text-align: right; /* right-aligns the label text in its 96px column */
}
.controls.modes .ctl > select {
  justify-self: start;
  text-align: left; /* don't inherit the label's right-alignment */
}
/* 2-Player name inputs (namesCtl) — same grid as the selects, dashed-underline style */
.controls.modes .ctl > .ctlname {
  justify-self: start;
  text-align: left;
  font-size: 14px;
  max-width: 200px;
}
.ctl {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 700;
  opacity: 0.62;
}
.ctl select {
  font: inherit;
  font-weight: 700;
  font-size: 13px;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 10px 12px 9px 11px;
  padding: 6px 10px;
  cursor: pointer;
}
.ctl input[type="range"] {
  accent-color: var(--slider);
  width: 96px;
  cursor: pointer;
}
.ctl-val {
  color: var(--ink);
  font-size: 14px;
  white-space: nowrap;
}

/* ---- action buttons ---- */
.actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: center;
  margin-top: 20px;
}
.btnrow {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  width: min(340px, 90vw);
}
/* single full-width button, same footprint as a two-button row above/below it */
.btnrow.one {
  grid-template-columns: 1fr;
}
/* Start Round 2 CTA — fills the strip slot (same width/footprint as .tipstrip/.rateblock) */
.slotbtn {
  width: min(400px, 92vw);
  min-height: 68px;
  margin-top: 14px;
  font-size: 16px;
}
/* Confirm-move bar — nope | yes! in the strip slot, + a hint line. Keeps the 68px slot
   footprint (no layout shift) but the buttons themselves are shorter than the gamebar's. */
.confirmbar {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: min(400px, 92vw);
  min-height: 68px;
  margin-top: 14px;
}
.confirmrow {
  display: flex;
  gap: 12px; /* same gap as the INVITE/FORK/REVIEW gamebar */
  width: 100%;
}
.cfm-btn {
  flex: 1; /* equal size, flush left & right */
  min-height: 32px; /* shorter than the 40px small buttons */
  border: 2px solid var(--ink);
  border-radius: 8px 6px 9px 7px;
  color: var(--ink);
  font: inherit;
  font-weight: 800;
  font-size: 15px;
  cursor: pointer;
}
.cfm-yes { background: #63e29d; }
.cfm-no { background: #f0a0bc; }
.cfm-btn:active { filter: brightness(0.94); }
.confirmhint {
  font-size: 10px;
  letter-spacing: 0.04em;
  opacity: 0.55;
  text-align: center;
}
/* "Confirm each move" toggle in the ≡ drawer */
.drawsetting {
  display: flex;
  align-items: center;
  gap: 8px;
  /* first row of .actions now — the flex gap handles spacing. Sized to the sentence
     (~one .btnrow wide) so the checkbox lines up with the buttons below it; wraps
     instead of overflowing on narrow phones. */
  width: fit-content;
  max-width: min(352px, 92vw);
  font-size: 13px;
  cursor: pointer;
}
.drawsetting.speedrow {
  cursor: default;
  gap: 6px;
}
.drawsetting input {
  width: 18px;
  height: 18px;
}
.btn {
  padding: 9px 0;
  min-height: 44px; /* mobile guide: 44pt touch targets */
  text-align: center;
  border: 2px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-weight: 800;
  font-size: 14px;
  cursor: pointer;
  border-radius: 12px 10px 13px 9px;
}
/* interaction states per visual-design-outline: tint on roll, shade on click */
.btn:not(:disabled):hover {
  filter: brightness(1.05);
}
.btn:not(:disabled):active {
  filter: brightness(0.94);
}
.btn.r2 {
  border-radius: 11px 13px 10px 12px;
}
.btn.r3 {
  border-radius: 13px 9px 12px 11px;
}
.btn.r4 {
  border-radius: 10px 12px 11px 13px;
}
.btn.accent {
  background: var(--accent);
}
.btn.small {
  padding: 6px 14px;
  font-size: 13px;
  min-height: 40px;
}
.btn:active {
  transform: translateY(1px);
}
.btn:disabled {
  opacity: 0.5;
  cursor: default;
  transform: none;
}

/* ---- legend + footer ---- */
.legend {
  margin-top: 22px;
  font-size: 11px;
  letter-spacing: 0.04em;
  opacity: 0.6;
  max-width: 520px;
  text-align: center;
  line-height: 2;
}
.legend span {
  white-space: nowrap;
  margin: 0 8px;
}
.legend svg {
  vertical-align: middle;
  margin-right: 5px;
}
.sw-orig {
  stroke: var(--orig);
}
.sw-placed {
  stroke: var(--placed);
}
.lg-star {
  color: var(--star);
}
.legend .help {
  margin-top: 6px;
  line-height: 1.6;
}
.themeswitch {
  margin-top: 12px;
}
.themebtn {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  opacity: 0.75;
}
.themebtn.active {
  text-decoration: underline;
  font-weight: 800;
  opacity: 1;
}
.copyright {
  margin-top: 12px;
  opacity: 0.42;
  letter-spacing: 0.12em;
}

/* ---- online play (Phase 2.5-c): join/watch transit screens + invite/code dialogs ---- */
.joinmsg {
  font-family: var(--mono);
  color: var(--ink);
  text-align: center;
  padding: 18vh 24px 0;
  font-size: 18px;
  letter-spacing: 0.04em;
}
.linkrow {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 6px 0 14px;
}
.linkrow input {
  flex: 1;
  min-width: 0;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 8px 6px 9px 7px;
  padding: 8px;
}
.gbdialog .invname {
  justify-content: center;
  margin: 10px 0;
}
.gbdialog .codein {
  width: 100%;
  margin: 4px 0 2px;
}
.gbdialog .btn + .btn {
  margin-left: 8px;
}

/* ---- home game lists (Phase 2.5-f) ---- */
.gamelists {
  margin: 18px auto 0;
  max-width: 420px;
  text-align: left;
}
.listhead {
  font-family: var(--mono);
  color: var(--ink);
  font-size: 15px;
  letter-spacing: 0.08em;
  margin: 0 0 6px 4px;
}
.gamelist {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* cap note under "games on this device" — tells players local history is bounded */
.listcap {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.55;
  color: var(--ink);
  margin: 6px 4px 0;
}
.gamerow {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 10px 8px 11px 9px;
  padding: 8px 12px;
  cursor: pointer;
  text-align: left;
}
.gamerow:hover { filter: brightness(1.04); }
.gamerow:active { filter: brightness(0.94); }
.gamerow .g-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 800;
}
.gamerow .g-status { white-space: nowrap; opacity: 0.85; }
.gamerow .g-status.s-waiting { opacity: 0.6; }
.gamerow.yourturn { border-width: 3px; }
.gamerow.yourturn .g-status {
  opacity: 1;
  font-weight: 800;
  color: var(--ink);
  background: var(--badge, #bfe66b);
  padding: 1px 6px;
  border-radius: 6px 5px 7px 5px;
}
.gamerow .g-when { white-space: nowrap; opacity: 0.55; font-size: 11px; }

/* Watch & rate rows (2026-08-01): narrow-first for phones — the stamp moved into the left
   column as a third line, leaving ONE flush-right column for the score / JOIN ME! badge. */
.gamerow.watchrow .g-status {
  margin-left: auto;
  text-align: right;
  font-weight: 800;
  opacity: 1; /* beats the .s-waiting dimming below — the badge must not be faded */
}
.gamerow.watchrow .g-status.g-join {
  background: var(--accent);
  color: var(--ink);
  padding: 1px 6px;
  border-radius: 6px 5px 7px 5px;
}
/* right-column score at 16px on both watch & local cards (2026-09-19). The JOIN ME!
   badge isn't a score, so it keeps the row's 13px. */
.gamerow.watchrow .g-status:not(.g-join):not(.g-stack),
.gamerow.watchrow .g-status .g-score {
  font-size: 16px;
}
/* local cards: score on top, "round 2" / "mario won" / "tie" small underneath */
.gamerow.watchrow .g-status.g-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
}
.gamerow .g-status .g-sub {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 700;
  opacity: 0.6;
  white-space: nowrap;
}

/* 2.5-f tweak: name + created-stamp stack in "your games" rows */
.gamerow .g-left {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.gamerow .g-created {
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.55;
  white-space: nowrap;
}

/* GUI tweak #1 (2026-07-18): banner label rides high above the playername, ROUND-style */
.runbanner #turnline {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.1;
  min-width: 0;
}
.runbanner .turnlabel {
  font-size: 9px;
  letter-spacing: 0.12em;
  font-weight: 700;
  opacity: 0.8;
}
.runbanner .turnname {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* version tag in the learn dialog (small gray, under "got it!") */
.versiontag {
  margin-top: 12px;
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--ink);
  opacity: 0.4;
  text-align: center;
}

/* Review blink — the move under review (runner or the just-placed wall) blinks on/off so
   it's clear which move is being rated. Period set from REVIEW_BLINK_MS via --blink-ms. */
@keyframes gbblink {
  0%, 49.999% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
.gbboard .blink {
  animation: gbblink var(--blink-ms, 700ms) infinite;
}

/* ---- "what the what?!" walkthrough dialog (2026-08-14) --------------------------------
   7 panes (intro + 6 basic-gameplay diagrams) paged by ◀ ▶ / dots / arrow keys / swipe.
   Sized fluidly with min() rather than breakpoints, like the rest of this sheet. The KEY
   constraint: .learnbody has a FIXED height, so the nav row and "got it!" sit in the same
   spot on every pane — no jumping as you page. Panes with no image (the intro) give the
   whole body to the text; long copy scrolls inside the box instead of growing the dialog. */
.learndlg {
  /* ---- THE TWO KNOBS (change these, nothing else) ------------------------------------
     --learn-w    dialog width. FIXED, not a max: a <dialog> is width:fit-content by
                  default, so a max-width alone let the text-heavy intro pane run to 420px
                  while an image pane shrank to hug the .png. Same width on every pane now;
                  a narrow image just gets more space either side of it.
     --learn-fig-h  height of the image box. The body below is a fixed total, so whatever
                  you give the image is taken from the copy underneath it.

     USABLE IMAGE BOX (measured, CSS px) = --learn-w minus 50px of padding+border,
     by --learn-fig-h. Height drops below 300 once the viewport is under ~790px tall:
        viewport ≥457px wide   370 × 300   ← the 420px ceiling; design to THIS
        414px phone            331 × 300
        390px phone            309 × 300
        375px phone            295 × 300
        360px phone            281 × 243
        320px phone            244 × 216
     Images are never upscaled past their natural size (width:auto + max-width:100%), so a
     300px-wide .png stays 300px and centres with space either side. To fill the box on
     desktop author at 370px wide; export at 2× (740px) for crisp retina and let CSS scale
     it down. Landscape art wastes less: the box is 370×300, so anything up to ~1.23:1 fills
     the width. Raising --learn-fig-h takes that height straight off the copy underneath. */
  --learn-w: min(420px, 92vw);
  --learn-fig-h: min(300px, 38svh);

  width: var(--learn-w);
  max-width: var(--learn-w);
  max-height: 94svh; /* small-viewport unit: survives mobile browser chrome */
}
/* [open] is load-bearing. A <dialog> is display:none until it's opened; a bare
   `.learndlg { display: flex }` overrides that, so the dialog renders in the page flow
   forever — visible on load and still visible after close() strips the attribute. */
.learndlg[open] {
  display: flex;
  flex-direction: column;
}
.learndlg h3 {
  min-height: 1.3em; /* reserved even when a pane supplies no title, so nothing shifts */
  margin-bottom: 10px;
}
.learnbody {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  /* image box + text box, locked. Falls back to a share of the viewport on short screens.
     min-height:0 is load-bearing: without it a flex item's automatic minimum is its own
     content height, so a wordy pane would refuse to shrink and shove the arrows down. */
  min-height: 0;
  height: min(452px, 60svh);
}
.learnfig {
  flex: 0 0 auto;
  height: var(--learn-fig-h);
  min-height: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.learnfig[hidden] {
  display: none; /* beats the display:flex above — [hidden] alone would lose to it, and the
                    empty box would push a text-only pane's copy down the dialog */
}
.learnfig img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain; /* diagrams are ~square; never distort them */
}
/* The text box keeps whatever the figure leaves — full body on the text-only intro pane. */
.learntext {
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain; /* don't scroll-chain to the page behind the dialog */
  text-align: left;
  font-size: 13px;
  line-height: 1.5;
}
.learntext > :first-child {
  margin-top: 0;
}
.learntext > :last-child {
  margin-bottom: 0;
}
/* pane-1 logo lives INSIDE #learnText so learn.js swaps it out with the intro copy */
/* .gbdialog prefix needed: ".gbdialog p { text-align: left }" would otherwise win */
.gbdialog .learnlogo {
  text-align: center;
}
.gbdialog .learnlogo img {
  display: block;
  margin: 0 auto; /* centered as a block, independent of text-align */
  max-width: 100%;
  height: auto;
}

/* nav row: ◀ at the left edge · [got it!] centred · ▶ at the right edge (2026-09-19).
   The "N of M" count is hidden for now (hidden attr on #learnCount). */
.learnnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 12px 0 10px;
}
.learnnav #learnClose {
  flex: 0 0 auto; /* shrink-wrap the label instead of stretching across the dialog */
  min-width: 180px;
  min-height: 44px; /* same height as the arrows beside it */
}
.learncount[hidden] {
  display: none;
}
.navarrow {
  flex: 0 0 auto;
  width: 44px;
  min-height: 44px; /* mobile guide: 44pt touch targets */
  border: 2px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  border-radius: 12px 10px 13px 9px;
}
.navarrow.r2 {
  border-radius: 11px 13px 10px 12px;
}
.navarrow.r4 {
  border-radius: 10px 12px 11px 13px;
}
.navarrow:not(:disabled):hover {
  filter: brightness(1.05);
}
.navarrow:not(:disabled):active {
  filter: brightness(0.94);
  transform: translateY(1px);
}
.navarrow:disabled {
  opacity: 0.3;
  cursor: default;
}
/* "3 of 7" — replaces the dot row (2026-08-14): the pane count is discovered from the
   assets folder at runtime, so a fixed row of dots can't be laid out in advance, and a
   growing set of diagrams would eventually crowd the arrows off a phone. Fixed width and
   tabular figures so the arrows never twitch as the number changes. */
.learncount {
  flex: 0 0 auto;
  min-width: 5.5em;
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  opacity: 0.65;
  font-variant-numeric: tabular-nums;
}