:root {
  --bg: #0b0e14;
  --bg-2: #0e131b;
  --panel: #141a24;
  --panel-2: #1a2130;
  --raise: #212a3a;
  --line: #29313f;
  --line-soft: #202836;
  --ink: #e2e8f1;
  --dim: #8996a9;
  --accent: #6fd3ff;
  --accent-warm: #ffb057;
  --danger: #ff6b6b;
  --good: #7ee787;
  --font: "Segoe UI", Roboto, system-ui, sans-serif;
  --mono: "JetBrains Mono", "Cascadia Mono", Consolas, monospace;
  --radius: 10px;
  --seat: var(--accent);
}

* { box-sizing: border-box; }

/* .lobby and .app set `display`, which would otherwise beat the UA rule for
   the `hidden` attribute and leave both screens stacked on the page. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background:
    radial-gradient(1100px 700px at 15% -10%, #17202e 0%, transparent 60%),
    radial-gradient(900px 600px at 100% 0%, #16222c 0%, transparent 55%),
    var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
}

h1, h2 { font-weight: 600; letter-spacing: .04em; }
h2 {
  font-size: 10px; text-transform: uppercase; color: var(--dim);
  margin: 18px 0 8px; letter-spacing: .16em;
  display: flex; align-items: center; gap: 8px;
}
h2::after { content: ""; flex: 1; height: 1px; background: var(--line-soft); }

button { font-family: inherit; font-size: 13px; cursor: pointer; }
select, input {
  font-family: inherit; font-size: 13px; color: var(--ink);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 7px; padding: 7px 9px; transition: border-color .15s, box-shadow .15s;
}
select:focus, input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 13%, transparent);
}
input[type="search"] { width: 100%; }

.primary {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 62%, #ffffff), var(--accent));
  color: color-mix(in srgb, var(--accent) 22%, #04161f); border: 0; border-radius: 9px;
  padding: 11px 20px; font-weight: 700; letter-spacing: .05em;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 22%, transparent);
  transition: transform .12s, box-shadow .15s, filter .15s;
}
.primary:hover { filter: brightness(1.06); transform: translateY(-1px); }
.primary:active { transform: translateY(0); }
.ghost {
  background: var(--panel-2); color: var(--ink); border: 1px solid var(--line);
  border-radius: 7px; padding: 6px 11px; transition: all .15s;
}
.ghost:hover { border-color: var(--accent); color: var(--accent); background: var(--raise); }
.ghost[disabled] { opacity: .38; cursor: default; border-color: var(--line); color: var(--dim); background: var(--panel-2); }
.ghost.on {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 62%, #ffffff), var(--accent));
  color: color-mix(in srgb, var(--accent) 22%, #04161f); border-color: transparent; font-weight: 600;
}

/* ---------------- lobby ---------------- */
.lobby { min-height: 100vh; display: grid; place-items: center; padding: 26px; }
.lobby-card {
  width: min(780px, 100%); background: linear-gradient(180deg, #18202c, var(--panel));
  border: 1px solid var(--line); border-radius: 16px; padding: 30px 32px 26px;
  box-shadow: 0 30px 70px rgba(0, 0, 0, .5);
}
.lobby-card h1 {
  margin: 0; font-size: 32px; letter-spacing: .26em;
  background: linear-gradient(90deg, #cfefff, var(--accent) 55%, #9ad6ff);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.tagline { color: var(--dim); margin: 10px 0 24px; }
.field-row { display: flex; gap: 14px; align-items: flex-end; margin-bottom: 18px; }
.field-row label { display: flex; flex-direction: column; gap: 6px; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim); }
.seat-list { display: grid; gap: 10px; }
.seat-row {
  display: grid; grid-template-columns: 34px minmax(0, .8fr) 1fr 1fr auto; gap: 12px;
  align-items: center; background: var(--panel-2);
  border: 1px solid var(--line); border-left: 4px solid var(--seat);
  border-radius: 11px; padding: 11px 13px; transition: background .15s;
}
.seat-row:hover { background: var(--raise); }
.seat-row .tag { font-weight: 700; font-size: 12px; letter-spacing: .08em; color: var(--seat); }
.seat-row .blurb { color: var(--dim); font-size: 11px; grid-column: 2 / -1; line-height: 1.4; }
.seat-row .ctrl { color: var(--dim); font-size: 11px; }
.seat-row select { width: 100%; }
.error { color: var(--danger); }
.hint { color: var(--dim); font-size: 12px; margin: 14px 0 0; }
.lobby-actions { display: flex; align-items: center; gap: 12px; margin-top: 22px; }
.lobby-actions .ghost { padding: 10px 16px; }

/* ---------------- app shell ---------------- */
.app {
  display: grid; height: 100vh; height: 100dvh; position: relative;
  /* The map is the game, so it gets the whole window under the top bar: the
     players and your options are no longer rows and columns stealing from it
     but drawers that ride *over* it, each collapsed to a handle until you ask
     for it.  Their docks are positioned against this grid, hence `relative`. */
  --side-w: 390px;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 54px minmax(0, 1fr);
  grid-template-areas:
    "top"
    "board";
}
.topbar {
  grid-area: top; display: flex; align-items: center; gap: 18px;
  /* whose move it is and which phase, and then one button for everything else */
  padding: 0 16px;
  /* the strip carries a wash of whoever is on the clock: enough to name the
     seat across the room, not enough to fight the text sitting on it */
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 9%, #18202c),
      color-mix(in srgb, var(--accent) 5%, var(--panel)));
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
}
.brand {
  font-weight: 700; letter-spacing: .2em; font-size: 13px; color: var(--accent);
  text-shadow: 0 0 18px color-mix(in srgb, var(--accent) 35%, transparent);
}
.status {
  flex: 1; min-width: 0; display: flex; gap: 10px; align-items: center;
  font-size: 13px; overflow-x: auto; scrollbar-width: none;
}
.status::-webkit-scrollbar { display: none; }
.status .chip {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 11px; font-size: 12px; white-space: nowrap;
}
/* the seat whose turn it is, filled rather than outlined: at 12px among four
   other chips an outline is not a signal, it is decoration */
.status .chip.turn-of {
  border-color: currentColor; font-weight: 600;
  background: color-mix(in srgb, currentColor 16%, var(--panel-2));
  box-shadow: 0 0 14px -4px currentColor;
}
.status .chip.turn-of.mine {
  background: color-mix(in srgb, currentColor 30%, var(--panel-2));
  letter-spacing: .06em;
  box-shadow: 0 0 18px -3px currentColor;
}

/* ---------------- whose turn it is ---------------- */
/* A rail across the whole top of the frame in the active seat's colour.  It
   reads from the far side of the screen and costs no layout: the top bar had a
   1px line under it already, so this only changes what that line is. */
.app { --turn-seat: transparent; }
.app.turn-live .topbar {
  border-bottom-color: var(--turn-seat);
  box-shadow: 0 1px 0 var(--turn-seat), 0 7px 26px -14px var(--turn-seat);
}
/* and it breathes only when the game is waiting on *you*.  "Somebody is
   thinking" and "you are holding this up" must not look the same, and a pulse
   that ran for both would just be a screen that always moves. */
.app.turn-mine .topbar { animation: turn-rail 2.6s ease-in-out infinite; }
@keyframes turn-rail {
  0%, 100% { box-shadow: 0 1px 0 var(--turn-seat), 0 7px 26px -14px var(--turn-seat); }
  50%      { box-shadow: 0 2px 0 var(--turn-seat), 0 10px 30px -8px var(--turn-seat); }
}
/* marching has no panel: this is the one line that says so */
.hint-line.march { display: flex; align-items: baseline; flex-wrap: wrap; gap: 5px; }
.hint-line.march b { color: var(--accent); }

/* nothing left in this phase but the phase-ender itself */
.all-clear {
  margin-bottom: 9px; padding: 9px 11px; font-size: 12px; line-height: 1.5;
  border: 1px dashed var(--good); border-radius: 9px;
  background: color-mix(in srgb, var(--good) 8%, var(--panel-2));
}
.all-clear b { color: var(--good); }

.controls { display: flex; gap: 8px; align-items: center; position: relative; margin-left: auto; }

/* ---------------- the active player's numbers ----------------
   Always on the bar, because every decision in an ACTION phase is priced
   against them: what there is to spend, and the ceiling it is spent against. */
.active-stats {
  display: flex; align-items: center; gap: 6px; flex: none;
  padding-left: 12px; border-left: 1px solid var(--line);
}
.stat-chip {
  display: inline-flex; align-items: baseline; gap: 4px; white-space: nowrap;
  font-size: 11px; letter-spacing: .04em; color: var(--dim);
  border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px;
  background: var(--panel-2);
}
.stat-chip b { font-family: var(--mono); font-size: 12.5px; color: var(--ink); }
/* what you spend is the seat's own colour; what caps it is not */
.stat-chip.res { border-color: color-mix(in srgb, var(--seat) 55%, var(--line)); }
.stat-chip.res b { color: var(--seat); }
.stat-chip.has-parts { cursor: help; }
.stat-chip.cap.over { border-color: var(--danger); color: var(--danger); }
.stat-chip.cap.over b { color: var(--danger); }
.stat-chip.has-parts:hover { border-color: var(--accent); color: var(--ink); }
/* what the number *is*, under the working that produced it */
.stat-help {
  margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line-soft);
  color: var(--dim); font-size: 11.5px; line-height: 1.5;
}
.tooltip > div > .stat-help:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.tip-warn { color: var(--danger); font-size: 11.5px; }
.icon-btn { padding: 5px 9px; font-size: 14px; line-height: 1; }

/* ---------------- the menu ----------------
   Everything that is not the board, behind one button.  Seven controls across
   the top bar were seven things competing with the one thing you are looking
   at; the bar keeps only what changes every turn. */
.menu-open { font-size: 17px; padding: 4px 10px; }
.menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 60;
  min-width: 210px; display: flex; flex-direction: column; gap: 2px;
  padding: 6px; border: 1px solid var(--line); border-radius: 11px;
  background: linear-gradient(180deg, var(--raise), var(--panel));
  box-shadow: 0 22px 60px rgba(0, 0, 0, .75);
}
.menu-item {
  background: transparent; border: 0; color: var(--ink);
  text-align: left; padding: 8px 11px; border-radius: 7px; font-size: 12.5px;
}
.menu-item:hover:not(:disabled) { background: var(--panel-2); color: var(--accent); }
.menu-item:disabled { color: var(--line); }
.menu-item.on { color: var(--accent); }
.menu-rule { height: 1px; margin: 4px 6px; background: var(--line-soft, var(--line)); }

/* ---------------- the swap panel ---------------- */
/* Two shelves, one at a time.  A swap cannot be taken back, so the two clicks
   that choose the pair are not the click that spends it: the confirm sits at
   the top of the panel, where it never has to be scrolled to, and it prints
   what the swap costs before you press it. */
/* ---------------- somebody else's swap ----------------
   A swap is the one move that leaves no mark on the board, so it is announced
   over it: out, arrow, in, under whose row it was.  It clears itself. */
@keyframes swap-fx {
  0%   { opacity: 0; transform: translateY(-10px) scale(.96); }
  9%   { opacity: 1; transform: none; }
  85%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-6px); }
}

/* the pair, once both halves are chosen: nothing else is on screen */

/* Abilities mode wears the same square in the warm accent, so the two modes
   are told apart by colour rather than by memory (see `.chip-mark` above). */
.seat-picker { display: flex; align-items: center; gap: 6px; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--dim); }
/* a seat is somebody, not a number; blank falls back to `P0` */
.seat-name {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  color: var(--ink); padding: 7px 10px; font-size: 13px; font-family: inherit;
  min-width: 0;
}
.seat-name:focus { outline: none; border-color: var(--accent); }
.seat-name::placeholder { color: var(--dim); }

.panel { overflow-y: auto; padding: 8px 14px 26px; background: var(--panel); }

/* ---------------- the players drawer ----------------
   At rest this is one small icon per seat in the top-left corner of the map.
   Point at it and the full cards slide down over the board; the dock itself
   takes no pointer events, so the hexes beside the rail stay clickable. */
.players-dock {
  position: absolute; top: 54px; left: 0; right: 0; z-index: 24;
  pointer-events: none;
  transition: right .24s cubic-bezier(.4, 0, .2, 1);
}
/* the options drawer sits above this one, so when it is out the players stop
   at its edge rather than sliding behind it and losing the last seat */
.app.side-open .players-dock { right: var(--side-w); }
.players-dock > * { pointer-events: auto; }
.player-rail {
  display: flex; gap: 6px; align-items: center; width: max-content;
  padding: 5px 8px; background: color-mix(in srgb, var(--panel) 86%, transparent);
  border: 1px solid var(--line); border-top: 0; border-left: 0;
  border-radius: 0 0 12px 0; backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}
.rail-seat {
  position: relative; display: flex; padding: 3px; border-radius: 9px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-bottom: 2px solid var(--seat); line-height: 0;
  transition: border-color .15s, box-shadow .2s, transform .15s;
}
.rail-seat:hover { transform: translateY(1px); }
.rail-seat .card-art { border-radius: 6px; display: block; }
/* whoever is holding the game up is lit, the way their card is in the strip */
.rail-seat.active { box-shadow: 0 0 0 1px var(--seat), 0 0 14px -3px var(--seat); }
.rail-seat.mine { animation: rail-mine 2.2s ease-in-out infinite; }
@keyframes rail-mine {
  0%, 100% { box-shadow: 0 0 0 1px var(--seat), 0 0 10px -4px var(--seat); }
  50% { box-shadow: 0 0 0 1px var(--seat), 0 0 20px 0 var(--seat); }
}
.rail-seat.out { opacity: .4; filter: grayscale(1); }
/* 0fr -> 1fr is the one height transition that works on content of unknown
   size; the child is what actually clips, so it carries the overflow */
.players-slide {
  display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows .22s ease, opacity .16s ease;
}
.players-slide > * { overflow: hidden; min-height: 0; }
.players-dock:hover .players-slide, .players-dock.open .players-slide {
  grid-template-rows: 1fr; opacity: 1;
}
/* the players, side by side rather than stacked.  The padding and the rule
   under it live on the row inside, not here: as the drawer's grid item this
   has to be able to close to nothing, and its own padding would not clip */
.players-strip {
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  overflow-x: auto; scrollbar-width: thin;
  backdrop-filter: blur(9px); -webkit-backdrop-filter: blur(9px);
  box-shadow: 0 16px 34px -18px rgba(0, 0, 0, .85);
}
.players-strip #players {
  display: flex; gap: 8px; align-items: stretch; min-width: min-content;
  padding: 6px 10px; border-bottom: 1px solid var(--line);
}
/* every pixel here is a pixel the board does not get, so the cards are as
   short as they can be while still saying who is who and what they hold */
.players-strip .player {
  margin-bottom: 0; flex: 1 1 0; min-width: 240px; padding: 7px 10px;
}
.players-strip .player .stats { margin-top: 4px; font-size: 11.5px; }
.players-strip .chosen { margin-top: 5px; gap: 3px; }
.players-strip .card-chip { font-size: 10.5px; padding: 2px 7px; }
.players-strip .gauge { margin-top: 2px; font-size: 10px; }
.players-strip .gauge-track { height: 5px; }

/* ---------------- the button that ends a phase ----------------
   Not a tool, so not on the rail: it is what you press when you have finished
   using them.  Bottom-right, where the eye finishes, and it says where it goes
   rather than that you are done. */
.phase-ender {
  position: absolute; right: 8px; bottom: 30px; z-index: 28;
  transition: right .24s cubic-bezier(.4, 0, .2, 1);
}
/* the drawer slides over the map, so the button steps aside rather than under */
.app.side-open .phase-ender { right: calc(var(--side-w) + 10px); }
.phase-ender-btn {
  display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
  padding: 11px 18px; border-radius: 12px; border: 1px solid transparent;
  font-size: 14px; font-weight: 600; letter-spacing: .03em;
  box-shadow: 0 10px 30px -8px rgba(0, 0, 0, .8);
  transition: transform .12s, box-shadow .12s, background .15s;
}
.phase-ender-btn:hover { transform: translateY(-1px); }
/* The accent blue the rail wears, not a green of its own.  It sits an inch
   from four buttons in the interface's one colour, and a green button beside
   them read as a different *kind* of thing rather than as the last step of the
   same one.  It looks the same in both phases: it is the same button doing the
   same job, and dulling it because something *could* still act made the
   ordinary case -- you have finished and would like to move on -- look like
   the discouraged one.  What is left to do is said in the subtitle. */
.phase-ender-btn.ready, .phase-ender-btn.waiting {
  background: linear-gradient(180deg, var(--accent),
    color-mix(in srgb, var(--accent) 74%, #0b3348));
  color: color-mix(in srgb, var(--accent) 20%, #04161f); border-color: color-mix(in srgb, var(--accent) 70%, transparent);
}
.phase-ender-btn.waiting {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 76%, #ffffff),
    color-mix(in srgb, var(--accent) 62%, #0b3348));
}
.phase-ender-btn:hover {
  box-shadow: 0 12px 34px -8px color-mix(in srgb, var(--accent) 55%, transparent);
}
.ender-sub {
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase; opacity: .8;
  font-weight: 500;
}

/* ---------------- the tool rail and the drawer ----------------
   An ACTION phase lets you swap, build, fire abilities and march in any order,
   so no single handle can say what the drawer ought to be showing.  The rail is
   one button per tool, each sliding its own panel out over the map; the phase
   itself is drawn in the top bar's stepper instead.  Rail and panel are one
   dock so a single transform moves both -- parked, the panel is off-screen and
   the rail is what is left against the right edge.

   Marching has no button on purpose: a move is made on the map. */
.side-dock {
  position: absolute; top: 54px; right: 0; bottom: 0; z-index: 26;
  display: flex; align-items: stretch;
  transform: translateX(var(--side-w));
  transition: transform .24s cubic-bezier(.4, 0, .2, 1);
  /* The rail is centred, so most of this box is empty -- and an empty box on
     z-index 26 still swallows every click that lands in it.  That is what made
     the zoom controls in the top-right corner of the map unclickable. */
  pointer-events: none;
}
.side-dock > * { pointer-events: auto; }
.side-dock.open { transform: none; }
.tool-rail {
  align-self: center; flex: none; margin-right: -1px;
  display: flex; flex-direction: column; gap: 5px;
  /* it must never grow past the window and take "Finished" off the bottom */
  max-height: 100%; overflow-y: auto; scrollbar-width: none;
}
.tool-rail::-webkit-scrollbar { width: 0; }
/* Nothing to do here right now.  It fades, it does not go: a rail that
   reshuffles itself is one you have to read every time. */
.tool-btn.idle { opacity: .38; }
.tool-btn.idle:hover:not(:disabled) { opacity: .85; }
.tool-btn.on { opacity: 1; }
.tool-btn:disabled { cursor: default; }
.tool-dot.zero { background: var(--line); color: var(--dim); }
.tool-btn {
  position: relative; width: 46px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 10px 0 11px;
  background: linear-gradient(180deg, var(--raise), var(--panel));
  border: 1px solid var(--line); border-right: 0;
  border-radius: 12px 0 0 12px; color: var(--dim);
  box-shadow: -6px 0 20px -10px rgba(0, 0, 0, .9);
  transition: color .15s, background .15s, border-color .15s;
}
.tool-btn:hover:not(:disabled) {
  color: var(--ink); border-color: var(--accent);
  background: linear-gradient(180deg, var(--panel-2), var(--raise));
  box-shadow: -2px 0 18px -6px var(--accent);
}
.tool-btn:hover:not(:disabled) .phase-icon { filter: drop-shadow(0 0 6px var(--accent)); }
.tool-btn .phase-icon { color: var(--accent); }
.tool-btn .tool-name {
  font-size: 8.5px; letter-spacing: .11em; font-weight: 600;
  writing-mode: vertical-rl; text-orientation: mixed; text-transform: uppercase;
}
/* the count behind the button, so a shut drawer still says what is waiting */
.tool-btn .tool-dot {
  position: absolute; top: 4px; right: 5px;
  min-width: 15px; padding: 1px 4px; border-radius: 999px;
  font-size: 9px; font-weight: 700; font-family: var(--mono);
  background: var(--accent); color: color-mix(in srgb, var(--accent) 20%, #04161f);
}
.tool-btn.mine { color: var(--ink); }
.tool-btn.on {
  color: var(--ink); border-color: var(--accent);
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
}
.tool-btn.on .phase-icon { animation: tab-mine 2.2s ease-in-out infinite; }
@keyframes tab-mine { 0%, 100% { opacity: .78; } 50% { opacity: 1; } }
/* "I am finished" is the only thing that ends a phase, so it is on the rail
   rather than inside a drawer you may have shut */
.tool-btn.done { margin-top: 10px; }
.tool-btn.done .tool-tick { font-size: 15px; line-height: 1; }
.tool-btn.done.waiting { color: var(--dim); }
.tool-btn.done.ready {
  color: #08240f; border-color: var(--good);
  background: linear-gradient(180deg, #9df0b0, var(--good));
}
.tool-btn.done.ready .tool-name { color: #08240f; }
/* a fight takes the rail for itself: the map is suspended, so the tools are */
.tool-btn.fight { color: var(--ink); border-color: var(--accent-warm); }
.tool-btn.fight .phase-icon { color: var(--accent-warm); }
.tool-btn.fight .tool-dot { background: var(--accent-warm); }
.panel.side {
  width: var(--side-w); flex: none;
  border-left: 1px solid var(--line); display: flex; flex-direction: column;
  box-shadow: -20px 0 44px -26px rgba(0, 0, 0, .9);
}
.board {
  grid-area: board; display: flex; flex-direction: column; min-width: 0;
  background: var(--bg-2);
  /* the head floats over this, not above it */
  position: relative;
}
/* The board's own furniture -- what am I looking at, which board, how close --
   sits *on* the map at its top edge instead of in a bar above it. Three small
   controls were costing the board a full strip of height, and the board is the
   thing the window is for. Translucent and dimmed at rest so the hexes beneath
   stay readable; solid the moment the pointer is on them. */
.board-head {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding: 8px 12px;
  pointer-events: none;           /* the map underneath keeps its clicks */
}
.board-head > *, .board-controls > * { pointer-events: auto; }
.board-controls { display: flex; align-items: flex-start; gap: 8px; pointer-events: none; }
.board-head #board-title, .board-head .tabs {
  opacity: .55; transition: opacity .15s, background .15s;
  backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
}
.board-head:hover #board-title, .board-head:hover .tabs { opacity: 1; }
/* the players' rail owns the top-left corner of the map now, so the board's
   own title starts below it rather than under it */
#board-title { margin-top: 42px; }
#board-title {
  font-weight: 600; letter-spacing: .04em; white-space: nowrap;
  font-size: 12px; color: var(--dim);
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  border-radius: 8px; padding: 6px 11px;
}
.board-head .tabs {
  background: color-mix(in srgb, var(--panel-2) 70%, transparent);
  border-color: color-mix(in srgb, var(--line) 55%, transparent);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .3);
}
.tabs { display: flex; gap: 0; background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px; padding: 2px; }
.tab {
  background: transparent; border: 0; color: var(--dim);
  padding: 5px 12px; border-radius: 6px; font-size: 12px; transition: all .15s;
}
.tab:hover { color: var(--ink); }
.tab.on { background: var(--raise); color: var(--accent); box-shadow: 0 1px 6px rgba(0,0,0,.35); }
.legend { color: var(--dim); font-size: 11px; display: flex; gap: 12px; flex-wrap: wrap; margin-left: auto; }
.swatch { display: inline-flex; align-items: center; gap: 5px; }
.swatch::before {
  content: ""; width: 11px; height: 11px; border-radius: 3px;
  background: var(--sw); border: 1px solid rgba(0, 0, 0, .5);
}
.swatch.round::before { border-radius: 50%; }
.swatch.plaque::before { border-radius: 2px; border-bottom: 3px solid rgba(0, 0, 0, .65); }
.roster-row .rname.structure::after {
  content: "structure"; margin-left: 6px; font-size: 9px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--dim); font-weight: 500;
}
.board-wrap {
  flex: 1; min-height: 0; overflow: auto; padding: 8px;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  /* the board is width-bound, so there is usually a band of spare height: put
     it above and below rather than all below, and the floating head sits in it
     instead of on the map.  "safe" so an oversized board still scrolls from its
     top edge rather than having it cut off */
  justify-content: safe center;
}
.board-svg { max-width: none; max-height: none; flex: none; }
.tabs.zoom .tab { min-width: 34px; font-variant-numeric: tabular-nums; }

/* ---------------- players ---------------- */
.player {
  border: 1px solid var(--line); border-left: 4px solid var(--seat);
  border-radius: var(--radius); padding: 10px 12px; margin-bottom: 10px;
  background: linear-gradient(100deg, color-mix(in srgb, var(--seat) 7%, var(--panel-2)), var(--panel-2) 60%);
  transition: box-shadow .2s, transform .2s;
}
.player.active {
  border-color: var(--seat);
  box-shadow: 0 0 0 1px var(--seat) inset, 0 0 26px -6px var(--seat);
}
/* Pushing the others back is what makes the active one stand out; brightening
   it alone just made four bright cards.  Only while a turn is actually live,
   though -- once the game is over nobody is active, and dimming all four would
   grey out the final scores. */
.app.turn-live .players-strip .player:not(.active) { opacity: .6; }
.players-strip .player.active { opacity: 1; }
.player .who { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.player .lead { display: flex; align-items: center; gap: 7px; min-width: 0; }

/* the card that is holding everyone up says so on itself */
.turn-badge {
  flex: none; font-size: 9.5px; font-weight: 700; letter-spacing: .1em;
  padding: 2px 7px; border-radius: 999px; white-space: nowrap;
  color: var(--seat); border: 1px solid var(--seat);
  background: color-mix(in srgb, var(--seat) 14%, transparent);
}
.turn-badge.mine {
  color: #08101a; background: var(--seat); border-color: var(--seat);
  animation: turn-badge 2.6s ease-in-out infinite;
}
@keyframes turn-badge {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--seat) 60%, transparent); }
  50%      { box-shadow: 0 0 0 5px color-mix(in srgb, var(--seat) 0%, transparent); }
}

/* a pulse is a nudge, not a requirement: anyone who has asked for less motion
   still gets the colour, the fill and the badge, which carry the message */
@media (prefers-reduced-motion: reduce) {
  .app.turn-mine .topbar,
  .turn-badge.mine { animation: none; }
}
.player .name { font-weight: 600; color: var(--seat); }
.player .ctrl { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--dim); }
.player .stats { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 7px; font-size: 12px; color: var(--dim); }
.player .stats b { color: var(--ink); font-weight: 600; }
.player .stats .has-parts { cursor: help; border-bottom: 1px dotted var(--dim); }
.opt.hold { color: var(--dim); }
.opt.hold .idx { color: var(--accent-warm); }
.tooltip .tip-row { display: flex; justify-content: space-between; gap: 18px; margin: 2px 0; }
.tooltip .tip-row b { color: var(--accent); }
.tooltip .tip-row.total {
  margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line); font-weight: 600;
}
.team-tag {
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px; color: var(--dim);
}
.warn { color: var(--danger); }
.gauge {
  display: flex; align-items: center; gap: 6px; margin-top: 5px; font-size: 10.5px;
}
.gauge-label {
  color: var(--dim); text-transform: uppercase; letter-spacing: .08em;
  min-width: 62px;
}
.gauge-track {
  flex: 1; height: 6px; border-radius: 3px; background: #0c1119;
  border: 1px solid var(--line); overflow: hidden;
}
.gauge-fill { display: block; height: 100%; background: var(--seat); }
.gauge-value { font-family: var(--mono); color: var(--ink); min-width: 42px; text-align: right; }
.gauge.over .gauge-fill { background: var(--danger); }
.gauge.over .gauge-value { color: var(--danger); }

.chosen { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 9px; }
.card-chip {
  font-size: 11px; padding: 3px 8px; border-radius: 999px;
  background: #0d1219; border: 1px solid var(--line); cursor: help;
  transition: border-color .15s, color .15s;
}
.card-chip:hover { border-color: var(--seat); color: #fff; }
.card-chip.spent { opacity: .42; text-decoration: line-through; }
.kv { font-size: 11.5px; color: var(--dim); font-family: var(--mono); word-break: break-word; }
.kv div { margin-bottom: 3px; }
.kv b { color: var(--ink); font-weight: 600; }


/* ---------------- inspector: what is standing on the hex you clicked ------ */
/* it is pinned beside that hex, because an answer about one hex belongs at it */
/* The card, in the middle, at a size you can read from a chair.
   It used to sit in whichever bottom corner the thing it was about was not
   in -- which kept the board whole, but 300px of 11px type made it a
   reference card rather than something to look at.  Reading a card is a thing
   you stop to do, so it stops the board and takes the middle. */
.inspector {
  position: fixed; z-index: 45; width: min(560px, calc(100vw - 48px));
  max-height: min(84vh, 860px);
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  background: linear-gradient(180deg, #19212d, var(--panel));
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  border-radius: 14px;
  box-shadow: 0 26px 70px rgba(0, 0, 0, .72);
  display: flex; flex-direction: column; overflow: hidden;
  animation: ins-in .16s ease-out;
}
@keyframes ins-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.97); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
/* a wash over the board, so the card reads as the thing in front of you and
   the click that puts it away has somewhere obvious to land */
.inspector-veil {
  position: fixed; inset: 0; z-index: 44;
  background: rgba(6, 9, 14, .55);
  animation: veil-in .16s ease-out;
}
@keyframes veil-in { from { opacity: 0; } to { opacity: 1; } }
.inspector-head {
  display: flex; align-items: center; gap: 10px; flex: none;
  font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--dim); padding: 12px 12px 12px 18px;
  border-bottom: 1px solid var(--line); background: var(--panel-2);
}
#inspector-title {
  flex: 1; min-width: 0; color: var(--accent);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#inspector-close { padding: 4px 10px; font-size: 13px; line-height: 1.2; }
.inspector-body {
  overflow-y: auto; padding: 16px 20px 20px;
  display: flex; flex-direction: column; gap: 10px;
}
.ins-facts { font-size: 13.5px; color: var(--dim); }
.ins-empty { font-size: 11.5px; color: var(--dim); font-style: italic; }
.ins-item {
  border: 1px solid var(--line); border-left: 3px solid var(--seat);
  border-radius: 8px; padding: 7px 9px; background: var(--panel-2);
}
.ins-name {
  display: flex; align-items: center; gap: 5px;
  font-weight: 600; font-size: 12.5px; color: var(--seat); margin-bottom: 3px;
}
.ins-line { font-size: 13.5px; color: var(--ink); line-height: 1.5; }
.ins-line.dim { color: var(--dim); font-family: var(--mono); font-size: 12.5px; }
.ins-line.warn { color: var(--accent-warm); }
.ins-line.gate { color: var(--accent-warm); }
.ins-line.met { color: var(--good); }
.ins-line.pick { color: var(--accent); font-weight: 600; }
.ins-hp { display: flex; align-items: center; gap: 6px; margin: 4px 0 2px; }
.ins-hp-track {
  flex: 1; height: 6px; border-radius: 3px; background: #0c1119;
  border: 1px solid var(--line); overflow: hidden;
}
.ins-hp-fill { display: block; height: 100%; }
.ins-hp-value { font-family: var(--mono); font-size: 10.5px; color: var(--dim); }
.ins-card { margin-top: 5px; }
.ins-card > summary {
  cursor: pointer; font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); padding: 2px 0;
}
.ins-card > summary::-webkit-details-marker { display: none; }
.ins-card > summary:hover { color: var(--accent); }
.ins-card[open] > summary { color: var(--accent); }
.ins-opt { font-size: 10.5px; line-height: 1.4; margin-top: 3px; color: var(--ink); }
.ins-opt b { color: var(--accent); font-weight: 600; }

/* ---------------- options ---------------- */
.options-head { position: sticky; top: 0; z-index: 2; background: var(--panel); padding-bottom: 8px; }
.options-head h2 { margin-top: 12px; }
.options { flex: 1; min-height: 120px; overflow-y: auto; }
.phase-actions { margin: 6px 0 4px; }
.end-phase {
  width: 100%; padding: 9px 12px; font-size: 13px; letter-spacing: .06em;
  background: linear-gradient(180deg, #7fd8a0, #4fbd84); color: #052a19;
  box-shadow: 0 5px 16px rgba(79, 189, 132, .22);
}
.row-title {
  font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--dim); margin: 12px 0 6px; font-weight: 600;
}
.card-row { display: flex; flex-wrap: wrap; gap: 6px; }
.card-tile {
  flex: 1 1 110px; min-width: 102px; max-width: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 6px; border-radius: 10px; text-align: center;
  background: var(--panel-2); border: 1px solid var(--line);
  border-top: 3px solid var(--seat, var(--line));
  color: var(--ink); transition: all .14s;
}
.card-tile:hover { background: var(--raise); border-color: var(--accent); transform: translateY(-1px); }
.card-tile.picked {
  border-color: var(--accent); background: var(--raise);
  box-shadow: 0 0 0 1px var(--accent) inset, 0 0 18px -6px var(--accent);
}
.card-tile.off { opacity: .3; cursor: default; transform: none; }
.card-tile.off:hover { background: var(--panel-2); border-color: var(--line); }
.card-tile .tile-name { font-size: 11.5px; font-weight: 600; line-height: 1.2; }
.card-tile .tile-meta { font-size: 10px; color: var(--dim); font-family: var(--mono); }
.card-tile .tile-gate {
  font-size: 9.5px; letter-spacing: .04em; color: var(--accent-warm);
  border-top: 1px dashed rgba(255, 176, 87, .3); margin-top: 2px; padding-top: 2px;
  width: 100%; line-height: 1.25;
}
.card-tile .tile-gate.met { color: var(--good); border-top-color: rgba(126, 231, 135, .3); }
.tooltip .gate {
  display: flex; align-items: center; gap: 4px; margin: 4px 0 2px;
  font-size: 11.5px; color: var(--accent-warm);
}
.tooltip .gate.met { color: var(--good); }
.card-tile .tile-all { margin-top: 4px; font-size: 10px; padding: 1px 7px; }
.card-art { image-rendering: auto; }
/* the paintings are square with their own frame baked in, so they only need
   the corner rounded off to sit on a tile without a hard edge */
.card-tile .card-art { border-radius: 9px; }

/* The header of a token's hover tip: the card's art, big enough to read,
   with the name and the live line in the column beside it. */
.tooltip .tip-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 6px; }
.tooltip .tip-head > .card-art, .tooltip .tip-head > .card-icon {
  flex: none; border-radius: 10px; margin: 1px 0 0;
}
.tooltip .tip-head-text { min-width: 0; flex: 1; }
.tooltip .tip-head-text > h4 { margin: 0 0 3px; }
.tooltip .tip-head-text > .meta, .tooltip .tip-head-text > .tip-live { margin-bottom: 0; }

.hint-line {
  font-size: 11.5px; color: var(--dim); background: var(--panel-2);
  border: 1px dashed var(--line); border-radius: 8px; padding: 7px 10px; margin-bottom: 8px;
}
.focus-bar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 9px; }
.focus-label { font-size: 11.5px; color: var(--accent); }
.pill {
  font-size: 11px; padding: 3px 9px; border-radius: 999px; color: var(--ink);
  background: var(--raise); border: 1px solid var(--accent);
}
.pill:hover { background: var(--accent); color: color-mix(in srgb, var(--accent) 22%, #04161f); }
.pill.clear { border-color: var(--line); color: var(--dim); background: var(--panel-2); }
.pill.clear:hover { border-color: var(--danger); color: var(--danger); background: var(--panel-2); }

.opt-group {
  margin-bottom: 7px; border: 1px solid var(--line); border-radius: 9px;
  overflow: hidden; background: var(--panel-2);
}
.opt-group > summary {
  cursor: pointer; padding: 8px 11px; background: var(--panel-2);
  font-size: 12px; display: flex; justify-content: space-between; gap: 8px;
  transition: background .15s;
}
.opt-group > summary:hover { background: var(--raise); }
.opt-group[open] > summary { border-bottom: 1px solid var(--line-soft); color: var(--accent); }
.opt-group > summary::-webkit-details-marker { display: none; }
.opt-group > summary .count {
  color: var(--dim); font-family: var(--mono); font-size: 11px;
  background: #0d1219; border-radius: 999px; padding: 0 7px;
}
.opt {
  display: block; width: 100%; text-align: left; background: transparent;
  border: 0; border-top: 1px solid var(--line-soft); color: var(--ink);
  padding: 7px 11px; font-size: 12px; line-height: 1.4;
  border-left: 2px solid transparent; transition: background .12s, border-color .12s;
}
.opt:first-of-type { border-top: 0; }
.opt:hover { background: var(--raise); border-left-color: var(--accent); color: #fff; }
.opt .idx { color: var(--dim); font-family: var(--mono); margin-right: 7px; font-size: 11px; }
.opt-more { padding: 7px 11px; }
.waiting { color: var(--dim); font-size: 13px; padding: 12px 2px; }

.log-wrap {
  margin-top: 10px; border: 1px solid var(--line); border-radius: 9px;
  background: var(--panel-2); overflow: hidden;
}
.log-wrap > summary {
  cursor: pointer; padding: 7px 11px; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--dim);
}
.log-wrap > summary::-webkit-details-marker { display: none; }
.log-wrap > summary:hover { color: var(--ink); }
.log-wrap[open] > summary { border-bottom: 1px solid var(--line-soft); }

.log {
  font-family: var(--mono); font-size: 11px; color: var(--dim);
  background: #090d12; border: 1px solid var(--line); border-radius: 9px;
  padding: 9px; height: 132px; overflow-y: auto; white-space: pre-wrap;
  border: 0; border-radius: 0;
}
.log div { margin-bottom: 2px; }
.log div:last-child { color: var(--ink); }
.log.combat { height: 130px; border-color: rgba(255, 138, 107, .3); }
.log .round-mark {
  color: var(--accent-warm); margin: 6px 0 3px; letter-spacing: .1em;
  text-transform: uppercase; font-size: 10px;
}
.log .round-mark:first-child { margin-top: 0; }

/* the reveal: what the round that just resolved actually did */
.reveal {
  border: 1px solid rgba(255, 138, 107, .38); border-radius: 9px;
  background: linear-gradient(100deg, rgba(255, 138, 107, .09), var(--panel-2) 65%);
  margin-bottom: 9px; overflow: hidden;
}
.reveal > summary {
  cursor: pointer; padding: 8px 11px; font-size: 12px; font-weight: 600;
  color: #ffb9a3; display: flex; justify-content: space-between; gap: 8px;
}
.reveal > summary::-webkit-details-marker { display: none; }
.reveal > summary .count {
  color: var(--dim); font-family: var(--mono); font-size: 11px;
  background: #0d1219; border-radius: 999px; padding: 0 7px; font-weight: 400;
}
.reveal-lines { max-height: 170px; overflow-y: auto; }
.reveal-line {
  font-family: var(--mono); font-size: 11px; color: var(--ink);
  padding: 3px 12px; border-top: 1px solid rgba(255, 138, 107, .12);
}
.hint-line.secret { border-style: solid; border-color: rgba(111, 211, 255, .28); color: #a9c8dd; }
.hint-line.resolving {
  border-style: solid; border-color: rgba(255, 176, 87, .38); color: #ffd7a8;
  background: linear-gradient(100deg, rgba(255, 176, 87, .1), var(--panel-2) 70%);
}
.reveal.live > summary { color: #ffd7a8; }
.reveal.live { border-color: rgba(255, 176, 87, .45); }
.roster-row .rstat.locked { color: var(--accent); font-weight: 600; }

.roster { width: min(560px, 100%); display: flex; flex-direction: column; gap: 4px; }
/* while a fight is on, the roster stands beside the board: the blob of hexes
   is height-bound and the pane has width to spare (see sizeBoard) */
.board-wrap.fighting {
  flex-direction: row; align-items: flex-start; justify-content: center; gap: 16px;
}
.board-wrap.fighting .board-svg { flex: none; }
.board-wrap.fighting .roster {
  width: 320px; flex: none; align-self: stretch;
  max-height: 100%; overflow-y: auto; scrollbar-width: thin;
}
/* The floating controls live at the pane's top-right, and during a fight that
   is where the roster is.  Nudging the controls left only moved them onto the
   board instead, so the roster starts below them and keeps its full width. */
.board-wrap.fighting .roster { padding-top: 40px; }
.roster-head { font-family: var(--mono); font-size: 11px; color: var(--dim); margin-bottom: 2px; }
.roster-row {
  display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
  font-size: 11.5px; padding: 5px 9px; border-radius: 7px;
  background: var(--panel-2); border-left: 3px solid var(--seat);
}
.roster-row .rname { font-weight: 600; color: var(--seat); min-width: 150px; }
.roster-row .rstat { color: var(--dim); font-family: var(--mono); font-size: 11px; }

/* ---------------- board svg ---------------- */
.region-g, .cell-g, .fig-g { cursor: pointer; }
.hex { stroke: #090c11; stroke-width: 1.5; transition: opacity .15s; }
.hex-bevel { pointer-events: none; }
.token-text { font-weight: 800; pointer-events: none; }
.token-chip { cursor: pointer; }

/* ---------------- the token picker ---------------- */
/* One chip can be several tokens.  When they are all movable it has to ask
   which, because they are not interchangeable: one may be hurt, one loaded. */
.token-picker {
  position: fixed; z-index: 72; min-width: 190px; max-width: 260px;
  background: rgba(9, 13, 19, .98); border: 1px solid var(--accent);
  border-radius: 10px; padding: 9px 10px; font-size: 12px;
  box-shadow: 0 16px 44px rgba(0, 0, 0, .75);
}
.picker-head { display: flex; flex-direction: column; margin-bottom: 6px; }
.picker-head b { color: var(--accent); }
.picker-sub { font-size: 10.5px; color: var(--dim); }
.picker-row {
  display: flex; align-items: center; gap: 7px; width: 100%; margin: 2px 0;
  padding: 5px 8px; border-radius: 7px; cursor: pointer; text-align: left;
  font: inherit; color: var(--ink);
  background: var(--panel-2); border: 1px solid var(--line);
}
.picker-row:hover { border-color: var(--accent); background: var(--raise); }
.picker-row .picker-tick { color: transparent; font-weight: 700; }
.picker-row.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, var(--panel-2)); }
.picker-row.on .picker-tick { color: var(--accent); }
.picker-foot { display: flex; gap: 6px; margin-top: 7px; }
.picker-hint { margin-top: 6px; font-size: 10.5px; color: var(--dim); font-style: italic; }
/* Built this turn, so it cannot march (rules 6.1).  Drawn on the token rather
   than explained in a panel: the reason a piece is not being offered a move
   belongs on the piece. */
/* Built this turn.  The chip is *not* faded any more -- a picture at 55% reads
   as an image that failed to load rather than as a piece that is not ready --
   so this is only the badge, which pulses gently to say the same thing. */
.fresh-badge { animation: fresh-wait 2.6s ease-in-out infinite; }
@keyframes fresh-wait {
  0%, 100% { opacity: .72; }
  50%      { opacity: 1; }
}
/* "This one can act": a ring round the chip, turning.
   Round, to follow the shape of the piece -- and it is the *motion* that tells
   it apart from the token's own rim, not the shape.  It is on the token rather
   than on its hex, because the token is the answer: ringing the ground said
   "something here", which is what the chip already says, and lit the ground
   you were marching *away* from as loudly as the ground you were choosing
   between.

   The animation lives on this rect and nowhere else.  An animated `filter` on
   the chip *group* re-rasterises it every frame, which is both expensive and,
   in Chrome, enough to move where a click lands. */
.chip-mark {
  stroke: none; fill: none; pointer-events: none;
  stroke-dasharray: 7 6; stroke-linecap: round;
  transform-box: fill-box; transform-origin: center;
}
.token-chip.can-act .chip-mark {
  stroke: #ffd479; stroke-width: 2.6;
  animation: chip-march 1.9s linear infinite, chip-breathe 2.4s ease-in-out infinite;
}
/* A ring can turn and still be the same ring; a square cannot -- a box at
   thirty degrees reads as a box that has come loose.  So only the round mark
   spins, and the structure's box is carried by its dashes and its breath. */
.token-chip.can-act circle.chip-mark {
  animation: chip-march 1.9s linear infinite, chip-breathe 2.4s ease-in-out infinite,
             chip-spin 7s linear infinite;
}
.token-chip.can-fire .chip-mark { stroke: var(--accent-warm); }
.token-chip.picked .chip-mark {
  stroke: #ffffff; stroke-width: 3.2; stroke-dasharray: none; animation: none;
}
/* The bracket says it on its own.  It used to say it three times over -- a
   coloured stroke round the token's own circle, a glow round the whole chip,
   and the bracket -- and the two round ones fought the square: a ring on a
   round token reads as part of the drawing, which is exactly what the bracket
   was drawn to avoid. */
@keyframes chip-spin { to { rotate: 360deg; } }
@keyframes chip-march { to { stroke-dashoffset: -26; } }
@keyframes chip-breathe {
  0%, 100% { scale: 1; opacity: .82; }
  50%      { scale: 1.06; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .token-chip.can-act .chip-mark { animation: none; }
  .fresh-badge { animation: none; }
}
.card-icon { vertical-align: -2px; margin-right: 5px; flex: none; }
.card-chip { display: inline-flex; align-items: center; }
.tip-token { display: flex; align-items: center; gap: 2px; }
.tooltip h4.tip-title { display: flex; align-items: center; gap: 4px; }
.marker { font-size: 12px; pointer-events: none; }
/* Hovering an option highlights the regions it touches.  The rest are pushed
   back, not switched off: at .3 the board read as "everything is disabled",
   which is the opposite of the intent -- the point is to draw the eye to the
   few, not to grey out the many. */
.region-g.dim { opacity: .62; }
.region-g.lit .hex { stroke: var(--accent); stroke-width: 3.5; }
.region-g.lit { filter: drop-shadow(0 0 10px color-mix(in srgb, var(--accent) 45%, transparent)); }
.move-source {
  stroke: #ffd479; stroke-width: 4; stroke-dasharray: 7 5; pointer-events: none;
  animation: dash 1.4s linear infinite;
}
.move-target {
  stroke: #7ee787; stroke-width: 5; pointer-events: none;
  filter: drop-shadow(0 0 6px rgba(126, 231, 135, .55));
}
.region-g.can-enter:hover .hex { stroke: #7ee787; stroke-width: 3.5; }
.region-g:hover .hex { stroke: var(--accent); stroke-width: 2.5; }
.ctrl-ring { fill: none; stroke-width: 3; opacity: .95; }

.cell { stroke: #090c11; stroke-width: 1.2; }
.cell-hit { stroke: none; }
.cell-g:hover .cell { stroke: var(--accent); stroke-width: 2; }
.zone { stroke-width: 1.6; stroke-dasharray: 5 4; pointer-events: none; }
.pick-ring { fill: none; stroke: #ffffff; stroke-width: 3; pointer-events: none; }
.to-choose {
  stroke: #ffd479; stroke-width: 3.5; stroke-dasharray: 6 5; pointer-events: none;
  animation: dash 1.4s linear infinite;
}
.to-choose.chosen { stroke: #ffffff; stroke-dasharray: none; stroke-width: 4; }
.aim-move { fill: none; stroke: #7ee787; stroke-width: 4; pointer-events: none; }
.aim-picked {
  fill: none; stroke: #ffffff; stroke-width: 4; pointer-events: none;
}
.aim-target {
  fill: none; stroke: #ff6b6b; stroke-width: 4.5; pointer-events: none;
  filter: drop-shadow(0 0 6px rgba(255, 107, 107, .55));
}
.move-ring {
  fill: none; stroke: var(--accent); stroke-width: 3.5; stroke-dasharray: 6 4;
  pointer-events: none; animation: spin 4s linear infinite; transform-box: fill-box;
  transform-origin: center;
}
.deploy-ring { fill: none; stroke: var(--good); stroke-width: 3.5; pointer-events: none; }
/* a figure standing on more than one hex: the weld joins its hexes into one
   plate and the caption says how much ground it covers -- one structure, not
   two.  The outline round the whole footprint is drawn in app.js as a fattened
   pass behind the fill, so there is no shape to style for it here. */
.fig-weld { pointer-events: none; }
.fig-span {
  font-size: 9px; letter-spacing: .06em; text-transform: uppercase;
  fill: var(--dim); font-weight: 600; pointer-events: none;
  paint-order: stroke; stroke: #0a0e14; stroke-width: 3px; stroke-linejoin: round;
}
/* a hex an arriving token may land on: click it.  Filled as well as ringed,
   because during deploy these are the *only* live hexes on the board */
.deploy-spot {
  fill: #7ee787; fill-opacity: .14; stroke: #7ee787; stroke-width: 3.5;
  pointer-events: none; filter: drop-shadow(0 0 5px rgba(126, 231, 135, .4));
}
.cell-g:hover .deploy-spot { fill-opacity: .3; stroke-width: 4.5; }
/* the rest of a multi-figure token, shown while its anchor is hovered */
.deploy-fill {
  fill: #7ee787; fill-opacity: 0; stroke: #7ee787; stroke-width: 0;
  pointer-events: none; transition: fill-opacity .1s, stroke-width .1s;
}
.cell-g.deploy-preview .deploy-fill { fill-opacity: .26; stroke-width: 3; }
.move-arrow {
  stroke: var(--accent); stroke-width: 3; stroke-dasharray: 7 5; opacity: .9;
  pointer-events: none; animation: dash 1s linear infinite;
}
.target-mark { pointer-events: none; }
.target-mark circle, .target-mark line { stroke: var(--danger); stroke-width: 2.5; }
.target-mark circle { stroke-dasharray: 5 4; animation: spin 6s linear infinite; transform-box: fill-box; transform-origin: center; }
.fig-label { font-size: 10px; font-weight: 800; fill: #0d1117; pointer-events: none; }
.hp-bar-bg { fill: #080b10; stroke: #000; stroke-width: .5; }
.pip { font-size: 11px; pointer-events: none; }
.pip.shield { fill: #9fd8ff; }
.pip.cond { fill: #ffd479; }

/* what just happened, played once over the board */
.fx { pointer-events: none; }
.fx-ring {
  stroke-width: 3; opacity: 0; transform-box: fill-box; transform-origin: center;
  animation: fx-ring 2.3s ease-out forwards;
}
.fx-text {
  font-weight: 800; opacity: 0; paint-order: stroke; stroke: #06090e;
  stroke-width: 3px; animation: fx-float 3.2s ease-out forwards;
}
.fx-trail {
  stroke: #ffd479; stroke-width: 4; stroke-linecap: round; stroke-dasharray: 5 6;
  opacity: 0; animation: fx-trail 2.6s ease-out forwards;
}
.fx-death .fx-ring { animation-duration: 2.9s; stroke-width: 5; }
.fx-map .fx-ring { stroke-width: 4; animation-duration: 2.7s; }
.fx-map .fx-trail { stroke-width: 5; animation-duration: 3s; }
.fx-map .fx-text { stroke-width: 4px; animation-duration: 3.4s; }
.fx-taken .fx-ring { stroke-width: 6; }
.fx-lost .fx-ring { stroke-dasharray: 6 5; }

@keyframes fx-ring {
  0% { opacity: .95; transform: scale(.35); }
  100% { opacity: 0; transform: scale(1.5); }
}
@keyframes fx-float {
  0% { opacity: 0; transform: translateY(6px); }
  18% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-16px); }
}
@keyframes fx-trail {
  0% { opacity: .9; stroke-dashoffset: 0; }
  100% { opacity: 0; stroke-dashoffset: -40; }
}

/* a number in the player panel that just moved, floating off it once */
.stat-pop {
  margin-left: 4px; font-weight: 700; font-size: 11px; font-variant-numeric: tabular-nums;
  opacity: 0; animation: stat-pop 3.2s ease-out forwards;
}
.stat-pop.up { color: var(--good); }
.stat-pop.down { color: var(--danger); }
@keyframes stat-pop {
  0% { opacity: 0; transform: translateY(5px); }
  12% { opacity: 1; transform: translateY(0); }
  70% { opacity: 1; transform: translateY(-2px); }
  100% { opacity: 0; transform: translateY(-9px); }
}

@keyframes dash { to { stroke-dashoffset: -24; } }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .move-ring, .move-arrow, .target-mark circle, .to-choose, .move-source { animation: none; }
  .fx-ring, .fx-text, .fx-trail, .stat-pop { animation-duration: .01s; opacity: 0; }
}

/* ---------------- tooltip ---------------- */
.tooltip {
  position: fixed; z-index: 70; max-width: 360px; pointer-events: none;
  background: rgba(9, 13, 19, .97); border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 12px; font-size: 12px; line-height: 1.45;
  box-shadow: 0 14px 40px rgba(0, 0, 0, .7);
}
.tooltip h4 { margin: 0 0 4px; font-size: 13px; }
.tooltip .meta { color: var(--dim); font-size: 11px; margin-bottom: 6px; }
.tooltip .opt-line { margin-top: 4px; }
.tooltip .opt-line b { color: var(--accent); font-weight: 600; }
.tooltip h5.tip-head {
  margin: 8px 0 2px; font-size: 11.5px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--accent);
}
.tooltip .tip-live { font-size: 11.5px; margin-bottom: 5px; }
.tooltip .tip-ability { margin-top: 5px; }
.tooltip .tip-hint {
  margin-top: 7px; font-size: 10.5px; color: var(--dim); font-style: italic;
}
/* the rulebook's own markdown, rendered inside a tip rather than the panel */
.tooltip p { margin: 5px 0; }
.tooltip ul, .tooltip ol { margin: 5px 0; padding-left: 17px; }
.tooltip li { margin: 2px 0; }
.tooltip .ins-hp { margin: 4px 0 6px; }

/* What the token can do, on the token's own card: one button per verb, in the
   accent the rail uses for the same two modes.  It sits above the printed card
   text because "so do it" outranks "here is what it says", and the printed
   half can be long enough to scroll. */
.tooltip .tip-acts {
  display: flex; gap: 6px; flex-wrap: wrap;
  margin: 9px 0 2px; padding-top: 8px; border-top: 1px solid var(--line-soft);
}
.tip-act {
  font-family: inherit; font-size: 11.5px; font-weight: 600; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--panel-2));
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: 7px; padding: 5px 11px; cursor: pointer;
}
.tip-act:hover {
  background: color-mix(in srgb, var(--accent) 22%, var(--raise));
  border-color: var(--accent);
}
/* an ability is the warm mode, on the board and here */
.tip-act.fire {
  color: var(--accent-warm);
  background: color-mix(in srgb, var(--accent-warm) 12%, var(--panel-2));
  border-color: color-mix(in srgb, var(--accent-warm) 45%, var(--line));
}
.tip-act.fire:hover {
  background: color-mix(in srgb, var(--accent-warm) 22%, var(--raise));
  border-color: var(--accent-warm);
}

/* A tip you can point *into*.  Set when the tip names a mechanic or carries a
   button: the pointer has to be able to leave the board and land on what it
   wants, and a tip with nothing to point at must never eat a click meant for
   the hex underneath it. */
.tooltip.live { pointer-events: auto; max-height: 80vh; overflow-y: auto; }
/* the glossary layer sits above the tip that named it, and takes no pointer
   events of its own -- which is where the recursion stops */
.tooltip.gloss { z-index: 71; max-width: 340px; }

/* The third layer: one card's painting on its own, opened by pointing at the
   small one in a tip's header.  Above the glossary, and like it inert -- it is
   something to look at, not something to point into. */
.art-zoom {
  position: fixed; z-index: 72; pointer-events: none;
  padding: 10px 10px 8px; border-radius: 14px;
  background: rgba(9, 13, 19, .97); border: 1px solid var(--line);
  box-shadow: 0 20px 56px rgba(0, 0, 0, .8);
}
.art-zoom .card-art, .art-zoom .card-icon { display: block; border-radius: 10px; }
.art-zoom-name {
  margin-top: 7px; text-align: center; font-size: 12px; font-weight: 600;
  letter-spacing: .04em; color: var(--ink);
}
/* the header art says it can be opened the way a mechanic name says it can */
.tip-head .zoomable { cursor: zoom-in; transition: box-shadow .15s, transform .15s; }
.tip-head .zoomable:hover {
  box-shadow: 0 0 0 1px var(--accent), 0 0 18px -4px var(--accent);
  transform: translateY(-1px);
}
/* a status that names its own rule reads as a hover, like a mechanic does */
.status.has-gloss { cursor: help; border-style: dashed; }
.status.has-gloss:hover { border-style: solid; border-color: var(--accent); color: var(--accent); }

/* ---------------- mechanics ---------------- */
/* A mechanic is defined once in the rulebook and named by every card that uses
   it, so the name itself is the hover target.  Dotted, not link-blue: it opens
   an explanation where it stands, it does not go anywhere. */
.mech {
  color: var(--accent); border-bottom: 1px dotted currentColor;
  cursor: help; white-space: nowrap;
}
.mech-row { display: flex; flex-wrap: wrap; gap: 4px 5px; margin: 6px 0 2px; }
/* the card's ability list as the book prints it: the mechanic names are
   hoverable, and the parameters beside them are the part that was missing */
.ability-list { margin: 6px 0 2px; line-height: 1.6; font-size: 11.5px; }
/* what state a token is in, good news and bad told apart at a glance */
.status-row { display: flex; flex-wrap: wrap; gap: 4px; margin: 6px 0 2px; }
.status-row .status {
  font-size: 10.5px; padding: 1px 8px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel-2); white-space: nowrap;
  /* the row wraps, so a chip must keep its width rather than be squeezed until
     its own label is cut off ("Brownout: output halved" ran out of tip) */
  flex: none;
}
.status.good { color: var(--good); border-color: rgba(126, 231, 135, .4); }
.status.bad { color: var(--accent-warm); border-color: rgba(255, 176, 87, .4); }
.ability-list .mech { margin-right: 1px; }
.ins-card .ability-list { color: var(--ink); }
.mech-row .mech {
  border: 1px solid var(--line); border-radius: 999px; background: var(--panel-2);
  padding: 1px 8px; font-size: 10.5px; letter-spacing: .04em;
}
.mech-row .mech:hover { border-color: var(--accent); background: var(--raise); }
/* the rulebook's wording under a card's printed shorthand */
.opt-mech { display: block; margin-top: 3px; font-size: 11px; color: var(--dim); }
.ins-opt .opt-mech { margin-left: 0; }
/* a faction name opens its chapter; say so before it is clicked */
.player .name.faction-name {
  cursor: pointer; border-bottom: 1px dotted currentColor;
}

/* ---------------- rulebook ---------------- */
.modal {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: stretch; justify-content: center; padding: 3vh 3vw;
  background: rgba(4, 7, 12, .74);
}
.rules-card {
  display: flex; flex-direction: column; width: min(1180px, 100%);
  background: linear-gradient(180deg, #18202c, var(--panel));
  border: 1px solid var(--line); border-radius: 14px; overflow: hidden;
  box-shadow: 0 30px 80px rgba(0, 0, 0, .6);
}
.rules-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.rules-title {
  font-weight: 700; letter-spacing: .2em; font-size: 12px; color: var(--accent);
}
.rules-head input[type="search"] { width: 220px; margin-left: auto; }
.rules-count { color: var(--dim); font-size: 11px; font-family: var(--mono); min-width: 0; }
#rules-chips .tab.mine { box-shadow: inset 0 -2px 0 var(--accent-warm); }

.rules-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 262px minmax(0, 1fr); }
.rules-toc {
  overflow-y: auto; padding: 12px 8px 30px; background: var(--bg-2);
  border-right: 1px solid var(--line); display: flex; flex-direction: column; gap: 1px;
}
.toc-link {
  display: flex; gap: 8px; align-items: baseline; text-align: left; width: 100%;
  background: transparent; border: 0; border-left: 2px solid transparent;
  color: var(--dim); padding: 5px 9px; border-radius: 6px; font-size: 12px;
  transition: background .12s, color .12s, border-color .12s;
}
.toc-link:hover { background: var(--raise); color: var(--ink); border-left-color: var(--accent); }
.toc-link.lvl1 { color: var(--ink); font-weight: 600; margin-top: 8px; }
.toc-link.lvl2 { padding-left: 20px; font-size: 11.5px; }
.toc-link.mine { color: var(--accent); }
.toc-num { font-family: var(--mono); font-size: 10.5px; color: var(--dim); min-width: 24px; }

.rules-doc { overflow-y: auto; padding: 20px 30px 70px; line-height: 1.6; font-size: 13px; }
.rules-doc h2.rules-sec {
  display: flex; gap: 10px; align-items: baseline; text-transform: none;
  font-size: 20px; letter-spacing: .02em; color: var(--ink);
  margin: 26px 0 10px; padding-bottom: 7px; border-bottom: 1px solid var(--line);
}
.rules-doc h2.rules-sec::after { display: none; }
.rules-doc h2.rules-sec:first-child { margin-top: 4px; }
.rules-doc h3.rules-sub {
  display: flex; gap: 10px; align-items: baseline;
  font-size: 15px; font-weight: 600; color: var(--accent); margin: 22px 0 8px;
}
.rules-doc h4.rules-h4 { font-size: 13px; margin: 16px 0 6px; color: var(--accent-warm); }
.rules-doc .rules-num { font-family: var(--mono); font-size: 12px; color: var(--dim); }
.rules-doc .rules-tag {
  display: inline-block; font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent-warm); border: 1px solid rgba(255, 176, 87, .38);
  border-radius: 999px; padding: 2px 10px; margin-bottom: 10px;
}
.rules-doc p { margin: 9px 0; }
.rules-doc .rules-list { margin: 9px 0; padding-left: 20px; }
.rules-doc .rules-list li { margin: 4px 0; }
.rules-doc .rules-quote {
  margin: 12px 0; padding: 8px 14px; color: var(--accent-warm);
  border-left: 3px solid rgba(255, 176, 87, .5); background: rgba(255, 176, 87, .06);
  border-radius: 0 8px 8px 0;
}
.rules-doc code {
  font-family: var(--mono); font-size: 11.5px; background: #0d1219;
  border: 1px solid var(--line); border-radius: 5px; padding: 0 4px;
}
.rules-doc strong { color: #fff; }
.rules-doc a { color: var(--accent); }
.rules-doc hr { border: 0; border-top: 1px solid var(--line-soft); margin: 18px 0; }
.rules-doc mark { background: rgba(111, 211, 255, .28); color: #fff; border-radius: 3px; }
.rules-table-wrap { overflow-x: auto; margin: 12px 0; }
.rules-doc table.rules-table { border-collapse: collapse; font-size: 12px; min-width: 100%; }
.rules-doc table.rules-table th, .rules-doc table.rules-table td {
  border: 1px solid var(--line); padding: 6px 9px; text-align: left; vertical-align: top;
}
.rules-doc table.rules-table th { background: var(--panel-2); color: var(--ink); font-weight: 600; }
.rules-doc table.rules-table tbody tr:nth-child(even) { background: rgba(255, 255, 255, .018); }
.rules-doc .flash { animation: flash 1.1s ease-out; }
@keyframes flash {
  0% { background: rgba(111, 211, 255, .22); }
  100% { background: transparent; }
}

@media (max-width: 900px) {
  .rules-body { grid-template-columns: minmax(0, 1fr); }
  .rules-toc { display: none; }
  .rules-head input[type="search"] { width: 140px; }
}

/* ---------------- game over ---------------- */
.gameover {
  margin: 10px 0 14px; padding: 12px 14px; border-radius: var(--radius);
  background: linear-gradient(100deg, rgba(126, 231, 135, .16), var(--panel-2));
  border: 1px solid rgba(126, 231, 135, .35); font-weight: 600; font-size: 15px;
}

/* ---------------- scrollbars ---------------- */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #26303f; border-radius: 6px; border: 2px solid var(--panel); }
::-webkit-scrollbar-thumb:hover { background: #334054; }

@media (max-width: 1250px) {
  .app { --side-w: 330px; }
}

/* ---------------- orders: the one spine across every phase --------------- */

.orders {
  background: linear-gradient(180deg, var(--raise), var(--panel-2));
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 10px; padding: 9px 11px; margin-bottom: 10px;
}
.orders-head { display: flex; align-items: center; gap: 8px; }
.orders-tag {
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--dim); flex: none;
}
.orders-who {
  flex: 1; min-width: 0; font-weight: 600; font-size: 13px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.orders-who.none { color: var(--dim); font-weight: 400; font-style: italic; }
.orders-count {
  font-size: 11px; color: var(--accent); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.pill.step { padding: 1px 8px; font-size: 13px; line-height: 1.5; }

.orders-row { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.orders-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: inherit; font-size: 11.5px; color: var(--dim);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 999px; padding: 3px 9px; cursor: pointer;
  max-width: 190px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.orders-chip:hover { border-color: var(--accent); color: var(--ink); }
.orders-chip.on {
  background: var(--raise); color: var(--ink); border-color: var(--accent);
  box-shadow: 0 0 12px -4px var(--accent);
}
.orders-hint { margin-top: 7px; font-size: 10.5px; color: var(--dim); }

/* the board half of the same idea: what is waiting, and what is up now */
@keyframes await-pulse {
  0%, 100% { opacity: .30; }
  50%      { opacity: .85; }
}
.region-g.awaiting .hex {
  stroke: var(--accent-warm); stroke-width: 2.5;
  animation: await-pulse 1.9s ease-in-out infinite;
}
.region-g.acting .hex {
  stroke: #ffffff; stroke-width: 3.5; animation: none;
  filter: drop-shadow(0 0 8px rgba(255, 255, 255, .5));
}

/* End Turn, borrowed wholesale: dull until nothing of yours is waiting */
.end-phase.waiting {
  background: var(--panel-2); color: var(--dim);
  border: 1px solid var(--line); box-shadow: none; text-shadow: none;
}
.end-phase.waiting:hover { color: var(--ink); border-color: var(--accent); filter: none; }
.end-phase.ready {
  background: linear-gradient(180deg, #9df0b0, var(--good));
  color: #08240f;
}
.orders-where { color: var(--dim); font-size: 10.5px; font-family: var(--mono); }
.orders-chip.on .orders-where { color: var(--accent); }

/* the inspector's "and now what?" -- actions for the thing you clicked */
.ins-actions { margin-top: 8px; padding-top: 7px; border-top: 1px solid var(--line-soft); }
.ins-actions-head {
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim); margin-bottom: 5px;
}
.ins-action {
  display: block; width: 100%; text-align: left; margin-bottom: 4px;
  font-family: inherit; font-size: 11.5px; color: var(--ink);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 7px; padding: 5px 8px; cursor: pointer;
}
.ins-action:hover { border-color: var(--accent); color: var(--accent); background: var(--raise); }

/* a verb taken, waiting for a place on the board */
.armed-bar {
  display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
  background: linear-gradient(180deg, var(--raise), var(--panel-2));
  border: 1px solid color-mix(in srgb, var(--good) 55%, var(--line));
  border-left: 3px solid var(--good);
  border-radius: 10px; padding: 8px 11px;
}
.armed-verb { font-weight: 700; font-size: 13px; color: var(--good); }
.armed-hint { flex: 1; min-width: 0; font-size: 11.5px; color: var(--dim); }

/* the combat ground: cut tiles, not a flat sheet */
.cell-bevel { pointer-events: none; }

/* =====================================================================
 * THE COMBAT ROUND, MADE LEGIBLE
 *
 * A fight is a secret choice, a reveal, and a run through everybody's option
 * in initiative order.  The board drew the pieces and the panel drew the
 * options, and *nothing* drew the round: whose turn it was could only be
 * inferred from which figure the option list happened to be about.  The three
 * blocks below are that missing middle -- the running order, the figure on the
 * clock, and what each one chose.
 * ===================================================================== */

/* ---------- the initiative track ---------- */
.combat-track {
  margin: 0 0 10px; padding: 8px 9px;
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line); border-radius: var(--radius);
}
.track-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  margin-bottom: 7px; font-size: 12px;
}
.track-head b { font-size: 12.5px; letter-spacing: .04em; }
.track-phase { color: var(--accent); font-size: 11.5px; }
.track-warn { color: var(--accent-warm); font-family: var(--mono); font-size: 11px; }
.track-pool {
  color: var(--seat); font-family: var(--mono); font-size: 10.5px;
  border: 1px solid color-mix(in srgb, var(--seat) 45%, var(--line));
  border-radius: 999px; padding: 1px 7px;
}
.track-hint { color: var(--dim); font-size: 11.5px; }
.track-strip { display: flex; flex-direction: column; gap: 3px; max-height: 232px; overflow-y: auto; }

.track-step {
  display: flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 3px 7px; border-radius: 7px; font-size: 11.5px;
  border: 1px solid transparent; border-left: 3px solid var(--seat);
  background: var(--panel-2);
}
.track-step:hover { background: var(--raise); }
/* three states, and they have to be tellable apart at a glance: what has
   already happened is faded, what is happening is lit, what is still coming is
   plain */
.track-step.done { opacity: .45; }
.track-step.now {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--accent-warm) 20%, var(--panel-2)), var(--panel-2));
  border-color: var(--accent-warm);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-warm) 35%, transparent);
}
.track-init {
  font-family: var(--mono); font-size: 10.5px; color: var(--dim);
  min-width: 26px; text-align: right;
}
.track-step.now .track-init { color: var(--accent-warm); font-weight: 700; }
.track-name { font-weight: 600; color: var(--seat); white-space: nowrap; }
.track-option {
  flex: 1; min-width: 0; color: var(--ink); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.track-step.todo .track-option { color: var(--dim); }
.track-flag {
  font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-warm); font-weight: 700; white-space: nowrap;
}

/* ---------- the figure on the clock ---------- */
.acting-ring {
  stroke: var(--accent-warm); stroke-width: 4; pointer-events: none;
  filter: drop-shadow(0 0 7px rgba(255, 176, 87, .6));
  animation: pulse-ring 1.5s ease-in-out infinite;
}
@keyframes pulse-ring {
  0%, 100% { stroke-opacity: .55; }
  50% { stroke-opacity: 1; }
}
/* what a figure chose, written on the board once the round is public */
.fig-option {
  font-size: 9.5px; font-weight: 700; fill: var(--dim); pointer-events: none;
  paint-order: stroke; stroke: #0a0e14; stroke-width: 3.5px; stroke-linejoin: round;
}
.fig-option.now { fill: var(--accent-warm); }
.roster-row.acting {
  background: color-mix(in srgb, var(--accent-warm) 12%, transparent);
  border-radius: 7px;
}
.roster-row .rstat.now {
  color: var(--accent-warm); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: 9.5px;
}
.acting-bar { border-left: 3px solid var(--seat); padding-left: 8px; }
.aim-printed { color: var(--dim); font-size: 11.5px; margin: -4px 0 8px; }

/* ---------- a marked hex that has not gone off yet ---------- */
.doom-hex {
  fill: var(--danger); fill-opacity: .17; stroke: var(--danger); stroke-width: 2.5;
  stroke-dasharray: 4 4; pointer-events: none;
}
.doom-hex.imminent {
  fill-opacity: .3; stroke-width: 3.5; stroke-dasharray: none;
  animation: pulse-ring 1s ease-in-out infinite;
}
.doom-when {
  font-size: 8.5px; letter-spacing: .08em; text-transform: uppercase;
  font-weight: 700; fill: var(--danger); pointer-events: none;
  paint-order: stroke; stroke: #0a0e14; stroke-width: 3px; stroke-linejoin: round;
}
.doom-mark {
  font-size: 13px; font-weight: 800; fill: var(--danger); pointer-events: none;
  paint-order: stroke; stroke: #0a0e14; stroke-width: 3.5px; stroke-linejoin: round;
}

/* ---------- the hover card for a figure ---------- */
.fig-tip { min-width: 250px; }
.fig-tip h4 { display: flex; align-items: baseline; gap: 8px; }
.tip-seat {
  color: var(--seat); font-family: var(--mono); font-size: 10.5px; font-weight: 700;
}
.tip-gauge { display: flex; align-items: center; gap: 6px; margin: 2px 0; font-size: 11px; }
.tip-gauge-label { width: 52px; color: var(--dim); text-transform: lowercase; }
.tip-gauge-track {
  flex: 1; height: 6px; border-radius: 3px; background: #080b10;
  border: 1px solid var(--line-soft); overflow: hidden;
}
.tip-gauge-fill { display: block; height: 100%; }
.tip-gauge-num { font-family: var(--mono); font-size: 10.5px; color: var(--dim); min-width: 42px; }
.tip-conds { margin-top: 6px; padding-top: 5px; border-top: 1px solid var(--line-soft); }
.tip-cond { font-size: 11px; }
.tip-cond b { color: var(--accent-warm); }
.tip-cond-text { color: var(--dim); }
.tip-modules { margin-top: 5px; font-size: 11px; color: var(--dim); }
.tip-chosen {
  margin-top: 6px; padding: 5px 7px; border-radius: 7px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-left: 3px solid var(--accent);
}
.tip-chosen b { color: var(--accent); font-size: 11.5px; }
.tip-chosen-text { color: var(--dim); font-size: 11px; }
.tip-sub {
  margin-top: 7px; font-size: 9.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--dim);
}
.tip-from { color: var(--accent); font-size: 10.5px; }

/* ---------- what an option costs, before you pick it ---------- */
.opt-from {
  display: inline-block; margin-left: 6px; font-size: 10px; color: var(--accent);
}
.opt-cost {
  display: block; margin-top: 2px; font-family: var(--mono);
  font-size: 10.5px; color: var(--accent-warm);
}

/* =====================================================================
   RISE OF RIVALS
   ---------------------------------------------------------------------
   What this edition added to the shell above: two resource pools instead
   of one, five terrain colours the rules actually read, level piles to
   research out of, and a combat board whose every round is a secret
   choice followed by a hex.
   ===================================================================== */

/* ---------------- top bar ---------------- */
.status { display: flex; align-items: center; gap: 14px; font-size: 12.5px; }
.status-turn { font-family: var(--mono); color: color-mix(in srgb, var(--accent) 55%, var(--dim)); }
.status-phase { display: flex; align-items: center; gap: 6px; color: var(--ink); }
.status-phase .phase-icon { color: var(--accent); }
.status-who { font-weight: 600; }
.status-fight {
  color: var(--accent-warm); border: 1px solid rgba(255, 176, 87, .34);
  border-radius: 999px; padding: 2px 10px; font-size: 11.5px;
}

.active-stats { display: flex; gap: 8px; margin-left: auto; }
.stat-chip {
  display: flex; align-items: baseline; gap: 6px; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--panel-2);
  font-size: 11.5px;
}
.stat-chip .stat-key {
  color: var(--dim); text-transform: uppercase; letter-spacing: .1em; font-size: 10px;
}
.stat-chip .stat-val { font-family: var(--mono); font-weight: 600; }
.stat-chip.res .stat-val { color: var(--accent); }
.stat-chip.cap.over { border-color: rgba(255, 107, 107, .5); }
.stat-chip.cap.over .stat-val { color: var(--danger); }

/* ---------------- seats ---------------- */
.player-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
.player-head b { letter-spacing: .04em; }
.player-faction { color: var(--dim); font-size: 11px; margin-left: auto; }
.kv { display: flex; flex-wrap: wrap; gap: 4px 12px; }
.kv-cell { display: flex; align-items: baseline; gap: 5px; font-size: 11.5px; }
.kv-key {
  color: var(--dim); text-transform: uppercase; letter-spacing: .08em; font-size: 9.5px;
}
.kv-val { font-family: var(--mono); font-weight: 600; }
.kv-extra { color: var(--good); font-family: var(--mono); font-size: 10.5px; }
.income-wrap { margin-top: 8px; font-size: 11px; }
.income-wrap summary { color: var(--dim); cursor: pointer; }
.income-line {
  display: flex; justify-content: space-between; gap: 10px; padding: 2px 0;
  color: var(--dim);
}
.income-line b { color: var(--good); font-family: var(--mono); }
.chosen { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
.card-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px 2px 3px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--panel-2);
  color: var(--ink); font-size: 10.5px; cursor: pointer;
}
.card-chip:hover { border-color: var(--accent); background: var(--raise); }
.card-chip.spent { color: var(--dim); font-style: italic; padding-left: 8px; }
.chip-art { width: 16px; height: 16px; border-radius: 50%; object-fit: cover; }

/* ---------------- the map ---------------- */
.hex { stroke: #0a0d13; stroke-width: 1.2; }
.region-g { cursor: pointer; }
.region-g:hover .hex { stroke: var(--accent); stroke-width: 2; }
.region-g.lit .hex { filter: brightness(1.12); }
.token-chip { cursor: pointer; }
.token-chip.can-act circle:first-child { filter: drop-shadow(0 0 5px var(--accent)); }
.token-chip.picked circle:first-child { stroke-width: 3.6; }
.fresh-badge { pointer-events: none; }
.move-ring, .ctrl-ring { pointer-events: none; }

/* ---------------- the combat board ---------------- */
.cell-g { cursor: default; }
.cell-g.lit { cursor: pointer; }
.cell-g.lit:hover .cell { filter: brightness(1.35); }
.cell { stroke-width: 1; }
.to-choose { pointer-events: none; animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
.fig { cursor: pointer; }
.fig.acting circle:first-child { filter: drop-shadow(0 0 8px var(--accent-warm)); }
.fig-label {
  fill: var(--ink); font-size: 9.5px; letter-spacing: .02em; pointer-events: none;
  paint-order: stroke; stroke: #0a0d13; stroke-width: 3px; stroke-linejoin: round;
}
.fig-armor {
  fill: var(--accent); font-size: 9px; font-family: var(--mono); pointer-events: none;
}

.combat-track {
  padding: 8px 12px; margin-bottom: 8px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--panel-2); font-size: 11.5px;
}
.track-line { display: flex; justify-content: space-between; gap: 10px; }
.track-line.warn { color: var(--accent-warm); margin-top: 4px; }
.track-terrain { color: var(--dim); }

/* ---------------- the side panel ---------------- */
.hint-line {
  margin: 0 0 10px; padding: 8px 10px; font-size: 11.5px; line-height: 1.5;
  color: var(--dim); background: var(--panel-2); border-left: 2px solid var(--line);
  border-radius: 0 8px 8px 0;
}
.row-title {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .12em;
  color: var(--dim); margin: 12px 0 6px;
}
.pile-size { font-family: var(--mono); text-transform: none; letter-spacing: 0; }
.pile { border-top: 1px solid var(--line-soft); padding-top: 4px; }

.card-row {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 8px;
}
.card-tile {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 6px; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 10px; color: var(--ink); text-align: center; cursor: pointer;
  transition: border-color .12s, background .12s, transform .12s;
}
.card-tile:hover {
  border-color: var(--accent); background: var(--raise); transform: translateY(-1px);
}
.card-tile.picked {
  border-color: var(--accent-warm); box-shadow: 0 0 0 1px var(--accent-warm);
}
.card-tile.off { opacity: .4; cursor: default; }
.card-art { width: 54px; height: 54px; object-fit: contain; }
.card-tile-name { font-size: 11px; font-weight: 600; line-height: 1.25; }
.card-tile-meta { font-size: 10px; color: var(--dim); font-family: var(--mono); }
/* what has to be standing before this one can be: a gate, so it is warm
   rather than dim -- the point of it is that it is a condition, not a stat */
.card-tile-need {
  font-size: 9.5px; line-height: 1.35; color: var(--accent-warm);
  opacity: .85; text-wrap: balance;
}
/* the gate is already standing: not a warning, a green light */
.card-tile-need.met { color: var(--good); }
/* one on the map at a time -- a property of the card, pinned to its corner */
.card-tile { position: relative; }
.card-tile-tag {
  position: absolute; top: 5px; right: 5px;
  padding: 1px 5px; border-radius: 999px;
  font-size: 8px; font-weight: 700; letter-spacing: .1em;
  color: var(--accent-warm); background: rgba(255, 176, 87, .13);
  border: 1px solid rgba(255, 176, 87, .4);
}

.opt {
  display: block; width: 100%; text-align: left; margin: 3px 0; padding: 8px 11px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  color: var(--ink); font-size: 12px; cursor: pointer; transition: all .12s;
}
.opt:hover { border-color: var(--accent); background: var(--raise); }
.opt[disabled] { opacity: .45; cursor: default; }
.opt.danger { border-color: rgba(255, 107, 107, .5); color: var(--danger); }
.opt.primary-opt { border-color: var(--accent); color: var(--accent); }
.opt-sub { color: var(--dim); font-size: 11px; }
.opt-count { color: var(--dim); font-family: var(--mono); font-size: 10.5px; }
.opt-group > summary { cursor: pointer; padding: 6px 2px; font-size: 11.5px; }
.waiting { color: var(--dim); font-size: 12px; padding: 8px 2px; }

/* ---------------- mechanic chips ---------------- */
.mech-chip {
  border: 1px solid var(--line); border-radius: 999px; background: var(--panel-2);
  color: var(--accent); padding: 1px 9px; font-size: 10.5px; letter-spacing: .05em;
  cursor: pointer;
}
.mech-chip:hover { border-color: var(--accent); background: var(--raise); }
.mech-chip.sig { color: var(--accent-warm); border-color: rgba(255, 176, 87, .38); }

/* ---------------- inspector ---------------- */
.ins-art {
  width: 120px; height: 120px; object-fit: contain; margin: 0 auto 8px; display: block;
}
.ins-opt { margin-top: 5px; font-size: 11.5px; }

/* ---------------- tooltip lines ---------------- */
.tooltip .tip-line { margin-top: 3px; font-size: 11.5px; line-height: 1.4; }
.tooltip .tip-line.dim { color: var(--dim); }
.tooltip .tip-line.good { color: var(--good); }
.tooltip .tip-line.warn { color: var(--accent-warm); }

/* ---------------- rulebook headings ---------------- */
.rules-doc h1 { font-size: 22px; margin: 4px 0 12px; letter-spacing: .04em; }
.rules-doc h2 {
  display: block; text-transform: none; font-size: 19px; letter-spacing: .02em;
  color: var(--ink); margin: 26px 0 10px; padding-bottom: 7px;
  border-bottom: 1px solid var(--line);
}
.rules-doc h2::after { display: none; }
.rules-doc h3 {
  font-size: 15px; font-weight: 600; color: var(--accent); margin: 20px 0 8px;
}
.rules-doc h4 { font-size: 13px; margin: 16px 0 6px; color: var(--accent-warm); }
.rules-link { color: var(--accent); border-bottom: 1px dotted currentColor; cursor: pointer; }

/* ---------------- log ---------------- */
.log-line { padding: 2px 0; font-size: 11.5px; line-height: 1.4; color: var(--dim); }
.log-line.turn { color: var(--accent); margin-top: 6px; font-weight: 600; }

/* The options drawer rides over the map, and a 9x5 board is wide enough that
   two whole columns would go under it.  Give the map back the width the
   drawer takes rather than letting it hide the far side of the world. */
.app.side-open .board-wrap { padding-right: calc(var(--side-w) + 14px); }

/* A seat badge on the rail is two letters, not a faction painting, so it needs
   a size and a colour of its own. */
.rail-seat {
  min-width: 30px; height: 26px; align-items: center; justify-content: center;
  line-height: 1; font-size: 11px; font-weight: 700; letter-spacing: .06em;
  color: var(--seat); cursor: pointer;
}
.rail-seat.mine { background: color-mix(in srgb, var(--seat) 16%, var(--panel-2)); }

/* The board draws at its natural size (times the zoom) and is capped to the
   pane here, so a six-by-four skirmish board is not stretched to the width of
   a twelve-by-eight one. */
.board-svg { max-width: 100%; max-height: 100%; }
.fig-label { font-size: 8.5px; }

.cell-g.flash .cell { filter: brightness(1.6); }

/* The inspector is `position: fixed` with nothing to fix it to, which put it
   below the fold.  Park it in the bottom-left corner of the map, where the
   board has spare room and the tool rail and the phase button are not. */
.inspector { width: min(560px, calc(100vw - 28px)); max-height: 82vh; max-height: 82dvh; }

/* =====================================================================
   The second pass: a phase track, a rail that counts, stacked tokens,
   and a card you can actually read.
   ===================================================================== */

/* ---------------- the phase track ---------------- */
.phase-track { display: flex; align-items: center; gap: 2px; }
.phase-step {
  display: flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: 999px; font-size: 11.5px;
  color: var(--dim); border: 1px solid transparent; white-space: nowrap;
  transition: color .15s, background .15s, border-color .15s;
}
.phase-step .phase-icon { opacity: .7; }
.phase-step.done { color: color-mix(in srgb, var(--dim) 70%, transparent); }
.phase-step.done .phase-icon { opacity: .35; }
.phase-step.on {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  background: color-mix(in srgb, var(--accent) 12%, var(--panel-2));
}
.phase-step.on .phase-icon { opacity: 1; }
.phase-step + .phase-step::before {
  content: ""; width: 10px; height: 1px; background: var(--line);
  margin-left: -6px; margin-right: 4px;
}
@media (max-width: 1150px) { .phase-step:not(.on) b { display: none; } }

/* ---------------- the tool rail ---------------- */
.tool-btn { width: 54px; gap: 3px; }
.tool-name {
  font-size: 8.5px; letter-spacing: .08em; text-transform: uppercase;
  line-height: 1;
}
.tool-dot {
  position: absolute; top: 4px; right: 6px; min-width: 16px; height: 16px;
  border-radius: 999px; font-size: 10px; line-height: 16px; text-align: center;
  font-family: var(--mono); font-weight: 600; padding: 0 3px;
  background: var(--accent); color: #06222e;
}
.tool-dot.zero { background: var(--line); color: var(--dim); }
.tool-btn.on .tool-dot { box-shadow: 0 0 0 2px var(--panel); }

/* ---------------- tokens ---------------- */
.stack-badge { pointer-events: none; }
.stack-count {
  fill: var(--ink); font-size: 10px; font-family: var(--mono); font-weight: 600;
  pointer-events: none;
}
/* a token you can still march, and one whose ability is about to fire */
.token-chip.can-act circle:first-child {
  filter: drop-shadow(0 0 6px var(--accent));
  stroke-width: 2.4;
}
.token-chip.can-fire circle:first-child {
  filter: drop-shadow(0 0 7px var(--accent-warm));
  stroke: var(--accent-warm); stroke-width: 2.4;
}
.token-chip.picked circle:first-child {
  stroke: #fff; stroke-width: 3;
  filter: drop-shadow(0 0 9px var(--accent));
}

/* ---------------- tooltips with a picture in them ---------------- */
.tooltip { pointer-events: auto; }
.tip-card { max-width: 340px; }
.tip-head { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 5px; }
.tip-art {
  width: 62px; height: 62px; object-fit: contain; flex: none;
  border-radius: 8px; background: rgba(255, 255, 255, .04);
}
.zoomable { cursor: zoom-in; transition: box-shadow .15s, transform .12s; }
.zoomable:hover {
  box-shadow: 0 0 0 1px var(--accent), 0 0 18px -4px var(--accent);
  transform: translateY(-1px);
}
.tip-init {
  display: inline-block; min-width: 20px; font-family: var(--mono);
  color: var(--accent); font-size: 10.5px;
}

.art-zoom {
  position: fixed; z-index: 80; pointer-events: none;
  padding: 10px 10px 8px; border-radius: 14px;
  background: rgba(9, 13, 19, .97); border: 1px solid var(--line);
  box-shadow: 0 20px 56px rgba(0, 0, 0, .8);
}
.art-zoom-img { display: block; width: 260px; height: 260px; object-fit: contain; }
.art-zoom-name {
  margin-top: 7px; text-align: center; font-size: 12px; font-weight: 600;
  letter-spacing: .04em; color: var(--ink);
}

/* The card window's own picture, opened as far as the screen allows.  It is
   centred rather than parked beside its anchor, because the window it came
   from is already in the middle and there is no side wide enough to hold a
   picture this size next to it. */
.art-zoom.big {
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  padding: 16px 16px 12px; z-index: 82;
  border-color: color-mix(in srgb, var(--accent) 34%, var(--line));
  box-shadow: 0 30px 90px rgba(0, 0, 0, .85);
}
.art-zoom.big .art-zoom-img {
  width: min(52vh, 450px); height: min(52vh, 450px);
}
.art-zoom.big .art-zoom-name { margin-top: 10px; font-size: 14px; }

/* ---------------- the inspector, laid out like the card ---------------- */
.ins-head { display: flex; gap: 18px; align-items: flex-start; margin-bottom: 14px; }
.ins-head .ins-art {
  width: 132px; height: 132px; margin: 0; flex: none;
  border-radius: 12px; background: rgba(255, 255, 255, .04);
}
.ins-head-text { min-width: 0; }
.ins-name {
  font-size: 21px; font-weight: 600; color: var(--ink); line-height: 1.2;
}
.ins-section {
  margin: 20px 0 9px; font-size: 11px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim);
  display: flex; align-items: center; gap: 10px;
}
.ins-section::after {
  content: ""; flex: 1; height: 1px; background: var(--line-soft);
}
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.stat-cell {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 10px 4px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel-2); cursor: help;
}
.stat-cell-key {
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim);
}
.stat-cell-val { font-family: var(--mono); font-size: 22px; font-weight: 600; }

.ins-ability { margin: 5px 0 7px; line-height: 1.6; font-size: 14px; }
.ins-params { font-family: var(--mono); font-size: 14px; color: var(--ink); margin-left: 6px; }
.ins-ability .ins-line { margin-top: 2px; }

.ins-options { width: 100%; border-collapse: collapse; font-size: 15px; }
.ins-options td { padding: 7px 0; vertical-align: top; border-top: 1px solid var(--line-soft); }
.ins-options tr:first-child td { border-top: 0; }
.ins-init {
  width: 42px; font-family: var(--mono); color: var(--accent);
  font-size: 14px; cursor: help;
}
.ins-effect { color: var(--dim); font-size: 13.5px; margin-top: 3px; }

/* ---------------- one entity's live abilities ---------------- */
.ability-line {
  display: flex; align-items: baseline; gap: 6px; font-size: 11px;
  padding: 3px 0; line-height: 1.4;
}
.ability-line b { color: var(--accent); font-weight: 600; }
.ability-line.live b { color: var(--good); }
.ability-line.dim { color: var(--dim); }
.ability-hook {
  margin-left: auto; font-family: var(--mono); font-size: 9.5px; color: var(--dim);
  border: 1px solid var(--line); border-radius: 999px; padding: 0 6px; flex: none;
}

/* The rail is 54px wide and the labels are words, so they read down it on
   purpose rather than wrapping to one letter a line. */
.tool-name {
  writing-mode: vertical-rl; text-orientation: mixed;
  max-height: 76px; overflow: hidden;
}

/* The board's floating label carries the phase's instruction now, so it is a
   sentence rather than the word "Map" and needs room to be one. */
#board-title { max-width: 46ch; white-space: normal; line-height: 1.35; }

/* The label is an instruction now, not a caption, so it sits in the middle of
   the board's top edge -- clear of the seat rail in the corner -- and is
   readable without being pointed at. */
.board-head { justify-content: center; }
.board-controls { position: absolute; top: 8px; right: 12px; }
#board-title { margin-top: 0; }
.board-head #board-title { opacity: .82; }

/* =====================================================================
   Highlights that move.
   ---------------------------------------------------------------------
   A ring that is merely brighter is something you have to be looking for.
   A ring that breathes is something you notice on the far side of the map,
   which is the whole job: "these four still have an activation" has to
   survive a board with ninety tokens on it.
   ===================================================================== */
@keyframes token-ready {
  0%, 100% { stroke-width: 1.6; filter: drop-shadow(0 0 3px var(--glow)); }
  50%      { stroke-width: 3.2; filter: drop-shadow(0 0 11px var(--glow)); }
}
.token-chip.can-act circle:first-child {
  --glow: var(--accent);
  stroke: var(--accent);
  animation: token-ready 1.9s ease-in-out infinite;
}
.token-chip.can-fire circle:first-child {
  --glow: var(--accent-warm);
  stroke: var(--accent-warm);
  animation: token-ready 1.9s ease-in-out infinite;
}
/* the one you have actually picked stops breathing and goes solid: it is no
   longer asking for attention, it is waiting for a destination */
.token-chip.picked circle:first-child {
  animation: none; stroke: #fff; stroke-width: 3;
  filter: drop-shadow(0 0 10px var(--accent));
}

/* the regions that answer the pending question: marching ants, so "click one
   of these" reads as an instruction rather than as decoration */
@keyframes ants { to { stroke-dashoffset: -16; } }
.move-ring { animation: ants 1.1s linear infinite; }
.region-g.lit .hex { filter: brightness(1.18); }

/* =====================================================================
   Watching: the transport, the ledger, and the marks a frame leaves.
   ---------------------------------------------------------------------
   A bot turn arrives finished.  The bar says what was just played and
   lets you walk the tape; the marks on the board say what changed while
   you were reading the bar.  Both fade, because the next frame is two
   seconds away and a board covered in old news is worse than a plain one.
   ===================================================================== */
.spectator {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 14px;
  z-index: 30; width: min(720px, calc(100% - 120px));
  display: flex; flex-direction: column; gap: 6px;
  padding: 9px 12px 10px; border-radius: 13px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 10%, #10161f), rgba(13, 18, 26, .96));
  border: 1px solid color-mix(in srgb, var(--accent) 34%, var(--line));
  box-shadow: 0 16px 44px rgba(0, 0, 0, .62);
  transition: left .24s cubic-bezier(.4, 0, .2, 1);
}
.app.side-open .spectator { left: calc(50% - var(--side-w) / 2); }

.spec-said {
  --seat: var(--accent);
  display: flex; align-items: baseline; gap: 9px; min-height: 19px;
  font-size: 13px; line-height: 1.35;
}
.spec-seat {
  font-weight: 700; color: var(--seat); font-size: 11px;
  letter-spacing: .06em; text-transform: uppercase; flex: none;
}
.spec-text { flex: 1; min-width: 0; }
.spec-text.dim { color: var(--dim); }
.spec-where {
  flex: none; color: var(--dim); font-family: var(--mono); font-size: 10.5px;
}

.spec-row { display: flex; align-items: center; gap: 6px; }
.spec-mode { display: flex; gap: 2px; margin-right: 4px; }
.spec-tab {
  padding: 3px 9px; border-radius: 999px; font-size: 10.5px;
  letter-spacing: .05em; text-transform: uppercase;
  background: transparent; color: var(--dim);
  border: 1px solid transparent; cursor: pointer;
}
.spec-tab:hover { color: var(--ink); }
.spec-tab.on {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.spec-btn {
  min-width: 30px; padding: 4px 7px; border-radius: 8px; font-size: 13px;
  background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line); cursor: pointer; transition: all .12s;
}
.spec-btn:hover:not(:disabled) {
  border-color: var(--accent); color: var(--accent);
}
.spec-btn:disabled { opacity: .3; cursor: default; }
.spec-btn.play.on {
  background: var(--accent); border-color: var(--accent);
  color: color-mix(in srgb, var(--accent) 20%, #04161f);
}
.spec-scrub {
  flex: 1; min-width: 60px; accent-color: var(--accent); cursor: pointer;
}
.spec-count {
  flex: none; font-family: var(--mono); font-size: 10.5px; color: var(--dim);
  min-width: 62px; text-align: right;
}

/* the ledger in the drawer */
.ledger { display: flex; flex-direction: column; gap: 1px; }
.ledger-more {
  font-size: 11px; color: var(--dim); padding: 4px 2px 8px;
  border-bottom: 1px solid var(--line-soft); margin-bottom: 6px;
}
.ledger-head {
  --seat: var(--accent);
  margin: 9px 0 3px; padding-left: 7px; font-size: 11px; color: var(--dim);
  border-left: 2px solid var(--seat);
}
.ledger-head b { color: var(--seat); }
.ledger-row {
  --seat: var(--accent);
  display: flex; gap: 8px; align-items: baseline; width: 100%; text-align: left;
  padding: 4px 8px; border: 1px solid transparent;
  border-left: 2px solid color-mix(in srgb, var(--seat) 40%, transparent);
  border-radius: 0 7px 7px 0; background: transparent; color: var(--ink);
  font-size: 12px; cursor: pointer;
}
.ledger-row:hover { background: var(--raise); border-color: var(--accent); }
.ledger-row.now {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
}
.ledger-n {
  flex: none; font-family: var(--mono); font-size: 10px; color: var(--dim);
  min-width: 30px; text-align: right;
}
.ledger-text { min-width: 0; }

/* --- the marks a frame leaves on the board --- */
@keyframes fx-run { to { stroke-dashoffset: -36; } }
@keyframes fx-fade { 0% { opacity: 0; } 12% { opacity: 1; } 100% { opacity: 0; } }
@keyframes fx-swell { 0% { transform: scale(.45); } 100% { transform: scale(1.25); } }

.fx-layer { pointer-events: none; }
.fx-arrow {
  animation: fx-run .9s linear infinite, fx-fade 1.7s ease-out forwards;
  filter: drop-shadow(0 0 6px rgba(0, 0, 0, .8));
}
.fx-tip { animation: fx-fade 1.7s ease-out forwards; }
.fx-born, .fx-died, .fx-turned {
  transform-box: fill-box; transform-origin: center;
  animation: fx-swell 1.7s cubic-bezier(.2, .8, .3, 1) forwards,
             fx-fade 1.7s ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .fx-arrow { animation: fx-fade 1.7s ease-out forwards; }
  .fx-born, .fx-died, .fx-turned { animation: fx-fade 1.7s ease-out forwards; }
}

/* =====================================================================
   Keywords, and the layer that explains them.
   ---------------------------------------------------------------------
   A mechanic name printed on a card is a word you are expected to know.
   Underlining it and answering on hover is the cheapest way to learn one
   without leaving whatever you were reading -- which is why this is its
   own layer: keywords live inside tooltips, and a tooltip that rewrote
   itself to explain a word in it would destroy the word being pointed at.
   ===================================================================== */
.kw {
  color: var(--accent); cursor: help; white-space: nowrap;
  border-bottom: 1px dotted color-mix(in srgb, var(--accent) 55%, transparent);
}
.kw:hover { color: #fff; border-bottom-color: #fff; }
.kw-pop {
  position: fixed; z-index: 90; width: max-content; max-width: 330px;
  padding: 9px 11px 10px; font-size: 11.5px; line-height: 1.5;
  background: linear-gradient(180deg, #1b2431, var(--panel));
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: 9px; box-shadow: 0 16px 40px rgba(0, 0, 0, .62);
}
.kw-head {
  display: flex; align-items: baseline; gap: 8px;
  padding-bottom: 6px; margin-bottom: 6px;
  border-bottom: 1px solid var(--line-soft);
}
.kw-head b {
  color: var(--accent); font-size: 12.5px; letter-spacing: .07em;
  font-family: var(--mono);
}
.kw-sig {
  margin-left: auto; font-size: 10px; color: var(--accent-warm);
  letter-spacing: .04em;
}
/* two columns: what the row is, and what it says.  A mechanic has three or
   four separable facts and a paragraph makes you find them again each time. */
.kw-rows {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 10px;
  align-items: baseline;
}
.kw-key {
  font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); text-align: right;
}
.kw-val { font-family: var(--mono); font-size: 11px; color: var(--ink); }
.kw-blurb {
  margin-top: 7px; padding-top: 6px; border-top: 1px solid var(--line-soft);
  color: var(--dim);
}

/* the card window prints names and numbers; the names answer for themselves */
.ins-abilities { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.ins-abilities .ins-ability {
  display: inline-flex; align-items: baseline; gap: 4px;
}

/* the picked card, waiting for a yes */
.confirm-row { display: flex; gap: 8px; margin-top: 10px; }
.confirm-row .opt { flex: 1; text-align: center; }

/* =====================================================================
   The orders popup: one figure's options, beside the figure.
   ---------------------------------------------------------------------
   It borrows the region roster's shell, because it is the same thing --
   a short list anchored to what it is about -- and only the rows differ.
   ===================================================================== */
.orders-pop { width: 320px; }
.orders-pop .roster-rows .opt { width: 100%; }
.orders-pop .opt[disabled] { opacity: .4; }

/* a stack only part of which is marching: the badge stops being a head count
   and becomes the state of the choice, so it says so in white */
.stack-badge.part { filter: drop-shadow(0 0 5px var(--accent)); }

/* the drawer's roll call: read-only, so no button affordance on it */
.order-row {
  padding: 5px 8px; border-left: 2px solid var(--line);
  font-size: 12px; line-height: 1.45;
}
.order-row.settled { border-left-color: var(--accent); }
.order-row.settled .opt-sub { color: var(--accent); }

/* nothing left to confirm yet: the button says so rather than lying blue */
.phase-ender-btn[disabled] {
  background: var(--panel-2); color: var(--dim);
  border-color: var(--line); cursor: default; box-shadow: none;
}
.phase-ender-btn[disabled]:hover { transform: none; box-shadow: none; }

/* a figure the battle is waiting on breathes the same way a token does: at
   K4 it is the one whose initiative you are being asked to break */
.fig.can-act circle:first-child {
  --glow: var(--accent);
  stroke: var(--accent);
  animation: token-ready 1.9s ease-in-out infinite;
}
/* a figure whose order is already given: settled, but not yet sealed */
.fig.settled circle:first-child { stroke-dasharray: 4 3; }
/* the one whose option list is open stops asking and goes solid, exactly as a
   map token does once it is waiting for a destination */
.fig.picked circle:first-child {
  animation: none; stroke: #fff; stroke-width: 3.4; stroke-dasharray: none;
  filter: drop-shadow(0 0 10px var(--accent));
}

/* a pulse is a nudge, not a requirement */
@media (prefers-reduced-motion: reduce) {
  .token-chip.can-act circle:first-child,
  .token-chip.can-fire circle:first-child,
  .fig.can-act circle:first-child { animation: none; stroke-width: 2.6; }
  .move-ring, .to-choose { animation: none; }
}

/* An order already chosen this round folds itself away: the panel is then a
   list of what is left to decide, not a wall of every option you own. */
.opt-group.settled > summary { color: var(--accent); opacity: .85; }
.opt-group.settled > summary .opt-sub { color: var(--muted); }
.opt[disabled] { opacity: .45; cursor: default; border-style: dashed; }

/* =====================================================================
   The region roster: what a hex could not draw.
   ===================================================================== */
.more-chip { cursor: pointer; }
.more-chip circle { transition: stroke-width .12s; }
.more-chip:hover circle { stroke-width: 2.6; filter: drop-shadow(0 0 7px var(--accent)); }
.more-count {
  fill: var(--accent); font-size: 11px; font-family: var(--mono); font-weight: 600;
  pointer-events: none;
}

.roster-pop {
  position: fixed; z-index: 62; width: 292px; max-height: calc(100vh - 76px);
  display: flex; flex-direction: column; overflow: hidden;
  background: linear-gradient(180deg, #19212d, var(--panel));
  border: 1px solid var(--line); border-radius: 11px;
  box-shadow: 0 18px 46px rgba(0, 0, 0, .62);
}
.roster-head {
  display: flex; align-items: center; gap: 8px; flex: none;
  padding: 8px 8px 8px 11px; border-bottom: 1px solid var(--line);
  background: var(--panel-2); font-size: 11.5px;
}
.roster-head .roster-count {
  margin-left: auto; color: var(--dim); font-family: var(--mono); font-size: 10px;
}
.roster-head .ghost { padding: 2px 7px; font-size: 11px; line-height: 1.2; }
.roster-rows { overflow-y: auto; padding: 6px; display: grid; gap: 4px; }
.roster-row {
  --seat: var(--accent);
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 6px 8px; border: 1px solid var(--line); border-left: 3px solid var(--seat);
  border-radius: 8px; background: var(--panel-2); color: var(--ink);
  cursor: pointer; transition: background .12s, border-color .12s;
}
.roster-row:hover { background: var(--raise); border-color: var(--accent); }
.roster-row.can-act { box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }
.roster-art {
  width: 34px; height: 34px; flex: none; object-fit: contain;
  border-radius: 50%; background: rgba(255, 255, 255, .05);
}
.roster-text { min-width: 0; }
.roster-name { font-size: 12px; font-weight: 600; line-height: 1.3; }
.roster-x { color: var(--accent); font-family: var(--mono); }


/* =====================================================================
   Small screens.
   ---------------------------------------------------------------------
   The desktop shape is already the right one -- a full-bleed board with
   everything else riding over it -- so this turns it a quarter turn
   rather than rebuilding it.  The rail of tools stops being a column
   against the right edge and becomes the bar along the bottom, where a
   thumb is; the drawer it opens stops being a panel beside the map and
   becomes a sheet over the bottom of it.  Nothing changes about what
   any of them do.

   The board keeps the whole window and pans inside it.  A 9x5 hex map
   shrunk to fit 390 points would have tokens too small to hit, so it is
   held at a size a finger can land on and allowed to scroll instead.
   ===================================================================== */
@media (max-width: 700px) {
  /* the lobby, which is the first thing a phone ever sees */
  body { font-size: 15px; }
  .lobby { padding: 12px; align-items: flex-start; }
  .lobby-card { width: 100%; padding: 18px 16px 20px; }
  .lobby-card h1 { font-size: 22px; letter-spacing: .12em; }
  /* players, seed and the reroll on one line, not three */
  .field-row { display: grid; grid-template-columns: 1fr 1fr auto; gap: 10px;
               align-items: end; }
  .field-row #reroll { min-height: 38px; }
  /* four columns will not go: the tag keeps its column and everything else
     stacks under it, which is the same row read top to bottom */
  .seat-row { grid-template-columns: 34px minmax(0, 1fr); row-gap: 8px; padding: 10px; }
  .seat-row > .seat-name,
  .seat-row > .seat-faction,
  .seat-row > .seat-agent { grid-column: 2; }
  .seat-row .blurb { grid-column: 1 / -1; }
  .lobby-actions { flex-wrap: wrap; }
  .lobby-actions .primary { flex: 1; min-height: 44px; }
  select, input { min-height: 38px; }
}

@media (max-width: 860px), (pointer: coarse) and (max-width: 1100px) {
  /* one thumb's reach of furniture, and the rest is map */
  .app { --side-w: 100vw; grid-template-rows: 46px minmax(0, 1fr); }

  .topbar { gap: 8px; padding: 0 8px; }
  .status-turn { white-space: nowrap; }
  .brand { display: none; }                 /* the tab already says the name */
  .status { gap: 8px; font-size: 12px; }
  .status-turn { font-size: 11px; }
  .status-fight { display: none; }          /* the board's own head says this */
  .phase-step { padding: 3px 7px; }
  .phase-step:not(.on) b { display: none; } /* icons, and the one you are in */
  .phase-step + .phase-step::before { display: none; }
  .active-stats { gap: 4px; }
  .stat-chip { padding: 3px 6px; font-size: 10px; }

  /* the seats: badges under the top bar, their cards on a tap */
  .players-dock { top: 46px; }
  .app.side-open .players-dock { right: 0; }
  .players-strip { max-width: 100vw; overflow-x: auto; }

  /* the rail becomes the bottom bar, and is always on screen */
  .side-dock {
    top: auto; left: 0; right: 0; bottom: 0;
    flex-direction: column; align-items: stretch;
    transform: none;                        /* only the sheet moves now */
  }
  .tool-rail {
    order: 2; flex-direction: row; align-self: stretch; justify-content: space-around;
    max-height: none; gap: 2px; overflow-x: auto; overflow-y: hidden;
    padding: 5px 6px; padding-bottom: calc(5px + env(safe-area-inset-bottom, 0px));
    background: linear-gradient(180deg, rgba(13, 18, 26, .92), var(--panel));
    border-top: 1px solid color-mix(in srgb, var(--accent) 26%, var(--line));
  }
  .tool-btn {
    width: auto; min-width: 60px; flex: 1 1 0; padding: 5px 4px;
    flex-direction: column; gap: 2px;
  }
  /* the desktop rail stands its labels on end to save width; along the
     bottom there is width to spare and no reason to make you tilt your head */
  .tool-btn .tool-name {
    writing-mode: horizontal-tb; text-orientation: mixed;
    font-size: 8.5px; white-space: nowrap;
  }
  .tool-btn .tool-dot { top: 1px; right: 6px; }

  /* and the drawer becomes a sheet over the bottom of the map.  Closed it is
     nothing at all rather than something slid down over the bar: the bar has
     to stay hittable, and a sheet parked on top of it is not out of the way. */
  .panel.side {
    order: 1; width: auto; height: 0; min-height: 0; overflow: hidden;
    /* border-box will not let a box be shorter than its own padding, so the
       padding goes too -- otherwise "closed" is a 34px strip of nothing */
    padding-top: 0; padding-bottom: 0;
    border-left: 0; border-top: 0; border-radius: 15px 15px 0 0;
    transition: height .22s cubic-bezier(.4, 0, .2, 1), padding .22s;
    box-shadow: 0 -20px 44px -22px rgba(0, 0, 0, .9);
  }
  /* `dvh` is 2022 (Chrome 108, Safari 15.4) and an older browser drops the
     whole declaration -- which would leave the sheet at the height: 0 above
     and make the drawer impossible to open.  `vh` first, always. */
  .app.side-open .panel.side {
    height: 56vh; height: 56dvh;
    padding-top: 8px; padding-bottom: 20px;
    border-top: 1px solid var(--line);
  }
  .options-head { padding-top: 10px; }

  /* the two floating buttons sit above the bar rather than under it */
  .phase-ender { right: 8px; bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
  .app.side-open .phase-ender { right: 8px; }
  .phase-ender-btn { padding: 9px 14px; font-size: 13px; }
  .spectator {
    left: 8px; right: 8px; width: auto; transform: none;
    bottom: calc(72px + env(safe-area-inset-bottom, 0px));
  }
  .app.side-open .spectator { left: 8px; }
  .spec-said { font-size: 12px; }
  .spec-where { display: none; }

  /* the board's own furniture, out of the way of a thumb */
  .board-head { padding: 6px 8px; }
  .board-wrap { padding: 4px; padding-bottom: 62px; align-items: safe center; }
  /* the desktop caps the board to its pane so a small skirmish board is not
     stretched.  Here the cap is the thing in the way: held at a size a finger
     can hit, the map is wider than the phone on purpose and pans instead. */
  .board-wrap > .board-svg { max-width: none; max-height: none; }

  /* one card, one screen */
  .inspector {
    width: min(560px, calc(100vw - 20px));
    max-height: 80vh; max-height: 80dvh;
  }
  .ins-head .ins-art { width: 96px; height: 96px; }
  .ins-name { font-size: 18px; }
  .stat-cell-val { font-size: 19px; }
  .art-zoom.big .art-zoom-img { width: min(62vw, 62vh); height: min(62vw, 62vh); }

  /* every floating panel is a sheet at this width -- see placeBeside */
  .roster-pop, .orders-pop, .kw-pop, .tooltip { max-width: calc(100vw - 16px); }
}

/* A phone on its side is about 390 points tall, and the furniture that was
   comfortable in portrait now leaves a letterbox of map.  Everything gets
   shorter, and the sheet stops at half. */
@media (pointer: coarse) and (orientation: landscape), (max-height: 520px) {
  .app { grid-template-rows: 40px minmax(0, 1fr); }
  .players-dock { top: 40px; }
  .app.side-open .panel.side { height: 62vh; height: 62dvh; }
  .tool-rail { padding: 3px 6px; padding-bottom: calc(3px + env(safe-area-inset-bottom, 0px)); }
  .tool-btn { padding: 3px 4px; }
  .tool-btn .tool-name { display: none; }      /* the icon and the count fit */
  .phase-ender { bottom: calc(58px + env(safe-area-inset-bottom, 0px)); }
  .spectator { bottom: calc(54px + env(safe-area-inset-bottom, 0px)); }
  .inspector { max-height: 92vh; max-height: 92dvh; }
  .ins-head .ins-art { width: 76px; height: 76px; }
  .ins-name { font-size: 16px; }
  .stat-cell { padding: 5px 2px; }
  .stat-cell-val { font-size: 16px; }
  .ins-section { margin: 10px 0 5px; }
}

/* A pointer that cannot hover has nothing to reveal on the way past, so the
   things that only appeared on hover are simply always there. */
@media (pointer: coarse) {
  .board-head #board-title, .board-head .tabs { opacity: 1; }
  .tool-btn.idle { opacity: .6; }
  /* a finger is about 9mm: give every small control a target it can land on */
  .tab, .spec-btn, .ghost { min-height: 32px; }
  .spec-btn { min-width: 38px; }
}
