/* Ultimate Wizard Battle client. Colours are variables, so the dark theme only has to
   change them. Tile colours match the old map editor. */

:root {
  --bg: #f4f5f7;
  --panel: #ffffff;
  --panel-alt: #f9fafb;
  --text: #1f2328;
  --muted: #5f6b7a;
  --border: #d0d7de;
  --accent: #1f63c6;
  --accent-text: #ffffff;
  --link: #1a56b0;
  --danger: #c62828;
  --success: #1b7f3a;
  --warning: #a86400;
  --info-bg: #eef4ff;
  --error-bg: #fdecec;
  --warning-bg: #fff6e0;
  --success-bg: #e8f6ec;
  --tile-floor: #ffffff;
  --tile-wall: #3a3a3a;
  --tile-capture: #fff4d6;
  --tile-hill: #e3f5e1;
  --tile-border: #c8c8c8;
  --label: #777;
  /* Light blue, a little darker than in the dark theme so it's readable on white. */
  --current-step: #039be5;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  /* The pick panel's discipline columns. */
  --fire: #d32f2f;
  --water: #1a3d99;
  --ice: #4fc3f7;
  --storm: #8a8f98;
  --healing: #2e7d32;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: #15171c;
    --panel: #1e2128;
    --panel-alt: #252932;
    --text: #e6e8eb;
    --muted: #9aa4b1;
    --border: #3a3f4a;
    --accent: #1e54ad;
    --accent-text: #ffffff;
    --link: #8ab4ff;
    --info-bg: #1d2738;
    --error-bg: #3a1f22;
    --warning-bg: #3a3020;
    --success-bg: #1d3325;
    --tile-floor: #e9e9e9;
    --tile-wall: #2a2a2a;
    --tile-capture: #f3e6c2;
    --tile-hill: #d3e9d0;
    --tile-border: #555;
    --label: #9aa4b1;
    --current-step: #4fc3f7;
    --water: #3b63d1;
    --healing: #43a047;
    color-scheme: dark;
  }
}

:root[data-theme='dark'] {
  --bg: #15171c;
  --panel: #1e2128;
  --panel-alt: #252932;
  --text: #e6e8eb;
  --muted: #9aa4b1;
  --border: #3a3f4a;
  --accent: #1e54ad;
  --accent-text: #ffffff;
  --link: #8ab4ff;
  --info-bg: #1d2738;
  --error-bg: #3a1f22;
  --warning-bg: #3a3020;
  --success-bg: #1d3325;
  --tile-floor: #e9e9e9;
  --tile-wall: #2a2a2a;
  --tile-capture: #f3e6c2;
  --tile-hill: #d3e9d0;
  --tile-border: #555;
  --label: #9aa4b1;
  --current-step: #4fc3f7;
  --water: #3b63d1;
  --healing: #43a047;
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* The hidden attribute always wins, even over display: grid or flex below. */
[hidden] { display: none !important; }

html, body { margin: 0; }

body {
  font: 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
}

a { color: var(--link); }
code { font-family: Consolas, "Courier New", monospace; font-size: 0.95em; }
h1 { font-size: 22px; margin: 0 0 12px; }
h2 { font-size: 17px; margin: 18px 0 8px; }
h3 { font-size: 15px; margin: 14px 0 6px; }
p { margin: 6px 0; }

button, .button, input, select, textarea { font: inherit; color: inherit; }

button, .button {
  display: inline-block;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  cursor: pointer;
  text-decoration: none;
  color: var(--text);
}
button:hover:not(:disabled), .button:hover { border-color: var(--accent); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.primary, .button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
button.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
button.small, .button.small { padding: 2px 8px; font-size: 12px; }
button.active, button[aria-pressed='true'] { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }

input[type=text], input[type=number], input[type=url], input[type=password], input:not([type]), select, textarea {
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
}
input[type=number] { width: 90px; }
textarea { width: 100%; font-family: Consolas, "Courier New", monospace; font-size: 12px; }

/* ---------- Top bar ---------- */

.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  z-index: 10;
}
.brand { font-weight: 700; text-decoration: none; color: var(--text); }
.nav { display: flex; gap: 4px; flex-wrap: wrap; }
.nav a { padding: 5px 10px; border-radius: 6px; text-decoration: none; color: var(--text); }
.nav a:hover { background: var(--panel-alt); }
.nav a.active { background: var(--accent); color: var(--accent-text); }
.nav a.paused { opacity: 0.55; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.current-server { color: var(--muted); font-size: 12px; max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 1500px wide at most: wide enough for the Spells screen's 5 discipline columns (5 x 260px
   plus the gaps) to fit inside a panel without a sideways scrollbar. */
main { padding: 16px; max-width: 1500px; margin: 0 auto; outline: none; }

/* ---------- Panels and layout ---------- */

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 16px;
  box-shadow: var(--shadow);
}
.panel > h1:first-child, .panel > h2:first-child { margin-top: 0; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.stack > * + * { margin-top: 8px; }
.columns { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 16px; align-items: start; }
@media (max-width: 1000px) { .columns { grid-template-columns: 1fr; } }
/* The Play screen's "Last turn" panel: a narrow column left of the map and the instruction
   builder below it, as tall as both together (its list scrolls inside it). The panel's height
   of 0 keeps it from making the rows taller; min-height: 100% then stretches it to their height. */
.map-row { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: stretch; }
.map-row > .turn-log { grid-column: 1; grid-row: 1 / span 2; width: 260px; margin-right: 16px; height: 0; min-height: 100%; overflow-y: auto; }
.map-row > .turn-log h2 { font-size: 16px; }
/* Every step has a border, but only the one the map is showing has a visible one, so the
   list doesn't shift when the selection moves. */
.turn-steps > li { border: 2px solid transparent; border-radius: 6px; padding: 2px 6px; cursor: pointer; }
.turn-steps > li.turn-step-current { border-color: var(--current-step); color: var(--current-step); font-weight: 700; }
.turn-steps > li.turn-step-current .hint { color: inherit; }
.map-row > :not(.turn-log) { grid-column: 2; }
@media (max-width: 700px) {
  .map-row { grid-template-columns: minmax(0, 1fr); }
  .map-row > :not(.turn-log) { grid-column: 1; }
  .map-row > .turn-log { grid-column: 1; grid-row: auto; order: 1; width: auto; margin-right: 0; height: auto; min-height: 0; max-height: 400px; }
}
.hint { color: var(--muted); font-size: 12px; }
/* Help text (class "help") that a "Hide help" button can hide: see helpToggleButton in create.js. */
.hide-help .help { display: none; }
.muted { color: var(--muted); }
.spacer { flex: 1; }
.nowrap { white-space: nowrap; }

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

.field { display: flex; flex-direction: column; gap: 3px; margin-bottom: 10px; }
.field-label { font-weight: 600; font-size: 13px; }
.field-error { color: var(--danger); font-size: 12px; }
.field-error:empty { display: none; }
.field.has-error input, .field.has-error select { border-color: var(--danger); }
.form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 4px 16px; }
.checkbox { display: flex; align-items: center; gap: 6px; margin: 4px 0; }
/* A checkbox drawn as an on/off switch (the Board settings panel). */
input.switch {
  appearance: none;
  flex: none;
  width: 34px;
  height: 18px;
  margin: 0;
  border-radius: 9px;
  background: var(--border);
  position: relative;
  cursor: pointer;
  transition: background 0.15s;
}
input.switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  transition: left 0.15s;
}
input.switch:checked { background: var(--accent); }
input.switch:checked::before { left: 18px; }
input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.board-settings .checkbox { cursor: pointer; }
fieldset { border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; margin: 0 0 12px; }
legend { font-weight: 600; padding: 0 4px; }

/* ---------- Messages, badges, toasts ---------- */

.message { padding: 8px 12px; border-radius: 6px; margin: 8px 0; border: 1px solid transparent; }
.message-info { background: var(--info-bg); border-color: #b6cdf5; }
.message-error { background: var(--error-bg); border-color: #f1b0b0; color: var(--text); }
.message-warning { background: var(--warning-bg); border-color: #f0d080; }
.message-success { background: var(--success-bg); border-color: #a6d8b4; }

.badge { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px; font-weight: 600; border: 1px solid var(--border); background: var(--panel-alt); white-space: nowrap; }
.badge-turn { background: #ffd54f; border-color: #e0a800; color: #000; }
.badge-good { background: var(--success-bg); border-color: #a6d8b4; }
.badge-bad { background: var(--error-bg); border-color: #f1b0b0; }
.badge-info { background: var(--info-bg); border-color: #b6cdf5; }

#toasts { position: fixed; right: 16px; bottom: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 100; }
.toast { padding: 10px 14px; border-radius: 8px; background: var(--panel); border: 1px solid var(--border); box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-width: 360px; }
.toast-success { border-left: 4px solid var(--success); }
.toast-error { border-left: 4px solid var(--danger); }
.toast-info { border-left: 4px solid var(--accent); }

/* ---------- Dialogs ---------- */

.dialog { border: 1px solid var(--border); border-radius: 10px; padding: 0; max-width: 480px; width: calc(100% - 32px); background: var(--panel); color: var(--text); }
.dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
.dialog-form { padding: 16px; }
.dialog-form h2 { margin-top: 0; }
.dialog-form input { width: 100%; }
.dialog-buttons { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }

/* ---------- Tables and lists ---------- */

table.list { width: 100%; border-collapse: collapse; }
table.list th, table.list td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
table.list th { font-size: 12px; color: var(--muted); font-weight: 600; }
table.list tr.clickable { cursor: pointer; }
table.list tr.clickable:hover td { background: var(--panel-alt); }
.tabs { display: flex; gap: 4px; margin-bottom: 12px; flex-wrap: wrap; }
dl.facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 0; }
dl.facts dt { color: var(--muted); }
dl.facts dd { margin: 0; }

.big-secret { font-family: Consolas, "Courier New", monospace; font-size: 22px; font-weight: 700; letter-spacing: 0.06em; padding: 4px 10px; border: 1px dashed var(--border); border-radius: 6px; background: var(--panel-alt); }

/* ---------- The map ---------- */

.map-view { display: flex; flex-direction: column; gap: 6px; }
.map-hover { font-family: Consolas, "Courier New", monospace; font-size: 12px; color: var(--muted); min-height: 1.4em; }
.map-scroll { overflow: auto; max-height: 75vh; padding: 4px; }
/* The Play screen's board (fitWidth) is as wide as its panel, so it's as tall as it needs to
   be too: the page scrolls instead of the board. */
.map-fit-width .map-scroll { max-height: none; }
.map-grid { display: grid; width: max-content; user-select: none; -webkit-user-select: none; touch-action: none; }
.map-label { font-size: 10px; color: var(--label); display: flex; align-items: center; justify-content: center; }

.tile {
  position: relative;
  border: 1px solid var(--tile-border);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tile-floor);
}
.tile.clickable { cursor: pointer; }
.tile.type-plain_floor { background: var(--tile-floor); }
.tile.type-impassable { background: var(--tile-wall); }
.tile.type-unknown { background: #ff00ff; }
/* TILE12: the tiles a game mode scores on: a tinted floor with a coloured outline (effects
   use the inset border, so both show), and a mark in the top corner. */
.tile.type-capture_point { background: var(--tile-capture); outline: 2px solid #e0a100; outline-offset: -2px; }
.tile.type-hill { background: var(--tile-hill); outline: 2px solid #3a9b45; outline-offset: -2px; }
.tile.type-capture_point:not([class*="ov-"])::before, .tile.type-hill:not([class*="ov-"])::before {
  position: absolute; left: 1px; top: 0; font-size: 9px; line-height: 1; pointer-events: none;
}
.tile.type-capture_point:not([class*="ov-"])::before { content: "🚩"; }
.tile.type-hill:not([class*="ov-"])::before { content: "⛰"; color: #2e7d32; }
/* Effects: a coloured inset border (like the editor), plus a small mark in the corner. */
.tile.effect-on_fire { box-shadow: inset 0 0 0 4px #e53935; }
.tile.effect-wet { box-shadow: inset 0 0 0 4px #1e88e5; }
.tile.effect-frozen { box-shadow: inset 0 0 0 4px #81d4fa; background-image: linear-gradient(135deg, rgba(129,212,250,0.35), rgba(255,255,255,0)); }
.tile.effect-doomfire { box-shadow: inset 0 0 0 4px #7b1fa2; background-image: linear-gradient(135deg, rgba(123,31,162,0.35), rgba(123,31,162,0.1)); }
.tile.effect-unknown { box-shadow: inset 0 0 0 4px #ff00ff; }
.tile.effect-on_fire::after, .tile.effect-wet::after, .tile.effect-frozen::after, .tile.effect-doomfire::after {
  position: absolute; right: 1px; bottom: 0; font-size: 9px; line-height: 1; pointer-events: none;
}
.tile.effect-on_fire::after { content: "🔥"; }
.tile.effect-wet::after { content: "💧"; }
.tile.effect-frozen::after { content: "❄"; color: #0277bd; }
.tile.effect-doomfire::after { content: "☠"; color: #7b1fa2; }

/* Overlays drawn on top of the tile. */
.tile[class*="ov-"]::before { content: ""; position: absolute; inset: 0; pointer-events: none; }
.tile.ov-path::before { background: rgba(255, 193, 7, 0.45); }
.tile.ov-area::before { background: rgba(229, 57, 53, 0.30); }
.tile.ov-target::before { outline: 3px solid #e53935; outline-offset: -3px; }
.tile.ov-invalid::before { background: repeating-linear-gradient(45deg, rgba(198,40,40,0.5) 0 4px, transparent 4px 8px); }
.tile.ov-option::before { outline: 2px dashed var(--accent); outline-offset: -3px; background: rgba(91, 60, 196, 0.15); }
.tile.ov-error::before { outline: 3px solid #ff1744; outline-offset: -3px; background: rgba(255, 23, 68, 0.25); }
.tile.ov-selected::before { outline: 3px solid #ff00c8; outline-offset: -2px; }
.tile.ov-you::before { outline: 2px solid var(--accent); outline-offset: -2px; }
.tile.ov-step::before { background: rgba(0, 150, 136, 0.35); }
/* Where the spell you're aiming will hit (the tile under the mouse and its area). Last, so it wins over 'option'. */
.tile.ov-aim::before { background: rgba(255, 82, 82, 0.40); }

.token {
  width: 78%;
  height: 78%;
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);
  line-height: 1;
  position: relative;
  z-index: 1;
  pointer-events: none;
  font-size: calc(var(--tile-size, 26px) * 0.36);
}
.token-hp { font-size: calc(var(--tile-size, 26px) * 0.24); font-weight: 600; opacity: 0.95; }
/* "Show player names" (Board settings): the name instead of the slot number. It doesn't wrap
   and may be wider than the tile (it spills over the tiles next to it, centred on the token);
   the background is the token's own, so it can be read on any tile. */
.token-name {
  font-size: calc(var(--tile-size, 26px) * 0.26);
  white-space: nowrap;
  background: inherit;
  padding: 1px 3px;
  border-radius: 3px;
}
/* "Show hp as a bar" (Board settings): a bar instead of the hp number, sized from the tile
   (like the token), so it always stays inside it. Green like the Players panel's hp bars.
   Its width is a share of the tile, not of the token: the token is smaller than the tile,
   and the bar is meant to almost reach the tile's edges. It's wider than the token, which
   is fine: the token centres it, so it sticks out the same amount on both sides. */
.token-hp-bar {
  display: block;
  width: calc(var(--tile-size, 26px) * 0.9);
  height: max(3px, calc(var(--tile-size, 26px) * 0.12));
  margin-top: max(1px, calc(var(--tile-size, 26px) * 0.04));
  background: #2b2b2b; /* solid, so the token's colour doesn't show through the empty part */
  border: 1px solid rgba(255, 255, 255, 0.8);
  border-radius: 2px;
  overflow: hidden;
}
.token-hp-bar > span { display: block; height: 100%; background: var(--success); }
.token-you { box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--accent); }
.token-ghost { background: transparent; border: 2px dashed; box-shadow: none; opacity: 0.85; }
/* A border in the player's own colour, around a fill in their team's colour (both set on
   the token). A bit bigger than a plain token, so the border doesn't squeeze the slot
   number and hp. */
.token-team { border: max(2px, calc(var(--tile-size, 26px) * 0.09)) solid; width: 86%; height: 86%; }
/* "Show player icon" (Board settings): the token is a wizard with a staff (an SVG,
   js/ui/mapView.js) instead of a filled circle. The wizard fills the tile. The hp (number or
   bar) sits right at the bottom of the tile, with the slot number (or name) just above it. */
.token-icon {
  width: 100%;
  height: 100%;
  background: transparent;
  box-shadow: none;
  /* .token is a column: put its contents at the bottom instead of the middle. */
  justify-content: flex-end;
}
.token-icon-shape {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  /* A thin dark edge, so a wizard in a light colour still stands out on a light tile. */
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.7));
}
/* The number, name and hp go over the wizard, not under it. */
.token-icon > span { position: relative; z-index: 1; }
/* The hp comes after the name on the page (js/ui/mapView.js), so it's the bottom line. */
.token-icon .token-hp,
.token-icon .token-hp-bar {
  margin-top: 1px;
  margin-bottom: 1px;
}
/* The name box can't inherit a background (the token has none), so it uses the wizard's fill. */
.token-icon .token-name { background: var(--token-fill); }
.token-icon.token-you { box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--accent); }

.legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; color: var(--muted); margin-top: 6px; }
.legend-toggle { margin-top: 6px; }
/* The Replays screen: which round and turn the board is on, above the map. */
.replay-round { margin: 0 0 8px; font-weight: 600; }
.panel-heading + div:not([hidden]) { margin-top: 12px; }

/* An "i" in a circle; pointing at it (or Tab to it) shows its note in a box below it. The box
   opens to the left, as the panels it's used in sit on the right of the screen. */
.info-tip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: 1px solid var(--muted);
  border-radius: 50%;
  color: var(--muted);
  font-size: 12px;
  font-style: italic;
  font-weight: 700;
  line-height: 1;
  cursor: help;
  flex: none;
}
.info-tip:hover, .info-tip:focus-visible { border-color: var(--accent); color: var(--accent); }
.info-tip-text {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  width: 240px;
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  color: var(--text);
  font-size: 13px;
  font-style: normal;
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
}
.info-tip:hover > .info-tip-text, .info-tip:focus > .info-tip-text { display: block; }
/* Each note on its own line, with a little space between them. (They're spans, as a <p>
   isn't allowed inside the icon's <span>.) */
.info-tip-note { display: block; }
.info-tip-note + .info-tip-note { margin-top: 6px; }
.legend-item { display: inline-flex; align-items: center; gap: 6px; }
.legend-swatch { width: 18px; height: 18px; display: inline-flex; font-size: 10px; flex: none; }
.legend-swatch.token { width: 18px; height: 18px; font-size: 10px; }
.legend-swatch.token-team { border-width: 3px; }

/* ---------- Players, spells ---------- */

.player-list { list-style: none; margin: 0; padding: 0; }
.player-list > li { display: flex; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.player-dot { width: 14px; height: 14px; border-radius: 50%; display: inline-block; box-shadow: 0 0 0 1px rgba(0,0,0,0.3); flex: none; }
.player-dot-team { width: 18px; height: 18px; border: 4px solid; }

/* The player list's spells popup: hidden until you point at the player (or Tab to them),
   then shown under their name. pointer-events: none, so it never gets in the way of the
   rows under it: moving the mouse off the player closes it. */
.player-row { position: relative; }
.player-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.player-spells-popup {
  display: none;
  position: absolute;
  top: 100%;
  left: 26px;
  z-index: 20;
  min-width: 220px;
  max-width: 320px;
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  pointer-events: none;
}
.player-row:hover > .player-spells-popup, .player-row:focus > .player-spells-popup { display: block; }
.player-spells-title { font-weight: 600; margin-bottom: 4px; overflow-wrap: anywhere; }
.player-spells-list { margin: 0; padding-left: 20px; }
.player-spells-list li { padding: 1px 0; }
.player-spells-popup p { margin: 4px 0 0; }
.player-name { font-weight: 600; overflow-wrap: anywhere; }
/* The Play screen's player list. First line: the name, then the hp bar and hp on the right.
   A long name wraps onto more lines inside its own space rather than pushing the hp away. */
.player-row-main { flex-basis: 100%; display: flex; align-items: center; gap: 8px; }
.player-row-main > .player-name { flex: 1 1 auto; min-width: 0; }
/* Below it: "you", "bot", the team and "thinking". */
.player-row-badges { flex-basis: 100%; padding-left: 22px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 13px; }
/* The Play screen's player list: the player's spells on their own line(s) under the name. */
.player-row-spells { flex-basis: 100%; padding-left: 22px; font-size: 13px; }
.player-row-spells .player-spells-list { padding-left: 18px; }
.player-row-spells p { margin: 0; }
.player-row-note { flex-basis: 100%; padding-left: 22px; font-size: 13px; }
.players-filter { margin-bottom: 6px; }
.player-out { opacity: 0.55; }
.hp-bar { flex: none; width: 70px; height: 8px; background: var(--panel-alt); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
.hp-bar > span { display: block; height: 100%; background: var(--success); }

.spell-keywords { font-size: 12px; color: var(--muted); }

/* The Play screen's Spell details panel: the spell you're choosing or aiming. */
.spell-details-name { font-size: 16px; font-weight: 700; }
.spell-details-level { color: var(--muted); margin-top: 2px; }
.spell-details-keywords { margin-top: 6px; }
.spell-details-description { font-style: italic; margin: 8px 0 0; }

/* ---------- The instruction builder ---------- */

.action-row { border: 1px solid var(--border); border-radius: 8px; padding: 8px; margin-bottom: 8px; background: var(--panel-alt); }
.action-row.selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.action-row h4 { margin: 0 0 6px; font-size: 13px; }
.direction-pad { display: inline-grid; grid-template-columns: repeat(3, 34px); grid-template-rows: repeat(2, 30px); gap: 2px; }
.direction-pad button { padding: 0; }
.warning-list { margin: 6px 0 0; padding-left: 18px; font-size: 12px; }
.warning-list .fail { color: var(--danger); }
.warning-list .info { color: var(--warning); }
.sent-box { font-size: 12px; }

/* ---------- The "Round over" screen ---------- */

.round-over { border: 2px solid var(--accent); }
.round-over h2 { margin-top: 0; }

/* ---------- The "Game over" screen ---------- */

.game-over { text-align: center; }
.game-over .row { justify-content: center; }
.big-emoji { font-size: 40px; line-height: 1; }
.team-wins { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.team-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px; font-weight: 600; }
.team-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 50%; flex: none; }

/* Every player's stats, grouped by team, the teams side by side when there's room. */
.game-stats { margin-top: 16px; text-align: left; display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; }
.stats-team-title { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; font-size: 15px; }
.stats-players { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 6px; }
.stats-player { padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel-alt); }
.game-over .stats-player .row { justify-content: flex-start; }

/* One line per stat: the label on the left, the value on the right. */
.stat-list { margin: 6px 0 0; font-size: 13px; }
.stat-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 2px 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 pick panel ---------- */

/* One column per discipline, side by side. The row scrolls sideways when the columns
   don't fit; each column is split into Level 1, 2 and 3 from top to bottom. */
.pick-disciplines { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 8px; align-items: stretch; }
.pick-discipline { flex: 0 0 260px; border: 3px solid var(--discipline-colour, var(--border)); border-radius: 10px; padding: 8px; background: var(--panel); }
.pick-discipline > h3 { margin: 0 0 4px; color: var(--discipline-colour); }
.pick-level { display: flex; flex-direction: column; gap: 6px; padding: 6px 0; }
.pick-level + .pick-level { border-top: 1px dashed var(--border); }
.pick-level > h4 { margin: 0; font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.pick-level > p { margin: 0; }
.discipline-fire { --discipline-colour: var(--fire); }
.discipline-water { --discipline-colour: var(--water); }
.discipline-ice { --discipline-colour: var(--ice); }
.discipline-storm { --discipline-colour: var(--storm); }
.discipline-healing { --discipline-colour: var(--healing); }

.pick-card { border: 1px solid var(--border); border-radius: 8px; background: var(--panel-alt); }
/* A chosen card takes its discipline's colour (set by the .discipline-* column it sits in). */
.pick-card.chosen { border-color: var(--discipline-colour, var(--accent)); box-shadow: 0 0 0 2px var(--discipline-colour, var(--accent)); }
/* Hovering the clickable part of a card lights its border up a little, to show which card a
   click would add or take out. Hovering the ↑ ↓ buttons doesn't, as they do something else. */
.pick-card:has(.pick-card-toggle:hover) { border-color: var(--discipline-colour, var(--accent)); }
.pick-card.chosen:has(.pick-card-toggle:hover) { box-shadow: 0 0 0 3px var(--discipline-colour, var(--accent)); }
/* The card's main button fills the card and looks like plain text. */
.pick-card-toggle { display: block; width: 100%; padding: 8px; border: none; border-radius: 8px; background: none; text-align: left; }
.pick-card-toggle[aria-pressed='true'] { background: none; color: var(--text); }
.pick-card-slot { padding: 6px 8px 0; }
/* A card on the Spells screen: just text, padded like the pick panel's card button. */
.spell-card-body { padding: 8px; }
.pick-card:focus-visible { outline: 2px solid var(--discipline-colour, var(--accent)); outline-offset: 1px; }
/* What a spell does, or what a keyword does, shown while you point at it (js/ui/tooltip.js
   places it).
   pointer-events: none, so it never covers the card the mouse moves to next. */
.spell-tooltip {
  display: none;
  position: fixed;
  z-index: 30;
  width: 280px;
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--discipline-colour, var(--border));
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  color: var(--text);
  font-size: 13px;
  font-style: normal;
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
  pointer-events: none;
}
/* Something with a tooltip inside a card or the Spell details panel (a keyword, or a spell's
   name on the Spells screen): a dotted underline and a "?" pointer show you can point at it. */
.keyword-tip, .spell-name-tip { cursor: help; text-decoration: underline dotted; text-underline-offset: 2px; }
.keyword-tip:hover, .keyword-tip:focus-visible, .spell-name-tip:hover, .spell-name-tip:focus-visible { color: var(--accent); }
.spell-tooltip-note { margin-top: 6px; color: var(--muted); }
.pick-card .slot-number { font-weight: 700; color: var(--discipline-colour, var(--accent)); }

/* ---------- The map editor ---------- */

/* The Create screen: the game's settings on the left and, while it's open, the map editor
   (its settings and the board) on the right. On a narrow screen they go one above the other. */
.create-layout { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: 16px; align-items: start; }
.create-layout:has(> .create-editor:not([hidden])) { grid-template-columns: 420px minmax(0, 1fr); }
@media (max-width: 1200px) { .create-layout:has(> .create-editor:not([hidden])) { grid-template-columns: minmax(0, 1fr); } }

.editor { display: grid; grid-template-columns: 340px minmax(0, 1fr); gap: 16px; align-items: start; }
@media (max-width: 900px) { .editor { grid-template-columns: 1fr; } }
.editor-side section { border-top: 1px solid var(--border); padding: 10px 0; }
.editor-side section:first-child { border-top: none; padding-top: 0; }
.tools { display: flex; flex-wrap: wrap; gap: 4px; }
.tool { display: inline-flex; align-items: center; gap: 5px; padding: 3px 7px; font-size: 12px; }
.tool .key { color: var(--muted); font-size: 10px; }
.tool.active { box-shadow: 0 0 0 2px var(--accent); background: var(--warning-bg); color: var(--text); }
.swatch { display: inline-block; width: 16px; height: 16px; border: 1px solid #999; border-radius: 2px; }
.checklist { list-style: none; margin: 0; padding: 0; }
.checklist li { padding: 2px 0; }
.checklist li.ok { color: var(--success); }
.checklist li.warn { color: var(--warning); }
.checklist li.err { color: var(--danger); font-weight: 600; }
.checklist li.clickable { cursor: pointer; text-decoration: underline dotted; }

/* The Add a bot dialog: one choice per bot the server offers, with its description. */
.bot-choices { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.bot-choice { display: flex; align-items: flex-start; gap: 8px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; cursor: pointer; }
.bot-choice:has(input:checked) { border-color: var(--accent); }
.dialog-form .bot-choice input { width: auto; margin-top: 3px; }
.bot-choice-name { font-weight: 600; }
.bot-choice-description { display: block; }

/* Your teammates' plans (TURN12): blue, so they don't look like your own (yellow and red). */
.tile.ov-team-path::before { background: rgba(30, 136, 229, 0.35); }
.tile.ov-team-area::before { background: rgba(30, 136, 229, 0.22); }
.tile.ov-team-target::before { outline: 3px dashed #1e88e5; outline-offset: -3px; }

/* ---- Spell, fire and Doomfire effects, played over the map during a turn's playback ----
   The same effects as UWB Quicksilver's. js/game/stepEffects.js says which effect goes on
   which tile, and when; js/ui/mapView.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.

   The effects are drawn for a 44px tile (Quicksilver's size). On a smaller tile, its
   ".fx-slot" is shrunk to fit with a transform, so every effect keeps its shape. */
.map-stage { position: relative; width: max-content; }

.map-fx {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /* Big effects (Blizzard's swirl, a lightning bolt) stop at the edge of the map, instead
     of making the map's scroll bars appear while they play. */
  overflow: hidden;
}

.fx-slot {
  position: absolute;
  width: 44px;
  height: 44px;
  transform-origin: 0 0;
  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.) */
.map-fx .fx::before,
.map-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;
}

/* Flash Freeze: the same blast, but icy blue. */
.fx-ice-blast {
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0 12%, #e1f5fe 22%, #81d4fa 42%, #0288d1 62%, rgba(1, 87, 155, 0) 72%);
  animation: fx-explode 0.8s ease-out;
}

.fx-ice-blast::after {
  inset: 0;
  border-radius: 50%;
  border: 3px solid rgba(129, 212, 250, 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; }
.map-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; }
.map-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; }
.map-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;
}

