/* ==========================================================================
   HTN Family Game - stylesheet
   Warm, playful, mobile first. No framework, no external font.
   Screens are toggled with the class "active" on section.screen.
   ========================================================================== */

/* ---------------------------------------------------------------- tokens */
:root {
  color-scheme: dark light;

  /* warm dark palette is the default */
  --bg: #17110d;
  --card: #241a14;
  --text: #f7ece1;
  --text2: #c0a794;
  --border: #3d2c21;
  --primary: #ff8f45;
  --primary-ink: #2a1403;
  --x-color: #ff7a6b;
  --o-color: #58d3c8;
  --ok: #6bd68a;
  --warn: #ffce5c;
  --danger: #ff6a6a;

  /* extras used by the layout */
  --input: #1c1410;
  --board: #2e2018;
  --shadow: 0 10px 26px rgba(0, 0, 0, 0.38);
  --radius: 18px;

  /* Read by board.js: readColors() asks for exactly these names. Leaving any
     of them undefined makes the canvas fall back to its built in light theme,
     so the board would stay cream coloured inside the dark shell. */
  --board-bg: #3a2819;
  --board-line: rgba(247, 236, 225, 0.22);
  --board-edge: rgba(247, 236, 225, 0.44);
  --board-star: rgba(247, 236, 225, 0.38);
  --last-color: #ffce5c;
  --win-color: #6bd68a;

  /* Bàn cờ vua. Kem ấm và xanh rêu thay cho nâu gỗ quen thuộc, để bàn cờ
     thuộc về trang này chứ không phải dán từ nơi khác vào. */
  --cv-light: #dcc7a8;
  --cv-dark: #5f8a85;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #fff6ec;
    --card: #ffffff;
    --text: #35251a;
    --text2: #7c6555;
    --border: #ecd9c6;
    --primary: #e2661a;
    --primary-ink: #ffffff;
    --x-color: #d94436;
    --o-color: #128b81;
    --ok: #2e9d54;
    --warn: #a9760a;
    --danger: #cf3535;

    --input: #fffaf4;
    --board: #f6e3cb;
    --shadow: 0 10px 24px rgba(120, 80, 40, 0.14);

    --board-bg: #f6e3cb;
    --board-line: rgba(120, 84, 34, 0.34);
    --board-edge: rgba(120, 84, 34, 0.62);
    --board-star: rgba(120, 84, 34, 0.55);
    --last-color: #d97706;
    --win-color: #16a34a;

    --cv-light: #f5e6cd;
    --cv-dark: #7fb0aa;
  }
}

/* ------------------------------------------------------------------ base */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  font-size: 17px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
    "Noto Color Emoji";
  line-height: 1.5;
  min-height: 100dvh;
  overflow-x: hidden;
}

h1, h2, h3 { line-height: 1.25; margin: 0 0 8px; }
h1 { font-size: 1.6rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }
p { margin: 0 0 10px; }

::selection { background: var(--primary); color: var(--primary-ink); }

:focus-visible {
  outline: 3px solid var(--primary);
  outline-offset: 2px;
}

/* Generic visibility helpers. #modal is excluded because it may also be opened
   by a class while it still carries the hidden attribute, see the modal block
   at the end of this file. */
[hidden]:not(#modal) { display: none !important; }
.hidden:not(#modal) { display: none !important; }

/* ----------------------------------------------------------------- shell */
#app {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 14px 14px calc(18px + env(safe-area-inset-bottom));
}

/* --------------------------------------------------------------- screens */
.screen { display: none; }
.screen.active { display: block; }
.screen[hidden] { display: none; }

/* Pane switcher.
   app.js switches panes with the hidden attribute (setVisible) and, for the
   lobby only, additionally with the class "active". The hidden attribute is
   therefore the authority; the class based rule below exists only to keep every
   pane from flashing on screen during the first paint, before app.js runs, and
   it steps aside the moment any child carries hidden or .hidden.

   Do NOT add a `.switch > .pane:not(.active) { display: none }` fallback for
   browsers without :has(). #form-register never receives the class "active":
   app.js reveals it by clearing its hidden attribute only, so such a fallback
   makes the whole register form unreachable. A short flash of both auth forms
   on a pre 2023 browser is the lesser problem. */
.switch:has(> .active):not(:has(> [hidden])):not(:has(> .hidden)) > :not(.active) {
  display: none;
}

/* ----------------------------------------------------------------- cards */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: var(--shadow);
}

.section-title {
  margin: 16px 0 8px;
  font-size: 1rem;
  color: var(--text2);
  letter-spacing: 0.02em;
}

.hint {
  color: var(--text2);
  font-size: 0.88rem;
  margin: -4px 0 12px;
}

.foot-note {
  margin: 18px 0 0;
  text-align: center;
  color: var(--text2);
  font-size: 0.9rem;
}

/* --------------------------------------------------------------- buttons */
button,
.btn {
  font: inherit;
  font-weight: 700;
  min-height: 44px;
  padding: 11px 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card);
  color: var(--text);
  cursor: pointer;
  line-height: 1.2;
  text-align: center;
  transition: transform 0.08s ease, background 0.15s ease, border-color 0.15s ease;
}

button:hover,
.btn:hover { border-color: var(--primary); }

button:active,
.btn:active { transform: translateY(1px); }

button:disabled,
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Ô bàn cờ KHÔNG phải nút bấm, dù nó là thẻ <button>.
   Luật ngay trên kia viết cho nút thật: nút mờ đi nghĩa là "chưa bấm được".
   Nhưng bàn cờ dựng bằng <button> cho từng ô, và ô bị vô hiệu hoá chỉ có
   nghĩa "chưa chạm được vào đây lúc này" chứ không phải "quân này mờ đi".

   Hậu quả nếu không có khối này: thế khai cuộc cờ vua có 12 trên 16 quân
   không có nước đi hợp lệ, nên 12 quân ấy hiện ra ở độ mờ 45% và người chơi
   thấy đội quân của mình như bị xoá dở. Ở cờ tướng còn nặng hơn: khi chưa
   tới lượt thì CẢ BÀN mờ đi.

   Con trỏ vẫn để mặc định thay vì not-allowed, vì "không chạm được ô này"
   là chuyện bình thường của bàn cờ, không phải một lời từ chối. */
.cv-square:disabled,
.ct-point:disabled,
.oq-cell:disabled,
.bt-cell:disabled {
  opacity: 1;
  cursor: default;
}

.btn-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-ink);
}
.btn-primary:hover { filter: brightness(1.06); }

.btn-danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}

.btn-ghost { background: transparent; }

.btn-block { width: 100%; }
.btn-small { padding: 8px 12px; font-size: 0.92rem; white-space: nowrap; }
.btn-icon { min-width: 44px; padding: 8px; font-size: 1rem; }
.btn-big { min-height: 60px; font-size: 1.05rem; }
.btn-emoji { font-size: 1.25rem; }

/* ---------------------------------------------------------------- inputs */
input,
select,
textarea {
  font: inherit;
  font-size: 16px; /* keeps iOS from zooming on focus */
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--input);
  color: var(--text);
}

input::placeholder { color: var(--text2); opacity: 0.85; }

select {
  appearance: none;
  padding-right: 34px;
  background-image: linear-gradient(45deg, transparent 50%, var(--text2) 50%),
    linear-gradient(135deg, var(--text2) 50%, transparent 50%);
  background-position: right 16px center, right 11px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

input[type="checkbox"] {
  width: 22px;
  height: 22px;
  min-height: 0;
  margin: 0;
  accent-color: var(--primary);
}

.form .field:last-of-type { margin-bottom: 14px; }

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 12px;
}

.field-label {
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--text2);
}

.field-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.check {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  font-weight: 600;
}

/* ------------------------------------------------------------------ tabs */
.tabs {
  display: flex;
  gap: 6px;
  padding: 6px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
}

.tabs-wide { overflow-x: auto; }

.tab {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 9px 8px;
  font-size: 0.95rem;
  white-space: nowrap;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--text2);
  font-weight: 700;
}

.tab:hover { color: var(--text); }

.tab.active {
  background: var(--primary);
  color: var(--primary-ink);
}

/* ----------------------------------------------------------------- lists */
.listbox {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 2px 14px;
}

.listbox > * {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
  overflow-wrap: anywhere;
}

.listbox > *:last-child { border-bottom: 0; }

.listbox button { margin-left: auto; }

.listbox:empty {
  padding: 16px 14px;
  color: var(--text2);
  font-size: 0.93rem;
}

.listbox-tight > * { padding: 8px 0; }

#room-list:empty::after { content: "Chưa có phòng nào đang mở. Bấm Tạo phòng để mở ván mới."; }
#online-list:empty::after { content: "Chưa có ai trực tuyến."; }
#rank-list:empty::after { content: "Bảng xếp hạng còn trống. Chơi một ván để có tên ở đây."; }
#history-list:empty::after { content: "Bạn chưa chơi ván nào."; }
#spectator-list:empty::after { content: "Chưa có khán giả."; }
#chat-log:empty::after { content: "Chưa có tin nhắn. Chúc cả nhà chơi vui."; }

/* ------------------------------------------------------------ auth screen */
.brand {
  text-align: center;
  padding: 22px 0 18px;
}

.brand-mark {
  display: inline-flex;
  gap: 6px;
  font-size: 1.5rem;
  font-weight: 900;
  padding: 10px 16px;
  border-radius: 18px;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
}

.brand-name {
  margin: 12px 0 4px;
  font-size: 1.7rem;
}

.brand-sub {
  margin: 0;
  color: var(--text2);
}

.auth-card { max-width: 460px; margin: 0 auto; }
.auth-card .tabs { margin-bottom: 16px; }

.alert {
  margin: 12px 0 0;
  padding: 10px 14px;
  border: 1px solid var(--danger);
  border-radius: 12px;
  background: rgba(255, 106, 106, 0.12);
  color: var(--danger);
  font-weight: 600;
}

#auth-error:empty { display: none; }

/* ----------------------------------------------------------- lobby screen */
.me-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
}

.avatar {
  font-size: 2rem;
  line-height: 1;
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 14px;
  background: var(--bg);
  border: 1px solid var(--border);
}

.me-info {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1 1 auto;
  min-width: 0;
}

.me-name {
  font-weight: 800;
  font-size: 1.1rem;
  overflow-wrap: anywhere;
}

.me-meta {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.chip {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 0.82rem;
  color: var(--text2);
}

.chip-elo {
  color: var(--primary);
  font-weight: 800;
}

.actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin: 14px 0;
}

.actions .btn { flex: 1 1 45%; }
#btn-quick { flex-basis: 100%; }

.join-row {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}

#join-code {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  text-align: center;
  font-weight: 800;
}

/* the placeholder is a sentence, it should not be spaced out like a code */
#join-code::placeholder {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}

#btn-join { flex: 0 0 auto; }

/* ------------------------------------------------------------ room screen */
.room-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.room-id {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-right: auto;
}

.room-label {
  color: var(--text2);
  font-size: 0.9rem;
}

.room-code {
  font-size: 1.35rem;
  letter-spacing: 0.14em;
  color: var(--primary);
}

.room-opts {
  flex: 1 0 100%;
  order: 5;
  color: var(--text2);
  font-size: 0.9rem;
}

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

.board-col { min-width: 0; }
.side-col { min-width: 0; }

/* The two clocks take a fixed ~100px each, so on a phone the middle column was
   left with under 100px and the turn line broke over three lines. Let it wrap
   onto its own full width row instead; on a wide screen it still fits inline. */
.scoreline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  padding: 10px 14px;
  margin-bottom: 10px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
}

.side {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}

.side-right { justify-content: flex-end; }

.mid {
  display: flex;
  flex: 1 1 150px;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  min-width: 0;
}

/* Narrow phone: the two clocks share the first line, the turn line gets the
   whole second line. Without the explicit order it is the O clock that wraps,
   and the two clocks end up on different lines. This block must stay AFTER the
   .mid rule above, otherwise the flex shorthand there resets flex-basis. */
@media (max-width: 560px) {
  .side { order: 1; }
  .mid { order: 2; flex-basis: 100%; }
}

.turn {
  font-weight: 800;
  overflow-wrap: anywhere;
}

.moves {
  font-size: 0.82rem;
  color: var(--text2);
}

.mark {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid currentColor;
  border-radius: 10px;
  font-weight: 900;
}

.mark-x { color: var(--x-color); }
.mark-o { color: var(--o-color); }

.clock {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 1.15rem;
  font-weight: 800;
}

/* board
   app.js appends the replay bar INSIDE #board-wrap, so the wrapper must be a
   column that grows, not a fixed square. The square lives on the canvas itself
   (board.js later replaces that with an exact pixel size). */
.board-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
  max-width: min(620px, 76vh);
  margin: 0 auto;
  padding: 6px;
  background: var(--board);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  /* NOT touch-action: none. board.js sets touchAction on the canvas itself,
     "none" while it accepts moves and "auto" otherwise, so that spectators and
     players looking at a finished game can still scroll the page with a finger
     resting on the board. touch-action on this ancestor would override that. */
  touch-action: manipulation;
}

#board,
.board-canvas {
  display: block;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 12px;
}

.result {
  margin: 10px 0 0;
  padding: 12px 16px;
  border: 1px solid var(--primary);
  border-radius: 14px;
  background: var(--card);
  font-weight: 800;
  text-align: center;
}

#game-result:empty { display: none; }

/* seats */
.seats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.seat {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 12px;
}

.seat-x-card { border-left: 4px solid var(--x-color); }
.seat-o-card { border-left: 4px solid var(--o-color); }

.seat-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 0.95rem;
}

.seat-title .mark {
  width: 26px;
  height: 26px;
  font-size: 0.85rem;
}

/* #seat-x / #seat-o. app.js empties these and rebuilds the contents:
   .seat-head > .seat-mark, then .seat-body with .seat-avatar / .seat-name /
   .seat-elo, then .seat-status, then an optional .btn-kick. */
.seat-slot {
  display: block;
  min-height: 44px;
  margin-bottom: 8px;
  overflow-wrap: anywhere;
}

.seat-slot.is-turn {
  border-radius: 12px;
  outline: 2px solid var(--primary);
  outline-offset: 4px;
}

.seat-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}

.seat-mark {
  font-size: 0.82rem;
  font-weight: 800;
  color: var(--text2);
  letter-spacing: 0.04em;
}

.seat-body {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-weight: 700;
}

.seat-avatar { font-size: 1.4rem; line-height: 1; }
.seat-name { flex: 1 1 auto; min-width: 0; }
.seat-elo { color: var(--primary); font-weight: 800; font-size: 0.9rem; }

.seat-empty,
.seat-status {
  color: var(--text2);
  font-weight: 400;
  font-size: 0.88rem;
}

.seat-status { margin-top: 4px; }

/* Scoped to the seat card only. The same class also appears on the kick button
   inside a spectator row, where .listbox button { margin-left: auto } already
   places it and a full width button would break the row. */
.seat-slot .btn-kick { margin-top: 8px; width: 100%; }

/* Kept for the first paint, before app.js fills the seat. */
#seat-x:empty::after,
#seat-o:empty::after {
  content: "Ghế trống";
  color: var(--text2);
  font-weight: 400;
}

.btn-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin: 12px 0;
}

.btn-span2 { grid-column: span 2; }

/* chat
   Fixed height with its own scrollbar, as required. Messages are .chat-row
   elements built by app.js; the composer below is .chat-compose so the two do
   not share a class. */
.chat-log {
  height: 180px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.chat-log > * {
  display: block;
  padding: 7px 0;
  border-bottom: 0;
  overflow-wrap: anywhere;
}

.chat-log strong,
.chat-log b { color: var(--primary); }

.chat-who {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-right: 6px;
  font-weight: 800;
  color: var(--primary);
}

.chat-avatar { font-size: 1.05rem; line-height: 1; }
.chat-name { overflow-wrap: anywhere; }
.chat-text { overflow-wrap: anywhere; }

.chat-time {
  margin-left: 6px;
  color: var(--text2);
  font-size: 0.78rem;
  white-space: nowrap;
}

.chat-row.is-system {
  color: var(--text2);
  font-style: italic;
}

.chat-compose {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

.chat-compose .btn { flex: 0 0 auto; }

/* ------------------------------------------------------------ lobby picks */
/* Which game, and how strong the computer plays. One row above the buttons. */
.lobby-picks {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: flex-end;
  margin-bottom: 10px;
}

.field-inline {
  flex: 1 1 150px;
  margin-bottom: 0;
}

/* ================================================================= rows ===
   Everything below is built at run time by app.js. The class names are taken
   straight from that file; leaving them unstyled is what made the lists render
   as one unbroken run of text and hid the state flags (is-low, is-turn,
   is-me, is-win). Keep this section in step with app.js.
   ======================================================================== */

/* placeholder used by app.js when a list comes back empty */
.empty {
  color: var(--text2);
  font-size: 0.93rem;
}

/* ---- lobby: room list */
.room-row,
.online-row,
.rank-row,
.history-row,
.spectator-row {
  min-height: 44px;
}

.room-row-code {
  font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--primary);
}

.room-row-name {
  font-weight: 700;
  flex: 1 1 120px;
  min-width: 0;
}

.room-row-meta,
.room-row-players,
.room-row-status {
  color: var(--text2);
  font-size: 0.85rem;
}

.room-row-ranked {
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--warn);
  font-size: 0.78rem;
  font-weight: 700;
}

/* ---- lobby: online list */
.online-avatar,
.rank-avatar,
.history-avatar,
.spectator-avatar {
  font-size: 1.5rem;
  line-height: 1;
}

.online-name,
.spectator-name {
  font-weight: 700;
  flex: 1 1 auto;
  min-width: 0;
}

.online-elo {
  color: var(--primary);
  font-weight: 800;
  font-size: 0.88rem;
}

/* ---- leaderboard */
.rank-pos {
  min-width: 30px;
  font-weight: 800;
  color: var(--text2);
  font-variant-numeric: tabular-nums;
}

.rank-name {
  font-weight: 700;
  flex: 1 1 120px;
  min-width: 0;
}

.rank-elo {
  font-weight: 800;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
}

.rank-record {
  color: var(--text2);
  font-size: 0.85rem;
  flex-basis: 100%;
}

.rank-row.is-me {
  border-radius: 12px;
  box-shadow: inset 3px 0 0 var(--primary);
  padding-left: 10px;
}

/* ---- match history. The whole row is clickable (role="button"). */
.history-row { cursor: pointer; }
.history-row:hover { color: var(--primary); }

.history-outcome {
  min-width: 58px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 800;
  text-align: center;
}

.history-outcome.is-win { color: var(--ok); border-color: var(--ok); }
.history-outcome.is-loss { color: var(--danger); border-color: var(--danger); }
.history-outcome.is-draw { color: var(--warn); border-color: var(--warn); }

.history-opponent {
  font-weight: 700;
  flex: 1 1 110px;
  min-width: 0;
}

.history-meta,
.history-date {
  color: var(--text2);
  font-size: 0.83rem;
}

.history-date { flex-basis: 100%; }

.history-elo {
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--primary);
}

/* ---- replay bar, appended by app.js inside #board-wrap */
.replay-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-wrap: wrap;
  width: 100%;
  padding: 4px 2px 2px;
}

.replay-label {
  min-width: 74px;
  text-align: center;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* ---- yes/no dialog, appended by app.js inside #modal-body */
.dialog-title { margin: 0 0 6px; font-size: 1.1rem; }
.dialog-text { margin: 0 0 16px; color: var(--text2); }

.dialog-actions {
  display: flex;
  gap: 10px;
}

.dialog-actions .btn { flex: 1 1 auto; }

/* ---- state flags set by app.js */
/* clock running low. Without this the only warning was the digits themselves. */
.clock.is-low {
  color: var(--danger);
  animation: blink 1s steps(2, start) infinite;
}

@keyframes blink {
  to { opacity: 0.45; }
}

/* #btn-quick while queuing */
.btn.is-active {
  background: var(--warn);
  border-color: var(--warn);
  color: var(--primary-ink);
}

/* app.js sets the disabled property as well, this only covers non buttons */
.is-disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ----------------------------------------------------------------- toast */
#toast {
  position: fixed;
  left: 50%;
  bottom: calc(14px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: min(92vw, 430px);
  pointer-events: none;
}

#toast[hidden] { display: none; }
#toast:empty { display: none; }

#toast > * {
  width: 100%;
  padding: 12px 16px;
  background: var(--card);
  border: 1px solid var(--primary);
  border-radius: 14px;
  box-shadow: var(--shadow);
  color: var(--text);
  font-weight: 700;
  text-align: center;
  animation: pop 0.18s ease;
}

/* app.js calls toast(message, 'error') for failures, which adds .toast-error.
   Without this rule an error looked exactly like a confirmation. */
#toast > .toast-error {
  border-color: var(--danger);
  color: var(--danger);
}

/* when app.js writes the text straight into the container */
#toast:not(:empty):not(:has(*)) {
  padding: 12px 16px;
  background: var(--card);
  border: 1px solid var(--primary);
  border-radius: 14px;
  box-shadow: var(--shadow);
  font-weight: 700;
  text-align: center;
}

/* ----------------------------------------------------------------- modal */
#modal {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.55);
}

/* closed by the hidden attribute in the markup */
#modal[hidden] { display: none; }

/* opened by class as well, these come last so they win a tie */
#modal.open,
#modal.active,
#modal.show,
#modal.visible { display: flex; }

.modal-card {
  width: min(560px, 100%);
  max-height: min(88vh, 760px);
  overflow-y: auto;
  padding: 18px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 20px;
  box-shadow: var(--shadow);
  animation: pop 0.18s ease;
}

.modal-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}

/* app.js reuses #modal for yes/no dialogs and hides #form-create when it does.
   The header says "Tao phong moi", so it must not sit on top of a draw offer.
   Written as "hide when the form is hidden" on purpose: a browser without
   :has() simply keeps the header, which is the old behaviour, rather than
   losing the header of the create form as well. */
#modal:has(#form-create[hidden]) .modal-head { display: none; }

.modal-title {
  margin: 0;
  margin-right: auto;
  font-size: 1.2rem;
}

.modal-foot {
  display: flex;
  gap: 10px;
  margin-top: 16px;
}

.modal-foot .btn { flex: 1 1 auto; }

@keyframes pop {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ------------------------------------------------------------ wide screen */
@media (min-width: 900px) {
  #app { padding: 24px; }

  .room-grid {
    grid-template-columns: minmax(0, 1fr) 366px;
    gap: 20px;
    align-items: start;
  }

  .room-opts {
    flex: 0 1 auto;
    order: 0;
    margin-right: 8px;
  }

  .chat-log { height: 240px; }

  .btn-small { min-height: 38px; }

  .actions .btn { flex: 1 1 0; }
  #btn-quick { flex-basis: 0; }

  .board-wrap { max-width: min(640px, 74vh); }
}

@media (min-width: 1200px) {
  .room-grid { grid-template-columns: minmax(0, 1fr) 400px; }
}

/* --------------------------------------------------------- motion, print */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   Ô ăn quan
   Real elements rather than a canvas: the counts are the game, and text in
   the DOM stays readable and legible to a screen reader without redrawing
   any of it by hand.
   ───────────────────────────────────────────────────────────────────────── */

.oq {
  display: flex;
  align-items: stretch;
  gap: 8px;
  width: 100%;
}

.oq-rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}

/* Seat o sits on the far row. Flipping puts whoever is playing at the bottom,
   which is where a person expects their own side of a board to be. */
.oq.is-flipped .oq-rows { flex-direction: column-reverse; }

.oq-row {
  display: flex;
  gap: 6px;
}

.oq-cell {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  min-height: 62px;
  padding: 6px 4px 4px;
  border: 2px solid var(--border);
  border-radius: 12px;
  background: var(--card);
  color: var(--text);
  font: inherit;
  cursor: default;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  transition: border-color .15s, background .15s, transform .1s;
}

.oq-cell.is-mine { border-color: color-mix(in srgb, var(--primary) 55%, var(--border)); }
.oq-cell:not(:disabled) { cursor: pointer; }
.oq-cell:not(:disabled):hover { background: color-mix(in srgb, var(--primary) 12%, var(--card)); }
.oq-cell.is-selected {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 22%, var(--card));
  transform: translateY(-2px);
}
.oq-cell.is-trail { border-color: color-mix(in srgb, var(--o-color) 60%, var(--border)); }
.oq-cell.is-empty .oq-count { opacity: .45; }

.oq-count { font-size: 18px; font-weight: 700; line-height: 1; }

.oq-dots {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 3px;
  max-width: 100%;
  min-height: 14px;
}

.oq-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--primary);
  display: block;
}

/* The two arrows only appear on the square that has been picked up, so a tap
   never has to mean two different things depending on what happened before. */
.oq-arrows {
  position: absolute;
  inset: auto 0 -14px;
  display: flex;
  justify-content: center;
  gap: 6px;
  z-index: 2;
}

.oq-arrow {
  width: 34px;
  height: 30px;
  border: 2px solid var(--primary);
  border-radius: 8px;
  background: var(--primary);
  color: var(--primary-ink);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.oq-arrow:hover { filter: brightness(1.08); }

.oq-quan {
  flex: 0 0 74px;
  border: 2px solid var(--border);
  border-radius: 999px;
  background: color-mix(in srgb, var(--o-color) 14%, var(--card));
  color: var(--text);
  font: inherit;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 8px 4px;
}
.oq-quan.is-taken { opacity: .4; }
.oq-quan-label { font-size: 11px; color: var(--text2); letter-spacing: .04em; }
.oq-quan-count { font-size: 17px; font-weight: 700; }

.oq-hint {
  margin: 20px 0 0;
  text-align: center;
  color: var(--text2);
  font-size: 13px;
}

@media (max-width: 560px) {
  .oq { gap: 5px; }
  .oq-row { gap: 4px; }
  .oq-quan { flex-basis: 52px; }
  .oq-cell { min-height: 56px; padding: 5px 2px 3px; }
  .oq-count { font-size: 16px; }
  .oq-dot { width: 6px; height: 6px; }
  /* Touch targets stay comfortable even when the squares get narrow. */
  .oq-arrow { width: 44px; height: 34px; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Bắn tàu
   Two seas. The left one is mine and shows my fleet; the right one is the
   opponent's and shows only what I have found out about it.
   ───────────────────────────────────────────────────────────────────────── */

.bt { display: flex; flex-direction: column; gap: 12px; width: 100%; }

.bt-setup {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 12px;
  border: 2px dashed var(--border);
  border-radius: 12px;
}
.bt-setup-text { flex: 1 1 auto; color: var(--text2); font-size: 13px; }
.bt-btn { min-height: 40px; padding: 0 14px; }

.bt-seas { display: flex; gap: 14px; align-items: flex-start; }
.bt-sea { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 6px; }

.bt-sea-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.bt-sea-title { font-weight: 700; font-size: 13px; }
.bt-sea-left { font-size: 12px; color: var(--text2); }

.bt-grid {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 2px;
  aspect-ratio: 1 / 1;
}

.bt-cell {
  border: 1px solid var(--border);
  border-radius: 3px;
  background: color-mix(in srgb, var(--o-color) 10%, var(--card));
  padding: 0;
  min-width: 0;
  font: inherit;
  color: var(--text);
  position: relative;
}
.bt-cell:not(:disabled) { cursor: pointer; }
.bt-cell:not(:disabled):hover { background: color-mix(in srgb, var(--primary) 30%, var(--card)); }

/* My own ships. Sunk ones stay visible: I already know they are gone. */
.bt-cell.is-ship { background: color-mix(in srgb, var(--text2) 55%, var(--card)); }
.bt-cell.is-ship-sunk { background: color-mix(in srgb, var(--x-color) 45%, var(--card)); }

/* A miss is a dot, a hit is a cross. Shape as well as colour, so the board
   still reads for someone who cannot tell the two colours apart. */
.bt-cell.is-miss::after {
  content: '';
  position: absolute;
  inset: 38%;
  border-radius: 50%;
  background: var(--text2);
}
.bt-cell.is-hit::after {
  content: '✕';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--x-color);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

.bt-ships { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px; }
.bt-ship {
  display: flex;
  gap: 4px;
  align-items: baseline;
  padding: 2px 7px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 11px;
  color: var(--text2);
}
.bt-ship.is-sunk { text-decoration: line-through; opacity: .5; border-color: var(--x-color); }
.bt-ship-len { font-variant-numeric: tabular-nums; }

.bt-hint { margin: 10px 0 0; text-align: center; color: var(--text2); font-size: 13px; }

@media (max-width: 620px) {
  /* Two ten by ten grids side by side stop being tappable on a phone, so they
     stack and each one gets the full width. */
  .bt-seas { flex-direction: column; gap: 18px; }
  .bt-grid { gap: 1px; }
  .bt-cell.is-hit::after { font-size: 13px; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Cờ tướng
   Pieces are labelled in Vietnamese rather than with the traditional
   characters: this board is for a family, and a child who has not learnt
   the characters can still tell a Mã from a Pháo at a glance.
   ───────────────────────────────────────────────────────────────────────── */

.ct { width: 100%; }

.ct-grid {
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  gap: 2px;
  background: color-mix(in srgb, var(--primary) 12%, var(--card));
  border: 2px solid var(--border);
  border-radius: 10px;
  padding: 4px;
}

/* Seat o reads the board from the far side. */
.ct.is-flipped .ct-grid { transform: rotate(180deg); }
.ct.is-flipped .ct-piece { transform: rotate(180deg); }

.ct-point {
  aspect-ratio: 1 / 1;
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  border-radius: 6px;
  background: transparent;
  padding: 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  color: var(--text);
}
.ct-point:not(:disabled) { cursor: pointer; }
.ct-point.is-mine:not(:disabled):hover { background: color-mix(in srgb, var(--primary) 18%, transparent); }
.ct-point.is-selected { background: color-mix(in srgb, var(--primary) 32%, transparent); }
.ct-point.is-last { border-color: var(--primary); }
.ct-point.is-check .ct-piece { outline: 3px solid var(--x-color); outline-offset: 1px; }

.ct-piece {
  width: 88%;
  height: 88%;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(8px, 1.9vw, 13px);
  font-weight: 700;
  line-height: 1;
  border: 2px solid;
  background: var(--card);
  text-align: center;
}
.ct-point.is-red .ct-piece { color: var(--x-color); border-color: var(--x-color); }
.ct-point.is-black .ct-piece { color: var(--o-color); border-color: var(--o-color); }

.ct-river {
  text-align: center;
  color: var(--text2);
  font-size: 11px;
  letter-spacing: .18em;
  margin: 6px 0 0;
}

.ct-hint { margin: 8px 0 0; text-align: center; color: var(--text2); font-size: 13px; }

@media (max-width: 560px) {
  .ct-grid { gap: 1px; padding: 2px; }
  .ct-piece { font-size: clamp(7px, 2.6vw, 12px); border-width: 1px; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Cờ vua

   Quân cờ do public/js/cv-board.js dựng bằng SVG, nhưng MỌI màu nằm ở đây.
   Hình khối là hình học, thuộc về mã; màu sắc là thẩm mỹ, thuộc về giao diện,
   và Content Security Policy cũng cấm đặt màu thẳng trong thẻ.

   Màu quân KHÔNG đổi theo chế độ sáng tối, khác với phần còn lại của trang.
   Một bộ cờ thật không đổi màu khi bật đèn, và quân trắng hoá xám trong nền
   tối thì không còn là quân trắng nữa.
   ───────────────────────────────────────────────────────────────────────── */

.cv { width: 100%; }

.cv-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  /* Không khe hở: bàn cờ vua là 64 ô liền nhau, hở ra là thành lưới. */
  gap: 0;
  border: 3px solid color-mix(in srgb, var(--cv-dark) 70%, #000);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: var(--shadow);
}

/* Bên đen nhìn bàn cờ từ phía đối diện. Xoay cả lưới rồi xoay ngược từng
   quân lại, nếu không quân cờ sẽ lộn ngược đầu. */
.cv.is-flipped .cv-grid { transform: rotate(180deg); }
.cv.is-flipped .cv-piece,
.cv.is-flipped .cv-dot { transform: rotate(180deg); }

.cv-square {
  aspect-ratio: 1 / 1;
  border: 0;
  padding: 0;
  min-width: 0;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  font: inherit;
}
.cv-square.is-light { background: var(--cv-light); }
.cv-square.is-dark { background: var(--cv-dark); }
.cv-square:not(:disabled) { cursor: pointer; }

/* Nước vừa đi: một vệt vàng mờ phủ lên ô, đủ thấy mà không át quân cờ. */
.cv-square.is-last::after {
  content: '';
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--warn) 34%, transparent);
  pointer-events: none;
}
.cv-square.is-selected::after {
  content: '';
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--primary) 42%, transparent);
  pointer-events: none;
}
.cv-square.is-mine:not(:disabled):hover::after {
  content: '';
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--primary) 20%, transparent);
  pointer-events: none;
}
/* Vua đang bị chiếu: quầng đỏ toả từ giữa ô ra. */
.cv-square.is-check::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%,
    color-mix(in srgb, var(--danger) 80%, transparent) 0%,
    transparent 72%);
  pointer-events: none;
}

/* Ô đi tới được: chấm tròn giữa ô. Viền thì đọc ra như một phần của lưới,
   chấm thì đọc ra như một chỗ để đi tới. */
.cv-dot {
  width: 30%;
  height: 30%;
  border-radius: 50%;
  background: color-mix(in srgb, #000 42%, transparent);
  box-shadow: 0 0 0 2px color-mix(in srgb, #fff 40%, transparent);
  pointer-events: none;
}
/* Ô ăn được quân: vòng khuyên ôm lấy quân địch, không phải chấm đè lên nó. */
.cv-square.is-take {
  box-shadow: inset 0 0 0 5px color-mix(in srgb, var(--danger) 72%, transparent);
}

/* Quân cờ phải nằm TRÊN các lớp phủ của ô.
   Lớp phủ là ::after của chính ô, mà ::after xếp trên mọi con không định vị,
   nên nếu không nâng quân lên thì vệt vàng "nước vừa đi" phủ lên quân và làm
   đổi màu nó: Hậu đen hoá nâu, không còn nhận ra là quân đen nữa.
   🔑 Một lớp phủ dùng để CHỈ ra một ô mà lại làm đổi màu thứ đứng trên ô ấy
   thì nó đang nói sai chứ không phải nói mờ. */
.cv-piece,
.cv-dot {
  position: relative;
  z-index: 1;
}

.cv-piece {
  width: 96%;
  height: 96%;
  display: block;
  pointer-events: none;
}

/* Bốn vai trò, dùng chung cho cả sáu quân. */
.cv-piece .cv-body {
  fill: var(--cv-body);
  stroke: var(--cv-line);
  stroke-width: 3.2;
  stroke-linejoin: round;
}
.cv-piece .cv-trim { fill: var(--cv-trim); }
.cv-piece .cv-eye { fill: var(--cv-eye); }
.cv-piece .cv-shine { fill: var(--cv-shine); }
.cv-piece .cv-blush { fill: var(--cv-blush); }
.cv-piece .cv-smile {
  fill: none;
  stroke: var(--cv-eye);
  stroke-width: 2.6;
  stroke-linecap: round;
}
.cv-piece .cv-shadow { fill: rgba(0, 0, 0, .17); }

.cv-piece.is-white {
  --cv-body: #fffaf0;
  --cv-line: #3a2a1e;
  --cv-trim: #efd9bb;
  --cv-eye: #3a2a1e;
  --cv-shine: #fff;
  --cv-blush: #ff9d8b;
}
/* Mắt quân đen KHÔNG dùng màu viền được: viền tối trên thân tối thì cả khuôn
   mặt biến mất, quân đen thành một cục không có tính cách. Mắt sáng lên. */
.cv-piece.is-black {
  --cv-body: #4c445f;
  --cv-line: #191327;
  --cv-trim: #837ba3;
  --cv-eye: #fdf6e8;
  --cv-shine: #4c445f;
  --cv-blush: #e8907e;
}

/* Phong cấp: bốn quân bày ra để chạm, không phải hộp thoại chữ. */
.cv-promo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 10px 12px;
  border: 2px solid var(--primary);
  border-radius: 14px;
  background: var(--card);
}
.cv-promo-label { flex: 1 1 100%; font-weight: 700; font-size: 13px; }
.cv-promo-btn {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 64px;
  min-height: 44px;
  padding: 4px;
  border: 2px solid var(--border);
  border-radius: 12px;
  background: var(--input);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.cv-promo-btn:hover { border-color: var(--primary); }
.cv-promo-btn .cv-piece { width: 40px; height: 40px; }
.cv-promo-name { font-size: 11px; font-weight: 700; }
.cv-promo-btn.is-cancel {
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  color: var(--text2);
}

.cv-hint { margin: 8px 0 0; text-align: center; color: var(--text2); font-size: 13px; }

@media (max-width: 560px) {
  .cv-grid { border-width: 2px; }
  .cv-square.is-take { box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--danger) 72%, transparent); }
  .cv-promo-btn { width: 56px; }
  .cv-promo-btn .cv-piece { width: 34px; height: 34px; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Cá ngựa
   The ring is drawn round the edge of a 15 by 15 grid, whose perimeter is
   exactly 56 cells: 15 + 14 + 14 + 13. That is where the ring length comes
   from. The board is display only; the four horse buttons underneath are
   what a player taps.
   ───────────────────────────────────────────────────────────────────────── */

.cn { width: 100%; }

.cn-grid {
  display: grid;
  grid-template-columns: repeat(15, 1fr);
  grid-template-rows: repeat(15, 1fr);
  gap: 1px;
  aspect-ratio: 1 / 1;
  padding: 4px;
  border: 2px solid var(--border);
  border-radius: 12px;
  background: color-mix(in srgb, var(--primary) 8%, var(--card));
}

.cn-cell {
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: 3px;
  background: var(--card);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
}
.cn-cell.is-start { border-width: 2px; }

.cn-horse-dot {
  width: 82%;
  height: 82%;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(6px, 1.2vw, 10px);
  font-weight: 700;
  color: var(--primary-ink);
}

/* One colour per chair, used by the ring, the strips and the buttons alike. */
.is-x { --seat-color: var(--x-color); }
.is-o { --seat-color: var(--o-color); }
.is-p { --seat-color: #f0c419; }
.is-q { --seat-color: #b48ce8; }
.cn-cell.is-start { border-color: var(--seat-color); }
.cn-horse-dot { background: var(--seat-color); }

.cn-middle {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
  padding: 6px;
}
.cn-die {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 14px;
  border: 2px solid var(--primary);
  border-radius: 12px;
  background: var(--card);
}
.cn-die-label { font-size: 10px; color: var(--text2); letter-spacing: .08em; }
.cn-die-num { font-size: clamp(20px, 5vw, 34px); font-weight: 800; line-height: 1; }
.cn-turn { margin: 0; font-size: 13px; font-weight: 700; }
.cn-event { margin: 0; font-size: 11px; color: var(--text2); }

.cn-tracks { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; }
.cn-track {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--seat-color);
  border-radius: 8px;
  font-size: 12px;
}
.cn-track-name { font-weight: 700; flex: 1 1 auto; }
.cn-stable { color: var(--text2); }
.cn-home { display: flex; gap: 3px; }
.cn-home-slot {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  border: 1px solid var(--border);
  display: block;
}
.cn-home-slot.is-taken { background: var(--seat-color); border-color: var(--seat-color); }

.cn-picker { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.cn-horse {
  flex: 1 1 0;
  min-width: 84px;
  min-height: 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 6px 8px;
  border: 2px solid var(--border);
  border-radius: 12px;
  background: var(--card);
  color: var(--text);
  font: inherit;
}
.cn-horse:disabled { opacity: .4; }
.cn-horse.is-ready { border-color: var(--seat-color); cursor: pointer; }
.cn-horse.is-ready:hover { background: color-mix(in srgb, var(--seat-color) 18%, var(--card)); }
.cn-horse-num { font-weight: 700; font-size: 13px; }
.cn-horse-where { font-size: 11px; color: var(--text2); }

.cn-hint { margin: 10px 0 0; text-align: center; color: var(--text2); font-size: 13px; }

.mark-p { background: #f0c419; color: #2a1403; }
.mark-q { background: #b48ce8; color: #2a1403; }

@media (max-width: 560px) {
  .cn-grid { gap: 0; padding: 2px; }
  .cn-horse { min-width: 70px; min-height: 52px; }
}

/* A chair held by the computer says so, next to the name. */
.seat-bot {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 10px;
  color: var(--text2);
  letter-spacing: .04em;
}

/* Invite code, admin only. */
.invite-card { margin-bottom: 12px; }
.invite-note { margin: 0 0 8px; color: var(--text2); font-size: 12px; }
.invite-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.invite-code {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 12px;
  border: 1px dashed var(--border);
  border-radius: 8px;
  background: color-mix(in srgb, var(--primary) 10%, var(--card));
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 15px;
  letter-spacing: .04em;
  word-break: break-all;
}

/* Host for the DOM based boards. It sits beside the caro canvas, never inside
   it: children of a <canvas> are fallback content and are never painted. */
.board-dom { width: 100%; }
