/* P6.5 board — minimal functional layout. */
.board { font-family: system-ui, sans-serif; margin: 1rem; }
/* Compact header: the title and the status strip share one horizontal row. */
.app-header { display: flex; align-items: baseline; flex-wrap: wrap; gap: 1rem; margin: 1rem; }
.app-header h1 { margin: 0; font-size: 1.4rem; }
.status-bar { display: flex; align-items: baseline; flex-wrap: wrap; gap: 1rem; }
.status, .rejection { font-size: 0.85rem; color: #555; }
/* Center the board content (act/agenda, map, investigators) horizontally in the
   center column rather than hugging the left edge. */
.game { display: flex; flex-direction: column; gap: 1rem; align-items: center; }
.investigators { display: flex; flex-wrap: wrap; gap: 1rem; }
/* Full-width so the heading wraps onto its own line ABOVE the panels rather
   than sitting beside them as a flex sibling. */
.investigators h2 { flex-basis: 100%; margin: 0 0 0.25rem; }
.investigator { border: 1px solid #ccc; border-radius: 4px; padding: 0.5rem; display: flex; flex-direction: column; gap: 0.5rem; min-width: 30rem; }
.investigator span { font-size: 0.9rem; }
/* Top row: in-play (left) and threat (right), each boxed for clear separation. */
.inv-zones-top { display: flex; gap: 1rem; align-items: flex-start; }
.inv-zones-top > div { flex: 1; border: 1px solid #e3e3e3; border-radius: 4px; padding: 0.4rem; min-width: 0; }
/* Bottom row: the stat block (left) beside the hand (right). */
.inv-zones-bottom { display: flex; gap: 1rem; align-items: flex-start; }
.investigator-block { flex: 0 0 auto; display: flex; gap: 0.6rem; align-items: flex-start; }
.inv-vitals {
    display: flex; flex-direction: column; gap: 0.1rem;
    font-size: 0.8rem; padding: 0.25rem 0.4rem;
    border: 1px solid #ccc; border-top: none; border-radius: 0 0 6px 6px;
}
.inv-skills { font-weight: bold; letter-spacing: 0.05rem; }
.inv-meta { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.85rem; }
.inv-actions .pip { color: #c8a020; margin-right: 1px; }
.inv-zones-bottom .hand { flex: 1; min-width: 0; }
.card-line { font-family: ui-monospace, monospace; font-size: 0.85rem; }

/* Spatial board map (#497). Nodes are absolutely positioned at computed
   left/top (set inline); the SVG line layer sits behind them. */
.map { position: relative; margin: 1rem 0; }
.map-lines { position: absolute; top: 0; left: 0; z-index: 0; pointer-events: none; }
.map-line { stroke: #999; stroke-width: 2; }
/* The node itself is a transparent, borderless ROW: the location card on the
   left, the token rail on its right (#848). It has no height of its own and
   does not clip — before this, a fixed-height `overflow: hidden` node silently
   ate the tokens of any location whose printed text ran long (the Parlor). */
.map-location {
    position: absolute; z-index: 1; box-sizing: border-box; font-size: 0.8rem;
    display: flex; flex-direction: row; align-items: flex-start; gap: 0.4rem;
}
/* The card is the bordered surface, and the only sized part. Its box comes from
   `map.rs` as `--loc-card-w` / `--loc-card-h` (its `CARD_W` / `CARD_H`), which
   is also where the connection lines anchor — the min-height keeps a short card
   (an unrevealed node is barely a header tall) reaching its own anchor. */
.loc-card {
    flex: none; width: var(--loc-card-w); min-height: var(--loc-card-h); box-sizing: border-box;
    border: 1px solid #333; border-radius: 4px; background: #fff; padding: 0.35rem;
    display: flex; flex-direction: column; gap: 0.2rem;
}
/* Unrevealed locations read as dimmed/face-down WITHOUT element opacity —
   opacity would let the connection lines behind the node bleed through. A muted
   opaque background + greyed text + dashed border keep the card solid. */
.map-location.unrevealed .loc-card { background: #ececec; color: #888; border-style: dashed; }

/* Header: `[shroud] Name ★victory [clues]` on three fixed columns, so both
   numerals sit at the same x in every node and are read by position and colour
   instead of by a label. */
.loc-head {
    display: grid; grid-template-columns: 1.35rem 1fr 1.35rem;
    align-items: center; gap: 0.3rem; margin-bottom: 0.15rem;
}
/* The name yields first when the header is tight; the victory pip is its
   sibling, not its content, so an ellipsis can never swallow the star. */
.loc-title { display: flex; align-items: baseline; min-width: 0; }
.loc-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.badge {
    width: 1.35rem; height: 1.35rem; box-sizing: border-box;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 0.75rem; font-weight: 700; font-variant-numeric: tabular-nums;
    border-radius: 3px; border: 1px solid transparent;
}
/* Shroud — dark slate, hexagonal (the shape of the printed icon). Always left. */
.badge--shroud {
    background: #2b2b3a; color: #fff; border-color: #14141c; border-radius: 0;
    clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
/* Clues — a tan circle, the colour of a clue token. Always right. A zero value
   keeps its slot but fades, so "no clues here" never looks like "no clue slot". */
.badge--clues { background: #d9a441; color: #3a2a08; border-color: #a87b23; border-radius: 999px; }
.badge--clues.is-zero { background: #f0ece4; color: #a9a297; border-color: #ddd6c9; }
/* Unrevealed: the side with no shroud/clue value at all — a dashed placeholder,
   never a numeral. */
.badge--unknown { background: #fff; color: #aaa; border: 1px dashed #bbb; border-radius: 3px; }
.victory-pip { color: #b8860b; font-size: 0.72rem; font-weight: 700; margin-left: 0.25rem; }

/* The satellite rail beside the card. Each occupant is its own bordered box, so
   each can carry its own glow and its own context menu. */
.node-tokens { flex: none; width: 150px; display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.78rem; }
.node-tokens > * { background: #fff; border: 1px solid #ddd; border-radius: 3px; padding: 0.1rem 0.25rem; }
.node-tokens:empty { display: none; }
.inv-token { color: #157a3a; }
.enemy-token { color: #a3261b; }
/* A card put into play at a location, controlled by nobody (Lita Chantler
   01117 in the Parlor). They are in play, but in no investigator's play area. */
.at-location-token { color: #6a4c93; }
/* An encounter card attached to the location itself (Obscuring Fog 01168). */
.attachment-token { color: #8a6a1b; }
/* Both zones are interactive surfaces (#847): an ability granted to the card is
   anchored to its instance, so the token holds the hit-layer and the glow, and
   `position: relative` is what the hit-layer's `inset: 0` resolves against. */
.at-location-token, .attachment-token { position: relative; }
.at-location-token.actionable, .attachment-token.actionable {
    box-shadow: 0 0 0 2px #e0b84c; cursor: pointer;
}
/* Column direction stacks the map above the investigators panel: the map's
   location nodes are absolutely positioned, so a flex *row* would let a shrunk
   map overflow them onto the panel. The map reserves its inline height; the
   panel flows below it. (Children are centered via align-items: center.) */
.board-main { display: flex; flex-direction: column; gap: 1rem; align-items: center; }

/* Event log panel (#505): fixed-width column left of the board, scrolls with
   the newest entry pinned to the bottom. Monospace so raw Debug lines align. */
.layout { display: flex; gap: 1rem; align-items: flex-start; }
/* Right of the event log: the board (the full-width header stays above the whole
   row). min-width:0 lets the column shrink within the flex row instead of
   overflowing. */
.main-column { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1rem; }
.event-log { flex: 0 0 auto; width: 22rem; }
/* Collapsed: shrink to a thin strip (just the expand toggle) so the log frees
   the left side when hidden, instead of leaving a 22rem empty column. */
.event-log.collapsed { width: auto; }
.event-log.collapsed h2 { display: none; }
.event-log h2 { font-size: 1rem; margin: 0 0 0.25rem; }
.event-log-head { display: flex; align-items: baseline; justify-content: space-between; }
.log-toggle { font-size: 0.75rem; }
.hidden { display: none; }
.log-scroll { max-height: 80vh; overflow-y: auto; font-family: ui-monospace, monospace; font-size: 0.8rem; border: 1px solid #ccc; border-radius: 4px; padding: 0.5rem; }
.log-batch { border-top: 1px solid #eee; padding: 0.25rem 0; }
.log-batch:first-child { border-top: none; }
.log-action { font-weight: 600; color: #2a4d69; }
.log-event { white-space: pre-wrap; word-break: break-word; color: #333; }

.turn-tracker { flex: 0 0 auto; width: 18rem; font-size: 0.8rem; }
.turn-tracker h2 { font-size: 1rem; margin: 0 0 0.25rem; }
.tracker-round { font-weight: 600; margin-bottom: 0.4rem; }
.tracker-phase { border-left: 3px solid transparent; padding: 0.15rem 0 0.15rem 0.4rem; margin-bottom: 0.3rem; }
.tracker-phase.current { border-left-color: #2f6fb3; background: #eef4fb; }
.tracker-phase-label { font-weight: 600; }
.tracker-phase ul { list-style: none; padding-left: 0.5rem; margin: 0.1rem 0; }
.tracker-step { color: #444; }
.tracker-window { color: #8a5a00; font-style: italic; }

/* --- Cards (hand slice) ------------------------------------------------ */
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.card {
  width: 12rem;
  border: 2px solid #888;
  border-radius: 6px;
  padding: 0.4rem 0.5rem;
  background: #faf9f6;
  color: #1a1a1a;
  font-size: 0.82rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.card-head {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}
.card-cost {
  font-weight: bold;
  border: 1px solid currentColor;
  border-radius: 50%;
  padding: 0 0.4rem;
}
.card-name { font-weight: bold; }
.card-fast, .card-weakness, .card-exhausted {
  margin-left: auto;
  font-size: 0.7rem;
  text-transform: uppercase;
}
.card-weakness { color: #c0392b; }
.card-exhausted { color: #b06f00; }
.chip--live { background: #e3ecf5; }
.chip--keyword { background: #f3dede; }
.chip--enemy-stat { background: #eef0f2; }
.card-traits { font-style: italic; color: #555; }
.card-text { line-height: 1.3; color: #222; }
.card-footer { display: flex; justify-content: space-between; gap: 0.3rem; flex-wrap: wrap; margin-top: auto; }
.chip {
  display: inline-block;
  padding: 0 0.3rem;
  border: 1px solid #b0b0b0;
  border-radius: 3px;
  background: #eceae5;
  color: #222;
  font-size: 0.72rem;
  font-style: normal;
  font-weight: 600;
  margin: 0 0.1rem;
}
.card-trait-ref { font-style: italic; font-weight: bold; }
.unknown-token { color: #c0392b; font-weight: bold; }

/* Class palette (border colour). */
.card--guardian { border-color: #2f6fb3; }
.card--seeker   { border-color: #c8a020; }
.card--rogue    { border-color: #3a8f4f; }
.card--mystic   { border-color: #7a4fb0; }
.card--survivor { border-color: #c0432f; }
.card--neutral  { border-color: #888; }
.card--mythos   { border-color: #555; }
.card--enemy    { border-color: #a3261b; }
.card--unknown, .card--generic { border-style: dashed; }
.act-agenda { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.5rem; }
/* The act/agenda advance threshold. */
.threshold-stats { display: flex; gap: 0.4rem; align-items: baseline; }
.card--act { border-color: #c8a020; }
.card--agenda { border-color: #7a2f2f; }
/* Advance-flip (#558): the reverse ("1b") face of an advancing act/agenda —
   a warmer tint + doubled border so the flipped side reads as distinct while
   its on-advance effect resolves. `double` needs >=3px to paint two lines. */
.card--reverse { background: #efe7d8; border-style: double; border-width: 4px; }
.card-kind { font-size: 0.7rem; text-transform: uppercase; opacity: 0.7; margin-right: 0.3rem; }
/* Exhausted in-play asset: dimmed + dashed so it reads as unavailable.
   Declared after the class palette so its border-color wins over the class hue. */
.card--exhausted { opacity: 0.6; border-style: dashed; border-color: #aaa; }

/* Interactivity S1/S2 (#536/#537): actionable glow + cursor-anchored menu. */
/* `.map-location` sets `z-index:1` (a stacking context), so its fixed menu is
   confined to that band. The sticky `.action-bar` that used to overpaint it is
   gone (#541), but the `z-index:20` lift stays: the banner (25) and the result
   modal still sit above the board, and removing layout code on a reasoning chain
   is how the map band breaks silently. Cards (`.card.actionable`) set no
   z-index, so their menu escapes to the root context. The lift is keyed on
   `.has-menu` rather than on `.actionable`, because a rail token can be the
   node's only actionable surface and its menu needs the same band (#847). */
.map-location.has-menu { z-index: 20; }
/* Glow and cursor go on the card and its hit-layer, never on the node: the node
   also spans the token rail, and a rail token that shows a pointer while the
   hit-layer doesn't reach it is advertising a click that does nothing. Rail
   tokens become clickable when they carry their own options (#847). */
.map-location.actionable > .loc-card { box-shadow: 0 0 0 2px #e0b84c; }
.map-location.actionable > .loc-card,
.map-location.actionable > .menu-hit { cursor: pointer; }
.card.actionable { position: relative; box-shadow: 0 0 0 2px #e0b84c; cursor: pointer; }
.menu-hit { position: absolute; inset: 0; z-index: 5; }
/* A map node's own hit-layer covers only its card: the token rail beside it
   holds separate surfaces, which carry their own menus. */
.map-location > .menu-hit { right: auto; width: var(--loc-card-w); }
.menu-backdrop { position: fixed; inset: 0; z-index: 15; }
.context-menu {
    position: fixed; z-index: 20;
    display: flex; flex-direction: column; gap: 2px;
    background: #1b1b1b; border: 1px solid #666; padding: 4px;
}
.context-menu .menu-item { display: block; width: 100%; text-align: left; cursor: pointer; }

/* Interactivity S3 (#538): hand-card wrapper — glow (Play) / ring (select). */
.hand-slot { position: relative; }
.hand-slot.actionable { box-shadow: 0 0 0 2px #e0b84c; cursor: pointer; }
.hand-slot.selected { box-shadow: 0 0 0 3px #4a90d9; cursor: pointer; }
.hand-slot.selectable { cursor: pointer; }
/* The multi-select prompt banner is a required decision, so it's styled to be
   hard to miss: a warm amber bar that slides up with a one-shot glow pulse, a
   bold prompt, and a high-contrast primary Confirm. */
@keyframes prompt-banner-in {
    0%   { transform: translateY(100%); opacity: 0; box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.3); }
    60%  { transform: translateY(0); opacity: 1; box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.3); }
    72%  { box-shadow: 0 -4px 26px 4px rgba(232, 162, 61, 0.85); }
    100% { transform: translateY(0); opacity: 1; box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.3); }
}
.prompt-banner {
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 25;
    display: flex; align-items: center; gap: 0.75rem;
    background: #e8a23d; color: #1a1a1a;
    padding: 0.75rem 1rem;
    border-top: 2px solid #b06f00;
    box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.3);
    animation: prompt-banner-in 0.4s ease-out;
}
.prompt-banner .prompt { font-weight: bold; font-size: 1.05rem; margin-right: auto; }
.prompt-banner .provisional-verdict { font-style: italic; }
.prompt-banner .confirm {
    background: #1a1a1a; color: #fff; border: none; border-radius: 4px;
    padding: 0.4rem 1.1rem; font-weight: bold; cursor: pointer;
}
.prompt-banner .pass {
    background: transparent; color: #1a1a1a; border: 1px solid #1a1a1a;
    border-radius: 4px; padding: 0.4rem 0.9rem; cursor: pointer;
}
.prompt-banner .banner-option {
    background: #1a1a1a; color: #fff; border: none; border-radius: 4px;
    padding: 0.4rem 0.9rem; cursor: pointer;
}

/* Interactivity S4 (#539): in-play/threat card wrapper glow. */
.card-slot { position: relative; }
.card-slot.actionable { box-shadow: 0 0 0 2px #e0b84c; cursor: pointer; }

/* Interactivity S6 (#541): the board is the whole input surface. The three named
   controls are always present and `disabled` off-turn, so the panel's shape
   never jumps as prompts arrive; `.actionable` is the same amber glow every
   other live surface uses, and a disabled control deliberately has none. */
.turn-control, .resource-control, .draw-control, .encounter-draw {
    font-size: 0.8rem; padding: 0.15rem 0.5rem; margin-left: 0.4rem;
    border: 1px solid #999; border-radius: 4px; background: #f6f6f6; cursor: pointer;
}
.turn-control { margin-left: 0; margin-top: 0.35rem; align-self: flex-start; }
.turn-control:disabled, .resource-control:disabled,
.draw-control:disabled, .encounter-draw:disabled {
    opacity: 0.45; cursor: default; box-shadow: none;
}
.turn-control.actionable, .resource-control.actionable,
.draw-control.actionable, .encounter-draw.actionable {
    box-shadow: 0 0 0 2px #e0b84c; background: #fff;
}
/* Deck elements: a card back, a remaining count, and the draw affordance. Both
   render at zero — the board must not silently lose an element at the worst
   possible moment — and neither glows unless something anchors to it. */
.player-deck, .encounter-deck {
    display: flex; align-items: center; gap: 0.4rem;
}
.deck-back {
    width: 2rem; height: 2.8rem; border-radius: 3px;
    background: repeating-linear-gradient(45deg, #2a2a4a, #2a2a4a 4px, #3a3a5e 4px, #3a3a5e 8px);
    border: 1px solid #1a1a2e;
}
.deck-count { font-size: 0.8rem; font-variant-numeric: tabular-nums; }
.deck-label { font-size: 0.7rem; text-transform: uppercase; opacity: 0.7; }

/* Pre-game picker: a full-viewport overlay, not a strip sharing a container with
   in-game controls. It only renders at `AwaitingRoster`, so it covers nothing. */
.picker-overlay {
    position: fixed; inset: 0; z-index: 30;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 0.75rem; background: #f4f1ea;
}

/* Skill-test result modal (#478 panel, made modal by #541): centred over an inert
   scrim. The scrim has no click handler on purpose — dismissal submits engine
   input, so only the Confirm button may do it. Both the scrim's background and
   the modal's transform are set inline by drag.rs (#857): the scrim's alpha is a
   function of how far the modal has been dragged rather than a constant, and the
   drag offset rides inside this centring translate via calc, so there is one
   transform rather than two properties fighting. */
.str-backdrop { position: fixed; inset: 0; z-index: 28; }
.skill-test-result {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 29;
    cursor: grab; min-width: 18rem; background: #fff; border: 2px solid #1a1a1a; border-radius: 6px;
    padding: 1rem 1.5rem; text-align: center;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.skill-test-result .str-outcome { font-weight: bold; font-size: 1.1rem; }
.str-confirm {
    background: #1a1a1a; color: #fff; border: none; border-radius: 4px;
    padding: 0.4rem 1.4rem; font-weight: bold; cursor: pointer;
}

/* Decision modal (#856): a choice among alternatives printed on one card
   presents itself the moment it arises. The same scrim-and-card shape as the
   skill-test result modal, and stacked in the same band — the two cannot be up
   at once (a decision is a PickSingle, that pause is a Confirm), so they never
   compete. No click handler on the scrim: every exit is engine input, and
   dragging the modal aside (#857) is not one of them. */
.decision-backdrop { position: fixed; inset: 0; z-index: 28; }
.decision-modal {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 29;
    cursor: grab; min-width: 20rem; max-width: 30rem; background: #fff; border: 2px solid #1a1a1a;
    border-radius: 6px; padding: 1rem 1.5rem; text-align: center;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.decision-modal .decision-source {
    font-weight: bold; font-size: 1.05rem; margin-bottom: 0.35rem;
}
/* The source card's own printed text, so the card the modal covers is
   redundant rather than missed. `.card-text` supplies the symbol chips. */
.decision-modal .decision-printed {
    font-size: 0.85rem; text-align: left; margin-bottom: 0.75rem;
    padding-bottom: 0.6rem; border-bottom: 1px solid #ddd;
}
.decision-modal .decision-prompt { margin: 0 0 0.6rem; font-weight: bold; }
.decision-branches { display: flex; flex-direction: column; gap: 0.4rem; }
.decision-branch {
    background: #1a1a1a; color: #fff; border: none; border-radius: 4px;
    padding: 0.5rem 1rem; font-weight: bold; cursor: pointer; text-align: left;
}
.decision-branch:hover { background: #333; }

/* Version-mismatch overlay (#770): the same scrim-and-card shape as the
   skill-test modal, stacked above every other overlay (including the pre-game
   picker at 30). The state is terminal — the transport has stopped reconnecting
   — so anything it covers is already unreachable, and the board stays visible
   beneath the scrim as evidence of what was happening when the skew hit. No
   click handler on the scrim: there is nothing to dismiss back to. */
.vm-backdrop { position: fixed; inset: 0; z-index: 40; background: rgba(0, 0, 0, 0.6); }
.version-mismatch {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 41;
    max-width: 26rem; background: #fff; border: 2px solid #a11; border-radius: 6px;
    padding: 1rem 1.5rem; text-align: center;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
}
.version-mismatch .vm-title {
    margin: 0 0 0.5rem; font-weight: bold; font-size: 1.1rem;
    text-transform: uppercase; letter-spacing: 0.05em; color: #a11;
}
.version-mismatch .vm-detail { margin: 0 0 0.75rem; }
.vm-reload {
    background: #a11; color: #fff; border: none; border-radius: 4px;
    padding: 0.4rem 1.4rem; font-weight: bold; cursor: pointer;
}
