/* UWB Quicksilver. Mobile first: the plain rules are for a phone held in one hand, and the
   @media rules near the end let it grow on bigger screens.

   Colours are variables, so the light theme only has to change them. The dark theme is the
   default when the phone is set to dark (or the player picks it in Settings). */

:root {
  --bg: #eef2f6;
  --surface: #ffffff;
  --surface-2: #e6ebf0;
  --surface-3: #dae1e8;
  --border: #cbd4dd;
  --text: #16202a;
  --muted: #55626f;
  --accent: #0a74b0;
  --accent-strong: #08608f;
  --accent-soft: #dbeffa;
  --accent-text: #ffffff;
  --quicksilver: linear-gradient(135deg, #9ad8f7 0%, #dfe6ec 50%, #b8c4cf 100%);
  --danger: #d4334f;
  --danger-soft: #fde7eb;
  --success: #138a55;
  --success-soft: #dcf5e8;
  --warning: #9a5b00;
  --warning-soft: #fff1d6;
  --info-soft: #e0f0fa;
  --shadow: 0 6px 24px rgba(20, 35, 50, 0.12);
  /* The board. */
  --board-bg: #dbe2e9;
  --floor: #f5f7f9;
  --grid-line: rgba(30, 50, 70, 0.14);
  --wall: #37424e;
  --capture-floor: #fff4d6;
  --capture-ring: #e0a100;
  --hill-floor: #e3f5e1;
  --hill-ring: #3a9b45;
  --label: #85919e;
  --tab-height: 60px;
  --bar-height: 56px;
  color-scheme: light;
}

/* The dark theme: followed from the phone's setting unless the player chose light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: #0d1319;
    --surface: #151d25;
    --surface-2: #1e2832;
    --surface-3: #28343f;
    --border: #2e3a46;
    --text: #e7edf2;
    --muted: #99a7b4;
    --accent: #6cc6f2;
    --accent-strong: #92d6f7;
    --accent-soft: #143349;
    --accent-text: #062033;
    --danger: #ff6680;
    --danger-soft: #3b1d29;
    --success: #43d79a;
    --success-soft: #16352a;
    --warning: #ffc659;
    --warning-soft: #3a2f17;
    --info-soft: #16283a;
    --shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
    --board-bg: #090e13;
    --floor: #25303b;
    --grid-line: rgba(255, 255, 255, 0.07);
    --wall: #06090c;
    --capture-floor: #3d3520;
    --capture-ring: #f2b705;
    --hill-floor: #23392a;
    --hill-ring: #5cc46a;
    --label: #6a7987;
    color-scheme: dark;
  }
}

:root[data-theme='dark'] {
  --bg: #0d1319;
  --surface: #151d25;
  --surface-2: #1e2832;
  --surface-3: #28343f;
  --border: #2e3a46;
  --text: #e7edf2;
  --muted: #99a7b4;
  --accent: #6cc6f2;
  --accent-strong: #92d6f7;
  --accent-soft: #143349;
  --accent-text: #062033;
  --danger: #ff6680;
  --danger-soft: #3b1d29;
  --success: #43d79a;
  --success-soft: #16352a;
  --warning: #ffc659;
  --warning-soft: #3a2f17;
  --info-soft: #16283a;
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
  --board-bg: #090e13;
  --floor: #25303b;
  --grid-line: rgba(255, 255, 255, 0.07);
  --wall: #06090c;
  --capture-floor: #3d3520;
  --capture-ring: #f2b705;
  --hill-floor: #23392a;
  --hill-ring: #5cc46a;
  --label: #6a7987;
  color-scheme: dark;
}

/* ---- The basics ------------------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.45 system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  /* No grey flash when a button is tapped on iOS and Android. */
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: none;
}

h1,
h2,
h3 {
  line-height: 1.2;
  margin: 0 0 8px;
}

h1 { font-size: 1.5rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }

p { margin: 0 0 10px; }

a {
  color: var(--accent);
}

code,
.mono {
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
}

[hidden] {
  display: none !important;
}

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

.muted { color: var(--muted); }
.hint { color: var(--muted); font-size: 0.85rem; }
.nowrap { white-space: nowrap; }
.spacer { flex: 1; }

.stack { display: flex; flex-direction: column; gap: 12px; }
.row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.row-tight { display: flex; align-items: center; gap: 6px; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ---- The app's frame: the top bar, the screen and the tabs ---------------------------------- */

.shell {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.app-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: calc(var(--bar-height) + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) calc(8px + env(safe-area-inset-right)) 0 calc(8px + env(safe-area-inset-left));
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}

.app-bar-titles {
  flex: 1;
  min-width: 0;
  padding: 0 6px;
}

.app-bar-title {
  margin: 0;
  font-size: 1.08rem;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-bar-subtitle {
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.brand-mark {
  width: 30px;
  height: 30px;
  margin-left: 6px;
  border-radius: 9px;
  flex: none;
}

.view {
  flex: 1;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 16px calc(16px + env(safe-area-inset-right)) calc(var(--tab-height) + 28px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
}

.view:focus { outline: none; }

.boot-message {
  margin-top: 30vh;
  text-align: center;
  color: var(--muted);
}

.tab-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
}

.tab {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  height: var(--tab-height);
  color: var(--muted);
  text-decoration: none;
  font-size: 0.7rem;
  font-weight: 600;
}

.tab .icon { width: 24px; height: 24px; }

.tab.active { color: var(--accent); }

.tab.active::before {
  content: '';
  position: absolute;
  top: 0;
  width: 32px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--accent);
}

.tab.paused { opacity: 0.45; }

.tab-count {
  position: absolute;
  top: 6px;
  left: calc(50% + 6px);
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--danger);
  color: #fff;
  font-size: 0.68rem;
  line-height: 18px;
  text-align: center;
}

.offline-banner {
  position: sticky;
  top: calc(var(--bar-height) + env(safe-area-inset-top));
  z-index: 19;
  padding: 6px 16px;
  background: var(--warning-soft);
  color: var(--warning);
  font-size: 0.85rem;
  text-align: center;
}

/* The Play screen fills the whole screen: no tabs, nothing scrolls but its panels. */
body.immersive .tab-bar { display: none; }
body.immersive .shell { height: 100vh; height: 100dvh; min-height: 0; }
body.immersive .view {
  max-width: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

/* ---- Buttons ---------------------------------------------------------------------------------- */

.button,
button.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  transition: transform 0.08s ease, background 0.15s ease;
}

.button:active { transform: scale(0.97); }

.button:disabled,
.button[aria-disabled='true'] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}

.button.primary:hover:not(:disabled) { background: var(--accent-strong); }

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

.button.ghost {
  background: transparent;
  border-color: transparent;
}

.button.small {
  min-height: 36px;
  padding: 0 12px;
  border-radius: 10px;
  font-size: 0.9rem;
}

.button.small .icon { width: 18px; height: 18px; }

.button.icon-only {
  width: 44px;
  padding: 0;
  background: transparent;
  border-color: transparent;
}

.button.icon-only.filled {
  background: var(--surface-2);
  border-color: var(--border);
}

.button.wide { width: 100%; }

.button.icon-only.big { width: 52px; min-height: 52px; }

.danger-text { color: var(--danger); }

.button.big {
  min-height: 52px;
  font-size: 1.05rem;
  border-radius: 14px;
}

.button-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.button-row > .button { flex: 1; }

/* ---- Cards, lists, badges and messages -------------------------------------------------------------- */

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

.card + .card,
.section + .section { margin-top: 14px; }

.section-title {
  margin: 22px 4px 10px;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 700;
  white-space: nowrap;
  background: var(--surface-3);
  color: var(--muted);
}

.badge-good { background: var(--success-soft); color: var(--success); }
.badge-bad { background: var(--danger-soft); color: var(--danger); }
.badge-warn { background: var(--warning-soft); color: var(--warning); }
.badge-info { background: var(--info-soft); color: var(--accent); }
.badge-hot { background: var(--danger); color: #fff; }

.message {
  padding: 12px 14px;
  border-radius: 14px;
  background: var(--info-soft);
  font-size: 0.95rem;
}

.message + .message { margin-top: 8px; }
.message-error { background: var(--danger-soft); color: var(--danger); }
.message-warning { background: var(--warning-soft); color: var(--warning); }
.message-success { background: var(--success-soft); color: var(--success); }

.loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 40px 0;
  color: var(--muted);
}

.spinner {
  width: 20px;
  height: 20px;
  border: 3px solid var(--surface-3);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.empty-state {
  padding: 40px 12px;
  text-align: center;
  color: var(--muted);
}

.empty-state .icon { margin-bottom: 8px; opacity: 0.7; }

/* ---- The welcome / connect screen --------------------------------------------------------------- */

.hero {
  padding: 28px 8px 12px;
  text-align: center;
}

.hero-logo {
  width: 96px;
  height: 96px;
  border-radius: 26px;
  box-shadow: var(--shadow);
}

.hero h1 {
  margin-top: 16px;
  font-size: 1.9rem;
  letter-spacing: -0.02em;
}

.hero .tagline { color: var(--muted); }

.server-choice {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.server-choice .grow { flex: 1; min-width: 0; overflow-wrap: anywhere; }

.facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 16px;
  margin: 0;
  font-size: 0.92rem;
}

.facts dt { color: var(--muted); }
.facts dd { margin: 0; text-align: right; overflow-wrap: anywhere; }

/* ---- Game cards (Games and Browse) ---------------------------------------------------------- */

.game-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 18px;
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.game-card:active { transform: scale(0.99); }

.game-card.waiting {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--shadow);
}

.game-card-top {
  display: flex;
  align-items: center;
  gap: 8px;
}

.game-card-name {
  flex: 1;
  min-width: 0;
  font-weight: 700;
  font-size: 1.02rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.game-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  color: var(--muted);
  font-size: 0.85rem;
}

.game-card-meta .icon { width: 15px; height: 15px; vertical-align: -2px; }

.load-more { margin-top: 12px; }

.game-card-wrap { position: relative; }
.game-card.has-remove .game-card-top { padding-right: 36px; }

.card-remove {
  position: absolute;
  top: 6px;
  right: 6px;
}

/* ---- Forms --------------------------------------------------------------------------------------- */

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

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

.field-error {
  color: var(--danger);
  font-size: 0.85rem;
}

.field-error:empty { display: none; }

/* 16px text in inputs: iOS zooms the page in on anything smaller. */
input[type='text'],
input[type='number'],
input[type='url'],
input:not([type]),
select,
textarea {
  width: 100%;
  min-height: 48px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 16px;
}

input::placeholder { color: var(--muted); opacity: 0.7; }

.code-input {
  min-height: 64px !important;
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 2rem !important;
  font-weight: 700;
  letter-spacing: 0.35em;
  text-align: center;
  text-transform: uppercase;
}

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px 12px;
}

.form-grid .span-2 { grid-column: span 2; }

.stepper {
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  overflow: hidden;
}

.stepper input {
  min-height: 44px;
  border: 0;
  border-radius: 0;
  text-align: center;
  font-weight: 700;
  background: transparent;
}

.stepper-button {
  height: 44px;
  border: 0;
  background: var(--surface-2);
  color: var(--text);
  font-size: 1.3rem;
  cursor: pointer;
  touch-action: manipulation;
}

.segmented {
  display: flex;
  gap: 4px;
  padding: 4px;
  border-radius: 14px;
  background: var(--surface-2);
}

.segmented button {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  padding: 0 8px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-weight: 700;
  font-size: 0.9rem;
  cursor: pointer;
  touch-action: manipulation;
}

.segmented button.chosen {
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}

.check-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  cursor: pointer;
}

.check-row input[type='checkbox'] {
  width: 22px;
  height: 22px;
  accent-color: var(--accent);
  flex: none;
}

.check-row .grow { flex: 1; }

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
  touch-action: manipulation;
}

.chip[aria-pressed='true'] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

details.more-settings > summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  font-weight: 700;
  cursor: pointer;
  color: var(--accent);
}

/* ---- Sheets (see js/ui/sheet.js) ---------------------------------------------------------------- */

dialog.sheet {
  position: fixed;
  inset: auto 0 0 0;
  width: 100%;
  max-width: 640px;
  max-height: 88vh;
  max-height: 88dvh;
  margin: 0 auto;
  padding: 0;
  border: 0;
  border-radius: 22px 22px 0 0;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  animation: sheet-up 0.22s cubic-bezier(0.2, 0.9, 0.3, 1);
  transition: transform 0.18s ease;
}

dialog.sheet:not([open]) { display: none; }

dialog.sheet.sheet-tall { height: 88vh; height: 88dvh; }

dialog.sheet.sheet-leaving { transform: translateY(100%); }

dialog.sheet::backdrop {
  background: rgba(6, 8, 20, 0.55);
  animation: fade-in 0.2s ease;
}

@keyframes sheet-up {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

@keyframes fade-in {
  from { opacity: 0; }
}

.sheet-header {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 18px 8px 6px 20px;
  touch-action: none;
  cursor: grab;
}

.sheet-handle {
  position: absolute;
  top: 7px;
  left: 50%;
  width: 40px;
  height: 5px;
  margin-left: -20px;
  border-radius: 3px;
  background: var(--surface-3);
}

.sheet-title {
  flex: 1;
  margin: 0;
  font-size: 1.15rem;
}

.sheet-body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px 20px 16px;
}

.sheet-footer {
  display: flex;
  gap: 10px;
  padding: 12px 20px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
}

.sheet-footer > .button { flex: 1; }

/* ---- Toasts --------------------------------------------------------------------------------------- */

.toasts {
  position: fixed;
  left: 50%;
  bottom: calc(var(--tab-height) + 14px + env(safe-area-inset-bottom));
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: min(92vw, 440px);
  transform: translateX(-50%);
  pointer-events: none;
}

/* On the Play screen, toasts sit over the top of the board, below the status strip and the
   replay bar, so they never cover a control. */
body.immersive .toasts { bottom: auto; top: calc(var(--bar-height) + 130px + env(safe-area-inset-top)); }

.toast {
  max-width: 100%;
  padding: 10px 16px;
  border-radius: 14px;
  background: #1e2832;
  color: #fff;
  font-size: 0.92rem;
  box-shadow: var(--shadow);
  animation: toast-in 0.2s ease;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.toast-success { background: #0f6f46; }
.toast-error { background: #b3243d; }
.toast-leaving { opacity: 0; transform: translateY(8px); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
}

/* ---- The board (see js/ui/board.js) ----------------------------------------------------------------- */

.board-viewport {
  position: relative;
  overflow: hidden;
  background: var(--board-bg);
}

.board-viewport.interactive {
  position: absolute;
  inset: 0;
  touch-action: none;
  cursor: grab;
}

.board-viewport.interactive:active { cursor: grabbing; }

.board-viewport.static {
  width: 100%;
  max-height: 340px;
  border-radius: 14px;
}

.board-stage {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
}

.board-stage.animating { transition: transform 0.22s ease; }

.board-grid {
  display: grid;
}

.board-label {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--label);
  font-size: 9px;
  font-weight: 600;
  user-select: none;
}

.tiny-tiles .board-label { visibility: hidden; }

.tile {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--floor);
  box-shadow: inset 0 0 0 0.5px var(--grid-line);
}

.tile.type-impassable {
  background: var(--wall) repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.05) 0 4px, transparent 4px 9px);
}

.tile.type-unknown { background: #ff00c8; }

/* TILE12: the tiles a game mode scores on. A coloured ring (it stays under any effect's
   tint) and, on a tile with no effect, a small mark in the bottom corner. */
.tile.type-capture_point { background-color: var(--capture-floor); box-shadow: inset 0 0 0 3px var(--capture-ring); }
.tile.type-hill { background-color: var(--hill-floor); box-shadow: inset 0 0 0 3px var(--hill-ring); }

.tile.type-capture_point.effect-none::after,
.tile.type-hill.effect-none::after {
  position: absolute;
  right: 2px;
  bottom: 1px;
  font-size: 13px;
  line-height: 1;
  pointer-events: none;
}

.tile.type-capture_point.effect-none::after { content: '🚩'; }
.tile.type-hill.effect-none::after { content: '⛰️'; }

/* The effects: a tint over the whole tile (readable from far away) and a small emoji in the
   corner (hidden when the tiles are small). */
.tile.effect-on_fire { background-image: linear-gradient(160deg, rgba(255, 150, 60, 0.55), rgba(232, 60, 30, 0.75)); }
.tile.effect-wet { background-image: linear-gradient(160deg, rgba(90, 170, 255, 0.45), rgba(30, 110, 230, 0.6)); }
.tile.effect-frozen { background-image: linear-gradient(160deg, rgba(225, 248, 255, 0.85), rgba(140, 210, 245, 0.85)); }
.tile.effect-doomfire { background-image: radial-gradient(circle at 50% 60%, rgba(220, 80, 255, 0.85), rgba(70, 0, 110, 0.95)); }
.tile.effect-unknown { background-image: repeating-linear-gradient(45deg, #ff00c8 0 3px, transparent 3px 6px); }

.tile.effect-on_fire::after,
.tile.effect-wet::after,
.tile.effect-frozen::after,
.tile.effect-doomfire::after {
  position: absolute;
  top: 1px;
  left: 2px;
  font-size: 13px;
  line-height: 1;
  pointer-events: none;
}

.tile.effect-on_fire::after { content: '🔥'; }
.tile.effect-wet::after { content: '💧'; }
.tile.effect-frozen::after { content: '❄️'; }
.tile.effect-doomfire::after { content: '☠️'; }

/* P9: a wall can have an effect too (an area spell can soak, burn or freeze it). The tints
   above would cover its stripes and make it look like floor, so a wall keeps its own look
   and shows the effect as a coloured border (and the emoji) instead. */
.tile.type-impassable.effect-on_fire,
.tile.type-impassable.effect-wet,
.tile.type-impassable.effect-frozen,
.tile.type-impassable.effect-doomfire,
.tile.type-impassable.effect-unknown {
  background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.05) 0 4px, transparent 4px 9px);
}

.tile.type-impassable.effect-on_fire { box-shadow: inset 0 0 0 3px rgba(232, 60, 30, 0.9); }
.tile.type-impassable.effect-wet { box-shadow: inset 0 0 0 3px rgba(30, 110, 230, 0.9); }
.tile.type-impassable.effect-frozen { box-shadow: inset 0 0 0 3px rgba(140, 210, 245, 0.9); }
.tile.type-impassable.effect-doomfire { box-shadow: inset 0 0 0 3px rgba(170, 40, 220, 0.9); }
.tile.type-impassable.effect-unknown { box-shadow: inset 0 0 0 3px #ff00c8; }

.small-tiles .tile::after { display: none; }

/* Overlays: highlights drawn over a tile (the move you're planning, a spell's area...). */
.tile[class*=' ov-']::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.tile.ov-path::before { background: rgba(255, 196, 0, 0.42); box-shadow: inset 0 0 0 2px rgba(255, 196, 0, 0.95); }
.tile.ov-area::before { background: rgba(255, 64, 90, 0.38); }
.tile.ov-area-heal::before { background: rgba(40, 220, 140, 0.38); }
.tile.ov-area-tiles::before { background: rgba(70, 150, 255, 0.38); }
.tile.ov-aim::before { background: rgba(255, 64, 90, 0.22); }
.tile.ov-target::before { box-shadow: inset 0 0 0 3px #ff2d55; }
.tile.ov-option::before { background: rgba(70, 175, 235, 0.28); box-shadow: inset 0 0 0 2px var(--accent); }
.tile.ov-invalid::before { background: repeating-linear-gradient(45deg, rgba(220, 30, 60, 0.6) 0 4px, transparent 4px 8px); }
.tile.ov-you::before { box-shadow: inset 0 0 0 2px var(--accent); }
.tile.ov-step::before { background: rgba(0, 200, 180, 0.4); }
.tile.ov-danger::before { box-shadow: inset 0 0 0 3px rgba(190, 60, 255, 0.95); animation: danger-pulse 1.4s ease-in-out infinite; }
/* A teammate's plan (TURN12): cyan and dashed, so it doesn't look like your own. */
.tile.ov-team-path::before { background: rgba(0, 210, 255, 0.24); outline: 2px dashed rgba(0, 210, 255, 0.95); outline-offset: -3px; }
.tile.ov-team-area::before { background: rgba(0, 210, 255, 0.18); }
.tile.ov-team-target::before { outline: 3px dashed #00d2ff; outline-offset: -3px; }

@keyframes danger-pulse {
  50% { box-shadow: inset 0 0 0 1px rgba(190, 60, 255, 0.4); }
}

/* ---- Spell, fire and Doomfire effects, played over the board during a turn's playback ----------------------
   js/game/stepEffects.js says which effect goes on which tile, and when; js/ui/board.js
   puts a ".fx" box over each of those tiles. Each kind's animation length must match
   PART_DURATION_MS in stepEffects.js. Every animation starts invisible ("both" keeps the
   first frame during its delay), so parts that wait their turn don't show early. */
.board-fx {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.fx {
  position: absolute;
  pointer-events: none;
  animation-fill-mode: both;
}

.fx::before,
.fx::after {
  content: '';
  position: absolute;
  animation-fill-mode: both;
}

/* A part's inner pieces wait as long as the part does. (This is more specific than the
   ".fx-...::before" rules below, so their "animation:" shorthands don't reset it to 0.) */
.board-fx .fx::before,
.board-fx .fx::after {
  animation-delay: var(--fx-delay);
}

/* Fireball: a white-hot blast and a shock ring on the target. */
.fx-explosion {
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0 12%, #ffe066 22%, #ff9a1f 42%, #e8341c 62%, rgba(140, 20, 10, 0) 72%);
  animation: fx-explode 0.8s ease-out;
}

.fx-explosion::after {
  inset: 0;
  border-radius: 50%;
  border: 3px solid rgba(255, 170, 60, 0.9);
  animation: fx-shockwave 0.8s ease-out;
}

@keyframes fx-explode {
  0% { opacity: 0; transform: scale(0.2); }
  15% { opacity: 1; transform: scale(1.3); }
  60% { opacity: 0.9; transform: scale(2); }
  100% { opacity: 0; transform: scale(2.4); }
}

@keyframes fx-shockwave {
  0% { opacity: 0; transform: scale(0.3); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: scale(3.2); }
}

/* Fireball's spread and Fire Lance: flames licking up from the tile. */
.fx-flame { animation: fx-fade 0.9s ease-out; }

.fx-flame::before {
  left: 10%;
  right: 10%;
  bottom: 4%;
  height: 90%;
  border-radius: 50% 50% 35% 35% / 65% 65% 35% 35%;
  background: radial-gradient(ellipse at 50% 85%, #fff3b0 0 15%, #ffb02e 35%, #f2541b 65%, rgba(200, 30, 10, 0) 80%);
  transform-origin: 50% 100%;
  animation: fx-flicker 0.9s ease-out;
}

.fx-flame::after {
  left: 28%;
  right: 28%;
  bottom: 8%;
  height: 55%;
  border-radius: 50% 50% 40% 40% / 70% 70% 30% 30%;
  background: radial-gradient(ellipse at 50% 80%, #fff 0 20%, #ffe27a 55%, rgba(255, 200, 60, 0) 80%);
  transform-origin: 50% 100%;
  animation: fx-flicker 0.9s ease-out reverse;
}

@keyframes fx-flicker {
  0% { opacity: 0; transform: scale(0.3, 0.1); }
  20% { opacity: 1; transform: scale(1, 1.15); }
  40% { transform: scale(0.85, 0.95) skewX(-6deg); }
  60% { opacity: 1; transform: scale(1.05, 1.2) skewX(5deg); }
  100% { opacity: 0; transform: scale(0.6, 1.5) translateY(-20%); }
}

@keyframes fx-fade {
  0% { opacity: 0; }
  10% { opacity: 1; }
  80% { opacity: 1; }
  100% { opacity: 0; }
}

/* Lightning strike (and Thunderstorm's bolt): a jagged bolt dropping from the sky, and a
   white flash where it lands. */
.fx-lightning {
  /* The glow goes on the box, not the bolt: clip-path would cut a glow on the bolt off. */
  filter: drop-shadow(0 0 3px #2f7dff) drop-shadow(0 0 7px #8fd0ff);
  animation: fx-fade 0.8s linear;
}

.fx-lightning::before {
  left: 5%;
  width: 90%;
  bottom: 45%;
  height: 320%;
  background: linear-gradient(to bottom, rgba(150, 205, 255, 0), #bfe3ff 25%, #fff 60%, #fffbe0);
  clip-path: polygon(62% 0, 24% 52%, 47% 52%, 28% 100%, 82% 40%, 57% 40%, 80% 0);
  transform-origin: 50% 0;
  animation: fx-bolt 0.8s linear;
}

.fx-lightning::after {
  inset: -30%;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0 18%, rgba(140, 200, 255, 0.95) 38%, rgba(47, 125, 255, 0.55) 55%, rgba(47, 125, 255, 0) 70%);
  animation: fx-flash 0.8s ease-out;
}

@keyframes fx-bolt {
  0% { opacity: 0; transform: scaleY(0); }
  12% { opacity: 1; transform: scaleY(1); }
  25% { opacity: 0.2; }
  35% { opacity: 1; }
  50% { opacity: 0.4; }
  60% { opacity: 1; }
  100% { opacity: 0; transform: scaleY(1); }
}

@keyframes fx-flash {
  0%, 10% { opacity: 0; transform: scale(0.4); }
  18% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.6); }
}

/* Ice Lance: crystals of ice shooting up out of the tile. */
.fx-ice-shard { animation: fx-fade 0.8s ease-out; }

.fx-ice-shard::before {
  inset: 12%;
  background: linear-gradient(135deg, #fff, #bfeaff 45%, #5cbcf0);
  clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  filter: drop-shadow(0 0 3px #bfeaff);
  animation: fx-shard 0.8s ease-out;
}

.fx-ice-shard::after {
  inset: 0;
  border-radius: 6px;
  box-shadow: inset 0 0 10px 3px rgba(220, 245, 255, 0.95);
  animation: fx-fade 0.8s ease-out;
}

@keyframes fx-shard {
  0% { opacity: 0; transform: scale(0) rotate(-60deg); }
  35% { opacity: 1; transform: scale(1.2) rotate(10deg); }
  70% { opacity: 1; transform: scale(1) rotate(0); }
  100% { opacity: 0; transform: scale(1.3) rotate(0); }
}

/* Blizzard: a swirl of cold on the target, frosting the square around it. */
.fx-vortex {
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(from 0deg, rgba(255, 255, 255, 0.95), rgba(120, 200, 255, 0.1), rgba(210, 240, 255, 0.9), rgba(120, 200, 255, 0.1), rgba(255, 255, 255, 0.95));
  mask: radial-gradient(circle, transparent 0 18%, #000 30%, #000 60%, transparent 71%);
  -webkit-mask: radial-gradient(circle, transparent 0 18%, #000 30%, #000 60%, transparent 71%);
  animation: fx-swirl 1s ease-out;
}

@keyframes fx-swirl {
  0% { opacity: 0; transform: scale(0.3) rotate(0); }
  20% { opacity: 1; }
  70% { opacity: 0.9; transform: scale(4.2) rotate(400deg); }
  100% { opacity: 0; transform: scale(5) rotate(540deg); }
}

.fx-frost {
  animation: fx-frost 0.8s ease-out;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.9), rgba(180, 230, 255, 0.6) 55%, rgba(140, 210, 245, 0) 75%);
}

.fx-frost::before {
  inset: 22%;
  background: #fff;
  clip-path: polygon(50% 0, 56% 44%, 100% 50%, 56% 56%, 50% 100%, 44% 56%, 0 50%, 44% 44%);
  animation: fx-shard 0.8s ease-out;
}

@keyframes fx-frost {
  0% { opacity: 0; transform: scale(0.5); }
  30% { opacity: 1; transform: scale(1.05); }
  100% { opacity: 0; transform: scale(1); }
}

/* Magic Healing: a green glow, and crosses floating up. */
.fx-heal {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(170, 255, 200, 0.9), rgba(40, 220, 140, 0.5) 50%, rgba(40, 220, 140, 0) 72%);
  animation: fx-glow 1s ease-out;
}

.fx-heal::before,
.fx-heal::after {
  width: 12px;
  height: 12px;
  background:
    linear-gradient(#eafff2, #eafff2) center / 4px 100% no-repeat,
    linear-gradient(#eafff2, #eafff2) center / 100% 4px no-repeat;
  filter: drop-shadow(0 0 3px #1ed990);
  animation: fx-rise 1s ease-out;
}

.fx-heal::before { left: 18%; bottom: 20%; }
.fx-heal::after { right: 16%; bottom: 35%; animation-duration: 0.8s; }
.board-fx .fx-heal::after { animation-delay: calc(var(--fx-delay) + 200ms); }

@keyframes fx-glow {
  0% { opacity: 0; transform: scale(0.4); }
  30% { opacity: 1; transform: scale(1.3); }
  100% { opacity: 0; transform: scale(1.5); }
}

@keyframes fx-rise {
  0% { opacity: 0; transform: translateY(6px) scale(0.5); }
  25% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-26px) scale(0.8); }
}

/* Thaw: puffs of steam rising as the ice melts. */
.fx-steam {
  background: radial-gradient(circle at 50% 70%, rgba(255, 170, 90, 0.45), rgba(255, 170, 90, 0) 65%);
  animation: fx-fade 0.9s ease-out;
}

.fx-steam::before,
.fx-steam::after {
  width: 55%;
  height: 55%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.95), rgba(240, 245, 250, 0.6) 50%, rgba(240, 245, 250, 0) 70%);
  animation: fx-puff 0.9s ease-out;
}

.fx-steam::before { left: 8%; bottom: 10%; }
.fx-steam::after { right: 8%; bottom: 25%; animation-duration: 0.75s; }
.board-fx .fx-steam::after { animation-delay: calc(var(--fx-delay) + 150ms); }

@keyframes fx-puff {
  0% { opacity: 0; transform: translateY(8px) scale(0.4); }
  30% { opacity: 1; }
  100% { opacity: 0; transform: translateY(-30px) scale(1.6); }
}

/* Tidal Wave: a wave rolling along the line, away from the caster. The box is turned to
   face the line's direction, so the wave inside it always rolls "up". */
.fx-wave {
  overflow: hidden;
  transform: rotate(var(--fx-angle));
  animation: fx-fade 0.7s ease-out;
}

.fx-wave::before {
  left: -10%;
  right: -10%;
  height: 70%;
  top: 0;
  border-radius: 50% 50% 0 0 / 40% 40% 0 0;
  background: linear-gradient(to bottom, #f2fbff 0 8%, #7cc8ff 22%, rgba(30, 110, 230, 0.85) 60%, rgba(30, 110, 230, 0));
  animation: fx-roll 0.7s ease-in-out;
}

@keyframes fx-roll {
  0% { opacity: 0; transform: translateY(110%); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translateY(-60%); }
}

/* Thunderstorm: a dark cloud gathering over the target, then rain on the area. */
.fx-cloud { animation: fx-fade 1.1s linear; }

.fx-cloud::before {
  left: -100%;
  right: -100%;
  top: -60%;
  height: 130%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 30% 60%, rgba(70, 80, 100, 0.95) 0 22%, rgba(70, 80, 100, 0) 36%),
    radial-gradient(circle at 55% 45%, rgba(85, 95, 115, 0.95) 0 26%, rgba(85, 95, 115, 0) 40%),
    radial-gradient(circle at 75% 62%, rgba(60, 68, 88, 0.95) 0 20%, rgba(60, 68, 88, 0) 34%);
  animation: fx-cloud 1.1s ease-out;
}

@keyframes fx-cloud {
  0% { opacity: 0; transform: scale(0.5); }
  25% { opacity: 1; transform: scale(1); }
  80% { opacity: 1; }
  100% { opacity: 0; transform: scale(1.1); }
}

.fx-rain,
.fx-heal-rain {
  overflow: hidden;
  animation: fx-fade 0.9s linear;
}

.fx-rain::before,
.fx-heal-rain::before {
  left: 0;
  right: 0;
  top: -100%;
  height: 200%;
  background: repeating-linear-gradient(100deg, transparent 0 7px, rgba(200, 230, 255, 0.95) 7px 8.5px, transparent 8.5px 13px);
  background-size: 100% 22px;
  animation: fx-fall 0.9s linear;
}

.fx-rain::after {
  inset: 0;
  background: rgba(30, 110, 230, 0.35);
  animation: fx-fade 0.9s ease-out;
}

@keyframes fx-fall {
  0% { opacity: 0; transform: translateY(0); }
  15% { opacity: 1; }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translateY(50%); }
}

/* Healing Rain: the same rain, but green and glowing. */
.fx-heal-rain { animation-duration: 1s; }

.fx-heal-rain::before {
  background-image: repeating-linear-gradient(100deg, transparent 0 7px, rgba(200, 255, 220, 0.95) 7px 8.5px, transparent 8.5px 13px);
  animation-duration: 1s;
}

.fx-heal-rain::after {
  inset: 0;
  background: radial-gradient(circle, rgba(120, 255, 180, 0.85), rgba(40, 220, 140, 0.45) 50%, rgba(40, 220, 140, 0) 72%);
  animation: fx-glow 1s ease-out;
}

/* Fire burning the wizard on its tile (at the start of the turn): a red hurt flash, and
   flames flaring up around them. */
.fx-burn {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 60, 40, 0) 30%, rgba(255, 60, 40, 0.75) 48%, rgba(255, 60, 40, 0) 70%);
  animation: fx-hurt 1s ease-out;
}

.fx-burn::before,
.fx-burn::after {
  bottom: 0;
  width: 45%;
  height: 85%;
  border-radius: 50% 50% 35% 35% / 65% 65% 35% 35%;
  background: radial-gradient(ellipse at 50% 85%, #fff3b0 0 15%, #ffb02e 35%, #f2541b 65%, rgba(200, 30, 10, 0) 80%);
  transform-origin: 50% 100%;
  animation: fx-flicker 1s ease-out;
}

.fx-burn::before { left: -4%; }
.fx-burn::after { right: -4%; animation-duration: 0.85s; }
.board-fx .fx-burn::after { animation-delay: calc(var(--fx-delay) + 150ms); }

@keyframes fx-hurt {
  0% { opacity: 0; transform: scale(0.6); }
  20% { opacity: 1; transform: scale(1.1); }
  35% { opacity: 0.4; }
  50% { opacity: 1; transform: scale(1.2); }
  100% { opacity: 0; transform: scale(1.4); }
}

/* Doomfire destroying the wizard on its tile: a column of purple fire swallows them, with a
   dark flash. */
.fx-doom {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(40, 0, 60, 0.9) 0 25%, rgba(190, 60, 255, 0.8) 45%, rgba(190, 60, 255, 0) 70%);
  animation: fx-doom 1.2s ease-out;
}

.fx-doom::before {
  left: 0;
  right: 0;
  bottom: 0;
  height: 160%;
  border-radius: 50% 50% 30% 30% / 70% 70% 30% 30%;
  background: radial-gradient(ellipse at 50% 90%, #fff 0 10%, #e7a6ff 22%, #b23cff 45%, #4b0082 70%, rgba(40, 0, 70, 0) 82%);
  transform-origin: 50% 100%;
  animation: fx-flicker 1.2s ease-out;
}

.fx-doom::after {
  inset: -40%;
  border-radius: 50%;
  border: 3px solid rgba(190, 60, 255, 0.9);
  animation: fx-shockwave 1.2s ease-out;
}

@keyframes fx-doom {
  0% { opacity: 0; transform: scale(0.3); }
  20% { opacity: 1; transform: scale(1.4); }
  70% { opacity: 1; transform: scale(1.6); }
  100% { opacity: 0; transform: scale(2); }
}

/* Doomfire spreading at the end of an Overtime turn: purple flames rise on each tile it
   takes, sweeping round the ring. */
.fx-doomfire { animation: fx-fade 0.9s ease-out; }

.fx-doomfire::before {
  left: 8%;
  right: 8%;
  bottom: 0;
  height: 100%;
  border-radius: 50% 50% 35% 35% / 65% 65% 35% 35%;
  background: radial-gradient(ellipse at 50% 85%, #f6d9ff 0 14%, #c35cff 38%, #6a0dad 66%, rgba(50, 0, 80, 0) 80%);
  transform-origin: 50% 100%;
  animation: fx-flicker 0.9s ease-out;
}

.fx-doomfire::after {
  inset: 0;
  background: rgba(150, 40, 220, 0.35);
  animation: fx-fade 0.9s ease-out;
}

/* A spell this app has no effect for: a plain sparkle. */
.fx-sparkle {
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0 15%, rgba(255, 240, 160, 0.8) 35%, rgba(255, 240, 160, 0) 65%);
  animation: fx-glow 0.7s ease-out;
}

/* The wizards. */
.token {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 74%;
  height: 74%;
  border-radius: 50%;
  font-weight: 800;
  line-height: 1;
  user-select: none;
}

.token-slot { font-size: 15px; }

.token-hp {
  position: absolute;
  bottom: -3px;
  padding: 1px 4px;
  border-radius: 6px;
  background: rgba(10, 12, 25, 0.82);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
}

.small-tiles .token-hp { display: none; }

.token-you { animation: you-pulse 2s ease-in-out infinite; }

@keyframes you-pulse {
  50% { transform: scale(1.08); }
}

.token-ghost {
  background: transparent !important;
  border: 2px dashed;
  opacity: 0.9;
}

/* ---- The Play screen ------------------------------------------------------------------------------ */

.play {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.status-strip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px calc(12px + env(safe-area-inset-right)) 8px calc(12px + env(safe-area-inset-left));
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  font-size: 0.86rem;
  overflow-x: auto;
  scrollbar-width: none;
  white-space: nowrap;
}

.status-strip::-webkit-scrollbar { display: none; }

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--surface-2);
  font-weight: 700;
}

.status-pill .icon { width: 15px; height: 15px; }
.status-pill.urgent { background: var(--danger-soft); color: var(--danger); }
.status-pill.overtime { background: #3a1450; color: #f0c8ff; }
.status-pill.points { background: var(--capture-floor); color: inherit; box-shadow: inset 0 0 0 1.5px var(--capture-ring); }

.board-area {
  position: relative;
  flex: 1;
  min-height: 160px;
}

.board-tools {
  position: absolute;
  right: calc(8px + env(safe-area-inset-right));
  bottom: 8px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.board-tools .button.icon-only { width: 40px; min-height: 40px; border-radius: 12px; }
.board-tools .icon { width: 20px; height: 20px; }

.board-tools .button.icon-only {
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  border-color: var(--border);
  box-shadow: var(--shadow);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

/* Holds the dock, and the playback bar that covers it while a turn is replayed. */
.dock-holder {
  position: relative;
}

/* Pinned to the bottom and at least as tall as the dock, so it covers all of it; if the dock
   is shorter than the bar (collapsed), the bar grows up over the board's bottom edge. */
.playback-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  min-height: 100%;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px calc(8px + env(safe-area-inset-right)) calc(10px + env(safe-area-inset-bottom)) calc(14px + env(safe-area-inset-left));
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.12);
  font-size: 0.9rem;
}

.playback-text {
  flex: 1;
  min-width: 0;
}

.playback-step {
  font-weight: 700;
  line-height: 1.25;
  /* At most two lines. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.playback-outcomes {
  color: var(--muted);
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.playback-bar .button.icon-only { width: 44px; min-height: 44px; }

/* The dock: what you're doing this turn, at the bottom where your thumb is. */
.dock {
  position: relative;
  z-index: 4;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 10px calc(12px + env(safe-area-inset-right)) calc(10px + env(safe-area-inset-bottom)) calc(12px + env(safe-area-inset-left));
  box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.12);
}

.dock-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  font-weight: 700;
}

.dock-banner .grow { flex: 1; min-width: 0; }
.dock-banner .hint { font-weight: 400; }

.dock-banner.your-turn { color: var(--accent); }

/* While you build a turn, the top row is your action slots (and the fold button). */
.dock-banner.slots-row { margin-bottom: 0; }
.dock-banner.slots-row .slot-chips { flex: 1; min-width: 0; }

.slot-chips {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}

.slot-chips::-webkit-scrollbar { display: none; }

.slot-chip {
  flex: 1 0 0;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  min-height: 48px;
  /* Slim sides: the chips share their row with the fold button, so leave room for the label. */
  padding: 6px 6px;
  border: 2px solid var(--border);
  border-radius: 14px;
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.slot-chip.selected {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.slot-chip.has-warning { border-style: dashed; }

.slot-number {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--surface-3);
  font-size: 0.75rem;
  font-weight: 800;
}

.slot-chip.selected .slot-number { background: var(--accent); color: var(--accent-text); }

/* A label can wrap onto a second line (a spell and its target is long), so the chips
   fit beside the fold button on a phone. Anything longer than 2 lines ends in "…". */
.slot-text {
  min-width: 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  font-weight: 700;
  font-size: 0.8rem;
  line-height: 1.15;
  overflow-wrap: anywhere; /* a long word breaks instead of being clipped */
}

.dock-editor {
  margin-top: 10px;
}

.dock-editor .message { margin-bottom: 10px; }

/* Nothing / Move / Cast down the left, and the chosen one's controls beside it. The column
   stretches to the height of the controls, so its buttons share that height. */
.dock-editor-row {
  display: flex;
  align-items: stretch;
  gap: 10px;
}

.dock-editor-body {
  flex: 1;
  min-width: 0;
  align-self: center;
}

.dock-editor-body > .dock-hint:first-child { margin-top: 0; }

.segmented.vertical {
  flex: none;
  flex-direction: column;
}

.segmented.vertical button { padding: 0 10px; }

.move-editor {
  display: flex;
  align-items: center;
  gap: 12px;
}

.dpad {
  display: grid;
  grid-template-columns: repeat(3, 48px);
  grid-template-rows: repeat(2, 44px);
  gap: 6px;
  flex: none;
}

.dpad button {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2);
  color: var(--text);
  font-size: 1.3rem;
  font-weight: 700;
  cursor: pointer;
  touch-action: manipulation;
}

.dpad button:active { background: var(--accent-soft); }
.dpad button:disabled { opacity: 0.35; }

/* The steps so far, with Undo under them. */
.move-summary {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

.move-steps {
  font-size: 1.4rem;
  font-weight: 800;
  letter-spacing: 0.1em;
}

/* The 4 spells, one per row: just the name, so each row stays a thin 44px touch target. */
.spell-buttons {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.spell-button {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 4px 10px;
  border: 2px solid var(--border);
  border-radius: 14px;
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.spell-button[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* INS9: a spell another action already casts this turn can't be chosen again. */
.spell-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.spell-button .muted {
  margin-left: auto;
  font-size: 0.8rem;
}

/* CSP22: a custom spell's keywords, on the right of its name. */
.spell-button .spell-button-keywords {
  max-width: 60%;
  text-align: right;
  line-height: 1.2;
}

.spell-button .spell-name {
  min-width: 0;
  font-weight: 700;
  font-size: 0.9rem;
  line-height: 1.15;
  overflow-wrap: anywhere;
}

.spell-orb {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 0.8rem;
  font-weight: 800;
  color: #fff;
}

.spell-orb.kind-damage { background: linear-gradient(135deg, #ff8a3d, #e5304f); }
.spell-orb.kind-heal { background: linear-gradient(135deg, #3ddc97, #138a55); }
.spell-orb.kind-tiles { background: linear-gradient(135deg, #5ab4ff, #3c5cd6); }
.spell-orb.kind-none { background: linear-gradient(135deg, #c39bff, #7a55e0); }

.dock-hint {
  margin-top: 8px;
  color: var(--muted);
  font-size: 0.85rem;
}

.warning-list {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
}

.warning-list li { padding: 2px 0; }
.warning-list li.fail { color: var(--danger); }
.warning-list li.info { color: var(--warning); }

.dock-actions {
  display: flex;
  gap: 10px;
  margin-top: 10px;
}

/* Send (the last button) takes the spare room; Pick, beside it in a pick phase, stays its own size. */
.dock-actions .button:last-child { flex: 1; }

.sent-line {
  margin-top: 6px;
  font-size: 0.82rem;
  color: var(--muted);
}

.sent-line.sent { color: var(--success); font-weight: 700; }

/* Your teammates' plans in the dock: one short line each. */
.team-plans {
  margin: 0 0 8px;
  padding: 0;
  list-style: none;
  font-size: 0.82rem;
}

.team-plans li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 0;
  color: var(--muted);
}

.team-plans li.sent { color: var(--text); }
.team-plans .player-dot { width: 22px; height: 22px; font-size: 0.7rem; flex: none; }
.team-plan-text { min-width: 0; overflow-wrap: anywhere; }

/* Collapsed dock: only the slots row and Send, for more board. */
.dock.collapsed .dock-editor,
.dock.collapsed .warning-list,
.dock.collapsed .team-plans,
.dock.collapsed .dock-hint { display: none; }

.button.icon-only.dock-toggle {
  width: 36px;
  min-height: 36px;
  flex: none;
  color: var(--muted);
}

/* With a finger you pinch to zoom: the +/- buttons are for a mouse. */
@media (pointer: coarse) {
  .board-tools .zoom-button { display: none; }
}

/* ---- Players, spells and picks --------------------------------------------------------------------- */

.player-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 4px 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.player-row:last-child { border-bottom: 0; }
.player-row.out { opacity: 0.55; }

.player-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-weight: 800;
  font-size: 0.85rem;
}

.player-main { min-width: 0; }

.player-name {
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.player-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: center;
  font-size: 0.8rem;
  color: var(--muted);
}

.hp-bar {
  grid-column: 2 / 4;
  height: 6px;
  border-radius: 3px;
  background: var(--surface-3);
  overflow: hidden;
}

.hp-bar > span {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, #3ddc97, #7ee0d2);
}

.hp-bar.low > span { background: linear-gradient(90deg, #ff6680, #ffb15c); }

.player-spells {
  grid-column: 2 / 4;
  font-size: 0.78rem;
  color: var(--muted);
}

.pick-slots {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-bottom: 14px;
}

.pick-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 78px;
  padding: 6px 4px;
  border: 2px dashed var(--border);
  border-radius: 14px;
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: 0.72rem;
  font-weight: 700;
  text-align: center;
  line-height: 1.15;
  cursor: pointer;
}

.pick-slot.filled {
  border-style: solid;
  border-color: var(--accent);
  background: var(--accent-soft);
}

.pick-slot .slot-label {
  font-size: 0.66rem;
  color: var(--muted);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.spell-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.spell-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 4px 12px;
  width: 100%;
  padding: 12px;
  border: 2px solid var(--border);
  border-radius: 16px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.spell-card.chosen {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* PICK14: a spell you can't afford with the Spell Points you have left. */
.spell-card.unaffordable {
  opacity: 0.45;
  cursor: not-allowed;
}

.spell-card .spell-name { font-weight: 800; }
.spell-card .spell-keywords { font-size: 0.8rem; color: var(--accent); font-weight: 700; }
.spell-card .spell-category { font-size: 0.78rem; color: var(--muted); font-weight: 600; }
.spell-card .spell-description { grid-column: 2 / 4; font-size: 0.82rem; color: var(--muted); }

/* PICK14: the top right corner of a card in a spell_points game: its cost, and its slot. */
.spell-card .spell-corner {
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}

.spell-card .spell-cost {
  font-size: 1.25rem;
  font-weight: 800;
  line-height: 1;
}

.spell-card .slot-badge {
  min-width: 52px;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 0.72rem;
  font-weight: 800;
  text-align: center;
}

/* The Spells screen (js/views/spells.js): a tab for each discipline, and under them one page
   per discipline in a row that scrolls sideways. Scroll snapping stops the row on a whole
   page, so a swipe moves one discipline at a time. */
.discipline-fire { --discipline-colour: #e5484d; }
.discipline-water { --discipline-colour: #3b63d1; }
.discipline-ice { --discipline-colour: #4fc3f7; }
.discipline-storm { --discipline-colour: #8a8f98; }
.discipline-healing { --discipline-colour: #2fa04a; }
/* The last page: the Keyword List (custom spells are made of keywords). */
.discipline-keywords { --discipline-colour: #9b59d0; }

.spells-intro { margin: 0 4px 12px; }

/* The tabs stay at the top (under the app bar) while you scroll down a page. The bar
   around them reaches out over the screen's side padding, in the page's colour, so the
   spells scroll out of sight behind it. */
.spell-tabs-bar {
  position: sticky;
  top: calc(var(--bar-height) + env(safe-area-inset-top));
  z-index: 10;
  margin: 0 calc(-16px - env(safe-area-inset-right)) 0 calc(-16px - env(safe-area-inset-left));
  padding: 6px calc(16px + env(safe-area-inset-right)) 6px calc(16px + env(safe-area-inset-left));
  background: var(--bg);
}

/* On a narrow phone the 6 tabs (5 disciplines and Keywords) don't all fit: the strip then
   scrolls sideways, and the shown tab is kept in view (js/views/spells.js). */
.spell-tabs {
  position: relative;
  display: flex;
  gap: 4px;
  padding: 4px;
  border-radius: 14px;
  background: var(--surface-2);
  overflow-x: auto;
  scrollbar-width: none;
}

.spell-tabs::-webkit-scrollbar { display: none; }

.spell-tab {
  flex: 1 0 auto;
  min-height: 44px;
  padding: 0 8px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-weight: 700;
  font-size: 0.85rem;
  white-space: nowrap;
  cursor: pointer;
  touch-action: manipulation;
}

/* The shown tab: raised like a chosen segment, with a bar in its discipline's colour. */
.spell-tab[aria-selected='true'] {
  background: var(--surface);
  color: var(--text);
  box-shadow: inset 0 -3px 0 var(--discipline-colour), 0 1px 4px rgba(0, 0, 0, 0.12);
}

.spell-pages {
  display: flex;
  align-items: flex-start;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Swiping past the first or last page doesn't swipe the browser back or forward. */
  overscroll-behavior-x: contain;
  /* No scrollbar under the pages: the tabs show where you are. */
  scrollbar-width: none;
}

.spell-pages::-webkit-scrollbar { display: none; }

.spell-page {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
  /* One page per swipe, even a fast one. */
  scroll-snap-stop: always;
}

.spell-book-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  border-left: 6px solid var(--discipline-colour);
  cursor: default;
}

.spell-book-card .spell-range { font-size: 0.8rem; color: var(--muted); font-weight: 600; }
.spell-book-card .spell-description { margin-top: 4px; }

/* A small phone: smaller tab names, so "Healing" still fits. */
@media (max-width: 359px) {
  .spell-tab { font-size: 0.75rem; }
}

/* The turn log. */
.log-steps {
  margin: 0;
  padding: 0;
  list-style: none;
}

.log-step {
  padding: 10px 12px;
  border-radius: 12px;
  cursor: pointer;
}

.log-step + .log-step { margin-top: 4px; }
.log-step.current { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.log-step .hint { display: block; }
.log-step.failed .log-step-text::after { content: ' ✕'; color: var(--danger); }

.result-banner {
  padding: 22px 18px;
  border-radius: 20px;
  text-align: center;
  background: var(--quicksilver);
  color: #10202e;
}

.result-banner .big-emoji { font-size: 2.6rem; line-height: 1; }
.result-banner h2 { margin: 8px 0 4px; font-size: 1.4rem; }

.team-wins {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
}

.team-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.6);
  font-weight: 700;
  font-size: 0.85rem;
}

.team-swatch {
  width: 12px;
  height: 12px;
  border-radius: 50%;
}

/* Every player's stats on the "Game over" sheet, grouped by team. */
.game-stats { margin-top: 18px; }

.stats-team + .stats-team { margin-top: 18px; }

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

.stats-team-title .grow { flex: 1; min-width: 0; }
.stats-team-title .team-swatch { width: 14px; height: 14px; flex: none; }

.stats-player {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface-2);
}

.stats-player + .stats-player { margin-top: 6px; }

.stats-player-head {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 12px;
}

/* One line per stat: the label on the left, the value on the right. */
.stat-list { margin: 8px 0 0; font-size: 0.9rem; }

.stat-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 3px 0;
}

.stat-row + .stat-row { border-top: 1px solid var(--border); }
.stat-row dt { color: var(--muted); flex: none; }
.stat-row dd { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; overflow-wrap: anywhere; }

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

.legend-item {
  display: flex;
  align-items: center;
  gap: 12px;
}

.legend-swatch {
  position: relative;
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  overflow: hidden;
}

/* The lobby. */
.join-code {
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: 2.2rem;
  font-weight: 800;
  letter-spacing: 0.25em;
  text-align: center;
  padding: 8px 0 4px;
}

.secret {
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.slot-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.slot-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  border-bottom: 1px solid var(--border);
}

.slot-list li:last-child { border-bottom: 0; }
.slot-list .grow { flex: 1; min-width: 0; }
.slot-list .empty { color: var(--muted); font-style: italic; }

.bot-choice {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px;
  border: 2px solid var(--border);
  border-radius: 14px;
  cursor: pointer;
}

.bot-choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.bot-choice input { margin-top: 4px; width: 20px; height: 20px; accent-color: var(--accent); }
.bot-choice-name { display: block; font-weight: 800; }

.install-card {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--quicksilver);
  color: #10202e;
  border: 0;
}

.install-card img { width: 44px; height: 44px; border-radius: 12px; }
.install-card .grow { flex: 1; }

/* ---- Bigger screens -------------------------------------------------------------------------------- */

@media (min-width: 720px) {
  .view { padding-top: 24px; }

  .tab-bar {
    left: 50%;
    right: auto;
    width: 640px;
    bottom: 12px;
    transform: translateX(-50%);
    border: 1px solid var(--border);
    border-radius: 20px;
    box-shadow: var(--shadow);
    padding-bottom: 0;
  }

  dialog.sheet {
    inset: 0;
    margin: auto;
    border-radius: 22px;
    max-height: 80vh;
  }

  dialog.sheet.sheet-tall { height: 80vh; }

  .legend { grid-template-columns: 1fr 1fr; }
}

/* A tablet or computer sideways: the board on the left, the dock on the right. */
@media (min-width: 900px) and (orientation: landscape) {
  .play {
    display: grid;
    grid-template-columns: 1fr 380px;
    grid-template-rows: auto 1fr;
  }

  .status-strip { grid-column: 1 / 3; }

  .dock {
    grid-column: 2;
    grid-row: 2;
    overflow-y: auto;
    border-top: 0;
    border-left: 1px solid var(--border);
    box-shadow: none;
  }

  .dock .dock-toggle { display: none; }
  .dock.collapsed .dock-editor,
  .dock.collapsed .warning-list,
  .dock.collapsed .team-plans,
  .dock.collapsed .dock-hint { display: block; }
}

/* Less motion for people who asked their phone for it. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---- Building custom spells (js/views/customPickSheet.js, CSP3) ---------------------------- */

/* The Spell Points spent, kept in sight at the top of the sheet while you scroll the spells. */
.points-sticky {
  position: sticky;
  top: -6px;
  z-index: 1;
  margin: 0 -20px 10px;
  padding: 8px 20px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.points-meter {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.points-bar {
  display: block;
  height: 8px;
  border-radius: 4px;
  background: var(--surface-3);
  overflow: hidden;
}

.points-bar > span {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, #3ddc97, #7ee0d2);
}

.points-bar.over > span { background: linear-gradient(90deg, #ff6680, #ffb15c); }

.custom-spell {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 10px;
}

.custom-spell-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.custom-spell-head input {
  flex: 1;
  min-width: 0;
  font-weight: 700;
}

/* One keyword: its name (tap it for what it does), its X, and the buttons that move it or
   take it off. It wraps onto two lines on a narrow phone. */
.keyword-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
  padding: 4px 0;
  border-top: 1px solid var(--border);
}

.keyword-row .keyword-name {
  flex: 1 1 8em;
  min-width: 0;
}

.keyword-name summary {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  cursor: pointer;
}

.keyword-name p { margin: 0 0 6px; }

.keyword-value {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.keyword-x {
  min-width: 1.8em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.keyword-row .button.small.icon-only {
  background: var(--surface-2);
  border-color: var(--border);
}

.keyword-row .button.small.icon-only:disabled { opacity: 0.4; }

.keyword-adder select { width: 100%; }

.keyword-range { margin: 0; }

/* A reward offered to your team, and the spell it goes on. */
.reward-row {
  padding: 4px 12px 10px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}

.reward-row.taken {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.reward-choices .chip {
  min-width: 44px;
  min-height: 44px;
  justify-content: center;
  padding: 0 10px;
}
