/* /game/ lobby styles (WU-2). Self-contained: styles.css keeps owning the site
   header/nav, nothing here leaks back into it. Layout mirrors the reference
   lobby (deck panel · deck list · match rail); the palette is our own navy. */

.page-game {
  --game-bg: #070b14;
  --game-bg-2: #0b1220;
  --game-panel: #0e1729;
  --game-panel-2: #131f36;
  --game-line: #1e2b45;
  --game-line-soft: #17233a;
  --game-ink: #e8edf7;
  --game-muted: #93a2bd;
  --game-faint: #64748b;
  --game-accent: #6d5bf5;
  --game-accent-2: #22d3ee;
  --game-warn: #f6a03a;
  --game-gold: #c6a04e;
  --game-error: #f2637f;
  --game-ok: #4ade80;
  background:
    radial-gradient(1200px 620px at 50% -160px, #16243f 0%, rgb(9 14 26 / 0%) 62%),
    linear-gradient(180deg, #070b14 0%, #0a1120 52%, #070b14 100%);
  min-height: 100vh;
}

.page-game .site-header {
  border-bottom-color: var(--game-line-soft);
  background: rgb(7 11 20 / 82%);
}

.game-shell {
  margin: 0 auto;
  padding: 18px 20px 40px;
  max-width: 1420px;
}

.game-loading {
  padding: 8px 0 14px;
  color: var(--game-muted);
  font-size: 13px;
}

.game-loading[hidden] {
  display: none;
}

/* ------------------------------------------------------------------ head - */

.lobby-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 16px;
}

.lobby-head .eyebrow {
  color: var(--game-faint);
  font-size: 11px;
  font-weight: 760;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.lobby-head h1 {
  margin-top: 2px;
  color: var(--game-ink);
  font-size: 30px;
  letter-spacing: -0.01em;
}

.lobby-sub {
  margin-top: 4px;
  color: var(--game-muted);
  font-size: 14px;
}

.lobby-status {
  min-height: 18px;
  color: var(--game-accent-2);
  font-size: 13px;
  font-weight: 640;
}

/* ------------------------------------------------------------------ grid - */

.lobby-grid {
  display: grid;
  align-items: start;
  gap: 20px;
  grid-template-columns: 216px minmax(0, 1fr) minmax(340px, 400px);
}

.lobby-grid > .match-rail {
  padding-left: 20px;
  border-left: 1px solid var(--game-line-soft);
}

.deck-panel,
.deck-body,
.rail-block {
  border: 1px solid var(--game-line);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--game-panel) 0%, var(--game-bg-2) 100%);
}

.deck-panel {
  padding: 14px;
}

.deck-art {
  display: grid;
  overflow: hidden;
  border: 1px solid var(--game-line);
  border-radius: 10px;
  background: linear-gradient(160deg, #1b1430 0%, #101a2e 100%);
  aspect-ratio: 3 / 4;
  place-items: center;
}

.deck-art-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.deck-art-empty {
  padding: 0 12px;
  color: var(--game-faint);
  font-size: 12px;
  text-align: center;
}

.deck-counts {
  display: grid;
  margin: 14px 0 0;
  gap: 8px;
}

.deck-count {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.deck-count dt {
  color: var(--game-muted);
  font-size: 11px;
  font-variant-caps: all-small-caps;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.deck-count dd {
  margin: 0;
  color: var(--game-ink);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  font-weight: 780;
}

/* ------------------------------------------------------------- deck body - */

.deck-body {
  display: flex;
  flex-direction: column;
  padding: 16px 18px 18px;
  min-height: 460px;
  gap: 14px;
}

.deck-body-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--game-line-soft);
}

.deck-body-title h2 {
  color: var(--game-ink);
  font-size: 20px;
}

.deck-notice {
  display: inline-block;
  margin-top: 8px;
  padding: 5px 10px;
  border: 1px solid var(--game-line);
  border-radius: 7px;
  background: rgb(242 99 127 / 12%);
  color: var(--game-error);
  font-size: 12px;
  font-weight: 660;
}

/* .deck-notice / .deck-notice.tone-error share the blocking (red) look. */
.deck-notice.tone-ok {
  border-color: rgb(74 222 128 / 32%);
  background: rgb(74 222 128 / 10%);
  color: var(--game-ok);
}

.deck-tabs {
  display: flex;
  overflow: hidden;
  border: 1px solid var(--game-line);
  border-radius: 8px;
}

.deck-tabs button {
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--game-muted);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.deck-tabs button.is-active {
  background: var(--game-panel-2);
  color: var(--game-ink);
}

.deck-sections {
  display: grid;
  flex: 1;
  align-content: start;
  gap: 18px 22px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.deck-sections h3 {
  margin: 0 0 6px;
  color: var(--game-gold);
  font-size: 11px;
  font-variant-caps: all-small-caps;
  font-weight: 760;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.deck-empty {
  color: var(--game-faint);
  font-size: 13px;
}

.deck-line {
  display: grid;
  align-items: baseline;
  gap: 8px;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  padding: 2px 0;
  font-size: 13px;
}

.deck-line-qty {
  color: var(--game-accent-2);
  font-variant-numeric: tabular-nums;
  font-weight: 780;
}

.deck-line-name {
  overflow: hidden;
  color: var(--game-ink);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.deck-line-id {
  color: var(--game-faint);
  font-size: 11px;
}

/* ------------------------------------------------------------ deck stats - */

.deck-stats {
  display: grid;
  flex: 1;
  align-content: start;
  gap: 16px;
}

.stat-figures {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
}

.stat-figure {
  display: grid;
  padding: 10px 12px;
  border: 1px solid var(--game-line-soft);
  border-radius: 10px;
  background: var(--game-panel-2);
  gap: 2px;
}

.stat-value {
  color: var(--game-ink);
  font-size: 19px;
  font-weight: 800;
}

.stat-label {
  color: var(--game-muted);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.stat-block h3 {
  margin: 0 0 8px;
  color: var(--game-muted);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.stat-bar {
  display: grid;
  align-items: center;
  gap: 10px;
  grid-template-columns: 92px minmax(0, 1fr) 32px;
  padding: 3px 0;
  font-size: 12px;
}

.stat-bar-label {
  overflow: hidden;
  color: var(--game-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stat-bar-track {
  overflow: hidden;
  border-radius: 999px;
  background: var(--game-line-soft);
  height: 8px;
}

.stat-bar-fill {
  display: block;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--game-accent) 0%, var(--game-accent-2) 100%);
  height: 100%;
}

.stat-bar-count {
  color: var(--game-ink);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ---------------------------------------------------------- deck actions - */

.deck-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding-top: 14px;
  border-top: 1px solid var(--game-line-soft);
}

.deck-actions button,
.deck-action-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 0 16px;
  border: 1px solid var(--game-line);
  border-radius: 9px;
  background: var(--game-panel-2);
  color: var(--game-ink);
  font-size: 12px;
  font-weight: 760;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.deck-actions button:hover:not(:disabled),
.deck-action-link:hover {
  border-color: var(--game-accent);
}

/* Import Deck is the one action that unblocks this column, so it carries the filled
   purple; New Deck / Choose Deck stay outline secondaries like the reference. */
.deck-actions #importDeckButton {
  border-color: var(--game-accent);
  background: linear-gradient(180deg, var(--game-accent) 0%, #5a49d6 100%);
  color: #fff;
}

.deck-actions #chooseDeckButton,
.deck-action-link {
  border-color: rgb(109 91 245 / 46%);
  background: rgb(109 91 245 / 12%);
}

.deck-actions button:disabled {
  color: var(--game-faint);
  cursor: not-allowed;
  opacity: 0.55;
}

/* ------------------------------------------------------------ match rail - */

.match-rail {
  display: grid;
  gap: 14px;
}

.rail-block {
  display: grid;
  padding: 14px 16px 16px;
  gap: 10px;
}

.rail-block h2 {
  padding-bottom: 8px;
  border-bottom: 1px solid rgb(198 160 78 / 30%);
  color: var(--game-ink);
  font-size: 12px;
  font-variant-caps: all-small-caps;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.rail-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.rail-hint {
  color: var(--game-faint);
  font-size: 11px;
}

.rail-label {
  color: var(--game-gold);
  font-size: 10px;
  font-variant-caps: all-small-caps;
  font-weight: 760;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.rail-block input {
  min-height: 42px;
  border: 1px solid var(--game-line);
  border-radius: 9px;
  background: var(--game-bg);
  color: var(--game-ink);
}

.rail-choice {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.rail-choice button,
.rail-pair button {
  min-height: 42px;
  padding: 0 10px;
  border: 1px solid var(--game-line);
  border-radius: 9px;
  background: var(--game-panel-2);
  color: var(--game-muted);
  font-size: 11px;
  font-weight: 740;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.rail-choice button.is-active {
  border-color: var(--game-accent);
  background: linear-gradient(180deg, #1c2a4d 0%, #16223c 100%);
  color: var(--game-ink);
}

.rail-choice button:disabled,
.rail-pair button:disabled {
  color: var(--game-faint);
  cursor: not-allowed;
  opacity: 0.5;
}

.rail-primary {
  min-height: 46px;
  border: 0;
  border-radius: 10px;
  background: linear-gradient(90deg, var(--game-accent) 0%, #8b5cf6 55%, var(--game-accent-2) 160%);
  color: #fff;
  font-size: 12px;
  font-weight: 820;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.rail-primary:disabled {
  cursor: not-allowed;
  filter: saturate(0.35);
  opacity: 0.6;
}

.rail-pair {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* MP P3a: Watch is a third control in a two-column pair, so it takes the row under the code field
   instead of a half-width slot beside nothing. */
.rail-pair button.rail-watch {
  grid-column: 1 / -1;
}

.rail-pair button.rail-solo {
  border-color: var(--game-accent);
  background: linear-gradient(180deg, var(--game-accent) 0%, #5a49d6 100%);
  color: #fff;
  font-weight: 820;
}

.rail-error {
  color: var(--game-error);
  font-size: 12px;
  font-weight: 660;
}

/* ---------------------------------------------------------------- modals - */

.game-modal {
  position: fixed;
  display: grid;
  z-index: 40;
  padding: 24px 16px;
  background: rgb(4 7 14 / 74%);
  inset: 0;
  overflow-y: auto;
  place-items: center;
}

.game-modal[hidden] {
  display: none;
}

/* Import and Choose Deck can be opened from inside the Solo Setup wizard (two-sided practice
   picks the opponent deck there), so they must stack above it instead of behind it. */
#importModal,
#libraryModal {
  z-index: 45;
}

.game-modal-card {
  display: grid;
  width: min(760px, 100%);
  padding: 20px 22px 18px;
  border: 1px solid var(--game-line);
  border-radius: 16px;
  background: linear-gradient(180deg, var(--game-panel) 0%, var(--game-bg-2) 100%);
  box-shadow: 0 26px 70px rgb(0 0 0 / 52%);
  gap: 16px;
}

.game-modal-card.is-wide {
  width: min(980px, 100%);
}

.game-modal-head .eyebrow {
  color: var(--game-faint);
  font-size: 11px;
  font-weight: 760;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.game-modal-head h2 {
  margin-top: 2px;
  color: var(--game-ink);
  font-size: 22px;
}

.game-modal-status {
  margin-top: 6px;
  color: var(--game-muted);
  font-size: 13px;
}

.game-modal-status.tone-error {
  color: var(--game-error);
}

.import-body {
  display: grid;
  align-items: start;
  gap: 16px;
  grid-template-columns: minmax(0, 1fr) 300px;
}

.import-body[hidden] {
  display: none;
}

.import-body textarea {
  min-height: 280px;
  padding: 12px;
  border: 1px solid var(--game-line);
  border-radius: 10px;
  background: var(--game-bg);
  color: var(--game-ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.5;
  resize: vertical;
}

.import-validation h3,
.import-code h3 {
  margin: 0 0 6px;
  color: var(--game-muted);
  font-size: 11px;
  font-weight: 760;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.import-help {
  color: var(--game-faint);
  font-size: 12px;
  line-height: 1.5;
}

.import-counts,
.import-issues {
  display: grid;
  margin: 10px 0 0;
  padding: 0;
  gap: 5px;
  list-style: none;
}

.count-chip {
  padding: 4px 8px;
  border: 1px solid var(--game-line-soft);
  border-radius: 7px;
  background: var(--game-panel-2);
  color: var(--game-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.count-chip.is-ok {
  border-color: rgb(74 222 128 / 34%);
  color: var(--game-ok);
}

.issue-error,
.issue-warning {
  padding-left: 10px;
  border-left: 2px solid var(--game-error);
  color: var(--game-error);
  font-size: 12px;
  line-height: 1.45;
}

.issue-warning {
  border-left-color: var(--game-warn);
  color: var(--game-warn);
}

.import-code {
  grid-template-columns: minmax(0, 1fr);
}

.import-code input {
  min-height: 42px;
  border: 1px solid var(--game-line);
  border-radius: 9px;
  background: var(--game-bg);
  color: var(--game-ink);
}

.game-modal-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 14px;
  border-top: 1px solid var(--game-line-soft);
}

.game-modal-spacer {
  flex: 1;
}

.game-modal-foot button {
  min-height: 40px;
  padding: 0 16px;
  border: 1px solid var(--game-line);
  border-radius: 9px;
  background: var(--game-panel-2);
  color: var(--game-ink);
  font-size: 12px;
  font-weight: 740;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.game-modal-foot button[hidden] {
  display: none;
}

.game-modal-foot button.is-primary {
  border-color: transparent;
  background: linear-gradient(90deg, var(--game-accent) 0%, #8b5cf6 100%);
  color: #fff;
}

.game-modal-foot button:disabled {
  cursor: not-allowed;
  filter: saturate(0.3);
  opacity: 0.55;
}

/* --------------------------------------------------------- deck library - */

.library-list {
  display: grid;
  gap: 8px;
  max-height: 320px;
  overflow-y: auto;
}

.library-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 46px;
  padding: 0 14px;
  border: 1px solid var(--game-line);
  border-radius: 10px;
  background: var(--game-panel-2);
  color: var(--game-ink);
  gap: 12px;
  text-align: left;
}

.library-item:hover {
  border-color: var(--game-accent);
}

.library-name {
  font-size: 14px;
  font-weight: 700;
}

.library-meta {
  color: var(--game-faint);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ------------------------------------------------------------ solo setup - */

.solo-body {
  display: grid;
  gap: 16px;
}

.solo-heading {
  color: var(--game-ink);
  font-size: 15px;
  font-weight: 780;
}

.solo-modes {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.solo-mode {
  display: grid;
  align-content: start;
  padding: 16px;
  border: 1px solid var(--game-line);
  border-radius: 12px;
  background: var(--game-panel-2);
  gap: 8px;
}

.solo-badge {
  justify-self: start;
  padding: 3px 9px;
  border: 1px solid var(--game-line);
  border-radius: 999px;
  color: var(--game-accent-2);
  font-size: 10px;
  font-weight: 780;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.solo-mode h4,
.solo-swap-column h4,
.solo-opponent h4 {
  margin: 0;
  color: var(--game-ink);
  font-size: 15px;
}

.solo-note {
  color: var(--game-muted);
  font-size: 13px;
  line-height: 1.5;
}

.solo-note.is-strong {
  color: var(--game-accent-2);
  font-weight: 660;
}

.solo-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 4px;
}

.solo-button {
  min-height: 40px;
  padding: 0 16px;
  border: 1px solid var(--game-line);
  border-radius: 9px;
  background: var(--game-panel);
  color: var(--game-ink);
  font-size: 12px;
  font-weight: 740;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.solo-button.is-primary {
  border-color: transparent;
  background: linear-gradient(90deg, var(--game-accent) 0%, #8b5cf6 100%);
  color: #fff;
}

.solo-seat {
  display: grid;
  gap: 10px;
}

.solo-seat h3 {
  margin: 0;
  color: var(--game-ink);
  font-size: 16px;
}

.solo-seat-counts {
  color: var(--game-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.solo-swap {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.solo-swap-column {
  display: grid;
  align-content: start;
  gap: 5px;
  max-height: 300px;
  padding-right: 4px;
  overflow-y: auto;
}

.solo-card {
  display: grid;
  align-items: baseline;
  gap: 8px;
  grid-template-columns: 22px minmax(0, 1fr);
  min-height: 32px;
  padding: 0 10px;
  border: 1px solid var(--game-line-soft);
  border-radius: 8px;
  background: var(--game-panel);
  color: var(--game-ink);
  font-size: 12px;
  font-weight: 560;
  text-align: left;
}

.solo-card.is-selected {
  border-color: var(--game-accent);
  background: #1b2545;
}

.solo-battlefields {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.solo-battlefield {
  display: grid;
  overflow: hidden;
  padding: 0 0 8px;
  border: 1px solid var(--game-line);
  border-radius: 10px;
  background: var(--game-panel-2);
  color: var(--game-ink);
  gap: 8px;
}

.solo-battlefield img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.solo-battlefield.is-selected {
  border-color: var(--game-accent);
}

.solo-battlefield-name {
  padding: 0 10px;
  font-size: 12px;
  font-weight: 700;
}

.solo-opponent {
  display: grid;
  gap: 8px;
}

.solo-card-backs {
  display: flex;
  gap: 10px;
}

.solo-card-back {
  width: 64px;
  border: 1px solid var(--game-line);
  border-radius: 8px;
  background: repeating-linear-gradient(135deg, #16213a 0 6px, #101a2e 6px 12px);
  aspect-ratio: 4 / 3;
}

.solo-log {
  display: grid;
  gap: 3px;
  max-height: 110px;
  padding: 10px 12px;
  border: 1px solid var(--game-line-soft);
  border-radius: 10px;
  background: var(--game-bg);
  overflow-y: auto;
}

.solo-log-line {
  color: var(--game-muted);
  font-size: 12px;
}

/* ----------------------------------------------------------- launch stub - */

.game-launch {
  display: grid;
  margin-top: 12px;
  padding: 24px;
  border: 1px solid var(--game-line);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--game-panel) 0%, var(--game-bg-2) 100%);
  gap: 14px;
}

.game-launch h1 {
  color: var(--game-ink);
  font-size: 24px;
}

.launch-summary {
  display: grid;
  margin: 0;
  gap: 6px;
}

.launch-row {
  display: grid;
  gap: 12px;
  grid-template-columns: 160px minmax(0, 1fr);
  padding: 6px 0;
  border-bottom: 1px solid var(--game-line-soft);
}

.launch-row dt {
  color: var(--game-muted);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.launch-row dd {
  margin: 0;
  color: var(--game-ink);
  font-size: 13px;
}

/* ----------------------------------------------------------------- legal - */

.legal-note {
  margin: 0 auto;
  padding: 0 20px 34px;
  max-width: 1420px;
  color: var(--game-faint);
  font-size: 12px;
  line-height: 1.6;
}

body.has-modal {
  overflow: hidden;
}

/* ------------------------------------------------------------ responsive - */

@media (max-width: 1180px) {
  .lobby-grid {
    grid-template-columns: 200px minmax(0, 1fr);
  }

  .match-rail {
    grid-column: 1 / -1;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    display: grid;
  }

  .deck-sections {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 760px) {
  .game-shell {
    padding: 14px 12px 32px;
  }

  .lobby-grid,
  .deck-sections,
  .import-body,
  .solo-swap,
  .rail-choice,
  .rail-pair {
    grid-template-columns: minmax(0, 1fr);
  }

  .deck-art {
    max-width: 220px;
  }
}

/* ================================================================= board == */
/* WU-5. The board is a single fixed-viewport screen: nothing below the fold,
   no page scroll. Layout mirrors the reference capture (.prd/game-client/loop.md
   "보드 시각 레퍼런스") and docs/playground-prd.md §10.1 — the two 0-8 score
   rails flank the WHOLE board full-height, the opponent strip sits on top, the
   two shared lanes take the dominant middle row, the player band runs along the
   bottom edge, and the utility rail (turn/room, Match Log, Chain, inspector) is
   a fixed right column. Our own branding and palette; only the arrangement is
   cloned. */

.game-board[hidden],
.game-overlay[hidden],
.board-popover[hidden],
.board-notice[hidden],
.board-watch[hidden],
.board-transport[hidden],
#switchPerspective[hidden] {
  display: none;
}

/* While a match is live the document itself is the viewport: header, board and
   the (compacted) legal note are the only three rows, and the board takes the
   rest. documentElement.scrollHeight then equals the viewport height. */
body.page-game.is-playing {
  display: flex;
  flex-direction: column;
  height: 100vh;
  min-height: 0;
  overflow: hidden;
}

/* The shell does the clipping, not the body: CSS propagates `overflow` set on body up to
   the viewport and leaves the body box itself visible, so a body-only rule would let an
   overlong rail grow documentElement.scrollHeight right back. */
body.page-game.is-playing .game-shell {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  padding: 8px 10px 6px;
  max-width: none;
}

body.page-game.is-playing .game-root {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
}

body.page-game.is-playing .legal-note {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  flex: 0 0 auto;
  padding: 0 12px 5px;
  max-width: none;
  font-size: 10px;
  line-height: 1.35;
}

/* UA element defaults leak straight into the generated board markup: a card is a <figure>
   and Chrome gives it `margin: 1em 40px`, which inflated every hand card's flex base by 80px
   and shrank the cards to a third of their width. <dl>/<dd>/<ol>/<h3> ship their own. */
.card,
.card-badges,
.seat-counters,
.seat-counter dt,
.seat-counter dd,
.match-log,
.lane-title {
  margin: 0;
  padding: 0;
}

.game-board {
  /* Card geometry is height-derived so the whole board scales with the viewport
     instead of overflowing it. Widths come from the printed 744x1039 ratio. */
  --card-h: clamp(62px, 10.4vh, 104px);
  --card-h-sm: clamp(46px, 7.4vh, 74px);
  --card-h-hand: clamp(104px, 16.6vh, 168px);
  --card-h-bf: clamp(56px, 9vh, 92px);
  /* Printed card is 744x1039; widths stay definite so flex never asks the <img> how wide it
     would like to be. */
  --card-w: calc(var(--card-h) * 744 / 1039);
  --card-w-sm: calc(var(--card-h-sm) * 744 / 1039);
  --card-w-hand: calc(var(--card-h-hand) * 744 / 1039);
  --card-w-bf: calc(var(--card-h-bf) * 1039 / 744);
  /* G2 (WU-H): the score steps are badges, not dots, so the flanking rails have to
     be wide enough for a badge plus the vertical seat name beside it. */
  --score-step: clamp(28px, 2.2vw, 34px);
  --rail-w: clamp(48px, 3.6vw, 62px);
  --util-w: clamp(248px, 19vw, 330px);

  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  /* MP P3i: the arrow overlay is absolutely placed against the board, so the board
     is what it is measured from. */
  position: relative;
}

/* MP P3i ④ (03-ui §6, [REF-IMG] 곡선): annotations over the table. `pointer-events: none`
   is the load-bearing line — an arrow is something a player SAYS, and a thing you say
   must never intercept a thing you do. Sized by viewBox from the board's own rect at
   paint time (game-render.js `paintArrows`) — and since N6 a ResizeObserver on the board
   repaints it, because "the next paint" only ever came from a frame or a gesture, and a
   window resize is neither: the curves stayed where the cards used to be. */
.board-arrows {
  position: absolute;
  inset: 0;
  /* R5-K: said outright rather than left to `inset` alone. An SVG is a replaced element with no
     intrinsic size here, and what an engine does with `auto` width/height on an absolutely
     positioned one is its own business — Chromium covers the board, which is one engine agreeing
     and not a contract. `paintArrows` writes a viewBox sized from the board's rect, so the box it
     is painted into has to BE the board's box on every engine, not just the one we looked at. */
  width: 100%;
  height: 100%;
  z-index: 3;
  overflow: visible;
  pointer-events: none;
}

.board-arrow {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  opacity: 0.85;
  /* Seat colours are assigned by class below; this is the fallback for a seat id the
     stylesheet has not been taught, which is a board with more seats than the room has. */
  stroke: var(--game-accent);
}

.board-arrow.seat-p1 {
  stroke: var(--game-accent-2);
}

.board-arrow.seat-p2 {
  stroke: var(--game-warn);
}

/* R5-H: the head, in the seat's own ink. A marker renders in its own context and inherits
   from where it is DEFINED rather than from the path that references it, so `currentColor`
   would resolve against the overlay and not against the line — the seat classes are spelled
   out here instead, from the same variables the strokes above use. */
.board-arrow-head {
  fill: var(--game-accent);
}

.board-arrow-head.seat-p1 {
  fill: var(--game-accent-2);
}

.board-arrow-head.seat-p2 {
  fill: var(--game-warn);
}

/* The ghost's head is quiet for the same reason its line is: nothing has been said yet.
   Opacity has to be set here too — the path's own opacity does not reach its marker. */
.board-arrow-head.is-ghost {
  opacity: 0.5;
}

/* The arrow being drawn right now: the same ink, said quietly, because it is not a
   statement until the pointer comes up. */
.board-arrow.is-ghost {
  opacity: 0.5;
  stroke-dasharray: 6 5;
}

.board-grid {
  display: grid;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  gap: 8px;
  grid-template-columns: var(--rail-w) minmax(0, 1fr) var(--rail-w) var(--util-w);
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "self opponent foe rail"
    "self lanes    foe rail"
    "self band     foe rail";
}

#scoreTrackSelf {
  grid-area: self;
}

#scoreTrackOpponent {
  grid-area: foe;
}

#opponentZone {
  grid-area: opponent;
}

#boardLanes {
  grid-area: lanes;
}

#playerZone {
  grid-area: band;
}

.board-rail {
  grid-area: rail;
}

.board-seat,
.board-lanes {
  border: 1px solid var(--game-line);
  border-radius: 12px;
  background: rgb(14 23 41 / 72%);
  padding: 7px 9px;
  min-width: 0;
  min-height: 0;
}

.board-seat.is-opponent {
  border-color: var(--game-line-soft);
}

/* ------------------------------------------------------ opponent strip ---- */

.board-seat.is-opponent .seat-row {
  display: grid;
  align-items: center;
  gap: 10px;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
}

.seat-identity {
  min-width: 0;
}

.seat-name {
  color: var(--game-ink);
  font-size: 13px;
  font-weight: 720;
}

.seat-counters {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 4px;
}

.seat-counter {
  display: flex;
  align-items: baseline;
  gap: 4px;
  border: 1px solid var(--game-line);
  border-radius: 7px;
  padding: 2px 6px;
  color: var(--game-muted);
  font-size: 11px;
}

.seat-counter dt {
  color: var(--game-faint);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.seat-counter dd {
  color: var(--game-ink);
  font-weight: 700;
}

.seat-cards,
.seat-decks {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

/* The opponent strip must stay shallow so the lanes keep the dominant row: their two deck
   piles sit side by side, not stacked like the player's own. */
.board-seat.is-opponent .seat-decks {
  flex-direction: row;
  align-items: flex-end;
  gap: 10px;
}

.seat-base,
.rune-row,
.base-units,
.resource-panel {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.card-row,
.hand-fan {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 5px;
  min-height: 26px;
}

/* WU-L: the hand card's Alt+click hint, for the readers a `title` attribute never reaches.
   Clipped rather than `display: none` — the latter drops it out of the accessibility tree, which
   is the one place this node exists to be. */
.board-hint {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The opponent's face-down hand overlaps into a fan like the reference. */
.hand-fan.is-facedown {
  flex-wrap: nowrap;
  justify-content: center;
  gap: 0;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
}

.hand-fan.is-facedown .card-back + .card-back {
  margin-left: -22px;
}

.card-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

.zone-label {
  color: var(--game-faint);
  font-size: 9px;
  font-weight: 720;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.zone-empty {
  color: var(--game-faint);
  font-size: 11px;
}

/* --------------------------------------------------------- player band ---- */
/* One horizontal band along the bottom edge like the reference: runes and base
   on the left, the hand fan (the largest player-facing cards) in the centre,
   resources/decks and the legend/champion pair on the right. */

.board-seat.is-player .seat-row {
  display: grid;
  align-items: end;
  gap: 6px 12px;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "left  left   identity"
    "right center identity";
}

.board-seat.is-player > .seat-row > .band-left {
  grid-area: left;
}

.board-seat.is-player > .seat-row > .band-right {
  grid-area: right;
}

.board-seat.is-player > .seat-row > .band-center {
  grid-area: center;
}

.board-seat.is-player > .seat-row > .seat-cards {
  grid-area: identity;
  align-items: flex-end;
}

.band-left,
.band-right {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  min-width: 0;
}

.band-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  min-width: 0;
  overflow: hidden;
}

/* The fan is centred inside a track it may not exceed: stretch it to the track and let
   justify-content do the centring, instead of leaving the width shrink-to-fit (which the
   card <img> children inflate past the track). */
.band-center .hand-fan {
  justify-content: center;
  flex-wrap: nowrap;
  gap: 0;
  align-self: stretch;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  /* The horizontal clip stays (a wide hand may not push the band), but the arc
     dips ~6px below the baseline and the hover lift rises ~6px above it, so the
     track carries that much padding instead of shearing both off. */
  padding: 10px 0 8px;
  overflow: hidden;
}

.band-center .hand-fan > .card {
  flex: 0 1 auto;
  min-width: 0;
}

.resource-panel {
  gap: 2px;
}

.seat-decks {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

/* -------------------------------------------------------------- cards ---- */

.card {
  position: relative;
  display: flex;
  flex: 0 0 auto;
  align-items: flex-end;
  justify-content: center;
  border: 1px solid var(--game-line);
  border-radius: 6px;
  background: var(--game-panel-2);
  height: var(--card-h);
  width: var(--card-w);
  overflow: hidden;
}

.card.is-small {
  height: var(--card-h-sm);
  width: var(--card-w-sm);
}

.card.is-battlefield {
  height: var(--card-h-bf);
  width: var(--card-w-bf);
}

.hand-fan > .card {
  height: var(--card-h-hand);
  width: var(--card-w-hand);
}

.card-art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card-name {
  padding: 3px;
  color: var(--game-ink);
  font-size: 9px;
  line-height: 1.2;
  text-align: center;
}

.card-badges {
  position: absolute;
  top: 2px;
  left: 2px;
  display: flex;
  gap: 3px;
}

.card-cost,
.card-might,
.card-counter,
.card-xp {
  border-radius: 4px;
  padding: 0 4px;
  font-size: 10px;
  font-weight: 760;
}

.card-cost {
  background: rgb(109 91 245 / 92%);
  color: #fff;
}

.card-might {
  background: rgb(242 99 127 / 92%);
  color: #fff;
}

.card-counter {
  background: rgb(74 222 128 / 92%);
  color: #04240f;
}

/* Z4: XP reads as its own track, so it must not be mistaken for the generic "+n" counter pile. */
.card-xp {
  background: rgb(250 204 21 / 94%);
  color: #2a1d00;
  letter-spacing: 0.02em;
}

/* Z4: free-text status chips along the bottom of the card. The engine caps them at 4 and the
   renderer slices to 4, so this row never needs to wrap past two lines. */
.card-labels {
  position: absolute;
  right: 2px;
  bottom: 2px;
  left: 2px;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 2px;
  pointer-events: none;
}

.card-label {
  border-radius: 3px;
  background: rgb(8 14 28 / 88%);
  padding: 0 3px;
  max-width: 100%;
  overflow: hidden;
  color: var(--game-ink);
  font-size: 8px;
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.card.is-clickable,
.card.is-inspectable {
  cursor: pointer;
}

.card.is-clickable:hover,
.card.is-inspectable:hover {
  outline: 2px solid var(--game-accent-2);
}

.card.is-playable {
  box-shadow: 0 0 0 1px var(--game-ok) inset;
}

.card.is-unplayable {
  filter: saturate(45%);
}

.card.is-exhausted {
  transform: rotate(8deg);
  filter: brightness(70%);
}

/* MP P3h (03-ui H3): where the other player is looking. Deliberately not the cyan
   hover ring — that ring is THIS pointer, and a peer's attention must never be
   mistaken for your own affordance. A dashed ring outside the card's own outline
   says "somebody else is reading this" without moving anything: no layout change,
   because the highlight arrives while a click may be mid-compose (H1). Ranked
   under selection below, which is a decision this player made. */
.card.is-peer-hovered {
  outline: 2px dashed var(--game-accent);
  outline-offset: 2px;
}

/* Selection has to survive the pointer: the mulligan is picked by hovering the
   very cards it selects, so the orange ring must beat the cyan hover ring.
   Same specificity as the hover rules above, declared after them. */
.card.is-selected,
.card.is-selected:hover,
.card.is-clickable.is-selected:hover,
.card.is-inspectable.is-selected:hover {
  outline: 3px solid var(--game-warn);
  outline-offset: 0;
  box-shadow:
    0 0 0 4px rgb(246 160 58 / 32%),
    0 6px 18px rgb(246 160 58 / 22%);
  filter: none;
}

/* ----------------------------------------------------- hand fan (G3) ----- */
/* The reference hand is a fan, not a strip: every card covers ~18% of the one
   before it and the outermost cards tilt a couple of degrees off vertical. CSS
   only — the DOM order IS the fan order, so nothing here moves a card out from
   under the drag (D1/D2) or the click guard. */

.hand-fan {
  --fan-overlap: calc(var(--card-w-hand) * 0.18);
  gap: 0;
}

.hand-fan > .card {
  /* The arc buckets below write only these custom properties, so :hover can lift a
     card without having to out-specify a :nth-child chain. */
  --fan-rot: 0deg;
  --fan-dy: 0px;
  --fan-lift: 0px;
  transform-origin: bottom center;
  transform: translateY(calc(var(--fan-dy) + var(--fan-lift))) rotate(var(--fan-rot));
  transition: transform 110ms ease-out;
}

.hand-fan > .card + .card {
  margin-left: calc(var(--fan-overlap) * -1);
}

/* Four symmetric buckets, and only from four cards up: a two-card hand stays flat
   rather than tilting the one card it is holding. */
.hand-fan > .card:nth-child(1):nth-last-child(n + 4) {
  --fan-rot: -4deg;
  --fan-dy: 6px;
}

.hand-fan > .card:nth-child(2):nth-last-child(n + 4) {
  --fan-rot: -2deg;
  --fan-dy: 2px;
}

.hand-fan > .card:nth-last-child(2):nth-child(n + 4) {
  --fan-rot: 2deg;
  --fan-dy: 2px;
}

.hand-fan > .card:nth-last-child(1):nth-child(n + 4) {
  --fan-rot: 4deg;
  --fan-dy: 6px;
}

/* Pointer or keyboard pulls a card clear of its neighbours, so the overlapped 18%
   never costs a click or a drag start. */
.hand-fan > .card:hover,
.hand-fan > .card:focus-visible,
.hand-fan > .card.is-selected {
  --fan-lift: -6px;
  z-index: 3;
}

/* liftGhost() (game-solo.js) clones the origin node and sizes the ghost from that
   node's own getBoundingClientRect(). A rotated card would hand it the ROTATED
   bounding box — wider and offset — so the card the pointer grabbed drops back to
   an upright, unlifted rect for the duration of the drag. */
.hand-fan > .card.is-dragging {
  transform: none;
  /* The transition has to go with it: liftGhost() reads the rect from a FORCED
     synchronous recalc one statement after `is-dragging` lands, and a running
     transform transition would still be sampling the old, rotated value there. */
  transition: none;
}

/* ---------------------------------------------------------- card back ---- */
/* Our own back, not the one in the reference capture: deep navy, a thin gold
   frame and a centred diamond behind the site mark. CSS only, no asset. */

.card-back {
  position: relative;
  container-type: inline-size;
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px solid rgb(198 160 78 / 55%);
  border-radius: 6px;
  background:
    radial-gradient(70% 55% at 50% 42%, rgb(120 104 232 / 26%) 0%, rgb(9 15 30 / 0%) 70%),
    linear-gradient(165deg, #101b34 0%, #0a1123 55%, #060b18 100%);
  height: var(--card-h-sm);
  width: var(--card-w-sm);
  box-shadow: inset 0 0 0 3px rgb(9 14 27 / 90%);
}

/* Deck piles are the reference's full-size backs, not thumbnails. */
.deck-stack > .card-back {
  height: var(--card-h);
  width: var(--card-w);
}

.card-back::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 46%;
  aspect-ratio: 1;
  transform: translate(-50%, -54%) rotate(45deg);
  border: 1px solid rgb(198 160 78 / 62%);
  background: linear-gradient(135deg, rgb(198 160 78 / 16%), rgb(109 91 245 / 14%));
}

.card-back::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 26%;
  aspect-ratio: 1;
  transform: translate(-50%, -54%) rotate(45deg);
  border: 1px solid rgb(198 160 78 / 40%);
}

.card > .card-back {
  height: 100%;
  width: 100%;
}

.card-back-mark {
  position: absolute;
  bottom: 7%;
  left: 0;
  right: 0;
  color: rgb(214 180 104 / 92%);
  font-size: 8.5cqw;
  font-weight: 800;
  letter-spacing: 0.02em;
  white-space: nowrap;
  text-align: center;
}

.card-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--game-line);
  border-radius: 6px;
  background: rgb(19 31 54 / 45%);
  height: var(--card-h-bf);
  width: var(--card-w-bf);
  color: var(--game-faint);
  font-size: 10px;
  text-align: center;
}

.card-placeholder.is-portrait {
  height: var(--card-h);
  width: var(--card-w);
}

.deck-stack {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--game-muted);
  font-size: 10px;
  white-space: nowrap;
}

.deck-stack-count {
  color: var(--game-ink);
  font-size: 14px;
  font-weight: 780;
}

/* Z1: a Trash pile is a pile, not a number — it shows its top card face-up and opens on click. */
.deck-stack.is-clickable {
  cursor: pointer;
}

.deck-stack.is-clickable:hover {
  color: var(--game-ink);
  outline: 2px solid var(--game-accent-2);
  outline-offset: 2px;
  border-radius: 6px;
}

.card-placeholder.is-pile {
  height: var(--card-h-sm);
  width: var(--card-w-sm);
  font-size: 8px;
}

/* -------------------------------------------------------------- lanes ---- */

.board-lanes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  min-height: 0;
}

.lane {
  /* G1 (WU-H): a lane is an OPEN ZONE, not a panel. The reference draws a thin,
     low-opacity rounded border around a transparent area and hangs the lane's
     identity in the corners — there is no header bar eating the top row, so the
     whole box belongs to the cards. */
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border: 1px solid rgb(180 205 255 / 15%);
  border-radius: 12px;
  background: transparent;
  padding: 17px 10px 9px;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

/* Control is a border tint, never a fill: the zone must stay readable through. */
.lane.is-held {
  border-color: rgb(246 160 58 / 42%);
}

.lane.is-mine {
  border-color: rgb(34 211 238 / 44%);
}

/* The head is lifted out of the flow into the top inset of the zone, and it is
   inert: D5 hit-tests the nearest [data-zone] under the pointer, and a lane label
   must never be the thing a dropped card lands on. */
.lane-head {
  position: absolute;
  top: 3px;
  right: 8px;
  left: 8px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  pointer-events: none;
}

.lane-title {
  color: rgb(232 237 247 / 46%);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  line-height: 1.4;
  text-transform: uppercase;
  white-space: nowrap;
}

.lane-controller {
  border: 1px solid rgb(180 205 255 / 15%);
  border-radius: 999px;
  padding: 0 7px;
  color: var(--game-faint);
  font-size: 9px;
  letter-spacing: 0.06em;
  line-height: 1.6;
  white-space: nowrap;
}

.lane.is-held .lane-controller {
  border-color: rgb(246 160 58 / 42%);
  color: var(--game-warn);
}

.lane.is-mine .lane-controller {
  border-color: rgb(34 211 238 / 44%);
  color: var(--game-accent-2);
}

/* The battlefield card is anchored left-of-centre and the unit stack takes the
   rest of the zone, centred, like the reference. */
.lane-body {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}

.lane-body > .card.is-battlefield,
.lane-body > .card-placeholder {
  flex: 0 0 auto;
  align-self: center;
}

.lane-units {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  gap: 5px;
  min-width: 0;
}

.lane-showdown {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border: 1px solid var(--game-error);
  border-radius: 8px;
  padding: 5px 7px;
  color: var(--game-error);
  font-size: 11px;
}

/* ------------------------------------------------------- score tracks ---- */
/* Full-height rails flanking the whole board, like the reference. */

.score-column {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  border: 1px solid var(--game-line-soft);
  border-radius: 12px;
  background: rgb(11 18 32 / 62%);
  padding: 6px 2px;
}

.score-track {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 100%;
  min-height: 0;
}

.score-track-name {
  writing-mode: vertical-rl;
  flex: 0 0 auto;
  overflow: hidden;
  max-height: 12ch;
  color: var(--game-faint);
  font-size: 10px;
  letter-spacing: 0.16em;
  line-height: 1;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

.score-track-list {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  justify-content: space-evenly;
  align-items: center;
  gap: 2px;
  min-height: 0;
}

.score-step {
  /* Rounded-square badges sized to be read and hit, not 24px dots. */
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border: 1px solid var(--game-line);
  border-radius: 8px;
  background: rgb(19 31 54 / 72%);
  width: var(--score-step);
  height: var(--score-step);
  color: var(--game-muted);
  font-size: 12px;
  font-weight: 720;
  cursor: pointer;
}

.score-step:disabled {
  cursor: default;
  opacity: 0.55;
}

/* Everything already scored is a soft violet fill; only the value the seat is ON
   gets the bright ring, so the current score reads from across the board. */
.score-step.is-reached {
  border-color: rgb(109 91 245 / 52%);
  background: rgb(109 91 245 / 20%);
  color: var(--game-ink);
}

.score-step.is-current {
  border: 2px solid var(--game-accent-2);
  background: rgb(34 211 238 / 18%);
  box-shadow:
    0 0 0 3px rgb(34 211 238 / 18%),
    0 0 14px rgb(34 211 238 / 42%);
  color: var(--game-ink);
}

/* ---------------------------------------------------------- resources ---- */

.stepper {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--game-muted);
  font-size: 10px;
}

.stepper-label {
  min-width: 44px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 9px;
}

.stepper-button {
  border: 1px solid var(--game-line);
  border-radius: 6px;
  background: var(--game-panel-2);
  width: 20px;
  height: 20px;
  color: var(--game-ink);
  line-height: 1;
  cursor: pointer;
}

.stepper-value {
  min-width: 16px;
  color: var(--game-ink);
  font-size: 12px;
  font-weight: 760;
  text-align: center;
}

.board-button {
  border: 1px solid var(--game-line);
  border-radius: 8px;
  background: var(--game-panel-2);
  padding: 5px 10px;
  color: var(--game-ink);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.board-button.is-primary {
  border-color: var(--game-accent);
  background: var(--game-accent);
  color: #fff;
}

.board-button:disabled {
  cursor: default;
  opacity: 0.45;
}

.board-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 5px;
}

/* --------------------------------------------------------------- rail ---- */

/* styles.css also owns a `.board-rail` (the /decks/ board sidebar) and gives it
   `position: sticky; height: fit-content`. Both are fatal here — fit-content opts the rail
   out of the grid stretch and it grows past the viewport — so this restates them. */
.board-rail {
  position: static;
  display: flex;
  flex-direction: column;
  gap: 7px;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}

.rail-panel {
  border: 1px solid var(--game-line);
  border-radius: 10px;
  background: rgb(14 23 41 / 82%);
  padding: 7px 9px;
  min-height: 0;
}

.rail-panel.is-room {
  flex: 0 0 auto;
}

.rail-panel.is-log {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 72px;
}

.rail-panel.is-chain {
  flex: 0 0 auto;
  overflow: hidden;
}

.rail-panel.is-inspector {
  display: flex;
  flex: 0 1 auto;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.rail-panel.is-active {
  border-color: var(--game-accent-2);
}

.rail-title {
  color: var(--game-faint);
  font-size: 10px;
  font-weight: 760;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.board-banner {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.banner-title {
  color: var(--game-ink);
  font-size: 18px;
  font-weight: 760;
  line-height: 1.15;
}

.banner-active {
  color: var(--game-accent-2);
  font-size: 12px;
}

.banner-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 3px;
  color: var(--game-muted);
  font-size: 11px;
}

.banner-view {
  border: 1px solid var(--game-accent);
  border-radius: 999px;
  background: rgb(109 91 245 / 18%);
  padding: 2px 8px;
  color: var(--game-ink);
  font-weight: 700;
}

.board-notice {
  color: var(--game-warn);
  font-size: 11px;
}

/* MP P3a: the standing statement that this board belongs to somebody else. It sits with the notice
   rather than over the cards because a watcher is here to watch them, and it is quiet (muted, not
   warn) because watching is not a problem being reported — the notice line above stays free for
   what just happened. */
.board-watch {
  color: var(--game-muted);
  font-size: 11px;
  letter-spacing: 0.02em;
}

/* MP P2b: the in-flight line, under the notice and above the rail buttons. It
   sits in the banner rather than over the board on purpose — an unanswered
   submission does not stop play (.prd/04-multiplayer.md §3 P2b ②, §4), so it
   must not take the pointer or the eye away from the cards. */
.board-transport {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin-top: 3px;
  color: var(--game-warn);
  font-size: 11px;
}

.board-transport.tone-error {
  color: var(--game-error);
}

.transport-discard {
  border: 1px solid currentcolor;
  border-radius: 999px;
  background: transparent;
  padding: 1px 7px;
  color: inherit;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.transport-discard:hover,
.transport-discard:focus-visible {
  background: rgb(242 99 127 / 18%);
}

.rail-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 7px;
}

.match-log {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 3px;
  margin-top: 5px;
  min-height: 0;
  overflow: auto;
  list-style: none;
}

.log-entry {
  display: flex;
  align-items: baseline;
  gap: 6px;
  border-left: 2px solid var(--game-line);
  padding-left: 6px;
  color: var(--game-muted);
  font-size: 11px;
}

.log-time {
  color: var(--game-faint);
  font-variant-numeric: tabular-nums;
}

.log-actor {
  color: var(--game-accent-2);
}

.log-text {
  flex: 1;
  color: var(--game-ink);
}

.log-rewind {
  border: 0;
  background: none;
  color: var(--game-warn);
  font-size: 12px;
  cursor: pointer;
}

.chain-list,
.chain-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 5px;
}

.chain-item {
  color: var(--game-ink);
  font-size: 12px;
}

.inspector-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 5px;
  min-height: 0;
  overflow: auto;
}

.inspector-art {
  align-self: center;
  border-radius: 8px;
  width: auto;
  max-width: 100%;
  max-height: clamp(120px, 21vh, 240px);
  object-fit: contain;
}

.inspector-name {
  color: var(--game-ink);
  font-size: 13px;
  font-weight: 720;
}

.inspector-meta {
  color: var(--game-accent-2);
  font-size: 11px;
}

.inspector-text {
  color: var(--game-muted);
  font-size: 11px;
  line-height: 1.45;
}

/* ----------------------------------------------------------- popover ----- */

.board-popover {
  position: absolute;
  z-index: 40;
  border: 1px solid var(--game-accent);
  border-radius: 10px;
  background: var(--game-panel);
  padding: 8px;
  min-width: 190px;
  box-shadow: 0 18px 40px rgb(0 0 0 / 45%);
}

.popover-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.popover-title {
  color: var(--game-ink);
  font-size: 12px;
  font-weight: 760;
}

.popover-entry,
.popover-close {
  border: 1px solid var(--game-line);
  border-radius: 7px;
  background: var(--game-panel-2);
  padding: 5px 8px;
  color: var(--game-ink);
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.popover-entry:disabled {
  cursor: default;
  opacity: 0.5;
}

.popover-reason {
  color: var(--game-warn);
  font-size: 11px;
}

.popover-label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  color: var(--game-faint);
  font-size: 11px;
}

.popover-input {
  border: 1px solid var(--game-line);
  border-radius: 6px;
  background: var(--game-bg-2);
  padding: 5px 7px;
  color: var(--game-ink);
  font-size: 12px;
}

/* Z4: one row per label already on the card, with its own remove. */
.popover-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

.popover-chip-remove {
  border: 1px solid var(--game-line);
  border-radius: 6px;
  background: var(--game-panel-2);
  padding: 0 6px;
  color: var(--game-warn);
  font-size: 12px;
  line-height: 1.6;
  cursor: pointer;
}

/* Z2: the Trash viewer is the popover with a two-column card list instead of a menu. */
.popover-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  min-width: 288px;
  max-height: 320px;
  overflow-y: auto;
}

.popover-card {
  display: flex;
  align-items: center;
  gap: 5px;
  border: 1px solid var(--game-line);
  border-radius: 7px;
  background: var(--game-panel-2);
  padding: 4px;
}

/* The popover lives outside .game-board, so the board's --card-* custom properties do not
   reach it (same reason .mulligan-hand .card pins its own size). */
.popover-card .card {
  flex: 0 0 auto;
  height: 54px;
  width: auto;
  aspect-ratio: 744 / 1039;
}

.popover-card-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.popover-card-name {
  overflow: hidden;
  color: var(--game-ink);
  font-size: 10px;
  line-height: 1.25;
}

.popover-card-banish {
  border: 1px solid var(--game-line);
  border-radius: 6px;
  background: var(--game-panel);
  padding: 2px 5px;
  color: var(--game-warn);
  font-size: 10px;
  text-align: left;
  cursor: pointer;
}

/* ---------------------------------------------------------- overlays ----- */

.game-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgb(4 7 14 / 78%);
  padding: 20px;
}

.overlay-card {
  border: 1px solid var(--game-line);
  border-radius: 16px;
  background: linear-gradient(180deg, var(--game-panel-2), var(--game-panel));
  padding: 20px 26px;
  width: min(620px, 100%);
  text-align: center;
}

.overlay-card h2 {
  margin-top: 4px;
  color: var(--game-ink);
  font-size: 24px;
}

.overlay-seat {
  margin-top: 4px;
  color: var(--game-accent-2);
  font-size: 13px;
  font-weight: 700;
}

.overlay-note {
  margin-top: 6px;
  color: var(--game-muted);
  font-size: 12px;
}

.mulligan-hand {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin: 14px 0 6px;
}

/* The opening hand is the whole point of this overlay: pin both axes so the flex line
   cannot shrink the four cards down to thumbnails. */
.mulligan-hand .card {
  flex: 0 0 auto;
  height: clamp(150px, 24vh, 208px);
  width: auto;
  aspect-ratio: 744 / 1039;
}

.overlay-actions {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 14px;
}

.result-scores {
  display: flex;
  justify-content: center;
  gap: 14px;
  margin-top: 10px;
  color: var(--game-muted);
  font-size: 13px;
}

.game-board.is-locked .board-seat,
.game-board.is-locked .board-lanes,
.game-board.is-locked .score-column {
  filter: saturate(60%) brightness(85%);
}

/* Narrow viewports give up the fixed-viewport promise and scroll, because the
   rail cannot sit beside a 2-lane board below ~1180px. */
@media (max-width: 1180px) {
  body.page-game.is-playing {
    display: block;
    height: auto;
    min-height: 100vh;
    overflow: visible;
  }

  body.page-game.is-playing .game-shell,
  body.page-game.is-playing .game-root {
    display: block;
  }

  .board-grid {
    grid-template-columns: 48px minmax(0, 1fr) 48px;
    grid-template-rows: auto auto auto auto;
    grid-template-areas:
      "self opponent foe"
      "self lanes    foe"
      "self band     foe"
      "rail rail     rail";
  }

  .board-rail {
    overflow: visible;
  }
}

/* ----------------------------------------------------- drag & drop (WU-B) - */
/* .prd/03-ui-interactions.md D3/D4/D7 + M1-M2. The card the board rendered never moves: it dims
   in place as the footprint the reference leaves behind, and a cloned ghost pinned to <body>
   follows the pointer by transform alone, so dragging costs no layout work and no re-render can
   tear the lifted card away. */

.game-board.is-dragging {
  cursor: grabbing;
  user-select: none;
}

.game-board.is-dragging .card.is-clickable {
  cursor: grabbing;
}

.card.is-dragging {
  opacity: 0.34;
  outline: 2px dashed var(--game-line);
  outline-offset: -2px;
  filter: grayscale(60%);
}

/* D3: the lift is an enlargement plus a shadow. The scale lives inside the same transform the
   pointer writes (game-solo.js dragTransform, GHOST_LIFT_SCALE = 1.06) — the snap-back rewrites
   the translation only, so the card never changes size while it flies home. */
.card-ghost {
  position: fixed;
  z-index: 60;
  margin: 0;
  opacity: 0.94;
  box-shadow:
    0 0 0 2px var(--game-accent-2),
    0 26px 52px rgb(4 7 14 / 68%);
  pointer-events: none;
  transform: translate(0, 0) scale(1.06);
  transform-origin: center center;
  will-change: transform;
}

.card-ghost.is-snapping {
  transition: transform 150ms ease-out;
}

[data-zone].is-drop-target {
  outline: 2px solid var(--game-ok);
  outline-offset: 2px;
  background: rgb(74 222 128 / 10%);
  transition: background 120ms ease-out;
}

[data-zone].is-drop-blocked {
  outline: 2px dashed var(--game-error);
  outline-offset: 2px;
  cursor: not-allowed;
}

/* M2: the lift and the snap-back are the only motion this round adds; both go instant here. */
@media (prefers-reduced-motion: reduce) {
  .card-ghost,
  .card-ghost.is-snapping,
  .hand-fan > .card,
  [data-zone].is-drop-target {
    transition-duration: 0.01ms;
  }
}
