﻿/* Shared lobby theme (elemental dark-green). Used by the landing, logout, lobby and in-room pages.
   Promoted to a global stylesheet so every page shares the look (scoped .razor.css would not). */

/* ── Theme tokens ─────────────────────────────────────────────────────────────
   Per-spirit skinning hangs off CSS custom properties. These are the DEFAULTS: the
   universal yellow "awaiting input" cue used by every attention blink, pending
   border, and act-here highlight. A spirit theme overrides them on a scoped wrapper
   so its panels can recolour the attention flash to something that still reads
   against the skin (e.g. an amber spirit whose panels are already yellow-ish).
     --flash-color : solid attention colour.
     --flash-rgb   : the same colour as raw "r, g, b" channels, for the
                     rgba(var(--flash-rgb), α) alpha variants. Keep it in sync with
                     --flash-color when overriding a theme. */
:root {
    --flash-color: #ffd36b;
    --flash-rgb: 255, 211, 107;

    /* A faint grayscale grain with elongated VERTICAL streaks (like aged paper / brushed woodgrain), not busy
       white-noise. baseFrequency 'x y' with x≫y stretches the noise into vertical fibres; numOctaves 1 keeps it
       smooth. Layered subtly (multiply on parchment, soft-light on the dark backdrop). Dial via the rect opacity. */
    --paper-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5 0.012' numOctaves='1' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)' opacity='0.28'/%3E%3C/svg%3E");

    /* The in-game panels' texture: same vertical-fibre grain but dialled way back (rect opacity 0.28 → 0.11)
       so the streaks are barely perceptible on the parchment. */
    --paper-texture-subtle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5 0.012' numOctaves='1' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)' opacity='0.11'/%3E%3C/svg%3E");
}

body {
    color: #efe7d6;
    font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
    background: #191410;
}

/* ── Shared hero backdrop ───────────────────────────────────
   Every page — landing, lobby, in-room and the game itself — sits on the same enveloping hero art, so walking
   from the lobby into a game is one continuous surface. The art rides a FIXED ::before layer behind the content
   (cover — no aspect stretching; whatever overflows is cropped) so it can be gently blurred without softening
   the panels/text on top, and a dark wash sits over it (the art is bright cyan) so the parchment panels keep
   contrast. The layer bleeds past the viewport (negative inset) so the blur doesn't reveal a soft edge. */
.landing,
.lobby,
.room,
.game {
    background: #0d0b08;   /* fallback base; the hero art rides the ::before layer below */
    position: relative;
    isolation: isolate;    /* own stacking context so the -1 backdrop paints above this bg but below content */
}

.landing::before,
.lobby::before,
.room::before,
.game::before {
    content: "";
    position: fixed;
    inset: -20px;
    z-index: -1;
    background:
        linear-gradient(rgba(10, 12, 14, 0.42), rgba(10, 12, 14, 0.42)),
        url("art/SpiritIslandHeroArt.jpg") center center / cover no-repeat;
    filter: blur(20px);
    pointer-events: none;
}

/* The lobby pages cover far less of the viewport with panels than the game does, so the art shows through
   almost everywhere and the light on-art chrome (topbar, room code) needs more to sit against: deepen the wash
   and add a vignette that darkens toward the edges. The in-game view keeps the lighter wash above — there the
   panels do the covering. */
.landing::before,
.lobby::before,
.room::before {
    background:
        radial-gradient(125% 105% at 50% 30%, rgba(6, 9, 12, 0.28), rgba(4, 7, 10, 0.78) 100%),
        linear-gradient(rgba(10, 12, 14, 0.42), rgba(10, 12, 14, 0.42)),
        url("art/SpiritIslandHeroArt.jpg") center center / cover no-repeat;
}

.landing {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1.5rem;
    box-sizing: border-box;
}

.hero {
    width: 100%;
    max-width: 34rem;
    text-align: center;
}

.hero-title {
    margin: 0;
    font-size: clamp(2.75rem, 8vw, 4.5rem);
    font-weight: 700;
    letter-spacing: 0.04em;
    line-height: 1.05;
    background: linear-gradient(180deg, #f4f1d0 0%, #d8c49a 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: 0 2px 24px rgba(211, 194, 158, 0.15);
}

.hero-tagline {
    margin: 1.5rem auto 0;
    max-width: 30rem;
    font-size: 1.05rem;
    line-height: 1.6;
    color: #cabb9f;
}

.login,
.account {
    margin-top: 2.75rem;
    max-width: 22rem;
    margin-left: auto;
    margin-right: auto;
}

.account-welcome {
    margin: 0 0 1rem;
    font-size: 1.05rem;
    color: #e2d6bf;
}

.login-tabs {
    display: flex;
    gap: 0.25rem;
    padding: 0.25rem;
    background: rgba(0, 0, 0, 0.25);
    border-radius: 0.6rem;
    margin-bottom: 1.25rem;
}

.login-tab {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.55rem 0.75rem;
    border: none;
    border-radius: 0.45rem;
    background: transparent;
    color: #b3a486;
    font-size: 0.9rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.login-tab.is-active {
    background: rgba(211, 194, 158, 0.16);
    color: #efe7d6;
}

.login-form {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.login-input {
    width: 100%;
    box-sizing: border-box;
    padding: 0.75rem 0.9rem;
    border-radius: 0.55rem;
    border: 1px solid rgba(211, 194, 158, 0.25);
    background: rgba(255, 255, 255, 0.06);
    color: #efe7d6;
    font-size: 0.95rem;
}

.login-input::placeholder { color: #93876f; }

.login-input:focus {
    outline: none;
    border-color: rgba(211, 194, 158, 0.7);
    background: rgba(255, 255, 255, 0.09);
}

.login-input:disabled { opacity: 0.6; }

.login-error {
    margin: 0;
    font-size: 0.85rem;
    color: #ffb4ab;
    text-align: left;
}

.login-divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 1.5rem 0 1rem;
    color: #8a7d64;
    font-size: 0.8rem;
}

.login-divider::before,
.login-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(211, 194, 158, 0.2);
}

.login-buttons {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin: 0 auto;
}

.login-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.8rem 1.25rem;
    border-radius: 0.6rem;
    border: 1px solid transparent;
    font-size: 0.95rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.06s ease, filter 0.15s ease, opacity 0.15s ease;
}

.login-btn:hover:not(:disabled) {
    filter: brightness(1.08);
    transform: translateY(-1px);
}

.login-btn:active:not(:disabled) { transform: translateY(0); }

.login-btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.login-btn--primary {
    background: linear-gradient(180deg, #5fb878 0%, #3f8f5a 100%);
    color: #120d08;
    margin-top: 0.25rem;
}

.login-btn--google { background: #ffffff; color: #1f2937; }
.login-btn--discord { background: #5865f2; color: #ffffff; }

.login-btn--guest {
    background: transparent;
    color: #e2d6bf;
    border-color: rgba(211, 194, 158, 0.4);
}

/* ---------- Lobby ---------- */

.lobby {
    min-height: 100vh;
    box-sizing: border-box;
    padding: 1.5rem clamp(1rem, 5vw, 4rem) 4rem;
}

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    max-width: 60rem;
    margin: 0 auto 2rem;
}

.topbar-brand {
    font-size: 1.4rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: #e6d9bf;
    text-decoration: none;
}

.topbar-user {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    font-size: 0.95rem;
    color: #cabb9f;
}

.topbar-user strong { color: #efe7d6; }

.lobby-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: 1.25rem;
    max-width: 60rem;
    margin: 0 auto;
    align-items: start;
}

/* Panels are the same framed-parchment sheets as the in-game communal panels: warm sandy parchment with dark
   ink, a stark dark-wood frame with an inset parchment hairline (its inner bevel) and a drop shadow lifting the
   sheet off the hero art behind. The parchment is slightly translucent so the art glows through faintly; the
   barely-there vertical grain multiplies over it. Only the metrics differ from in-game — the lobby has room to
   breathe, so the padding stays generous (the .game-layout override tightens it). Panels still read the
   spirit-skin vars where a theme sets them. */
.panel {
    background-color: rgba(233, 206, 156, 0.92);
    background-image: var(--paper-texture-subtle), var(--theme-panel-bg, none);
    background-blend-mode: multiply, normal;
    background-size: 220px 220px, auto;
    color: #190f04;
    border: 2px solid var(--theme-panel-border, #573a1c);
    border-radius: 2px;
    padding: 1.5rem;
    box-shadow: inset 0 0 0 1px rgba(246, 230, 194, 0.35), 0 2px 10px rgba(0, 0, 0, 0.45);
}

.panel h2 {
    margin: 0 0 1rem;
    font-size: 1.1rem;
    color: #120b02;
}

.panel h3 { color: #120b02; }

.panel .muted {
    margin: 0 0 1.25rem;
    color: #54401d;
    font-size: 0.9rem;
    line-height: 1.5;
}

.field-row {
    display: flex;
    gap: 0.6rem;
}

.field-row .login-input { flex: 1; }
.field-row .login-btn { flex: 0 0 auto; }

.room-list {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.room-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.75rem 0.9rem;
    border-radius: 0.55rem;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(211, 194, 158, 0.12);
}

.room-row-code {
    font-weight: 700;
    letter-spacing: 0.12em;
    color: #e6d9bf;
}

.room-row-meta {
    font-size: 0.82rem;
    color: #a89a80;
}

.room-empty {
    color: #93876f;
    font-size: 0.9rem;
    font-style: italic;
}

.badge {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: 1rem;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.badge--host { background: rgba(95, 184, 120, 0.22); color: #aef0c0; }
.badge--you  { background: rgba(120, 170, 255, 0.18); color: #bcd4ff; }
.badge--live { background: rgba(255, 196, 120, 0.18); color: #ffd9a8; }
.badge--off  { background: rgba(255, 120, 120, 0.16); color: #ffb4ab; }

/* ---------- In-room ---------- */

.room {
    min-height: 100vh;
    box-sizing: border-box;
    padding: 1.5rem clamp(1rem, 5vw, 4rem) 4rem;
}

.room-head {
    max-width: 52rem;
    margin: 0 auto 1.5rem;
    text-align: center;
}

.room-code-big {
    font-size: clamp(2.5rem, 7vw, 3.75rem);
    font-weight: 800;
    letter-spacing: 0.35em;
    margin: 0.25rem 0 0;
    color: #f4f1d0;
    text-indent: 0.35em;
}

.room-code-label {
    margin: 0;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    font-size: 0.75rem;
    color: #9c8b72;
}

.room-body {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: 1.25rem;
    max-width: 52rem;
    margin: 0 auto;
    align-items: start;
}

/* On a wide screen the setup column takes the larger share: the roster is a short list, while setup carries the
   spirit-thumbnail grid and wants the width to lay out real columns of tiles. */
@media (min-width: 62rem) {
    .room-body {
        grid-template-columns: minmax(16rem, 1fr) minmax(24rem, 1.55fr);
        max-width: 64rem;
    }
}

.player-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.player {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 0.8rem;
    border-radius: 0.55rem;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(211, 194, 158, 0.12);
}

.player-name { flex: 1; color: #efe7d6; }
.player.is-off .player-name { color: #a89a80; }

/* Per-spirit sub-rows beneath a player in the roster */
.player-spirit {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem;
    margin: 0.2rem 0 0.2rem 1.4rem;
    padding: 0.2rem 0.8rem;
    border-left: 2px solid rgba(211, 194, 158, 0.18);
    font-size: 0.85rem;
}

.player-spirit .spirit-name { color: #e6d9bf; }
.player-spirit .spirit-board { color: #a89a80; }

.seat-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.seat-list li {
    display: flex;
    justify-content: space-between;
    padding: 0.45rem 0.7rem;
    border-radius: 0.5rem;
    background: rgba(255, 255, 255, 0.04);
    font-size: 0.9rem;
}

.seat-list .seat-spirit { color: #e6d9bf; }
.seat-list .seat-board  { color: #a89a80; }

.btn-row {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-top: 1rem;
}

.start-note {
    margin: 0.75rem 0 0;
    font-size: 0.85rem;
    color: #ffd9a8;
}

.in-progress {
    text-align: center;
    color: #aef0c0;
    font-weight: 600;
}

/* ---------- Settings editor ---------- */

.setup-group {
    margin-bottom: 1.4rem;
}

.setup-group h3 {
    margin: 0 0 0.6rem;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: #9c8b72;
}

.check-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.3rem 0;
    color: #e2d6bf;
    font-size: 0.92rem;
}

.check-row input[type="checkbox"] {
    accent-color: #5fb878;
    width: 1.05rem;
    height: 1.05rem;
}

.check-row input:disabled + span,
.check-row.is-readonly {
    color: #a89a80;
}

.seat-edit {
    display: flex;
    align-items: center;
    flex-wrap: wrap;   /* board + spirit + colour controls wrap to a second line rather than overflow */
    gap: 0.5rem;
    padding: 0.5rem 0.7rem;
    border-radius: 0.55rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(211, 194, 158, 0.1);
    margin-bottom: 0.45rem;
}

.seat-edit-name {
    flex: 1;
    color: #efe7d6;
    font-size: 0.9rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mini-select {
    background: rgba(255, 255, 255, 0.06);
    color: #efe7d6;
    border: 1px solid rgba(211, 194, 158, 0.25);
    border-radius: 0.4rem;
    padding: 0.35rem 0.5rem;
    font-size: 0.85rem;
    max-width: 8.5rem;
}

.mini-select:disabled {
    opacity: 0.55;
}

.mini-select option {
    color: #111;
}

.mini-select--color {
    max-width: 6rem;
}

/* ---------- Spirit picker (thumbnail tiles) ----------
   Choosing a spirit is a picture choice, not a list choice: each candidate is a tile wearing the spirit's own
   artwork. The art is blurred hard and darkened so it reads as an atmospheric colour field behind the name
   rather than a fighting-for-attention illustration at thumbnail size — the same treatment the in-game spirit
   board gives its art, and the same reason it rides a separate layer: blurring the tile background would blur
   the name with it. */
.spirit-thumb {
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: 0.1rem;
    padding: 0.35rem 0.4rem;
    text-align: center;
    border-radius: 2px;
    border: 2px solid rgba(87, 58, 28, 0.55);
    background-color: #0d1210;
    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.35);
    user-select: none;
}

/* The art layer bleeds past the tile (negative inset ≥ the blur radius) so blurring never fades the edges in,
   and carries its own scrim + accent wash: without art (an un-skinned spirit) the accent alone fills the tile. */
.spirit-thumb__art {
    position: absolute;
    inset: -14px;
    background-image:
        linear-gradient(180deg, rgba(4, 8, 6, 0.22) 0%, rgba(4, 8, 6, 0.2) 40%, rgba(4, 8, 6, 0.78) 100%),
        var(--thumb-art, linear-gradient(150deg, var(--thumb-accent), #10151a));
    background-size: cover;
    background-position: center 32%;
    background-repeat: no-repeat;
    filter: blur(2.5px) saturate(1.25);
    pointer-events: none;
}

.spirit-thumb__name {
    position: relative;
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.15;
    color: #f4f1e2;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85);
}

.spirit-thumb__note {
    position: relative;
    font-size: 0.68rem;
    letter-spacing: 0.03em;
    color: #e8c9a0;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}

/* Clickable tiles lift on hover; the current pick wears its own accent as the frame so the grid reads at a
   glance even before the name is scanned. */
.spirit-thumb[role="button"] { cursor: pointer; transition: transform 0.08s ease, border-color 0.15s ease; }
.spirit-thumb[role="button"]:hover { transform: translateY(-2px); border-color: var(--thumb-accent); }
.spirit-thumb[role="button"]:focus-visible { outline: 2px solid var(--flash-color); outline-offset: 2px; }

.spirit-thumb.is-selected {
    border-color: var(--thumb-accent);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.28), 0 1px 6px rgba(0, 0, 0, 0.45);
}

/* Taken by another player (or somebody else's seat): visible for reference, but not a choice. */
.spirit-thumb.is-disabled { opacity: 0.45; filter: grayscale(0.5); cursor: default; }

/* The empty "Choose a spirit…" tile has no art to carry it — a dashed frame marks it as the blank slot. */
.spirit-thumb--empty { border-style: dashed; }
.spirit-thumb--empty .spirit-thumb__name { color: #d9cdb4; font-weight: 500; font-style: italic; }

/* Compact form living in the seat row: a wide, short bar showing the current pick. */
.spirit-thumb--chip {
    width: 11rem;
    height: 2.5rem;
    flex: 0 0 auto;
    padding: 0.2rem 0.5rem;
}
.spirit-thumb--chip { justify-content: center; }
.spirit-thumb--chip .spirit-thumb__name { font-size: 0.85rem; }

/* Marks the seat's existing pick inside the chooser, so the grid still says "this is what you have" even while
   you are reading about something else. */
.spirit-thumb--current { border-color: rgba(232, 201, 160, 0.75); }

/* ── Spirit chooser (full-screen) ───────────────────────────────────────────
   A spirit board is a tall document — special rules, growth, innates, two presence tracks and five power cards.
   There is no popover on a lobby page that can hold one and still promise to fit, so choosing takes the window:
   a fixed grid of candidates on the left, and the inspected spirit as a scrolling card on the right. */
.spirit-chooser {
    position: fixed;
    inset: 0;
    z-index: 80;
    display: flex;
    padding: 1.2rem;
    background: rgba(4, 7, 6, 0.82);
    backdrop-filter: blur(3px);
}

.spirit-chooser__frame {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    border-radius: 4px;
    border: 1px solid rgba(87, 58, 28, 0.6);
    background: #100d08;
    box-shadow: 0 18px 60px rgba(0, 0, 0, 0.7);
    overflow: hidden;
}

.spirit-chooser__head {
    display: flex;
    align-items: baseline;
    gap: 0.7rem;
    flex: 0 0 auto;
    padding: 0.6rem 0.9rem;
    border-bottom: 1px solid rgba(87, 58, 28, 0.45);
    background: rgba(87, 58, 28, 0.16);
}
.spirit-chooser__title { margin: 0; font-size: 1.05rem; color: #f4f1e2; }
.spirit-chooser__seat {
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #e8c9a0;
}
.spirit-chooser__close {
    margin-left: auto;
    align-self: center;
    width: 1.9rem;
    height: 1.9rem;
    font-size: 0.95rem;
    color: #f4f1e2;
    background: rgba(10, 14, 12, 0.6);
    border: 1px solid rgba(244, 241, 226, 0.45);
    border-radius: 50%;
    cursor: pointer;
}
.spirit-chooser__close:hover { background: rgba(10, 14, 12, 0.9); }

/* The two panes scroll independently: the tile grid is a short list you sweep, the detail is a long document
   you read. Both own `min-height: 0` so their own scrollbars appear instead of growing the dialog. */
.spirit-chooser__body {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

.spirit-chooser__list {
    flex: 0 0 auto;
    width: 17rem;
    min-height: 0;
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(7.4rem, 1fr));
    align-content: start;
    gap: 0.45rem;
    padding: 0.7rem;
    border-right: 1px solid rgba(87, 58, 28, 0.4);
    background: rgba(87, 58, 28, 0.08);
}
.spirit-chooser__list .spirit-thumb { height: 4.6rem; }

.spirit-chooser__detail {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    padding: 0.7rem 0.9rem 1.2rem;
}

/* Committing is a button, never a side effect of reading: it rides at the top of the detail pane and stays put
   while the board scrolls under it. */
.spirit-chooser__actions {
    position: sticky;
    top: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin: -0.7rem -0.9rem 0.6rem;
    padding: 0.55rem 0.9rem;
    background: #100d08;
    border-bottom: 1px solid rgba(87, 58, 28, 0.45);
}
.spirit-chooser__taken { font-size: 0.85rem; color: #cabb9f; }

/* Subheads outside the parchment (the Unique Powers rail) need the dark theme's ink, not the board's. */
.spirit-chooser .spirit-preview__cards .spirit-board-subhead { color: #e8c9a0; }

.spirit-chooser__empty { padding: 4rem 1rem; text-align: center; }

/* Narrow windows have no room for two panes: the grid becomes a short strip across the top and the detail
   takes what is left. */
@media (max-width: 60rem) {
    .spirit-chooser { padding: 0.5rem; }
    .spirit-chooser__body { flex-direction: column; }
    .spirit-chooser__list {
        width: auto;
        flex: 0 0 auto;
        max-height: 11rem;
        border-right: none;
        border-bottom: 1px solid rgba(87, 58, 28, 0.4);
    }
}

.spirit-preview {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.55rem;
    border-radius: 3px;
    border: 1px solid var(--theme-accent, rgba(87, 58, 28, 0.55));
    background: #14100a;
    box-shadow: 0 10px 34px rgba(0, 0, 0, 0.62);
}

/* Header: a strip of the spirit's own art with its name and box over the top. */
.spirit-preview__head {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-height: 3.4rem;
    padding: 0.4rem 0.6rem;
    overflow: hidden;
    border-radius: 2px;
    border: 1px solid rgba(87, 58, 28, 0.5);
}

.spirit-preview__art {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(90deg, rgba(6, 9, 7, 0.86) 0%, rgba(6, 9, 7, 0.5) 60%, rgba(6, 9, 7, 0.2) 100%),
        var(--preview-art, linear-gradient(120deg, var(--theme-accent, #8a7148), #10151a));
    background-size: cover;
    background-position: center 30%;
    pointer-events: none;
}

.spirit-preview__titles { position: relative; }
.spirit-preview__name { margin: 0; font-size: 1.15rem; color: #f4f1e2; text-shadow: 0 1px 5px rgba(0, 0, 0, 0.9); }
.spirit-preview__sub {
    font-size: 0.72rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #e8c9a0;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
}

.spirit-preview__close {
    position: relative;
    margin-left: auto;
    width: 1.8rem;
    height: 1.8rem;
    font-size: 0.95rem;
    color: #f4f1e2;
    background: rgba(10, 14, 12, 0.6);
    border: 1px solid rgba(244, 241, 226, 0.45);
    border-radius: 50%;
    cursor: pointer;
}
.spirit-preview__close:hover { background: rgba(10, 14, 12, 0.9); }

.spirit-preview__loading { padding: 2rem 0.6rem; text-align: center; }

.spirit-preview__body { display: flex; flex-direction: column; gap: 0.5rem; }

/* Summary of Play beside the power box — the panel's descriptive header, on the same parchment as the board. */
.spirit-preview__profile {
    display: flex;
    flex-direction: row;
    gap: 1rem;
    align-items: flex-start;
    padding: 0.6rem 0.75rem;
    border-radius: 3px;
    background: rgba(233, 219, 187, 0.9);
    border: 1px solid rgba(87, 58, 28, 0.4);
    color: #2a1c08;
}
.spirit-preview__playstyle { flex: 1 1 0; min-width: 14rem; }
.spirit-preview__playstyle p { margin: 0 0 0.4rem; font-size: 0.86rem; line-height: 1.4; }
.spirit-preview__playstyle p:last-child { margin-bottom: 0; }
.spirit-preview__ratings { flex: 0 0 auto; }

/* One axis of the power box: its name and five pips, filled to the score — the printed panel's own shorthand. */
.rating-row { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.18rem; }
.rating-label { flex: 0 0 4.2rem; font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase; color: #6b552e; }
.rating-pips { display: flex; gap: 0.16rem; }
.rating-pip {
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    border: 1px solid rgba(87, 58, 28, 0.55);
    background: transparent;
}
.rating-pip.is-on { background: var(--theme-accent, #8a7148); border-color: rgba(42, 28, 8, 0.7); }

/* Flavour text: the same parchment column as the summary, set quieter and italic so the two read as
   description-then-story rather than two competing blocks of rules. */
.spirit-preview__lore {
    flex: 1 1 0;
    min-width: 14rem;
    color: #57432a;
    font-size: 0.8rem;
    font-style: italic;
    line-height: 1.45;
    border-left: 1px solid rgba(87, 58, 28, 0.28);
    padding-left: 0.9rem;
}
.spirit-preview__lore p { margin: 0 0 0.5rem; }
.spirit-preview__lore p:last-child { margin-bottom: 0; }

.spirit-preview__sub { display: flex; gap: 0.4rem; }
.spirit-preview__dot { opacity: 0.6; }

/* The spirit board's in-game layout is height-locked: it lives in a pane of known height, so its bottom row
   (innates + tracks) is `flex: 1 1 0` and its cells clip. Dropped into a panel that sizes to its content those
   rules collapse the row to nothing, so the preview re-states the board as content-sized and unclipped — the
   row structure is the same, only the height source differs. */
.spirit-preview .panel.spirit-board {
    display: flex;
    flex-direction: row;
    gap: 0.6rem;
    overflow: visible;
}
.spirit-preview .spirit-board-row { flex-wrap: wrap; }
.spirit-preview .spirit-board-row--bottom { flex: 0 0 auto; }
.spirit-preview .spirit-board-innate,
.spirit-preview .spirit-board-tracks,
.spirit-preview .innate-list { min-height: 0; overflow: visible; }
/* Reading a spirit, every section gets the panel's full width and they stack down the page. In game these
   pair up two-per-row to save height, but here height is free (the pane scrolls) and width is not: a spirit
   with several special rules — Volcano Looming High — would otherwise shove the growth options off the side,
   and squeezing the tracks into half a panel collides their labels ("Reclaim One", "Any Element"). */
.spirit-preview .spirit-board-special,
.spirit-preview .spirit-board-growth,
.spirit-preview .spirit-board-innate,
.spirit-preview .spirit-board-tracks { flex: 1 1 100%; min-width: 0; }
/* Special rules stack rather than flow: their cards are prose, and a column reads as a list of rules where a
   wrapped row reads as a ragged grid. */
.spirit-preview .special-list { flex-direction: column; align-items: stretch; }
.spirit-preview .special-rule-card { width: auto; max-width: 44rem; }

/* Filling a pane rather than floating over the room: no drop shadow, no frame of its own, and the profile
   columns wrap instead of forcing the panel wider than the pane. */
.spirit-chooser .spirit-preview {
    padding: 0;
    border: none;
    background: transparent;
    box-shadow: none;
}
.spirit-chooser .spirit-preview__profile { flex-wrap: wrap; }

/* The uniques in one scrolling row — five cards at full size are wider than the panel on most screens. */
.spirit-preview__cardrow {
    display: flex;
    gap: 0.4rem;
    overflow-x: auto;
    padding-bottom: 0.25rem;
}

.seat-block {
    margin-bottom: 0.9rem;
}

.seat-owner {
    font-size: 0.85rem;
    color: #cabb9f;
    margin-bottom: 0.35rem;
}

.seat-remove {
    background: transparent;
    border: 1px solid rgba(255, 120, 120, 0.4);
    color: #ffb4ab;
    border-radius: 0.4rem;
    width: 1.9rem;
    height: 1.9rem;
    cursor: pointer;
    font-size: 1.05rem;
    line-height: 1;
}

.seat-remove:hover {
    background: rgba(255, 120, 120, 0.12);
}

.add-seat {
    background: transparent;
    border: 1px dashed rgba(211, 194, 158, 0.4);
    color: #aef0c0;
    border-radius: 0.5rem;
    padding: 0.4rem 0.75rem;
    font-size: 0.82rem;
    cursor: pointer;
    transition: background 0.15s ease;
}

.add-seat:hover {
    background: rgba(211, 194, 158, 0.08);
}

/* Chrome that sits DIRECTLY on the hero art (topbar, hero title, room code) rather than on a panel: the art is
   busy and locally bright, so a soft dark halo keeps the light text legible wherever it lands. */
.topbar,
.hero,
.room-head,
.login,
.account { text-shadow: 0 1px 3px rgba(0, 0, 0, 0.65); }

.hero-title,
.room-code-big { text-shadow: 0 2px 14px rgba(0, 0, 0, 0.7); }

/* …but not on controls: a halo behind dark label text on a light button reads as a smudge. */
.login-btn,
.login-input,
.login-tab,
.mini-select,
.panel { text-shadow: none; }

/* ---------- Lobby content on parchment ----------
   The lobby panels are now the same light parchment sheets as the in-game communal panels, so everything that
   used to be light-ink-on-dark inside them flips to dark ink on a warm surface. Scoped under .panel so the
   login card (which still sits directly on the dark backdrop) keeps its original look. The palette matches the
   in-game panels: #190f04 ink, #54401d muted, and warm-brown tints instead of white alphas for inset rows. */

.panel .room-row,
.panel .player,
.panel .seat-edit {
    background: rgba(87, 58, 28, 0.09);
    border-color: rgba(87, 58, 28, 0.25);
}

.panel .seat-list li { background: rgba(87, 58, 28, 0.07); }

.panel .room-row-code,
.panel .player-name,
.panel .seat-edit-name,
.panel .player-spirit .spirit-name,
.panel .seat-list .seat-spirit { color: #190f04; }

.panel .room-row-meta,
.panel .room-empty,
.panel .seat-owner,
.panel .player.is-off .player-name,
.panel .player-spirit .spirit-board,
.panel .seat-list .seat-board { color: #6b552e; }

.panel .player-spirit { border-left-color: rgba(87, 58, 28, 0.3); }

.panel .setup-group h3 { color: #6b552e; }

.panel .check-row { color: #2a1c08; }
.panel .check-row input[type="checkbox"] { accent-color: #3f8f5a; }
.panel .check-row input:disabled + span,
.panel .check-row.is-readonly { color: #7a6541; }

/* Form controls: a bright inset well on the parchment (the dark translucent fill vanished on a light sheet). */
.panel .login-input,
.panel .mini-select {
    background: rgba(255, 255, 255, 0.55);
    color: #190f04;
    border-color: rgba(87, 58, 28, 0.45);
}
.panel .login-input::placeholder { color: #8a7148; }
.panel .login-input:focus {
    border-color: rgba(87, 58, 28, 0.85);
    background: rgba(255, 255, 255, 0.75);
}

/* Outline buttons: the pale-cream outline disappeared against parchment — restate in wood/ink. */
.panel .login-btn--guest {
    color: #3a2a12;
    border-color: rgba(87, 58, 28, 0.5);
}
.panel .login-btn--guest:hover:not(:disabled) { background: rgba(87, 58, 28, 0.1); }

.panel .add-seat {
    color: #2f6b45;
    border-color: rgba(87, 58, 28, 0.5);
}
.panel .add-seat:hover { background: rgba(87, 58, 28, 0.1); }

.panel .seat-remove {
    color: #9c2f24;
    border-color: rgba(156, 47, 36, 0.45);
}
.panel .seat-remove:hover { background: rgba(156, 47, 36, 0.12); }

.panel .login-error { color: #9c2f24; }
.panel .start-note { color: #8a5a10; }
.panel .in-progress { color: #2f6b45; }

/* Badges: the light-on-translucent pills washed out on parchment — darken the ink and deepen the tint. */
.panel .badge--host { background: rgba(63, 143, 90, 0.22); color: #1d5433; }
.panel .badge--you  { background: rgba(60, 105, 190, 0.18); color: #21406e; }
.panel .badge--live { background: rgba(190, 130, 40, 0.22); color: #6d4406; }
.panel .badge--off  { background: rgba(180, 60, 50, 0.18); color: #7a2117; }

/* ============================ In-game view ============================ */

/* Backdrop (art + wash) is shared with the lobby pages — see "Shared hero backdrop" above. */
.game {
    min-height: 100vh;
    box-sizing: border-box;
    padding: 1.25rem clamp(1rem, 4vw, 3rem) 3rem;
}

.game-boards {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
    margin: 0 auto 1.5rem;
    max-width: 96rem;
}

.board-wrap {
    width: 62rem;
    /*max-width: 62rem;*/
}

.board-title {
    margin: 0 0 0.5rem;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: #9c8b72;
    text-align: center;
}

.board-svg {
    width: 100%;
    height: auto;
    max-height: 80vh;
    background: rgba(0, 0, 0, 0.25);
    border: 1px solid rgba(58, 38, 20, 0.45);
    border-radius: 2px;
    display: block;
}

.land {
    stroke: #211a13;
    stroke-width: 1.5;
    /* Inert: the outline layer drawn on top of every fill owns all land hit-testing (see BoardSvg). */
    pointer-events: none;
}

/* Pan/zoom island: swallow browser scroll/gesture handling so wheel-zoom and drag-pan reach board-camera.js;
   grab cursor signals the surface is draggable. */
.island-svg { touch-action: none; cursor: grab; }
.island-svg.is-panning { cursor: grabbing; }
/* Holding the point-modifier suspends pan/select for the duration, so say so with the cursor. */
.island-svg.is-pointing { cursor: crosshair; }

/* Shared pointers: another player's cursor, drawn by board-camera.js into the (Blazor-empty) .pointer-layer.
   Inert to the mouse — a teammate waving at a land must never intercept a click on it. The glyph is placed in
   base viewBox space rather than under the camera transform, so it is already zoom-invariant and needs no
   counter-scale here. The dark drop shadow is what keeps a pale skin readable over pale terrain. */
.pointer-layer { pointer-events: none; }
.remote-pointer path { stroke: rgba(20, 14, 8, 0.75); stroke-width: 1.2; paint-order: stroke; }
.remote-pointer-label {
    font: 600 13px/1 'Cinzel', Georgia, serif;
    stroke: rgba(20, 14, 8, 0.8);
    stroke-width: 3;
    paint-order: stroke;
}
/* Aimed outside what this view is looking at: the glyph pins to the edge nearest the real point, dimmed and
   pulsing so it reads as "over there, off your screen" rather than "here". */
.remote-pointer.is-offscreen { opacity: 0.6; animation: remote-pointer-edge 1.1s ease-in-out infinite; }
@keyframes remote-pointer-edge {
    50% { opacity: 0.95; }
}

/* The outline/hit layer: one fill-less copy of every land, above all fills. Invisible at rest; it exists so
   a land's border can never be overpainted by a neighbour drawn later, and so the border may straddle the
   seam into both lands — which is what hides the small gaps and overlaps between the two separately-traced
   copies of a shared edge. pointer-events: all hit-tests the (unpainted) interior, so the whole land stays
   clickable. */
.land-outline {
    fill: none;
    stroke: none;
    pointer-events: all;
    cursor: pointer;
    transition: fill 0.12s ease;
}

/* Hover highlight is pure CSS: it used to be a server-set .is-selected class, which meant a SignalR round
   trip and a full re-render just to outline the land under the cursor. Kept above the targeting rules so a
   valid target's stroke still wins over the hover outline, exactly as it did before. */
.land-outline:hover { fill: rgba(244, 241, 208, 0.14); stroke: #f4f1d0; stroke-width: 3.5; }

.land--jungle   { fill: #3f7a44; }
.land--wetland  { fill: #4f93a8; }
.land--sand     { fill: #cdb46a; }
.land--mountain { fill: #9a948c; }
.land--ocean    { fill: #2f5d8c; }
.land--none     { fill: #6e675c; }

/* Tile-targeting (PickTileQuestion): valid lands turn white & clickable, the rest grey out.
   Two-class selectors outrank the single-class .land--* terrain fills. */
.land.is-target-valid { fill: #f1f4ee; }
.land.is-target-invalid { fill: #4a4640; fill-opacity: 0.7; }
.land-outline.is-target-valid { stroke: var(--flash-color); stroke-width: 3; }
.land-outline.is-target-valid:hover { fill: rgba(255, 255, 255, 0.45); }
.land-outline.is-target-invalid { cursor: default; }

.land-num {
    fill: #161009;
    fill-opacity: 0.65;
    font-weight: 800;
    font-size: 13px;
    pointer-events: none;
}

/* Defense badge (TileDefenseBadge): a steel shield + the land's Defense value, on any defended land. */
.tile-defense__shield {
    fill: #6fa8d4;
    stroke: #12283b;
    stroke-width: 1.3;
    stroke-linejoin: round;
}
.tile-defense__chevron {
    fill: none;
    stroke: #eaf3fb;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.85;
}
.tile-defense__num {
    fill: #eaf3fb;
    stroke: #12283b;
    stroke-width: 0.7;
    paint-order: stroke;
    font-weight: 800;
    font-size: 15px;
}

/* Optional "counts as" chip (TileCountsAsChip): the piece a rule says a land *may* count as holding, drawn
   on a dashed ring because it is not physically there. Dimmed + slashed once the owner declines it. Only the
   owner's chips take pointer events; everyone else's are read-only status. */
.tile-counts-as { pointer-events: none; }
.tile-counts-as.is-mine { pointer-events: auto; cursor: pointer; }
.tile-counts-as__ring {
    fill: rgba(8, 16, 12, 0.45);
    stroke: rgba(240, 236, 225, 0.75);
    stroke-width: 1.2;
    stroke-dasharray: 3 2.5;
}
.tile-counts-as.is-mine:hover .tile-counts-as__ring {
    fill: rgba(20, 34, 26, 0.7);
    stroke: var(--flash-color, #f4e6b8);
    stroke-width: 1.8;
}
.tile-counts-as.is-declined { opacity: 0.45; }
.tile-counts-as__slash {
    stroke: #f0ece1;
    stroke-width: 2.2;
    stroke-linecap: round;
}

/* Land reminder pennant (TileReminderChip): a lasting, land-scoped effect is armed here ("the next time an
   Invader is Destroyed in this land…"). Pure status — pointer-events pass through to the land — and it is
   deliberately unlike a piece (a flag on a staff, not a disc) so nobody mistakes it for something Push can
   move. The flag's fill is the owning spirit's accent, set inline. */
.tile-reminder { pointer-events: none; }
.tile-reminder__staff {
    stroke: #2b2114;
    stroke-width: 1.6;
    stroke-linecap: round;
}
.tile-reminder__flag {
    stroke: #2b2114;
    stroke-width: 1.2;
    stroke-linejoin: round;
}
.tile-reminder__mark {
    fill: #1a140c;
    fill-opacity: 0.8;
    font-weight: 900;
    font-size: 11px;
}

/* Board tokens (BoardTokenGroup). A translucent chip sits behind every glyph so figures read on any
   terrain and give a comfortable click target; it brightens and rings on hover. */
.board-token { cursor: pointer; }
.board-token__chip {
    fill: rgba(8, 16, 12, 0.5);
    stroke: rgba(0, 0, 0, 0.25);
    stroke-width: 0.75;
    transition: fill 0.1s ease;
}
.board-token:hover .board-token__chip {
    fill: rgba(8, 16, 12, 0.82);
    stroke: #f4f1d0;
    stroke-width: 1.4;
}
/* A gather (or other token-pick) highlights its acceptable tokens with a pulsing yellow ring — click to pick. */
@keyframes gather-ring-blink {
    0%, 100% { stroke: rgba(var(--flash-rgb), 0.55); stroke-width: 2; }
    50%      { stroke: var(--flash-color); stroke-width: 3.2; }
}
.board-token__gather-ring {
    fill: none;
    stroke: var(--flash-color);
    animation: gather-ring-blink 1.2s ease-in-out infinite;
}
.board-token.is-gather-target { cursor: pointer; }
.board-token.is-gather-target:hover .board-token__chip { stroke: var(--flash-color); stroke-width: 1.8; }

/* ── Board change overlay (what changed since the previous cursor stop) ──────────
   A calm, NON-blinking visual language, deliberately distinct from the yellow "you have something to do"
   attention pulse. Changed pieces peel off into their own stack next to the untouched one and get a coloured
   border whose COLOUR reads good(green)/bad(red) FOR THE PLAYERS (a dead Invader is good, a new Invader is
   bad), while a "+"/"×" badge says added vs removed. Pieces that left are faded "ghosts" on the tile they
   vacated. So a jump across Ravage/Build/Explore shows the dead and the newly-placed instead of a silent swap. */
.land-outline.is-changed { stroke: #ffd98a; stroke-width: 3; }

.board-token__change-ring { pointer-events: none; }

/* The "+"/"×" change badge in the piece's upper-left. */
.board-token__marker-bg { stroke: rgba(255, 255, 255, 0.92); stroke-width: 1; pointer-events: none; }
.board-token__marker { fill: #fff; font-size: 11px; font-weight: 900; pointer-events: none; }

/* Ghosts: pieces gone from a tile. Faded and non-interactive; the change ring carries the good/bad colour. */
.board-token.is-ghost { pointer-events: none; opacity: 0.4; }
.board-token.is-ghost .board-token__chip { fill: rgba(24, 18, 16, 0.4); stroke: rgba(0, 0, 0, 0.3); }

/* The overlay toggle button; lit when the overlay is on. */
.board-token__sym {
    font-size: 10px;
    font-weight: 800;
    pointer-events: none;
}
.board-token__count-bg {
    fill: #211a13;
    stroke: rgba(255, 255, 255, 0.85);
    stroke-width: 1;
}
.board-token__count {
    fill: #fff;
    font-size: 9px;
    font-weight: 800;
    pointer-events: none;
}

/* Strife badge in the piece's lower-left. Strife sits on an individual Invader rather than the land, so it is
   drawn on the piece; the fill is the Strife token's own blue so it still reads as "that token". */
.board-token__strife-bg {
    stroke: rgba(255, 255, 255, 0.85);
    stroke-width: 1;
    pointer-events: none;
}
.board-token__strife {
    fill: #12202b;
    font-size: 9px;
    font-weight: 900;
    pointer-events: none;
}

.game-side {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 1.25rem;
    max-width: 75rem;
    margin: 0 auto;
    align-items: start;
}

.spirit-card {
    padding: 0.5rem;
    border-radius: 0.5rem;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(211, 194, 158, 0.12);
    margin-bottom: 0.6rem;
}

.spirit-card-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem;
}

.spirit-card-name { color: #efe7d6; font-weight: 600; }
.spirit-card-board { color: #a89a80; font-size: 0.82rem; }

.spirit-card-stats {
    display: flex;
    gap: 1rem;
    margin-top: 0.4rem;
    color: #e6d9bf;
    font-size: 0.9rem;
}

.spirit-card-elements {
    margin-top: 0.35rem;
    font-size: 0.8rem;
    color: #b3a486;
}

.land-detail-name {
    margin: 0 0 0.25rem;
    color: #efe7d6;
    font-weight: 600;
}

.token-list {
    list-style: none;
    margin: 0.5rem 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.token-list li {
    display: flex;
    justify-content: space-between;
    padding: 0.3rem 0.5rem;
    border-radius: 0.45rem;
    background: rgba(255, 255, 255, 0.05);
    font-size: 0.88rem;
    color: #e2d6bf;
}

/* ── Timeline scrubber ─────────────────────────────────────────────── */
.game-main {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}

.timeline {
    background: rgba(22, 14, 8, 0.72);
    border: 2px solid #573a1c;
    border-radius: 2px;
    padding: 0.5rem;
    /* The track is a slider-like drag surface; its chip/label text is never meant to be
       selected. Suppress selection so a drag (even one starting just off the track) never
       highlights timeline text. */
    user-select: none;
    -webkit-user-select: none;
}

/* One row — step-back │ track │ step-forward + Continue — so the bar costs a single row of height.
   The phase title is lifted out of the flow entirely (see .timeline-phase below). */
.timeline { position: relative; }   /* anchor for the floating phase flag */
.timeline-row { display: flex; align-items: center; gap: 0.5rem; }
.timeline-row .timeline-track { flex: 1 1 auto; min-width: 0; }

.timeline-nav { display: flex; align-items: center; gap: 0.45rem; flex: 0 0 auto; }

/* The phase title is a flag pinned above the bar's top centre, overlapping the board view a little — it's
   the "where are we" beacon, so it gets to be bigger without costing the timeline a row. pointer-events off:
   it's informational only and must never block clicks on the board/track beneath its overlap. */
.timeline-phase {
    position: absolute;
    left: 50%;
    top: -1.6rem;
    transform: translateX(-50%);
    z-index: 4;
    pointer-events: none;
    display: flex;
    align-items: baseline;
    gap: 0.55rem;
    padding: 0.25rem 1.1rem;
    background: rgba(22, 14, 8, 0.88);
    border: 2px solid #573a1c;
    border-radius: 2px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
    line-height: 1.15;
    white-space: nowrap;
    /* Plays once each time the flag remounts (TimelineBar bumps its @key on a round/phase change), pulsing to
       draw the eye to time passing. Keyframes keep the translateX(-50%) centring so it doesn't drift. */
    animation: timeline-phase-pulse 0.65s ease-out;
}
@keyframes timeline-phase-pulse {
    0%   { transform: translateX(-50%) scale(1);    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); border-color: #573a1c; }
    28%  { transform: translateX(-50%) scale(1.09); box-shadow: 0 3px 18px rgba(230, 192, 90, 0.6); border-color: #e6c05a; }
    100% { transform: translateX(-50%) scale(1);    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); border-color: #573a1c; }
}
.timeline-phase-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: #9a8d76; }
.timeline-phase-name { font-size: 1.3rem; font-weight: 700; color: #efe7d6; }

.tl-btn {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(211, 194, 158, 0.18);
    color: #e2d6bf;
    border-radius: 0.45rem;
    padding: 0.3rem 0.6rem;
    cursor: pointer;
    font-size: 0.9rem;
}
.tl-btn:hover:not(:disabled) { background: rgba(211, 194, 158, 0.16); }
.tl-btn:disabled { opacity: 0.35; cursor: default; }
.tl-btn--commit { font-weight: 600; color: #161009; background: #aec2a0; border-color: #aec2a0; transition: filter 0.15s ease; }
/* Brighten on hover (matching .login-btn) rather than swapping to a paler tint, which read as a muddy darken.
   Not while disabled — a greyed Continue must read as inert (the :disabled opacity handles the grey-out). */
.tl-btn--commit:not(:disabled):hover { background: #aec2a0; filter: brightness(1.08); }

/* History mode: the cursor has been scrubbed back into committed space; the whole timeline is read-only. */
.timeline.is-history .timeline-phase-label { color: #e6c05a; }

.timeline-track {
    display: flex;
    align-items: stretch;
    /* The live tip sits at the right; sealed history extends left. Anchor to the right so that when the
       capped window is wider than the bar the *recent* end stays visible and the oldest steps clip off the
       left (the track hides its overflow — no horizontal scrollbar, see overflow-x below). On a short
       timeline that fits, the groups simply sit flush right. */
    justify-content: flex-end;
    /* A gap between step groups so the two adjacent-but-distinct cursor stops at a phase boundary —
       end-of-step (s, count) and next-step entry (s+1, 0) — read as two separate snap points rather than
       one seam. Proposals *within* a step stay flush (see .tl-props). */
    gap: 5px;
    overflow-x: hidden;
    overflow-y: hidden;
    border-radius: 0.4rem;
    /* A lane below the cells for the caret triangle to hang in (it points up into the timeline, body below).
       overflow-y:hidden clips at the padding edge, so a triangle within this padding stays visible. */
    padding-bottom: 9px;
    /* The whole track is a drag surface (see timeline-drag.js); chips keep their own pointer cursor. */
    cursor: grab;
}
.timeline-track.is-dragging,
.timeline-track.is-dragging * { cursor: grabbing; }

.timeline-empty {
    color: #8a7d64;
    font-size: 0.82rem;
    font-style: italic;
    padding: 0.4rem 0.2rem;
}

/* One column-group per timing step: header on top, flush row of proposals beneath.
   Each group is self-contained, so the header spans exactly its proposals and the seams line up. */
.tl-group {
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;
    min-width: 0;
    /* No overflow clipping: the active seam's caret triangle hangs below the props row into the track's
       caret lane, so the group must not clip its bottom. The gap between groups marks phase boundaries. */
}

.tl-head, .tl-prop {
    background: transparent;
    border: none;
    color: inherit;
    cursor: pointer;
    font: inherit;
    text-align: center;
    white-space: nowrap;
    padding: 0.22rem 0.5rem;
}

/* Top row — the timing step. Stretches to the full group width (= width of its proposals). */
.tl-head {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
    border-bottom: 1px solid rgba(0, 0, 0, 0.25);
}

/* Bottom row — proposals, sitting flush with one another. */
.tl-props {
    display: flex;
    align-items: stretch;
    gap: 0;
    flex: 1 1 auto;
}
.tl-prop { font-size: 0.8rem; flex-grow: 1.0; }
.tl-prop:hover { filter: brightness(1.18); }
.tl-prop.is-skipped { text-decoration: line-through; opacity: 0.7; }
.tl-prop.is-inactive { opacity: 0.45; }

/* My own locked-in proposal: a soft amber underline marks the ones this seat may reorder. */
.tl-prop.is-mine { box-shadow: inset 0 -2px 0 rgba(255, 211, 107, 0.75); }

/* Reorder wrapper — keeps the ‹ pill › trio behaving as one flush flex cell in the proposal row. */
.tl-prop-wrap {
    display: flex;
    align-items: stretch;
    flex-grow: 1.0;
}
.tl-prop-wrap .tl-prop { flex-grow: 1.0; }

/* Nudge arrows flanking a reorderable proposal. Faint until the row is hovered, then legible. */
.tl-reorder {
    background: transparent;
    border: none;
    color: inherit;
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0 0.2rem;
    opacity: 0.35;
    transition: opacity 0.12s ease;
}
.tl-prop-wrap:hover .tl-reorder { opacity: 0.8; }
.tl-reorder:hover:not(:disabled) { opacity: 1; filter: brightness(1.3); }
.tl-reorder:disabled { opacity: 0.12; cursor: default; }

/* Alternating step tones; proposals get two paired shades of their step's tone. */
.tl-group.tone-a .tl-head     { background: rgba(120, 110, 78, 0.6);  color: #efe7d6; }
.tl-group.tone-a .tl-prop.shade-0 { background: rgba(120, 110, 78, 0.22); color: #e6d9bf; }
.tl-group.tone-a .tl-prop.shade-1 { background: rgba(120, 110, 78, 0.36); color: #e2d6bf; }

.tl-group.tone-b .tl-head     { background: rgba(58, 104, 122, 0.6);  color: #eaf1f3; }
.tl-group.tone-b .tl-prop.shade-0 { background: rgba(58, 104, 122, 0.22); color: #cde4eb; }
.tl-group.tone-b .tl-prop.shade-1 { background: rgba(58, 104, 122, 0.36); color: #dceaef; }

/* Sealed (committed) steps shown in History mode — greyed to read as non-interactive. The greying is
   applied to the head/proposal cells only, so the bright cursor caret between them stays visible. */
.tl-group.is-sealed .tl-head,
.tl-group.is-sealed .tl-prop { filter: grayscale(0.6) brightness(0.72); }
.tl-group.is-sealed .tl-head { font-style: italic; }

/* Seam = one cursor stop, rendered at every position between/around proposals. Normally invisible (a thin
   spacer); the active one is the bright caret and doubles as the drag handle. */
.tl-seam {
    flex: 0 0 auto;
    width: 2px;
    align-self: stretch;
    background: transparent;
    position: relative;
}

/* While dragging the caret, reveal every seam as a faint tick so the player sees the available snap points. */
.timeline-track.is-dragging .tl-seam { background: rgba(255, 211, 107, 0.28); }

/* Active seam — where the cursor currently rests. A thin boundary line through the cells plus a triangle
   caret hanging in the lane below: fat base at the bottom, tip pointing up so it just overlaps the timeline. */
.tl-seam.is-active { background: #ffd36b; }
.tl-seam.is-active::before {
    content: "";
    position: absolute;
    left: 50%;
    top: calc(100% - 2px);          /* tip overlaps the timeline ~2px; the body hangs below in the lane */
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-bottom: 9px solid #ffd36b;   /* upward-pointing triangle: base at bottom, tip at top */
}

/* ── Seat / spirit view switcher ───────────────────────────────────── */
.seat-switch {
    display: flex;
    gap: 0.3rem;
    margin: 0 auto 0 1rem;
}

.seat-tab {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(211, 194, 158, 0.18);
    color: #cdbfa4;
    border-radius: 0.45rem;
    padding: 0.3rem 0.5rem;
    cursor: pointer;
    font-size: 0.85rem;
}
.seat-tab:hover { background: rgba(211, 194, 158, 0.14); }
.seat-tab.is-active {
    background: rgba(211, 194, 158, 0.22);
    color: #efe7d6;
    border-color: rgba(211, 194, 158, 0.5);
    font-weight: 600;
}

.spirit-card.is-viewing {
    border-color: rgba(211, 194, 158, 0.5);
    box-shadow: inset 0 0 0 1px rgba(211, 194, 158, 0.25);
}

/* ── Question (decision) panel ──────────────────────────────────────── */
.q-stack { display: flex; flex-direction: column; gap: 0.4rem; }

/* One proposal's worth of decisions — the card / effect / phase step doing the asking. The header names the
   source so a question is never orphaned from what raised it; ? reveals that source's printed text. */
.q-group { display: flex; flex-direction: column; gap: 0.3rem; }
/* A spawned child (an event of an Event card, a repeated power) is stepped in under the proposal that caused
   it, with a rule down the gutter tying the two together. */
.q-group.is-child { margin-left: 0.65rem; padding-left: 0.5rem; border-left: 1px solid rgba(58, 38, 20, 0.25); }
.q-group-head {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    cursor: pointer;
    padding: 0.1rem 0.1rem 0.05rem;
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: #3a2614;
}
.q-group-head:hover { color: #190f04; }
.q-group-chevron { font-size: 0.7rem; opacity: 0.65; }
/* Wraps rather than ellipsizing: these titles carry the card name AND the event/effect title ("Explorers are
   Reluctant: Invaders Surge Inland"), and a clipped tail loses exactly the half that says what is being asked.
   Capped at three lines so a long one can't push the decisions themselves off screen — three fits the panel's
   width for even the wordiest growth effect, where two still clipped the spirit's name off the end. */
.q-group-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    overflow-wrap: anywhere;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
}
/* The count/✓/? sit on the first line of a wrapped title rather than floating in the middle of the block. */
.q-group-head { align-items: flex-start; }
.q-group-chevron, .q-group-done, .q-group-count, .q-group-info { margin-top: 0.05rem; }
/* Hovering a group also highlights the card behind it elsewhere on screen (GameView's source-highlight rule),
   so give the group itself a matching cue — otherwise the far-away card lights up for no visible reason. */
.q-group:hover > .q-group-head { color: #190f04; }
.q-group:hover > .q-group-head .q-group-title { text-decoration: underline; text-underline-offset: 2px; }
.q-group-done { color: #6f8a5c; font-size: 0.8rem; }
.q-group-count {
    flex: 0 0 auto;
    font-size: 0.7rem;
    font-weight: 500;
    opacity: 0.7;
    padding: 0 0.25rem;
    border-radius: 6px;
    background: rgba(58, 38, 20, 0.12);
}
/* The "what does this actually say?" affordance. Deliberately quiet until asked for. */
.q-group-info {
    flex: 0 0 auto;
    width: 1.05rem;
    height: 1.05rem;
    line-height: 1.05rem;
    text-align: center;
    border-radius: 50%;
    font-size: 0.72rem;
    border: 1px solid rgba(58, 38, 20, 0.35);
    opacity: 0.7;
}
.q-group-info:hover, .q-group-info.is-open { opacity: 1; background: rgba(58, 38, 20, 0.12); }
/* The source's printed rules text, rendered with the usual {Token}/{Element} icons. */
.q-group-detail {
    font-size: 0.76rem;
    line-height: 1.35;
    color: #54401d;
    background: rgba(255, 255, 255, 0.2);
    border-left: 2px solid rgba(58, 38, 20, 0.3);
    padding: 0.35rem 0.5rem;
    margin: 0 0 0.1rem 0.6rem;
}
/* A finished group folds to its header; keep it present but recessive so the open one draws the eye. */
.q-group.is-done .q-group-head { opacity: 0.72; font-weight: 500; }

/* Minimised question card — prompt on top, current answer beneath, ✕ on the right once answered.
   Click the card to open the full modal / re-arm its board flow; click the ✕ to withdraw the answer. */
.q-card {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    text-align: left;
    background: rgba(255, 255, 255, 0.25);
    border: 1px solid rgba(58, 38, 20, 0.35);
    border-left-width: 3px;
    border-radius: 2px;
    padding: 0.45rem 0.6rem;
    cursor: pointer;
    color: #190f04;
}
.q-card:hover { background: rgba(255, 255, 255, 0.4); }
/* Unanswered questions carry the universal yellow to-do blink. */
.q-card.is-pending { border-left-color: var(--flash-color); animation: attention-blink 1.3s ease-in-out infinite; }
.q-card.is-answered { border-left-color: #aec2a0; }
/* Locked in by a reveal — the player has already looked at the deck this answer helped decide, so it can no
   longer be changed. Rendered flat and unclickable rather than removed: the answer still matters, it is simply
   settled, and hiding it would make the timeline look like the decision never happened. */
.q-card.is-frozen {
    border-left-color: #8a7a5c;
    background: rgba(255, 255, 255, 0.12);
    color: #6b5a3c;
    cursor: not-allowed;
    animation: none;
}
.q-card.is-frozen:hover { background: rgba(255, 255, 255, 0.12); }
.q-card.is-frozen .q-card-prompt::after { content: " 🔒"; }

/* Why a swathe of the stack went grey at once. Sits with the questions it explains, above the collapse. */
.q-frozen-notice {
    margin: 0 0 0.4rem;
    font-size: 0.75rem;
    line-height: 1.35;
    color: #6b5a3c;
}

/* The consent gate. Sits above the decision stack and outside the collapse, because it must be answered before
   the player is shown something they cannot un-see. */
.q-lock {
    border: 1px solid rgba(58, 38, 20, 0.45);
    border-left: 3px solid var(--flash-color);
    border-radius: 2px;
    background: rgba(255, 245, 214, 0.75);
    padding: 0.55rem 0.7rem;
    margin-bottom: 0.5rem;
    color: #190f04;
}
.q-lock-title { margin: 0 0 0.2rem; font-size: 0.88rem; line-height: 1.25; font-weight: 600; }
.q-lock-detail { margin: 0 0 0.5rem; font-size: 0.78rem; color: #54401d; }
/* The consequence gate's second line: whose agreement is still missing, or which of your own decisions is
   holding it up. Its own block so it reads as a list rather than running on from the warning above it. */
.q-lock-owed { display: block; margin-top: 0.35rem; font-weight: 600; color: #6b4a12; }
.q-modal--lock .q-lock-owed { color: #e0c98f; }
.q-lock-actions { display: flex; gap: 0.4rem; }
.q-card-main { display: flex; flex-direction: column; gap: 0.15rem; flex: 1 1 auto; min-width: 0; }
.q-card-prompt { font-size: 0.85rem; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.q-card-answer { font-size: 0.78rem; color: #54401d; }
.q-card.is-answered .q-card-answer { color: #aec2a0; }

/* Cancel-answer ✕ — withdraws the explicit answer; effects roll back (presence returns to the track). */
.q-card-cancel {
    flex: 0 0 auto;
    width: 1.3rem;
    height: 1.3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.35rem;
    color: #54401d;
    font-size: 0.78rem;
    line-height: 1;
}
.q-card-cancel:hover { background: rgba(207, 75, 59, 0.25); color: #f3cdc6; }

.q-confirm { width: 100%; margin-top: 0.6rem; }

/* Full-question modal. */
.q-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(6, 14, 10, 0.62);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 50;
}
.q-modal {
    background: #1e1710;
    border: 1px solid rgba(211, 194, 158, 0.28);
    border-radius: 0.5rem;
    padding: 0.5rem;
    max-width: 30rem;
    width: calc(100% - 2rem);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
.q-modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.8rem; }
/* Optional-question Skip sits at the right of the modal header, just before the ✕ close. */
.q-modal-skip { margin-left: auto; flex: 0 0 auto; }
.q-modal-prompt { font-size: 1rem; font-weight: 600; color: #efe7d6; line-height: 1.3; }
.q-modal-close {
    background: none; border: none; color: #a89a80; cursor: pointer;
    font-size: 1.1rem; line-height: 1; padding: 0.1rem 0.3rem;
}
.q-modal-close:hover { color: #efe7d6; }

/* The exposure consent gate as a modal — the same block the panel renders inline (.q-lock), moved onto the dark
   modal surface, so its detail line needs light ink and its buttons the timeline's dark-surface button styles. */
.q-modal--lock { border-left: 3px solid var(--flash-color); padding: 0.7rem 0.85rem; max-width: 26rem; }
.q-modal--lock .q-lock-detail { color: #cdbb95; font-size: 0.85rem; margin-bottom: 0.8rem; }
.q-modal--lock .q-lock-actions { justify-content: flex-end; }

/* A power-card choice modal is wider than the option modal so several cards fit side-by-side. */
.q-modal--cards { max-width: min(72rem, calc(100% - 2rem)); }
.q-cardpick-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: center;
    max-height: 75vh;
    overflow-y: auto;
}
/* Each candidate is the full PowerCardView wrapped in a clickable frame; the selected one is ringed green. */
.q-cardpick {
    cursor: pointer;
    border-radius: 0.55rem;
    border: 2px solid transparent;
    transition: transform 0.12s ease, border-color 0.12s ease;
}
.q-cardpick:hover { transform: translateY(-4px); border-color: rgba(211, 194, 158, 0.45); }
.q-cardpick.is-selected { border-color: #aec2a0; box-shadow: 0 0 0 2px rgba(174, 194, 160, 0.4); }
/* The card inside the picker is display-only — its own (non-declarable) click is a no-op, so the frame's
   click drives the choice. */
.q-cardpick .pcard { cursor: pointer; }

/* Element picker (a "+Any" / cross-player element choice): the allowed elements as clickable icon tiles. */
.q-elementpick-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: center;
    padding: 0.4rem 0;
}
.q-elementpick {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.4rem;
    border-radius: 0.55rem;
    border: 2px solid transparent;
    background: transparent;
    transition: transform 0.12s ease, border-color 0.12s ease;
}
.q-elementpick:hover { transform: translateY(-3px); border-color: rgba(211, 194, 158, 0.45); }
.q-elementpick.is-selected { border-color: #aec2a0; box-shadow: 0 0 0 2px rgba(174, 194, 160, 0.4); }
/* The picker draws the element via the shared {Tag} DSL (a .card-sym SVG) — upsize it from its inline default. */
.q-elementpick .card-sym { width: 44px; height: 44px; }

.q-options { display: flex; flex-direction: column; gap: 0.4rem; }
.q-option {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(211, 194, 158, 0.2);
    color: #e2d6bf;
    border-radius: 0.5rem;
    padding: 0.5rem;
    cursor: pointer;
    text-align: left;
    font-size: 0.9rem;
}
.q-option:hover { background: rgba(211, 194, 158, 0.16); }
.q-option.is-selected {
    background: #aec2a0;
    border-color: #aec2a0;
    color: #161009;
    font-weight: 600;
}

/* Card whose question the board is currently targeting. */
.q-card.is-targeting {
    border-left-color: var(--flash-color);
    box-shadow: inset 0 0 0 1px rgba(var(--flash-rgb), 0.4);
}
.q-card.is-targeting .q-card-answer { color: var(--flash-color); }

/* Banner above the boards while picking a tile. */
.targeting-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: rgba(var(--flash-rgb), 0.12);
    border: 1px solid rgba(var(--flash-rgb), 0.4);
    border-radius: 0.5rem;
    padding: 0.45rem 0.5rem;
    margin-bottom: 0.6rem;
    color: #f0e3c0;
    font-size: 0.9rem;
}

/* Refused-gesture strip: the engine turned a click down (an information lock froze the answer, because
   changing it would rewrite something a player has already been shown). Amber rather than red — nothing broke,
   the click simply cannot do what it looks like it should. Spans the game view above the communal area. */
.game-notice {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: rgba(196, 132, 41, 0.16);
    border: 1px solid rgba(224, 168, 74, 0.55);
    border-left-width: 4px;
    border-radius: 0.5rem;
    padding: 0.5rem 0.7rem;
    margin-bottom: 0.6rem;
    color: #f4e6c6;
    font-size: 0.9rem;
    line-height: 1.35;
}
.game-notice-icon { flex: 0 0 auto; font-size: 1rem; }
.game-notice-text { flex: 1 1 auto; }
.game-notice-close {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: #e0a84a;
    cursor: pointer;
    font-size: 0.95rem;
    line-height: 1;
    padding: 0.15rem 0.3rem;
}
.game-notice-close:hover { color: #f4e6c6; }

/* ── Hand & power cards ────────────────────────────────────────────── */
.hand-count {
    margin-left: 0.4rem;
    font-size: 0.85rem;
    font-weight: 500;
    color: #54401d;
}

/* Cards inside a pile sit in a horizontal row; the pile scrolls vertically if a card is taller than the band. */
.pile-cards {
    display: flex;
    flex-direction: row;
    gap: 0.7rem;
    padding-top: 0.4rem;
    padding-bottom: 0.4rem;
}

/* The card echoes the physical tones: a deep dark-brown border, a woody element strip down the left, and a
   light parchment body. The parchment lives on .pcard-main; .pcard itself is just the bordered, clipped
   frame so the head band and the strip run cleanly into the rounded corners. */
.pcard {
    flex: 0 0 auto;
    width: 13.5rem;
    aspect-ratio: 5 / 7;   /* portrait, matching the physical cards (and the card-back tiles) */
    display: flex;
    flex-direction: column;
    border-radius: 0.5rem;
    background: #f0e6cf;
    border: 2px solid #3a2614;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}

/* Speed tints the head band behind the cost + title: fast = red, slow = blue (matching the physical
   cards). Lives on the head, not the border, so the yellow attention/resolving blinks never hide it. */
.pcard--fast .pcard-head { background: linear-gradient(180deg, #b6473c, #8f3429); }
.pcard--slow .pcard-head { background: linear-gradient(180deg, #3f6aa3, #2d4f80); }

/* Declarable: clickable to toggle play this turn. Declared (an In Play card): a calm green accent — the
   pile move already says "playing", so the accent just marks it toggleable-off. The gold glow is reserved
   for a RESOLVING card (its targeting question is waiting on the player). */
.pcard--declarable {
    cursor: pointer;
    transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.pcard--declarable:hover { transform: translateY(-4px); }

.pcard--declared { border-color: #7f9769; }
.pcard--declared .pcard-main { background: rgba(120, 138, 100, 0.16); }

/* The universal "you have something to do here" cue is a yellow BLINK (same period everywhere). For cards
   it pulses the body tint + glow — never the border, so the declared green and the speed band stay visible.
   Applied to: a resolving card (its targeting question awaits) and a hand card the player could still play
   (plays remaining + affordable). */
@keyframes pcard-attention-blink {
    0%, 100% { border-color: #3a2614; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); }
    50%      { border-color: var(--flash-color); box-shadow: 0 0 14px 3px rgba(var(--flash-rgb), 0.9); }
}
.pcard--resolving,
.pcard--attention { animation: pcard-attention-blink 1.3s ease-in-out infinite; }

/* Per-card targeting flow footer: Choose-target button, or the chosen land + cancel ✕. */
.pcard-targetflow {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: auto;
    padding-top: 0.4rem;
    border-top: 1px solid rgba(58, 38, 20, 0.25);
}
/* Solid amber action button — pops on the light parchment where the old translucent-yellow wash washed out. */
.pcard-targetflow-btn {
    flex: 1 1 auto;
    padding: 0.3rem 0.5rem;
    border-radius: 0.4rem;
    border: 1px solid #8a5212;
    background: linear-gradient(180deg, #e8b13a, #cc8a1e);
    color: #2a1808;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
}
.pcard-targetflow-btn:hover { background: linear-gradient(180deg, #f1bf4d, #d9962a); }
.pcard-targetflow-status {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.pcard-targetflow-status--targeting { color: #a85d10; }
.pcard-targetflow-status--answered { color: #7d8a49; }
.pcard-targetflow-cancel {
    flex: 0 0 auto;
    width: 1.3rem;
    height: 1.3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.35rem;
    border: none;
    background: transparent;
    color: #9a4a3a;
    font-size: 0.78rem;
    line-height: 1;
    cursor: pointer;
}
.pcard-targetflow-cancel:hover { background: rgba(176, 52, 36, 0.18); color: #8a2c1c; }

.hand-warning {
    margin: 0 0 0.5rem;
    padding: 0.4rem 0.6rem;
    border-radius: 0.4rem;
    background: rgba(207, 75, 59, 0.16);
    border: 1px solid rgba(207, 75, 59, 0.5);
    color: #f3cdc6;
    font-size: 0.8rem;
}

/* ── Card pile containers (Hand / In Play / Discard / Forgotten) ───────── */
.card-pile-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.card-pile-head h2 { margin: 0; white-space: nowrap; }
.card-pile--collapsible .card-pile-head { cursor: pointer; user-select: none; }
.card-pile--collapsible .card-pile-head:hover h2 { color: #120b02; }

/* A collapsed pile is a slim strip — trim the side padding so the rotated label sits tight. */
.card-pile.is-collapsed { padding-left: 0.35rem; padding-right: 0.35rem; }

.card-pile-chevron {
    color: #54401d;
    font-size: 0.72rem;
    width: 0.9rem;
    text-align: center;
}

.card-pile-empty { margin: 0.2rem 0 0; }

/* The head band carries the speed colour — a solid woody base (overridden by the speed tint) so the light
   cost + title read against it, spanning the full width above the strip + parchment body. */
.pcard-head {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.45rem 0.55rem;
    background: #46301a;
    border-radius: calc(0.5rem - 2px) calc(0.5rem - 2px) 0 0;
}

.pcard-cost {
    flex: 0 0 auto;
    min-width: 1.5rem;
    height: 1.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: rgba(255, 211, 107, 0.18);
    border: 1px solid rgba(255, 211, 107, 0.45);
    color: #f0e3c0;
    font-weight: 700;
    font-size: 0.85rem;
}

.pcard-name {
    color: #f6efe0;
    font-weight: 600;
    font-size: 0.92rem;
    line-height: 1.15;
}

/* The lower region: woody element strip on the left, parchment body on the right, filling to the bottom. */
.pcard-lower {
    display: flex;
    align-items: stretch;
    flex: 1 1 auto;
    min-height: 0;
}

/* The vertical element column — a woody brown gradient strip running the full height of the lower region,
   the eight markers spaced evenly top to bottom. */
.pcard-elementstrip {
    flex: 0 0 auto;
    width: 1.95rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    padding: 0.45rem 0;
    background: linear-gradient(180deg, #7a5630 0%, #5a3c20 55%, #3f290f 100%);
    border-right: 1px solid #2e1c0d;
    border-bottom-left-radius: calc(0.5rem - 2px);
}
.pcard-elementcell { display: flex; }
/* An element the card does not offer: greyed out and faded back into the strip. */
.pcard-elementcell.is-absent { filter: grayscale(1) opacity(0.28); }

/* The parchment body — black text, holding the meta tags, rules text and thresholds. */
.pcard-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.5rem;
    background: #f0e6cf;
    color: #190f04;
    border-bottom-right-radius: calc(0.5rem - 2px);
}

.pcard-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.pcard-tag {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 0.1rem 0.4rem;
    border-radius: 0.35rem;
    background: rgba(58, 38, 20, 0.12);
    color: #452f10;
}

/* Two-tone element markers (ElementIcon / ElementArt). */
.elem-icon { display: block; }

/* A marker + its count (player's banked elements on the spirit board). */
.elem-chip { display: inline-flex; align-items: center; gap: 0.18rem; }
.elem-chip-count { font-size: 0.82rem; font-weight: 700; color: #d7e0ea; }

/* A count + marker pair in an innate's element threshold. */
.elem-req { display: inline-flex; align-items: center; gap: 0.12rem; }
.elem-req-count { font-size: 0.74rem; font-weight: 700; }

/* Inline {Token}/{Element} symbols rendered into card / innate / fear / event text (CardText / CardSymbols).
   Sized to the surrounding text and nudged onto the baseline so an icon sits naturally mid-sentence. */
.card-sym {
    display: inline-block;
    width: 1.2em;
    height: 1.2em;
    vertical-align: -0.27em;
    margin: 0 0.04em;
}

/* Labelled pills for abstract symbols with no single piece (Fear, Invaders). */
.card-sym-pill {
    display: inline-block;
    padding: 0 0.34em;
    border-radius: 0.55em;
    font-size: 0.82em;
    font-weight: 700;
    line-height: 1.3;
    white-space: nowrap;
    vertical-align: 0.02em;
}
.card-sym-pill--fear { background: #5b3f86; color: #ece3f7; }
.card-sym-pill--invader { background: #5a3030; color: #f3dede; }

/* ── Targeting band (Speed · Range · Target) ───────────────────────────────
   The printed card's three equal columns under the title, shared by power cards and innate powers
   (TargetBandView). A self-contained "printed strip": a slightly darker parchment than the card body, deep
   brown text/arrow (currentColor), and two internal vertical rules — in the same deep brown as the card
   border — bracketing the Range column. Constraint icons carry their own terrain/piece colours; the speed
   label keeps its red/blue tint. The strip reads the same on a card's parchment and an innate's dark panel. */
.target-band {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    align-items: stretch;
    background: #e3d4b2;
    color: #3a2614;
    border-radius: 0.3rem;
    overflow: hidden;
}
.band-col {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    min-height: 1.7rem;
    min-width: 0;
    padding: 0 0.2rem;
}
/* Two internal vertical separators: the Range column's left + right edges (no outer borders). */
.band-col--range {
    border-left: 1px solid #3a2614;
    border-right: 1px solid #3a2614;
}
.band-col--speed {
    justify-content: flex-start;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.band-col--fast { color: #b6473c; }
.band-col--slow { color: #3f6aa3; }

/* The range number sitting on its archer's-arrow underline. */
.range-ind {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    line-height: 1;
}
.range-num { font-size: 0.95rem; font-weight: 700; }
.range-arrow { display: block; width: 2.7rem; height: 0.85rem; margin-top: 0.06rem; }

/* Constraint chips: an icon, or a short uppercase label (e.g. ANY), with a separator between several. */
.band-chip { display: inline-flex; align-items: center; }
.band-chip--captioned { flex-direction: column; line-height: 1; gap: 0.05rem; }
.band-chip-caption { font-size: 0.55rem; font-weight: 700; letter-spacing: 0.05em; opacity: 0.7; }
.band-chip--text { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em; opacity: 0.75; }
.band-sep { font-size: 0.72rem; font-weight: 700; opacity: 0.55; margin: 0 0.04rem; }
.band-sym { display: block; width: 1.3rem; height: 1.3rem; }
.target-band .card-sym { width: 1.3rem; height: 1.3rem; vertical-align: middle; margin: 0; }

/* Card type tag (Minor / Major / Unique) on its own line under the band, right-aligned. */
.pcard-typeline { display: flex; justify-content: flex-end; }

.pcard-body {
    margin-top: 0.1rem;
    padding-top: 0.45rem;
    border-top: 1px solid rgba(58, 38, 20, 0.22);
    font-size: 0.8rem;
    line-height: 1.4;
    color: #0f0a02;
    white-space: pre-wrap;   /* preserve the authored line breaks in BodyMd */
}

/* Element threshold panel — a tinted band under the body, echoing the physical card's threshold box.
   Lights green when the owning spirit currently meets it (mirrors .innate-tier.is-met). */
.pcard-threshold {
    margin-top: 0.4rem;
    padding: 0.3rem 0.45rem;
    border-radius: 0.4rem;
    border-left: 3px solid rgba(58, 38, 20, 0.3);
    background: rgba(58, 38, 20, 0.08);
}
.pcard-threshold.is-met {
    border-left-color: #7d8a49;
    background: rgba(63, 138, 79, 0.16);
}
.pcard-threshold-label {
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #524012;
}
.pcard-threshold.is-met .pcard-threshold-label { color: #7d8a49; }
.pcard-threshold-reqs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin: 0.18rem 0;
    font-weight: 700;
    color: #3a2a18;
}
.pcard-threshold.is-met .pcard-threshold-reqs { color: #7d8a49; }
.pcard-threshold-text {
    font-size: 0.76rem;
    line-height: 1.35;
    color: #0f0a02;
    white-space: pre-wrap;
}

/* ── High-level game layout ────────────────────────────────────────────
   Vertical stack: header (thin) → communal (~60%) → timeline (thin) →
   spirit (~40%). Locked to the viewport height (100dvh) with overflow
   hidden so the game NEVER scrolls the page: the thin rows take their
   content height and the two big areas partition the leftover by their
   6/4 flex ratio. This makes it fit any screen height (1080p ↔ 1440p);
   the areas clip and their inner panels scale/fan/scroll as space tightens
   (board SVG letterboxes, hand fans — see .pile-cards.is-fanned). */
.game-layout {
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.5rem;
}

/* Auto-compact by viewport HEIGHT: the whole UI is rem-based, so nudging the root font-size down on shorter
   screens tightens every padding/card/chip proportionally. A 1440p monitor (usable height ≳ 1200px) keeps the
   roomy 16px default; a 1080p monitor (usable ≈ 940px) drops to 15px so everything fits the reduced height
   without a page scroll; small laptops go smaller still. Keyed on height because the vertical budget is the
   constraint here — width is handled by the flex layout. */
@media (max-height: 1200px) { html { font-size: 15px; } }
@media (max-height: 900px)  { html { font-size: 14px; } }

/* In-game panels are dense — override the roomy default .panel padding/radius (kept generous for the lobby).
   Near-square corners (2px): the in-game look is boxy, like framed parchment sheets. */
.game-layout .panel { padding: 0.5rem; border-radius: 2px; }

/* In-game panels are warm sandy parchment with dark ink — a light, high-contrast "ancient island" surface (the
   lobby keeps its darker look). The frame is the statement: a stark dark-wood border (2px — defined, but no
   wider; layout space is precious) with an inset parchment-light hairline that reads as the frame's inner
   bevel, plus a drop shadow lifting the sheet off the hero art behind. The parchment is slightly translucent
   so the art glows through faintly; the barely-there grain multiplies over it. Dark ink cascades to any panel
   text that just inherits; SVG fills / explicit light colours are flipped where they occur. */
.game-layout .panel {
    background-color: rgba(233, 206, 156, 0.92);
    background-image: var(--paper-texture-subtle);
    background-blend-mode: multiply;
    background-size: 220px 220px;
    color: #190f04;
    border: 2px solid #573a1c;
    box-shadow: inset 0 0 0 1px rgba(246, 230, 194, 0.35), 0 2px 10px rgba(0, 0, 0, 0.45);
}
.game-layout .panel h2,
.game-layout .panel h3 { color: #120b02; }
.game-layout .panel .muted { color: #54401d; }

/* Spirit-owned surfaces — the spirit board + card piles (inside the themed .spirit-area wrapper) and the
   decision panel (skinned to the focused spirit) — drop the communal paper grain entirely. Instead: a smooth,
   vibrant accent gradient over light parchment — two soft accent glows breathing in from opposite corners with
   a gentle diagonal wash between — for an ethereal, spirit-magic look, framed by the accent rather than wood. */
.game-layout .spirit-area .panel,
.game-layout .panel.threshold-panel,
.game-layout .panel.question-panel {
    background-color: rgba(240, 227, 196, 0.90);
    background-image:
        radial-gradient(120% 140% at 10% -20%, rgba(var(--theme-accent-rgb, 120, 110, 78), 0.50), transparent 60%),
        radial-gradient(110% 130% at 95% 115%, rgba(var(--theme-accent-rgb, 120, 110, 78), 0.38), transparent 55%),
        linear-gradient(160deg, rgba(var(--theme-accent-rgb, 120, 110, 78), 0.14), rgba(var(--theme-accent-rgb, 120, 110, 78), 0.03) 50%, rgba(var(--theme-accent-rgb, 120, 110, 78), 0.16));
    background-blend-mode: normal;
    background-size: auto;
    border-color: rgba(var(--theme-accent-rgb, 120, 110, 78), 0.9);
}

/* ── Dark spirit skins ──────────────────────────────────────────────────────
   Opt-in per spirit (SpiritPalette Dark:true → GameView stamps "theme-dark" on the spirit-area + decision panel).
   Instead of the light-parchment accent wash, the spirit's own surfaces become a near-black sheet lit by three
   art-drawn glows — primary/secondary/tertiary accents (--theme-accent-rgb / -accent2-rgb / -accent3-rgb) — with
   light ink. This echoes a spirit whose art is a dark forest rather than washing a single tone to parchment.
   Only the spirit's surfaces flip: the communal parchment panels above and the physical power/innate cards stay
   light and pop against the dark board. */
.game-layout .spirit-area.theme-dark .panel,
.game-layout .panel.question-panel.theme-dark {
    background-color: var(--theme-surface-base, #0a120c);
    background-image:
        radial-gradient(120% 130% at 6% -15%, rgba(var(--theme-accent-rgb, 63, 195, 92), 0.55), transparent 55%),
        radial-gradient(115% 120% at 102% 6%, rgba(var(--theme-accent2-rgb, 42, 110, 168), 0.42), transparent 55%),
        radial-gradient(135% 145% at 55% 125%, rgba(var(--theme-accent3-rgb, 90, 59, 156), 0.45), transparent 60%),
        linear-gradient(160deg, rgba(var(--theme-accent-rgb, 63, 195, 92), 0.10), rgba(0, 0, 0, 0.30) 55%, rgba(var(--theme-accent3-rgb, 90, 59, 156), 0.16));
    background-blend-mode: normal;
    background-size: auto;
    border-color: rgba(var(--theme-accent-rgb, 63, 195, 92), 0.85);
    color: #e9f2e8;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06), 0 2px 12px rgba(0, 0, 0, 0.6);
}
/* Light-ink cascade for the dark surface's own text — headings, subheads, muted notes, section dividers, and the
   glassy special-rule chips (which were dark-ink-on-light). Physical cards inside (power/innate) keep their own
   dark-on-parchment styling and are untouched. */
.game-layout .spirit-area.theme-dark .panel h2,
.game-layout .spirit-area.theme-dark .panel h3,
.game-layout .panel.question-panel.theme-dark h2,
.game-layout .panel.question-panel.theme-dark h3 { color: #f4faf2; }
.game-layout .spirit-area.theme-dark .muted,
.game-layout .panel.question-panel.theme-dark .muted { color: #9db3a4; }
.game-layout .spirit-area.theme-dark .spirit-board-subhead { color: #86d69a; }
.game-layout .spirit-area.theme-dark .spirit-board-left { border-right-color: rgba(255, 255, 255, 0.14); }
.game-layout .spirit-area.theme-dark .spirit-board-row--bottom { border-top-color: rgba(255, 255, 255, 0.14); }
.game-layout .spirit-area.theme-dark .special-rule-chip {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.22);
    color: #e9f2e8;
}
.game-layout .spirit-area.theme-dark .special-rule-chip:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.42);
}
/* Resource pool: the big energy/plays values + their labels were dark ink on parchment — flip to light so they
   read on the dark surface (their faint white-tint cell backgrounds already suit dark). */
.game-layout .spirit-area.theme-dark .res-val { color: #f4faf2; }
.game-layout .spirit-area.theme-dark .res-lbl { color: #9db3a4; }

/* Decision cards on a dark spirit skin. The minimised question chips (.q-card) and their prompt/answer/cancel
   text are dark-ink-on-white for the parchment theme — unreadable on the near-black decision panel. Flip to
   light ink on a subtly lifted translucent surface, matching the panel's own light-ink cascade and the
   already-dark option modal. The is-pending blink and is-answered green accents are hue-driven and stay. */
.game-layout .panel.question-panel.theme-dark .q-card {
    background: rgba(255, 255, 255, 0.07);
    border-color: rgba(255, 255, 255, 0.16);
    color: #eef5ec;
}
.game-layout .panel.question-panel.theme-dark .q-card:hover { background: rgba(255, 255, 255, 0.13); }
.game-layout .panel.question-panel.theme-dark .q-card-answer { color: #9db3a4; }
.game-layout .panel.question-panel.theme-dark .q-card.is-answered .q-card-answer { color: #aec2a0; }
.game-layout .panel.question-panel.theme-dark .q-card-cancel { color: #9db3a4; }
/* Group headers, the causal gutter and the rules-text callout follow the same dark-ink → light-ink flip. */
.game-layout .panel.question-panel.theme-dark .q-group-head { color: #eef5ec; }
.game-layout .panel.question-panel.theme-dark .q-group-head:hover { color: #ffffff; }
.game-layout .panel.question-panel.theme-dark .q-group:hover > .q-group-head { color: #ffffff; }
.game-layout .panel.question-panel.theme-dark .q-group.is-child { border-left-color: rgba(255, 255, 255, 0.18); }
.game-layout .panel.question-panel.theme-dark .q-group-count { background: rgba(255, 255, 255, 0.12); }
.game-layout .panel.question-panel.theme-dark .q-group-info { border-color: rgba(255, 255, 255, 0.3); }
.game-layout .panel.question-panel.theme-dark .q-group-info:hover,
.game-layout .panel.question-panel.theme-dark .q-group-info.is-open { background: rgba(255, 255, 255, 0.14); }
.game-layout .panel.question-panel.theme-dark .q-group-detail {
    color: #cfe0cd;
    background: rgba(255, 255, 255, 0.06);
    border-left-color: rgba(255, 255, 255, 0.22);
}
/* Header furniture: the collapse chevron and the decision-count badge are dark ink; the head-hover darkens the
   title toward black. Flip all three light so the "Decisions" header reads on the dark panel. */
.game-layout .panel.question-panel.theme-dark .card-pile-chevron { color: #9db3a4; }
.game-layout .panel.question-panel.theme-dark .hand-count { color: #9db3a4; }
.game-layout .panel.question-panel.theme-dark.card-pile--collapsible .card-pile-head:hover h2 { color: #ffffff; }

/* A dark spirit may set --theme-surface-art (its official artwork). When present, the MAIN spirit board swaps its
   multi-hue gradient for the art, enveloped as `cover` (centred, no stretch or letterbox) — while the card piles
   and decision panel keep the gradient (this targets only .spirit-board).

   The art rides a ::before layer, not the panel background, so it can be CSS-blurred (--theme-surface-blur) WITHOUT
   blurring the text on top — no pre-blurred image needed, just drop in the raw art. Blur samples transparent pixels
   at the edges and would fade the border, so the layer bleeds past the panel (negative inset) and the panel clips
   it (.spirit-board already has overflow:hidden). ::after lays a scrim + vignette (transparent centre → dark
   edges/bottom) over the art for legibility and to ground the empty lower area. Content sits above both (z-index).
   Without art the var resolves to none and the board is a flat dark sheet. */
.game-layout .spirit-area.theme-dark .panel.spirit-board {
    position: relative;
    background-color: var(--theme-surface-base, #0a120c);
}
.game-layout .spirit-area.theme-dark .panel.spirit-board::before {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--theme-surface-blur, 12px) - 8px);   /* bleed ≥ blur radius so edges stay crisp */
    background-image: var(--theme-surface-art, none);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    filter: blur(var(--theme-surface-blur, 12px)) saturate(1.05);
    z-index: 0;
    pointer-events: none;
}
.game-layout .spirit-area.theme-dark .panel.spirit-board::after {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(125% 100% at 50% 18%, transparent 42%, rgba(3, 8, 5, 0.62) 100%),
        linear-gradient(160deg, rgba(4, 10, 6, var(--theme-art-dim, 0.42)), rgba(4, 10, 6, calc(var(--theme-art-dim, 0.42) + 0.16)));
    z-index: 0;
    pointer-events: none;
}
/* Lift the board's real content (resource column + right stack) above the art + scrim pseudo-layers. */
.game-layout .spirit-area.theme-dark .panel.spirit-board > * { position: relative; z-index: 1; }

/* The in-game title floats ON the board art rather than occupying a header row of its own: the layout is
   height-locked (100dvh, no page scroll), so a whole row spent on a static title is the most expensive space
   in the UI. Anything it covers can be panned/zoomed out from under it. */
.board-brand {
    position: absolute;
    top: 0.4rem;
    left: 0.6rem;
    z-index: 3;
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: #e6d9bf;
    text-decoration: none;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.75);
    opacity: 0.85;
    transition: opacity 0.12s ease;
}
.board-brand:hover { opacity: 1; }

.communal-area,
.spirit-area {
    min-height: 0;   /* partition the leftover height by flex (6/4) — never force overflow / a page scroll */
    display: flex;
    gap: 0.75rem;
    overflow: hidden;
}
/* While an exclusive board operation (tile targeting / gather) is mid-flight, the spirit area dims to signal
   that the one multi-part action on the board has the floor. Its own suppressed highlights (play prompts,
   growth picks) already go quiet via their VM flags; this is the at-a-glance cue. */
.spirit-area.is-operating { opacity: 0.78; filter: saturate(0.85); transition: opacity 0.12s ease; }

/* A spirit the viewer doesn't control: shown for reference (board state, hand, presence at the viewer's own
   scrub position) but with no actions. A subtle desaturation plus a corner badge make "you can look, not act"
   obvious; the interactive highlights are already absent because GameView feeds it empty toggle/target data. */
.spirit-area.is-readonly { filter: saturate(0.8); }
.spirit-area-inner { position: relative; }
.spirit-readonly-badge {
    position: absolute;
    top: 0.25rem;
    right: 0.5rem;
    z-index: 2;
    padding: 0.1rem 0.5rem;
    border-radius: 0.75rem;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    background: rgba(0, 0, 0, 0.45);
    color: #e8e8e8;
    pointer-events: none;
}

/* Give the board ~60% of the vertical space and the player area ~40%. */
.communal-area { flex: 6 1 0; }
.spirit-area   { flex: 4 1 0; }

.timeline-region {
    flex: 0 0 auto;
    /* No overflow clipping here: the track hides its own horizontal overflow, and the phase flag must be able
       to poke up out of this region to overlap the board view above. */
}

/* Tab row sitting on top of the spirit panel. Fixed-height (flex 0 0 auto); since .spirit-area has a
   min-height it doesn't give up space, so this row's height comes out of the communal area above — opening
   it pushes the timeline + board up rather than squeezing the spirit panel. */
.spirit-tabs {
    flex: 0 0 auto;
    display: flex;
    gap: 0.25rem;
    align-items: flex-end;
}
/* Each tab wears its spirit's accent (--tab-accent, set inline by SpiritTabs) with the same ethereal
   gradient-over-parchment treatment as the spirit's panels — so the row reads as a set of differently
   coloured tabs and no swatch dot is needed. Unselected tabs are darkened via filter; the active one is
   full-brightness and merges into the panel below. */
.spirit-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.7rem;
    border: 1px solid color-mix(in srgb, var(--tab-accent, #786e4e) 85%, transparent);
    border-bottom: none;
    border-radius: 2px 2px 0 0;
    background-color: rgba(240, 227, 196, 0.9);
    background-image:
        radial-gradient(140% 170% at 12% -40%, color-mix(in srgb, var(--tab-accent, #786e4e) 75%, transparent), transparent 75%),
        linear-gradient(160deg, color-mix(in srgb, var(--tab-accent, #786e4e) 35%, transparent), color-mix(in srgb, var(--tab-accent, #786e4e) 60%, transparent));
    color: #120b02;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    filter: brightness(0.68) saturate(1.1);
    transition: filter 0.12s ease;
}
.spirit-tab:hover { filter: brightness(0.85) saturate(1.05); }
/* A spirit I control with an open decision I'm not currently looking at — pulses a yellow glow ring to pull me
   over to it. Note: it CANNOT use the shared background-color `attention-blink` here — the tab paints an opaque
   spirit-accent background-image gradient (--tab-accent, above) on top of its background-color, which would
   occlude that pulse entirely. So the cue rides on border-color + box-shadow (which sit above the gradient),
   the same channels the power-card blink uses. The label stays dark ink — yellow text wouldn't read on the
   light tab. */
.spirit-tab.needs-attention {
    animation: spirit-tab-attention-blink 1.3s ease-in-out infinite;
}
@keyframes spirit-tab-attention-blink {
    0%, 100% { border-color: color-mix(in srgb, var(--tab-accent, #786e4e) 85%, transparent); box-shadow: none; }
    50%      { border-color: var(--flash-color); box-shadow: 0 0 12px 3px rgba(var(--flash-rgb), 0.9); }
}
/* The active tab reads as the front sheet: full-brightness, and it visually merges into the panel below it. */
.spirit-tab.is-active {
    filter: none;
    margin-bottom: -1px;
    padding-bottom: calc(0.3rem + 1px);
}
/* Read-only (not mine) tabs are slightly dimmer still, matching the read-only panel treatment. */
.spirit-tab.is-readonly { opacity: 0.9; }
.spirit-tab-label { white-space: nowrap; }
.spirit-tab-eye { font-size: 0.72rem; opacity: 0.75; }

/* A dark-skinned spirit's tab wears the SAME near-black multi-hue gradient as its panels (built from the same
   --theme-accent-rgb / -accent2-rgb / -accent3-rgb over --theme-surface-base, passed inline via the spirit's
   ThemeVars) with a light label — so the tab reads as the front edge of its dark panel rather than a light chip.
   The unselected dim is gentler than the light-tab brightness(0.68), which on near-black would crush the gradient
   to flat black; :not(.is-active) keeps the focused tab full-brightness (its .is-active filter:none still wins). */
.spirit-tab.theme-dark {
    background-color: var(--theme-surface-base, #0a120c);
    background-image:
        radial-gradient(140% 180% at 10% -40%, rgba(var(--theme-accent-rgb, 63, 195, 92), 0.55), transparent 60%),
        radial-gradient(130% 160% at 100% 10%, rgba(var(--theme-accent2-rgb, 42, 110, 168), 0.42), transparent 60%),
        radial-gradient(150% 190% at 60% 150%, rgba(var(--theme-accent3-rgb, 90, 59, 156), 0.45), transparent 65%),
        linear-gradient(160deg, rgba(var(--theme-accent-rgb, 63, 195, 92), 0.10), rgba(0, 0, 0, 0.30) 55%, rgba(var(--theme-accent3-rgb, 90, 59, 156), 0.16));
    border-color: rgba(var(--theme-accent-rgb, 63, 195, 92), 0.75);
    color: #ecf4ec;
}
.spirit-tab.theme-dark:not(.is-active) { filter: brightness(0.78) saturate(1.02); }
.spirit-tab.theme-dark:not(.is-active):hover { filter: brightness(0.94) saturate(1.02); }

/* The board floats directly on the hero art — no parchment sheet behind it (the island svg brings its own
   frame + dark tint, and the art showing through beats parchment letterbox bars around the island). */
.game-layout .panel.board-panel {
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
}

/* Communal: board on the left, the shared island column on the right. */
.board-panel {
    flex: 1 1 62%;
    min-width: 0;
    overflow: hidden;   /* board fits to height (below), so never scroll */
    display: flex;
    flex-direction: column;
    position: relative;   /* anchor for the floating "Fit" camera-reset button */
}

.board-panel .game-boards {
    position: relative;   /* anchor for the floating board title */
    flex: 1 1 auto;
    min-height: 0;      /* let the boards column shrink below content height */
    margin: 0;
    max-width: none;    /* fill however much width the row has (e.g. when the decisions panel collapses) —
                           the 96rem cap is a lobby-era limit that stopped the board stretching on wide screens */
    gap: 1rem;
    justify-content: center;
}

.board-panel .board-wrap {
    width: 100%;
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;   /* centre the (narrower) board once it's height-constrained */
}

.board-panel .board-svg {
    /* Fill the whole panel; preserveAspectRatio="meet" keeps the island centred and letterboxed inside,
       so the extra space becomes pan/zoom room and a bigger interaction surface. */
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
}

/* The island panel's title row: "Island" on the left, the adversary flag pinned to the right. */
.island-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}
.island-head h2 { margin: 0; }

.island-panel {
    flex: 0 0 650px;
    width: 650px;
    max-width: 100%;
    min-width: 0;
    overflow: auto;
}

/* Decisions panel: lives in the communal row, right of the board. It reuses the card-pile collapse classes
   so it folds to a slim full-height strip (with a rotated label) when there are no active questions, and pops
   back to an 18rem column when one arrives. */
.communal-area > .question-panel { margin: 0; }
.communal-area > .question-panel:not(.is-collapsed) { flex: 0 0 18rem; overflow-y: auto; overflow-x: hidden; }
.communal-area > .question-panel.is-collapsed { flex: 0 0 auto; }

/* ── Group threshold test panel ─────────────────────────────────────────────────
   The aggregate view of a live "Aided by <Element>" test: element + target header, one contribution row per
   player, running total, per-player lock-in. A table-wide moment, so it claims its own column beside the
   decision panel; it renders nothing (no column) when no test is live. */
.communal-area > .threshold-panel { margin: 0; flex: 0 0 21rem; overflow-y: auto; overflow-x: hidden; }

.threshold-test { display: flex; flex-direction: column; gap: 0.5rem; }
.threshold-test + .threshold-test { margin-top: 0.9rem; border-top: 1px solid rgba(58, 38, 20, 0.3); padding-top: 0.7rem; }

.threshold-head { display: flex; align-items: center; gap: 0.45rem; }
.threshold-head h2 { flex: 1 1 auto; margin: 0; font-size: 1rem; }
.threshold-total {
    font-size: 1.05rem;
    font-weight: 700;
    padding: 0.1rem 0.5rem;
    border-radius: 0.4rem;
    border: 1px solid rgba(58, 38, 20, 0.35);
}
/* Under target: the total carries the universal yellow to-do blink; met-but-unlocked reads calm green. */
.threshold-test.is-short .threshold-total { color: #7a1f12; animation: attention-blink 1.3s ease-in-out infinite; }
.threshold-test.is-met .threshold-total { background: rgba(174, 194, 160, 0.5); color: #24380f; }
.threshold-test.is-resolved .threshold-total { background: #aec2a0; color: #161009; }

.threshold-status { margin: 0; font-size: 0.78rem; color: #54401d; }

.threshold-row {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    background: rgba(255, 255, 255, 0.25);
    border: 1px solid rgba(58, 38, 20, 0.3);
    border-left-width: 3px;
    border-radius: 2px;
    padding: 0.4rem 0.55rem;
}
.threshold-row.is-mine { border-left-color: var(--theme-accent, #786e4e); background: rgba(255, 255, 255, 0.4); }
.threshold-row-head { display: flex; align-items: baseline; gap: 0.5rem; }
.threshold-player { flex: 1 1 auto; font-weight: 600; font-size: 0.85rem; }
.threshold-row-sum { font-size: 0.85rem; font-weight: 700; color: #24380f; }

.threshold-contribs { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; }
.threshold-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.78rem;
    padding: 0.1rem 0.4rem;
    border-radius: 0.8rem;
    border: 1px solid rgba(58, 38, 20, 0.3);
    background: rgba(255, 255, 255, 0.35);
}
.threshold-step {
    width: 1.15rem;
    height: 1.15rem;
    line-height: 1;
    border-radius: 50%;
    border: 1px solid rgba(58, 38, 20, 0.45);
    background: rgba(255, 255, 255, 0.5);
    cursor: pointer;
    font-size: 0.8rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
.threshold-step:hover:not(:disabled) { background: #fff; }
.threshold-step:disabled { opacity: 0.35; cursor: default; }

/* One pledgeable card: its name plus the Keep / Discard (+2) / Forget (+4) segments. */
.threshold-card {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.78rem;
    padding: 0.1rem 0.3rem 0.1rem 0.45rem;
    border-radius: 0.4rem;
    border: 1px solid rgba(58, 38, 20, 0.3);
    background: rgba(255, 255, 255, 0.3);
    flex-wrap: wrap;
}
.threshold-card-name { font-style: italic; }
.threshold-seg {
    font-size: 0.72rem;
    padding: 0.08rem 0.35rem;
    border-radius: 0.35rem;
    border: 1px solid rgba(58, 38, 20, 0.35);
    background: rgba(255, 255, 255, 0.3);
    cursor: pointer;
}
.threshold-seg:hover { background: rgba(255, 255, 255, 0.6); }
.threshold-seg.is-on { background: #aec2a0; border-color: #7d9469; color: #161009; font-weight: 600; }
.threshold-seg.is-static { cursor: default; }

.threshold-lock {
    align-self: flex-end;
    font-size: 0.78rem;
    padding: 0.18rem 0.6rem;
    border-radius: 0.4rem;
    border: 1px solid rgba(58, 38, 20, 0.45);
    background: rgba(255, 255, 255, 0.4);
    cursor: pointer;
}
.threshold-lock:hover:not(.is-static) { background: #fff; }
.threshold-lock.is-locked { background: #aec2a0; border-color: #7d9469; color: #161009; font-weight: 600; }
.threshold-lock.is-static { cursor: default; color: #54401d; }
/* The met-but-unlocked state is exactly a to-do for whoever hasn't pressed it. */
.threshold-test.is-met .threshold-lock:not(.is-locked) { animation: attention-blink 1.3s ease-in-out infinite; border-color: var(--flash-color); }

.spirit-area-inner {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    gap: 0.75rem;
    overflow: hidden;
}

/* The spirit board grows to fill the left of the player area; the card band (below) sizes to its piles.
   Grid layout: a tall resource column on the left (spanning two rows), growth (short) above the presence
   tracks (tall) on the right, and innates running full-width along the bottom under the title. */
/* Nested flow panels rather than a grid: the spirit's name + resource pool run down a fixed LEFT column, and
   everything else fills the RIGHT as a 2×2 (Special|Growth over Innate|Tracks). Each section sizes to its
   content and the tall bottom row flexes to share the leftover height, so the board shrinks to fit its area
   instead of growing a scrollbar. */
.spirit-area-inner .spirit-board {
    flex: 1 1 0;
    /* Hard floor: a huge hand must never squeeze the board below a usable width. The floor is PROPORTIONAL,
       not a small fixed pixel count — a fixed 30rem meant that on a wide screen the board sat pinned at 30rem
       (unreadable: tracks + innates crushed) while the hand happily spread out to 60rem+ of un-fanned cards.
       The board now claims a majority share of the row and the card band absorbs the squeeze, so the Hand
       starts fanning/overlapping much earlier — see .pile-cards.is-fanned. max() keeps the absolute 30rem
       floor on narrow screens; min(100%) stops the floor itself from overflowing a very narrow row. */
    min-width: min(100%, max(30rem, 58%));
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: row;
    gap: 0.6rem;
}

/* Left: title + resources, tucked together so the title costs no vertical space on the right. */
.spirit-board-left {
    flex: 0 0 13rem;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    border-right: 1px solid rgba(58, 38, 20, 0.25);
    padding-right: 0.6rem;
}
.spirit-board-resources { flex: 0 0 auto; min-width: 0; }

/* Right: everything else, stacked as two rows. The top row sizes to content; the bottom row (innates +
   tracks) takes the remaining height. */
.spirit-board-right {
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.spirit-board-row {
    display: flex;
    flex-direction: row;
    gap: 0.6rem;
    min-width: 0;
}
.spirit-board-row--top    { flex: 0 0 auto; }
.spirit-board-row--bottom { flex: 1 1 0; min-height: 0; border-top: 1px solid rgba(58, 38, 20, 0.25); padding-top: 0.4rem; }

/* In each row the left cell (special / innate) takes only the width it needs and the right cell (growth /
   tracks) absorbs the rest — so the innate cards get the room to sit side by side and the presence track
   fills whatever's left rather than being squashed into a corner. */
.spirit-board-special,
.spirit-board-innate { flex: 0 1 auto; min-width: 0; }
.spirit-board-growth,
.spirit-board-tracks { flex: 1 1 0; min-width: 0; }

/* The bottom row's cells are the tall ones: they soak up the leftover height and clip rather than scroll
   (the track SVG scales to fit; see .track-svg max-height). */
.spirit-board-innate,
.spirit-board-tracks { min-height: 0; overflow: hidden; }

.spirit-board-subhead {
    margin: 0 0 0.4rem;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: #452f10;
}

.growth-options { display: flex; flex-direction: row; flex-wrap: wrap; gap: 0.6rem; }
/* Innates sit side by side (wrapping only if they truly run out of width); the list clips rather than scrolls. */
.innate-list { display: flex; flex-direction: row; flex-wrap: wrap; align-items: flex-start; gap: 0.6rem; min-height: 0; overflow: hidden; }
/* Special-rule chips flow and wrap. */
.special-list { display: flex; flex-direction: row; flex-wrap: wrap; gap: 0.4rem; }

/* A special rule shown as a compact name-only chip; its full text appears as a hover tooltip (title attr),
   so it no longer eats vertical space. */
.special-rule-chip {
    flex: 0 0 auto;
    padding: 0.3rem 0.6rem;
    border-radius: 0.5rem;
    background: rgba(58, 38, 20, 0.06);
    border: 1px solid rgba(58, 38, 20, 0.35);
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: help;
}
.special-rule-chip:hover {
    background: rgba(58, 38, 20, 0.12);
    border-color: rgba(58, 38, 20, 0.6);
}

/* The expanded form of a special rule (SpiritBoard's ExpandSpecialRules): the chip's name becomes a heading with
   the rule's own text beneath it. Same parchment card as an Innate Power, so the two read as one family of
   reference cards — used where there is no hover to spare for the chip's tooltip. */
.special-rule-card {
    flex: 0 1 auto;
    width: 18rem;
    padding: 0.45rem 0.55rem;
    border-radius: 0.55rem;
    background: #f0e6cf;
    color: #190f04;
    border: 2px solid #3a2614;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.special-rule-card__name {
    font-weight: 700;
    font-size: 0.86rem;
    margin-bottom: 0.25rem;
}
.special-rule-card__text { font-size: 0.8rem; line-height: 1.35; }

/* An Innate Power card: a name + current-speed header, then one row per element-gated tier. Fixed width so
   several sit comfortably side by side. Shares the power card's light parchment body + black text and deep
   brown border, but has no woody element strip — the tiers carry the elements instead. */
.innate-power {
    flex: 0 0 auto;
    width: 14rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.45rem 0.55rem;
    border-radius: 0.55rem;
    background: #f0e6cf;
    color: #190f04;
    border: 2px solid #3a2614;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.innate-power-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.4rem;
}
.innate-power-name { font-weight: 700; font-size: 0.86rem; }

/* The innate's targeting band sits directly under the name; nudge it in line with the tiers below. */
.innate-power .target-band { margin: 0.1rem 0; }

.innate-tiers { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
/* A tier on the parchment body, echoing the power card's threshold box (.pcard-threshold). */
.innate-tier {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding: 0.25rem 0.4rem;
    border-radius: 0.4rem;
    border-left: 3px solid rgba(58, 38, 20, 0.3);
    background: rgba(58, 38, 20, 0.08);
}
/* A tier whose element threshold is currently met lights green — the same green that reads on the card's parchment. */
.innate-tier.is-met {
    border-left-color: #7d8a49;
    background: rgba(63, 138, 79, 0.16);
}
.innate-tier-threshold { display: flex; align-items: center; flex-wrap: wrap; gap: 0.3rem; font-size: 0.7rem; font-weight: 700; color: #524012; }
.innate-tier.is-met .innate-tier-threshold { color: #7d8a49; }
/* The "no threshold" em-dash: muted brown on parchment, not the dark-theme sage grey. */
.innate-tier-threshold .muted { color: #8a7350; }
.innate-tier-text { font-size: 0.74rem; line-height: 1.2; color: #0f0a02; }

/* While the power is resolvable this turn it carries the universal yellow attention blink. Pulse the border +
   glow (not the body) — same as the power card — so the parchment and green tiers stay readable underneath. */
.innate-power.is-resolvable { animation: pcard-attention-blink 1.3s ease-in-out infinite; }

/* The innate's target flow: a "Use" button, then the chosen land + an ✕ to withdraw. Sits on the parchment
   body, so it borrows the power card's amber-on-parchment buttons and deep-brown statuses (.pcard-targetflow). */
.innate-targetflow {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.1rem;
    padding-top: 0.4rem;
    border-top: 1px solid rgba(58, 38, 20, 0.25);
}
.innate-targetflow-btn {
    flex: 0 0 auto;
    padding: 0.2rem 0.6rem;
    border-radius: 0.5rem;
    border: 1px solid #8a5212;
    background: linear-gradient(180deg, #e8b13a, #cc8a1e);
    color: #2a1808;
    font-weight: 700;
    font-size: 0.76rem;
    cursor: pointer;
}
.innate-targetflow-btn:hover { background: linear-gradient(180deg, #f1bf4d, #d9962a); }
.innate-targetflow-pass {
    flex: 0 0 auto;
    padding: 0.2rem 0.55rem;
    border-radius: 0.5rem;
    border: 1px solid rgba(58, 38, 20, 0.35);
    background: rgba(58, 38, 20, 0.06);
    color: #452f10;
    font-size: 0.74rem;
    cursor: pointer;
}
.innate-targetflow-pass:hover { background: rgba(58, 38, 20, 0.14); }
.innate-targetflow-status { font-size: 0.76rem; }
.innate-targetflow-status--targeting { color: #a85d10; font-weight: 600; }
.innate-targetflow-status--answered { color: #7d8a49; font-weight: 600; }
.innate-targetflow-status--passed { color: #524012; font-style: italic; }
.innate-targetflow-cancel {
    flex: 0 0 auto;
    border: none;
    background: none;
    color: #9a4a3a;
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0 0.15rem;
}
.innate-targetflow-cancel:hover { color: #8a2c1c; }

/* A growth option card. Default is a neutral tile; when an open growth question exists it highlights yellow
   (declarable, clickable); when picked it turns green. Mirrors the power-card declarable/declared states. */
/* Auto-sizes to its icons (no fixed width, no title) — the effects are the physical-game icon shorthand. */
.growth-option {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    padding: 0.4rem 0.5rem;
    border-radius: 0.5rem;
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(58, 38, 20, 0.35);
}

.growth-effect-icons { display: flex; flex-direction: row; align-items: center; gap: 0.3rem; }
.growth-effect-slot { display: inline-flex; border-radius: 0.35rem; padding: 0.1rem; }
.geff-icon { display: block; width: 2.5rem; height: 2.5rem; }
/* Numbers baked into the glyphs: the "+N" on card/energy and the presence range number are bold. */
.geff-num { font-size: 12px; font-weight: 700; }
.geff-num--beside { font-size: 11px; }

/* Declarable: an open growth question — clickable, highlighted yellow with the universal to-do blink. */
.growth-option--declarable {
    cursor: pointer;
    border-color: var(--flash-color);
    transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
    animation: attention-blink 1.3s ease-in-out infinite;
}
.growth-option--declarable:hover { transform: translateY(-3px); }

/* Declared (picked): green. Overrides the yellow declarable state. */
.growth-option--declared {
    border-color: #aec2a0;
    background: rgba(174, 194, 160, 0.16);
    box-shadow: 0 6px 18px rgba(174, 194, 160, 0.24);
}

/* Deselect ✕ on a picked growth option. Its own target, because the card body is covered by effect icons and
   several of those are themselves clickable once pending. */
.growth-option-cancel {
    flex: 0 0 auto;
    align-self: flex-start;
    margin-left: 0.35rem;
    width: 1.3rem;
    height: 1.3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    border-radius: 0.35rem;
    color: #54401d;
    font-size: 0.78rem;
    line-height: 1;
    cursor: pointer;
}
.growth-option-cancel:hover { background: rgba(207, 75, 59, 0.25); color: #f3cdc6; }

/* A pending effect slot renders as a <button> (clickable) and every other as a <span> (clicks fall through to
   the card, so the option stays selectable by clicking its icons). Both share the slot's box so the row does
   not shift as effects resolve. */
button.growth-effect-slot { border: none; background: transparent; cursor: pointer; }
/* An effect whose resolution is blocked by a rule violation — it applied nothing and asks nothing, so it is
   struck out and the card carries the reason underneath. */
.growth-effect--blocked { opacity: 0.45; filter: grayscale(1); position: relative; }
.growth-effect--blocked::after {
    content: ""; position: absolute; left: 8%; right: 8%; top: 50%;
    border-top: 1.5px solid rgba(150, 40, 30, 0.85); transform: rotate(-12deg);
}
.growth-option-blocked {
    margin-top: 3px; padding: 0 2px;
    font-size: 10px; line-height: 1.25; text-align: center;
    color: rgba(150, 40, 30, 0.95);
}
.spirit-area.theme-dark .growth-option-blocked { color: rgba(255, 160, 145, 0.95); }
.spirit-area.theme-dark .growth-effect--blocked::after { border-top-color: rgba(255, 160, 145, 0.9); }

/* "You have a choice to make here" blink, shared by pending growth effects and playable hand cards. */
@keyframes attention-blink {
    0%, 100% { background-color: rgba(var(--flash-rgb), 0); }
    50%      { background-color: rgba(var(--flash-rgb), 0.22); }
}

/* An effect icon of a picked option whose resolution proposal is still awaiting answers — blinks + rings so
   the player sees which specific icon still needs a decision (answered on the board/track or Decisions panel). */
.growth-effect-slot.growth-effect--pending {
    box-shadow: 0 0 0 2px var(--flash-color);
    animation: attention-blink 1.3s ease-in-out infinite;
}

/* ── Presence track ──────────────────────────────────────────────────────────
   The track SVG: spaces are rings (dashed while still covered by presence, solid once revealed), directed
   edges flow left-to-right, labels sit under each space. The presence disc covers the bonus glyph; hovering
   a covered space fades the disc to peek at what placing it would unlock. */
/* Scales to its container's width; max-height lets it letterbox (preserveAspectRatio meet) down to fit a
   height-constrained tracks column instead of overflowing it. */
.track-svg { display: block; width: 100%; height: auto; max-height: 100%; }

.track-edge { stroke: rgba(90, 70, 40, 0.5); stroke-width: 1.6; fill: none; }

.track-ring { fill: rgba(42, 29, 14, 0.06); stroke: rgba(70, 52, 26, 0.55); stroke-width: 1.3; stroke-dasharray: 3 2.4; }
.track-node--revealed .track-ring { stroke-dasharray: none; }

.track-glyph { font-size: 14px; font-weight: 700; fill: #190f04; }
.track-coin { fill: rgba(242, 193, 78, 0.2); stroke: rgba(242, 193, 78, 0.6); stroke-width: 1.2; }
.track-cardplay { fill: rgba(42, 29, 14, 0.05); stroke: rgba(70, 52, 26, 0.55); stroke-width: 1.2; }
.track-cardplay-fold { fill: none; stroke: rgba(70, 52, 26, 0.55); stroke-width: 1.1; }
/* "+1"/"-2" needs two characters inside a 19px-wide card, so it drops a size and tightens. */
.track-glyph--tight { font-size: 11px; letter-spacing: -0.4px; }
.track-any { fill: none; stroke: rgba(70, 52, 26, 0.6); stroke-width: 1.2; }
.track-elem-name { font-size: 7.5px; font-weight: 700; }
.track-reclaim { font-size: 17px; fill: #190f04; }
.track-label { font-size: 9px; fill: #54401d; }

/* Fill + ring come inline from the shared PresenceDisc (so track presence matches the board tokens);
   this only carries the hover/committed opacity behaviour. */
.track-presence { transition: opacity 0.12s ease; }
.track-node--covered:hover .track-presence { opacity: 0.22; }

/* Placement flow. State shows on the ring so the hover-peek of the disc still works: placeable spaces ring
   yellow (clickable), the armed space (its tile is being picked) rings green, and spaces already claimed by
   an answered placement ring green with their presence half-faded (it's departing). */
.track-node--placeable { cursor: pointer; }
/* Placeable spaces blink — the same universal yellow to-do cue as cards and decision rows. */
@keyframes track-placeable-blink {
    0%, 100% { stroke: rgba(var(--flash-rgb), 0.55); }
    50%      { stroke: var(--flash-color); stroke-width: 3; }
}
.track-node--placeable .track-ring {
    stroke: var(--flash-color);
    stroke-width: 2.2;
    stroke-dasharray: none;
    animation: track-placeable-blink 1.3s ease-in-out infinite;
}
.track-node--armed .track-ring { stroke: #aec2a0; stroke-width: 2.4; stroke-dasharray: none; }

/* "Which placement is this presence for?" — shown under the track when a clicked space could serve more than
   one pending placement (two growth options each adding a Presence under a different constraint). Sits inline
   rather than as a modal: the track space it belongs to is right above it, still ringed green. */
.track-placement-prompt {
    display: flex; flex-direction: column; gap: 4px;
    margin: 6px 0 2px; padding: 7px 8px;
    border: 1px solid rgba(70, 52, 26, 0.35); border-radius: 6px;
    background: rgba(242, 233, 214, 0.55);
}
.track-placement-prompt__title { font-size: 11px; font-weight: 700; opacity: 0.75; }
.track-placement-prompt__option {
    text-align: left; font-size: 12px; line-height: 1.25;
    padding: 5px 7px; cursor: pointer;
    border: 1px solid rgba(70, 52, 26, 0.4); border-radius: 5px;
    background: rgba(255, 255, 255, 0.5); color: inherit;
}
.track-placement-prompt__option:hover { background: var(--flash-color); border-color: var(--flash-color); }
.track-placement-prompt__cancel {
    align-self: flex-start; font-size: 11px; padding: 2px 4px;
    border: none; background: transparent; color: inherit; opacity: 0.6; cursor: pointer;
}
.track-placement-prompt__cancel:hover { opacity: 1; text-decoration: underline; }
.spirit-area.theme-dark .track-placement-prompt {
    background: rgba(255, 255, 255, 0.07); border-color: rgba(228, 240, 230, 0.35);
}
.spirit-area.theme-dark .track-placement-prompt__option {
    background: rgba(255, 255, 255, 0.08); border-color: rgba(228, 240, 230, 0.4);
}
.track-node--committed .track-ring { stroke: #aec2a0; stroke-width: 1.6; stroke-dasharray: none; }
.track-node--committed .track-presence { opacity: 0.45; }

/* A revealed "+Any" slot owed an income element choice — clickable, and blinks with the same universal
   to-do cue as placeable spaces so the player knows a decision is owed on the track itself. */
.track-node--element-choice { cursor: pointer; }
.track-node--element-choice .track-ring {
    stroke: var(--flash-color);
    stroke-width: 2.2;
    stroke-dasharray: none;
    animation: track-placeable-blink 1.3s ease-in-out infinite;
}

/* …and once it has been answered: no longer owed, so no blink, but still live — clicking it withdraws the
   choice and re-asks, so a "+Any" slot is never a one-way door. */
.track-node--element-chosen { cursor: pointer; }
.track-node--element-chosen .track-ring {
    stroke: #aec2a0;
    stroke-width: 1.8;
    stroke-dasharray: none;
}

/* ── Presence track on a DARK spirit skin ───────────────────────────────────
   The track SVG is authored in dark ink (brown strokes, near-black labels/glyphs) for the light parchment board;
   on a dark art-backed board all of it vanishes. Re-ink the whole track light: bright labels + glyphs carry a
   dark halo (paint-order: stroke, so the outline sits *behind* the fill) so they pop over the variable blurred
   art, and the dashed rings, edges and arrowheads become light strokes. Element icons and the presence disc
   already bring their own vivid colour, so they're left alone. Placeable/armed/committed ring states already use
   the light flash colour / pale green and need no change. */
.spirit-area.theme-dark { --track-arrow-fill: rgba(228, 240, 230, 0.8); }
.spirit-area.theme-dark .track-edge { stroke: rgba(228, 240, 230, 0.5); }
.spirit-area.theme-dark .track-ring { fill: rgba(255, 255, 255, 0.05); stroke: rgba(228, 240, 230, 0.62); }
.spirit-area.theme-dark .track-cardplay { fill: rgba(255, 255, 255, 0.06); stroke: rgba(228, 240, 230, 0.6); }
.spirit-area.theme-dark .track-cardplay-fold { stroke: rgba(228, 240, 230, 0.6); }
.spirit-area.theme-dark .track-coin { stroke: rgba(242, 205, 120, 0.85); }
.spirit-area.theme-dark .track-glyph,
.spirit-area.theme-dark .track-reclaim,
.spirit-area.theme-dark .track-elem-name {
    fill: #f6fbf5;
    paint-order: stroke;
    stroke: rgba(2, 6, 3, 0.7);
    stroke-width: 2.2px;
    stroke-linejoin: round;
}
.spirit-area.theme-dark .track-label {
    fill: #ecf4ec;
    paint-order: stroke;
    stroke: rgba(2, 6, 3, 0.72);
    stroke-width: 2.4px;
    stroke-linejoin: round;
}

/* The Minor/Major picker inside the decision modal: the same deck tiles the island renders, so the choice
   looks the same wherever it is asked from. */
.q-deckpick-row { display: flex; flex-direction: row; gap: 0.6rem; justify-content: center; padding: 0.4rem 0; }
.q-deckpick-row .power-deck.is-selected { border-color: #aec2a0; box-shadow: 0 6px 18px rgba(174, 194, 160, 0.24); }

/* Generic dashed placeholder tile, used until growth / innate / special-rule renders exist. */
.placeholder-card {
    flex: 0 0 auto;
    width: 6rem;
    min-height: 4.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0.4rem;
    border-radius: 0.5rem;
    background: rgba(255, 255, 255, 0.03);
    border: 1px dashed rgba(211, 194, 158, 0.18);
    font-size: 0.78rem;
}

/* One horizontal band: In Play · Hand · Discard · Forgotten, side by side, all the same height. Open piles
   size to their card row; collapsed piles shrink to a thin vertical strip. The band scrolls horizontally only
   if the open piles together outrun the width (rare — declaring a card just moves it Hand → In Play, so the
   total card count, and thus the total width, is unchanged). */
.spirit-area-inner .card-piles {
    flex: 0 1 auto;   /* size to the piles; the spirit board takes the rest of the row */
    min-width: 0;
    display: flex;
    flex-direction: row;
    align-items: stretch;
    gap: 0.6rem;
    overflow-x: auto;
    overflow-y: hidden;
}

.card-pile {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

/* Open pile: width follows its cards (don't grow/shrink); the card row fills the remaining height. */
.card-pile:not(.is-collapsed) { flex: 0 0 auto; }

/* A fanned pile (every open pile — Hand, In Play, Discard, Forgotten) is allowed to SHRINK below its content
   width. Without this it sits at flex:0 0 auto (content width), which (a) pushes the spirit board down to its
   30rem floor and (b) hands cardfan.js a band whose clientWidth already equals the full card row, so the fan
   measures "everything fits" and never overlaps. Letting it shrink (min-width:0) gives the band a bounded
   width to measure, so the cards overlap as the pile grows; the board keeps its floor because its flex-basis 0
   gives it no shrink weight, so the piles absorb the squeeze (proportionally to their card counts). */
.card-pile--fan:not(.is-collapsed) { flex: 0 1 auto; min-width: 0; }

.card-pile .pile-cards {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}

/* Fanned pile (the Hand): cards advance by a JS-measured step instead of the fixed gap, so they sit shoulder
   to shoulder with NO overlap while they fit and only start overlapping once the band runs out of room. The
   step is set on --card-step by cardfan.js (see CardPileView): it equals the natural gap when everything fits
   and goes negative to overlap as the hand grows, clamped so a readable spine of each card always shows. Only
   at the extreme (spine clamp reached) does the band itself scroll. Later cards paint over earlier ones, so the
   visible spine is each card's left edge (cost + name); hovering lifts a card to the front to read it in full. */
.card-pile .pile-cards.is-fanned {
    gap: 0;
    overflow-x: auto;
}
.card-pile .pile-cards.is-fanned .pcard {
    position: relative;   /* ensure z-index on hover lifts the card above its neighbours */
}
.card-pile .pile-cards.is-fanned .pcard + .pcard {
    margin-left: var(--card-step, 0px);
}
.card-pile .pile-cards.is-fanned .pcard:hover {
    z-index: 5;
}

/* Collapsed pile: a slim full-height strip with a vertical (rotated) label. Clicking it expands the pile. */
.card-pile.is-collapsed { flex: 0 0 auto; }
.card-pile.is-collapsed .card-pile-head {
    flex: 1 1 auto;
    writing-mode: vertical-rl;
    align-items: center;
    justify-content: flex-start;
    gap: 0.5rem;
    padding: 0.2rem 0;
}

/* ── Terminal-outcome banner (Island Saved / Island Overrun) ──
   A big centred label floating over the whole view when the local replica reaches a terminal state. Bound to
   _game.TerminalState, so scrubbing back un-renders it. pointer-events:none keeps the board interactive. */
.outcome-banner {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.outcome-banner span {
    font-size: clamp(2.5rem, 9vw, 7rem);
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    text-align: center;
    padding: 0 1rem;
    animation: outcome-pop 0.45s cubic-bezier(0.2, 1.3, 0.4, 1) both;
}
.outcome-banner--victory span {
    color: #6fe39a;
    text-shadow: 0 0 18px rgba(111, 227, 154, 0.55), 0 2px 6px rgba(0, 0, 0, 0.6);
}
.outcome-banner--defeat span {
    color: #ff5d52;
    text-shadow: 0 0 18px rgba(255, 93, 82, 0.5), 0 2px 6px rgba(0, 0, 0, 0.6);
}
@keyframes outcome-pop {
    0%   { opacity: 0; transform: scale(0.6); }
    100% { opacity: 1; transform: scale(1); }
}

/* ── Floating land-inspection panel (replaces the dedicated land panel) ── */
.land-hover {
    /* Pinned to the board panel's top-right corner (below the … game menu's row) rather than following the
       cursor, so inspecting a land never blocks the map you're navigating. Highly transparent to stay
       unobtrusive over the board. */
    position: absolute;
    top: 3.1rem;
    right: 0.6rem;
    z-index: 3;
    pointer-events: none;   /* never steals the hover that spawned it */
    max-width: 14rem;
    background: rgba(8, 18, 14, 0.55);
    border: 1px solid rgba(211, 194, 158, 0.22);
    border-radius: 0.55rem;
    padding: 0.5rem 0.65rem;
    backdrop-filter: blur(2px);
    font-size: 0.82rem;
}

.land-hover p { margin: 0 0 0.3rem; }

/* Armed-effect notes in the land inspection panel — the full text behind a reminder pennant, one per note,
   ruled in its owner's accent (set inline). */
.land-reminders {
    list-style: none;
    margin: 0.45rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.land-reminders li {
    border-left: 3px solid rgba(211, 194, 158, 0.8);
    padding-left: 0.45rem;
    font-size: 0.78rem;
    line-height: 1.3;
}
.land-reminder-source {
    display: block;
    font-weight: 700;
    letter-spacing: 0.02em;
    opacity: 0.85;
}
.land-hover .token-list { margin: 0.4rem 0; }

/* ── Island panel internals ────────────────────────────────────────────── */
.island-section { margin-bottom: 1rem; }

.island-section-title {
    margin: 0 0 0.5rem;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: #452f10;
}

.island-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.7rem;
}

/* Pool row: the Fear pool + its terror track grouped together, then the Blight pool (wraps if narrow). */
.island-pools { display: flex; flex-wrap: wrap; gap: 0.7rem 1rem; align-items: flex-start; }
.fear-pool-group { display: flex; gap: 0.6rem; align-items: flex-start; }

/* Fear row: three equal-height bordered .pool sub-sections — Fear Pool, Fear Deck, Earned. They stretch to
   the tallest (the deck cards), share a small EARNED-style title, and vertically centre their content so the
   bar / cards / backs line up across the boxes. */
.fear-row { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: stretch; }
.fear-row .pool { display: flex; flex-direction: column; }
.fear-row .island-section-title {
    font-size: 0.6rem;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #54401d;
}
.fear-row .pool-body { flex: 1 1 auto; align-items: center; justify-content: center; }
.fear-row .fear-backs { margin-bottom: 0; }
.fear-earned-empty { color: #54401d; font-size: 1rem; }

/* Powers & Events decks on the left, the Blight pool pushed out to the far right of the same row, with open
   space between them (space-between). They align to the bottom so the deck tiles and the blight gauge share a
   baseline; wraps if the panel gets too narrow. */
.island-decks-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.island-decks-group { flex: 0 1 auto; }
.island-blight-block { flex: 0 0 auto; }
.island-blight-block .pool { min-width: 7rem; }

/* The bottom Face-up Cards row: a strip of small thumbnails (one per face-up fear/event card this round), plus
   the expand overlay that rises up over the panel when a thumbnail is clicked. Positioned so the overlay can be
   absolutely anchored to it. */
.island-facecards-row { position: relative; min-height: 4rem; }
.island-active-card-empty {
    display: flex; align-items: center; justify-content: center;
    min-height: 3rem;
    border-radius: 2px;
    border: 1px dashed rgba(58, 38, 20, 0.4);
    background: rgba(58, 38, 20, 0.03);
    color: #54401d;
    font-size: 0.8rem;
}

/* Face-up cards are split into labelled groups (Unearned Fear / Earned Fear / Events) so an upcoming deck
   reveal isn't mistaken for an earned card. Each group is a small-caps label above its own thumbnail row. */
.island-thumb-group { display: flex; flex-direction: column; gap: 0.25rem; margin-bottom: 0.5rem; }
.island-thumb-group:last-child { margin-bottom: 0; }
.island-thumb-group-label {
    font-size: 0.6rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: #6b5227;
}

/* Thumbnail strip: small tappable cards. Each shows a title + subtitle; the one owing this player an answer
   blinks yellow and flags a "!" ; the currently-open one is outlined. */
.island-thumb-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.island-thumb {
    position: relative;
    flex: 0 0 auto;
    min-width: 6.5rem; max-width: 11rem;
    display: flex; flex-direction: column; gap: 0.1rem;
    padding: 0.35rem 0.5rem;
    text-align: left;
    border-radius: 3px;
    background: rgba(58, 38, 20, 0.08);
    border: 1px solid rgba(58, 38, 20, 0.4);
    color: inherit; font: inherit;
    cursor: pointer;
}
.island-thumb:hover { background: rgba(58, 38, 20, 0.14); }
.island-thumb.is-open { border-color: #8a5212; background: rgba(138, 82, 18, 0.16); }
.island-thumb.is-attention {
    border-color: var(--flash-color);
    animation: attention-blink 1.3s ease-in-out infinite;
}
.island-thumb-title { font-weight: 600; color: #120b02; font-size: 0.78rem; line-height: 1.15; }
.island-thumb-sub { font-size: 0.6rem; color: #54401d; }
.island-thumb-flag {
    position: absolute; top: -0.4rem; right: -0.4rem;
    width: 1rem; height: 1rem;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: var(--flash-color); color: #120b02;
    font-size: 0.66rem; font-weight: 800;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* Expand overlay: rises up from just above the thumbnail strip to cover part of the panel; a near-opaque scrim
   hides the busy Fear/deck section behind so the card stays readable, and the card is centred on top. Clicking
   the scrim closes it. */
.island-card-overlay {
    position: absolute;
    left: -0.6rem; right: -0.6rem; bottom: 100%;
    z-index: 30;
    display: flex; justify-content: center; align-items: flex-end;
    padding: 0 0.6rem 0.5rem;
    max-height: 70vh;
    background: linear-gradient(to top, rgba(28, 18, 8, 0.94), rgba(28, 18, 8, 0.88));
}
.island-card-overlay-inner {
    width: 100%; max-width: 26rem;
    max-height: 68vh;
    overflow: auto;
}

/* Fear deck: a deck of cards broken into sections. Each section is a stack of overlapping face-down "Fear"
   card rectangles (top one printed "FEAR / n", like the power decks) followed by the terror divider card it
   precedes (TERROR II/III) and finally the FEAR VICTORY card. Card geometry matches .power-deck (5:7). */
.fear-deck { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }

/* A section's overlapping rectangles: a flex row where each card after the first slides left to overlap, so
   only a sliver of the buried cards shows and the last (top) card — bearing the count — sits fully on top. */
.fd-stack { display: flex; }
.fd-card {
    flex: 0 0 auto;
    width: 3.4rem; height: 4.76rem;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.12rem;
    border-radius: 0.4rem;
    background: linear-gradient(160deg, #3a2a52, #241834);
    border: 1px solid #4a3f6e;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
    color: #e7eef0;
}
.fd-card + .fd-card { margin-left: -3.0rem; }   /* overlap: ~0.4rem of each buried card peeks out */
.fd-card--top { box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); }
.fd-card-kind { font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.08em; color: #b9c4cf; }
.fd-card-count { font-size: 1.35rem; font-weight: 800; line-height: 1; }

/* The terror divider card that closes a section. */
.fd-divider {
    flex: 0 0 auto;
    width: 3.4rem; height: 4.76rem;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.1rem;
    border-radius: 0.4rem;
    background: linear-gradient(160deg, #6b4486, #3f2752);
    border: 1px solid rgba(240, 227, 247, 0.35);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
    color: #f0e3f7;
}
.fd-divider--victory { background: linear-gradient(160deg, #a86a30, #6a3f18); color: #ffe9c9; border-color: rgba(255, 233, 201, 0.4); }
.fd-divider-l1 { font-size: 0.56rem; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase; }
.fd-divider-l2 { font-size: 1.1rem; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; line-height: 1; }

.island-stub {
    padding: 0.6rem;
    border-radius: 2px;
    background: rgba(58, 38, 20, 0.04);
    border: 1px dashed rgba(58, 38, 20, 0.35);
}

.island-stub .muted { margin: 0; font-size: 0.78rem; }

/* Pool bars (Fear / Blight): a vertical track whose fill rises from the bottom to Value/Max, plus a readout. */
.pool {
    padding: 0.6rem;
    border-radius: 2px;
    background: rgba(58, 38, 20, 0.05);
    border: 1px solid rgba(58, 38, 20, 0.3);
}
.pool-body { display: flex; align-items: stretch; gap: 0.6rem; }
.pool-track {
    position: relative;
    width: 18px;
    min-height: 72px;
    border-radius: 0.3rem;
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.12);
    overflow: hidden;
}
.pool-fill {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    transition: height 0.25s ease;
}
.pool-info { display: flex; flex-direction: column; justify-content: center; gap: 0.15rem; }
.pool-value { font-size: 1.05rem; font-weight: 800; color: #120b02; }
.pool-caption { font-size: 0.68rem; color: #54401d; text-transform: uppercase; letter-spacing: 0.06em; }

/* Earned fear cards stay face-down until resolved — a row of small hatched card backs, one per held card. */
.fear-backs { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.5rem; }
.fear-back {
    width: 2rem; height: 2.7rem;
    display: flex; align-items: center; justify-content: center;
    border-radius: 0.3rem;
    font-size: 0.52rem; font-weight: 700; letter-spacing: 0.03em;
    color: #d9c9ef;
    background: repeating-linear-gradient(45deg, #2a2440, #2a2440 4px, #332b4e 4px, #332b4e 8px);
    border: 1px solid #4a3f6e;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* Minor & Major power decks: card-proportioned (5:7) tiles with deck + discard counts. They blink and become
   clickable while the viewing player owes a Minor-or-Major choice (PickPowerDeck question). */
.power-decks { display: flex; gap: 0.6rem; align-items: center; }
.power-deck {
    flex: 0 0 auto;
    box-sizing: border-box;   /* so the <button> minor/major tiles size identically to the <div> event tile */
    width: 3.4rem; height: 4.76rem;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.12rem;
    padding: 0.3rem;
    border-radius: 0.4rem;
    border: 1px solid rgba(211, 194, 158, 0.3);
    color: #e7eef0;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);   /* keep labels legible over the textured card-back art */
    font: inherit;
    cursor: default;
}
/* The Event deck sits next to the two power decks but is its own thing — a little extra space sets it apart. */
.power-deck--separated { margin-left: 0.9rem; }
/* Card-back art evoking the physical decks. Each is a stack of background layers (top→bottom): a couple of
   thin bright-white diagonal streaks reading as a faint magical glyph, a soft tinted glow, then the base
   colour wash. */

/* Minor: an earthy brown worn tome, warm light bleeding from the top, with hairline white glyph highlights. */
.power-deck--minor {
    background:
        linear-gradient(118deg, transparent 31%, rgba(255, 255, 255, 0.5) 32%, rgba(255, 255, 255, 0.5) 32.7%, transparent 33.7%),
        linear-gradient(47deg, transparent 60%, rgba(255, 255, 255, 0.32) 61%, transparent 62%),
        radial-gradient(120% 75% at 50% -12%, rgba(196, 154, 96, 0.55), transparent 60%),
        linear-gradient(160deg, #6b4e2d 0%, #4a361f 50%, #2d1f12 100%);
}
/* Major: a washed-out, low-saturation blue/green/yellow blend with brighter white glyph highlights. */
.power-deck--major {
    background:
        linear-gradient(114deg, transparent 37%, rgba(255, 255, 255, 0.6) 38%, rgba(255, 255, 255, 0.6) 38.8%, transparent 39.8%),
        linear-gradient(63deg, transparent 65%, rgba(255, 255, 255, 0.33) 66%, transparent 67%),
        radial-gradient(135% 95% at 24% 14%, rgba(214, 228, 205, 0.34), transparent 56%),
        linear-gradient(155deg, #7a6f5a 0%, #7d7358 44%, #867c52 74%, #464038 100%);
}
/* The Event deck is a read-only indicator (a div, never clickable) styled like the power decks — a smoky,
   dark top igniting into a red/orange fire-storm glow rising from the bottom edge. */
.power-deck--event {
    background:
        radial-gradient(70% 45% at 50% 108%, rgba(255, 226, 150, 0.95), transparent 55%),
        radial-gradient(95% 70% at 50% 120%, rgba(245, 130, 40, 0.9), transparent 62%),
        radial-gradient(125% 82% at 50% 138%, rgba(190, 42, 20, 0.85), transparent 72%),
        linear-gradient(180deg, #290a06 0%, #5e160a 40%, #94290e 70%, #c2410c 100%);
}
.power-deck-kind { font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.08em; color: #b9c4cf; }
.power-deck-count { font-size: 1.35rem; font-weight: 800; line-height: 1; }
.power-deck-foot { font-size: 0.54rem; color: #54401d; }
.power-deck.is-choosing { cursor: pointer; border-color: var(--flash-color); animation: attention-blink 1.3s ease-in-out infinite; }
.power-deck.is-choosing:hover { filter: brightness(1.15); }
.power-deck-cue { margin: 0.4rem 0 0; font-size: 0.72rem; color: var(--flash-color); }

.island-empty { margin: 0; font-size: 0.78rem; }

/* Expanded (readable) fear / event card shown inside the overlay. Opaque parchment so the dimmed panel behind
   doesn't bleed through, with a shadow lifting it off the scrim. */
.island-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.7rem 0.75rem;
    text-align: left;
    border-radius: 4px;
    background: #efe2c4;
    border: 1px solid rgba(58, 38, 20, 0.5);
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.4);
    color: inherit;
    font: inherit;
}

/* The maximised card must be fully opaque parchment — nothing behind it should ever bleed through. */
.island-card--expanded { background: #efe2c4; opacity: 1; }

/* A card with an open question for this player blinks the universal yellow to-do cue. Unlike the thumbnails —
   which sit on the light parchment panel — the expanded card floats over the near-opaque dark scrim, so it must
   keep its own opaque parchment background while blinking. attention-blink animates background-color (driving it
   transparent at the trough), which would clobber the parchment and leave near-black text on the dark scrim; use
   the ring-only pulse (border + shadow) so the parchment stays put. */
.island-card.is-attention {
    border-color: var(--flash-color);
    animation: pcard-attention-blink 1.3s ease-in-out infinite;
}

.island-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}
.island-card-heading { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }

/* Close (×) button in the card head. */
.island-card-close {
    flex: 0 0 auto;
    width: 1.5rem; height: 1.5rem;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    border: 1px solid rgba(58, 38, 20, 0.4);
    background: rgba(58, 38, 20, 0.06);
    color: #54401d; font-size: 1rem; line-height: 1;
    cursor: pointer;
}
.island-card-close:hover { background: rgba(58, 38, 20, 0.16); color: #120b02; }

/* Answer button on a card that owes this player a decision. */
.island-card-answer {
    align-self: flex-start;
    padding: 0.35rem 0.7rem;
    border-radius: 3px;
    border: 1px solid var(--flash-color);
    background: var(--flash-color);
    color: #120b02; font-weight: 700; font-size: 0.76rem;
    cursor: pointer;
}
.island-card-answer:hover { filter: brightness(1.08); }

.island-card-title { font-weight: 600; color: #120b02; font-size: 0.92rem; }
.island-card-sub { font-size: 0.66rem; color: #54401d; white-space: nowrap; }

.island-card-sections { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }

.island-card-section {
    font-size: 0.72rem;
    line-height: 1.3;
    color: #33240c;
    padding: 0.1rem 0.3rem;
    border-left: 2px solid transparent;
    border-radius: 0.2rem;
}

/* The tier / event currently in force. */
.island-card-section.is-active {
    color: #0f0a02;
    background: rgba(58, 38, 20, 0.12);
    border-left-color: #8a5212;
}

.island-card-section-label {
    display: inline-block;
    margin-right: 0.35rem;
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #54401d;
}

.island-card-cue {
    align-self: flex-start;
    font-size: 0.66rem;
    font-weight: 600;
    color: var(--flash-color);
}

/* Invader card slots: a single pipeline row — Discard ← Ravage ← Build ← Explore ← Deck. Cards share the
   5:7 geometry of the power/fear decks. Action slots are FACE-UP (land-coloured); Deck/Discard are FACE-DOWN
   cream backs drawn as overlapping stacks (one per stage). Scrolls horizontally if the pipeline is wide. */
.invader-slots { display: flex; flex-direction: column; gap: 0.6rem; }

.inv-slot-row { display: flex; gap: 0.6rem; align-items: flex-start; justify-content: space-between; overflow-x: auto; padding-bottom: 0.2rem; }

/* A column = a label above its card(s). */
.inv-col { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 0.3rem; }
.inv-slot-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #452f10;
    white-space: nowrap;
}

/* The action slot whose timing point is currently resolving pulses RED — title + card border — to draw the
   eye to the live step. Deliberately red (not the universal yellow attention blink) so it never reads as
   "a question is posed to you". */
@keyframes inv-active-blink {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.45; }
}
.inv-col.is-active .inv-slot-label {
    color: #ff6b6b;
    font-weight: 800;
    animation: inv-active-blink 1.1s ease-in-out infinite;
}
.inv-col.is-active .inv-card,
.inv-col.is-active .inv-card--empty {
    border-color: #ff6b6b;
    box-shadow: 0 0 0 1px #ff6b6b, 0 0 8px rgba(255, 107, 107, 0.55);
    animation: inv-active-blink 1.1s ease-in-out infinite;
}

/* Cards in a column sit in a row; stage stacks overlap (only a sliver of buried cards peeks out). */
.inv-row { display: flex; gap: 0.4rem; }
.inv-stack { display: flex; }
.inv-stack .inv-card + .inv-card { margin-left: -3.0rem; }

.inv-card {
    flex: 0 0 auto;
    width: 3.4rem; height: 4.76rem;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.1rem;
    border-radius: 0.4rem;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* Face-up action cards: background colour supplied inline (land tile colour / diagonal split). */
.inv-card--up {
    border: 1px solid rgba(0, 0, 0, 0.4);
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.75);
}

/* Face-down deck/discard backs: cream card with dark text. */
.inv-card--down {
    background: linear-gradient(160deg, #f3ecd9, #e2d7bb);
    border: 1px solid #b9ac86;
    color: #3a2f1c;
}
.inv-card--down-top { box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); }

.inv-card--empty {
    width: 3.4rem; height: 4.76rem;
    display: flex; align-items: center; justify-content: center;
    color: #54401d;
    background: rgba(58, 38, 20, 0.05);
    border: 1px dashed rgba(58, 38, 20, 0.4);
    border-radius: 0.4rem;
}

.inv-card-kind { font-size: 0.55rem; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.85; }
.inv-card-land { font-size: 0.72rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.02em; line-height: 1.05; }
.inv-card-stage-label { font-size: 0.62rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; line-height: 1; }
.inv-card-stage-num { font-size: 1.3rem; font-weight: 800; line-height: 1; }

/* ── Spirit resources ──────────────────────────────────────────────────── */
.res-grid { display: flex; gap: 0.5rem; margin-bottom: 0.7rem; }

.res-cell {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.1rem;
    padding: 0.4rem 0.3rem;
    border-radius: 0.5rem;
    background: rgba(255, 255, 255, 0.05);
}

/* The Reclaim resource becomes a real button while a reclaim is owed: strip the native button chrome so it
   still reads as a resource cell, then add the universal yellow attention blink + a ring so it's clearly
   clickable. Clicking opens the discard picker. */
.res-cell--action {
    font: inherit;
    color: inherit;
    border: none;
    cursor: pointer;
}
.res-cell--attention {
    box-shadow: 0 0 0 2px var(--flash-color);
    animation: attention-blink 1.3s ease-in-out infinite;
}

.res-icon { font-size: 1rem; }
.res-val { font-size: 1.2rem; font-weight: 700; color: #190f04; }
.res-lbl { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em; color: #452f10; }

.res-elements {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-bottom: 0.7rem;
}

.res-piles {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    font-size: 0.8rem;
    color: #b3a486;
}

/* ---------- Adversary flag + printed-card hover ---------- */
/* Shared by the lobby picker (flag + preview beside the level select) and the in-game header badge. The flag is
   a small national ensign; hovering or focusing it reveals the adversary card, laid out + coloured to mimic the
   printed card. Levels above the one in play render washed out. */

.adversary-flag {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    outline: none;
}

.adversary-flag-img {
    height: 2.6rem;
    width: auto;
    border-radius: 0.28rem;
    border: 1px solid rgba(211, 194, 158, 0.35);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
    display: block;
}

.adversary-flag--compact .adversary-flag-img { height: 1.9rem; }

.adversary-flag-caption {
    font-size: 0.85rem;
    color: #e2d6bf;
    font-weight: 600;
    white-space: nowrap;
}

/* Lobby picker row: adversary select + level select + flag preview, wrapping on narrow widths. */
.adversary-picker { display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem; }

/* ---- The printed card (revealed on hover / focus of the flag) ---- */
/* Palette taken from the wiki's card template: header band rgb(225,220,190), body band rgb(235,230,215),
   brown frame rgb(145,125,100), dividers rgb(50,50,50). Light-themed on purpose — it echoes the physical card. */
.adv-card {
    position: absolute;
    top: calc(100% + 0.5rem);
    left: 0;
    z-index: 60;
    width: 30rem;
    max-width: 88vw;
    border-radius: 0.85rem;
    background: #ffffff;
    border: 3px solid rgb(145, 125, 100);
    box-shadow: 0 14px 38px rgba(0, 0, 0, 0.55);
    color: rgb(35, 30, 22);
    font-size: 0.72rem;
    line-height: 1.35;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 0.12s ease, transform 0.12s ease;
    pointer-events: none;
}

/* Compact (in-game header, top-right): anchor the card to the flag's right edge so it never runs off-screen. */
.adversary-flag--compact .adv-card { left: auto; right: 0; }

/* In the island panel the flag lives inside a scrolling panel (overflow: auto), which would clip an
   absolutely-positioned hover card — so there the printed card is taken out to the viewport (position: fixed
   escapes a plain overflow clip) and anchored to the flag itself, opening to its RIGHT so it overlaps the board
   render rather than stranding itself in a far corner. Declared after the --compact rule so it wins the
   equal-specificity tie.

   Placement uses CSS anchor positioning; the declarations below are the fallback for engines without it
   (pinned under the viewport's top-right corner, the old behaviour). */
.island-adversary .adversary-flag .adv-card {
    position: fixed;
    top: 3.5rem;
    right: 0.9rem;
    bottom: auto;
    left: auto;
}

.island-adversary .adversary-flag { anchor-name: --adv-flag; }

@supports (anchor-name: --a) {
    /* Open from the flag's right edge, top-aligned with it. If the card would run off the bottom of the
       viewport it flips to being bottom-pinned; if it would run off the right (a narrow window) it flips back
       to the flag's left side. The card is a fixed 30rem tall-ish block, so it also gets a viewport-height cap
       with its own scrollbar rather than being cut off. */
    .island-adversary .adversary-flag .adv-card {
        position-anchor: --adv-flag;
        left: calc(anchor(right) + 0.6rem);
        top: anchor(top);
        right: auto;
        bottom: auto;
        max-height: calc(100vh - 1.2rem);
        overflow-y: auto;
        position-try-fallbacks: --adv-right-bottom, --adv-left-top, --adv-left-bottom;
    }

    @position-try --adv-right-bottom {
        left: calc(anchor(right) + 0.6rem);
        right: auto;
        top: auto;
        bottom: 0.6rem;
    }
    @position-try --adv-left-top {
        left: auto;
        right: calc(anchor(left) + 0.6rem);
        top: anchor(top);
        bottom: auto;
    }
    @position-try --adv-left-bottom {
        left: auto;
        right: calc(anchor(left) + 0.6rem);
        top: auto;
        bottom: 0.6rem;
    }
}

.adversary-flag:hover .adv-card,
.adversary-flag:focus-visible .adv-card,
.adversary-flag:focus-within .adv-card {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* Header band: name (left, large bold) · base difficulty · flag (right). */
.adv-card-head {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 0.8rem;
    background: rgb(225, 220, 190);
    border-bottom: 2px solid rgb(145, 125, 100);
}
.adv-card-name { font-size: 1.15rem; font-weight: 800; letter-spacing: 0.01em; }
.adv-card-basediff {
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: rgb(110, 95, 70);
    white-space: nowrap;
}
.adv-card-basediff b { font-size: 0.9rem; color: rgb(35, 30, 22); }
.adv-card-headflag {
    height: 2rem;
    width: auto;
    border-radius: 0.2rem;
    border: 1px solid rgba(0, 0, 0, 0.25);
}

/* Loss-condition + escalation bands, side by side. */
.adv-card-bands {
    display: grid;
    grid-template-columns: 1fr 1fr;
    background: rgb(235, 230, 215);
    border-bottom: 2px solid rgb(145, 125, 100);
}
.adv-card-band { padding: 0.45rem 0.7rem; }
.adv-card-band + .adv-card-band { border-left: 1px solid rgba(145, 125, 100, 0.5); }
.adv-card-band-title { font-weight: 700; font-style: italic; margin-bottom: 0.15rem; }
.adv-card-band-text { color: rgb(45, 38, 28); }

/* Cumulative per-level effects table (CSS grid rows). */
.adv-card-lrow {
    display: grid;
    grid-template-columns: 3.2rem 3.4rem 1fr;
    align-items: center;
    gap: 0.5rem;
    padding: 0.32rem 0.8rem;
}
.adv-card-lhead {
    font-style: italic;
    font-weight: 700;
    text-align: center;
    background: rgb(235, 230, 215);
    border-bottom: 1px solid rgb(50, 50, 50);
    padding-top: 0.4rem;
    padding-bottom: 0.4rem;
}
.adv-card-lhead .adv-card-lcol-effect { text-align: left; }
.adv-card-cum { font-weight: 400; font-size: 0.66rem; color: rgb(110, 95, 70); }

.adv-card-lbody + .adv-card-lbody,
.adv-card-lhead + .adv-card-lbody { border-top: 1px solid rgba(50, 50, 50, 0.35); }

.adv-card-lcol-level { display: flex; align-items: baseline; justify-content: center; gap: 0.15rem; }
.adv-card-lnum { font-size: 1.4rem; font-weight: 800; line-height: 1; }
.adv-card-ldiff { font-size: 0.7rem; color: rgb(90, 78, 58); }
.adv-card-lcol-fear { text-align: center; font-weight: 600; }
.adv-card-lcol-effect { color: rgb(40, 34, 25); }

/* The level currently in play: a warm gold accent so the eye lands on it. */
.adv-card-lbody.is-selected {
    background: rgba(233, 185, 90, 0.28);
    box-shadow: inset 3px 0 0 0 rgb(200, 150, 40);
}
/* Levels not yet reached (above the chosen one): washed out — not active. */
.adv-card-lbody.is-inactive { opacity: 0.38; filter: grayscale(0.7); }

/* ---------- Blight Card hover popup ---------- */
/* The Blight pool bar in the island panel doubles as the Blight Card itself (physically the Blight pieces sit
   on the card), so hovering / focusing it reveals the face-up side as a printed card — the same reveal idiom
   and palette as the adversary card above. Only the side actually face up is ever rendered (see
   BlightCardView); the reverse stays hidden information until the card flips. */

.blight-card-holder {
    position: relative;
    display: inline-flex;
    outline: none;
    anchor-name: --blight-card;
}

.blight-card {
    /* Fixed, not absolute: the island panel scrolls (overflow: auto), which would clip an absolutely
       positioned popup. Fallback placement (no anchor positioning) pins it under the viewport's top-right. */
    position: fixed;
    top: 3.5rem;
    right: 0.9rem;
    z-index: 60;
    width: 22rem;
    max-width: 88vw;
    border-radius: 0.85rem;
    background: #ffffff;
    border: 3px solid rgb(145, 125, 100);
    box-shadow: 0 14px 38px rgba(0, 0, 0, 0.55);
    color: rgb(35, 30, 22);
    font-size: 0.78rem;
    line-height: 1.4;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 0.12s ease, transform 0.12s ease;
    pointer-events: none;
}

@supports (anchor-name: --a) {
    /* Open to the LEFT of the bar (it sits at the far right of the decks row, so leftwards is the open space),
       top-aligned with it, flipping to bottom-pinned / right-side if it would run off the viewport. */
    .blight-card {
        position-anchor: --blight-card;
        right: calc(anchor(left) + 0.6rem);
        top: anchor(top);
        left: auto;
        bottom: auto;
        max-height: calc(100vh - 1.2rem);
        overflow-y: auto;
        position-try-fallbacks: --blight-left-bottom, --blight-right-top, --blight-right-bottom;
    }

    @position-try --blight-left-bottom {
        right: calc(anchor(left) + 0.6rem);
        left: auto;
        top: auto;
        bottom: 0.6rem;
    }
    @position-try --blight-right-top {
        left: calc(anchor(right) + 0.6rem);
        right: auto;
        top: anchor(top);
        bottom: auto;
    }
    @position-try --blight-right-bottom {
        left: calc(anchor(right) + 0.6rem);
        right: auto;
        top: auto;
        bottom: 0.6rem;
    }
}

.blight-card-holder:hover .blight-card,
.blight-card-holder:focus-visible .blight-card,
.blight-card-holder:focus-within .blight-card {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* Header band: card name + which side is face up. Green while Healthy, blighted red once flipped. */
.blight-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.6rem;
    padding: 0.5rem 0.75rem;
    border-bottom: 2px solid rgb(145, 125, 100);
}
.blight-card-head--healthy { background: rgb(206, 220, 190); }
.blight-card-head--blighted { background: rgb(214, 178, 172); }
.blight-card-name { font-size: 1.05rem; font-weight: 800; letter-spacing: 0.01em; }
.blight-card-side {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: rgb(95, 82, 62);
    white-space: nowrap;
}

.blight-card-body { background: rgb(235, 230, 215); padding: 0.6rem 0.75rem; }
.blight-card-text { margin: 0; color: rgb(40, 34, 25); }

.blight-card-foot {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.9rem;
    padding: 0.4rem 0.75rem;
    border-top: 1px solid rgba(145, 125, 100, 0.6);
    background: rgb(225, 220, 190);
    font-size: 0.72rem;
    color: rgb(60, 52, 38);
}

.blight-card-note,
.blight-card-warn {
    padding: 0.35rem 0.75rem;
    font-size: 0.7rem;
    font-style: italic;
    border-top: 1px solid rgba(145, 125, 100, 0.6);
}
.blight-card-note { background: rgb(238, 235, 226); color: rgb(95, 82, 62); }
.blight-card-warn {
    background: rgb(180, 70, 79);
    color: #fff5ef;
    font-style: normal;
    font-weight: 700;
}

/* ---------- In-game … menu (island top-right) ---------- */
/* Holds the actions that leave the table (return to lobby / abort). Floated over the board panel rather than
   given a header row: this layout partitions its height between the communal area, timeline and spirit area,
   so there is no spare row to spend on two rarely-used buttons. Anchored to .board-panel (not the viewport)
   so it sits over the island art instead of over the decision panel on its right. */

.game-menu {
    position: absolute;
    top: 0.5rem;
    right: 0.7rem;
    z-index: 70;
}

.game-menu-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0 0 0.45rem;   /* the … glyph sits on the baseline — nudge the box up around it */
    border-radius: 0.5rem;
    border: 1px solid rgba(211, 194, 158, 0.25);
    background: rgba(20, 15, 9, 0.72);
    color: #e6d9bf;
    font-size: 1.35rem;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
    transition: background 0.12s ease, opacity 0.12s ease;
    opacity: 0.72;
}
.game-menu-btn:hover,
.game-menu.is-open .game-menu-btn { opacity: 1; background: rgba(40, 30, 18, 0.92); }

/* Full-viewport click-away target, under the menu itself. */
.game-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: -1;   /* behind .game-menu-items, which is a later sibling in the same stacking context */
}

.game-menu-items {
    position: absolute;
    top: calc(100% + 0.35rem);
    right: 0;
    width: 17rem;
    display: flex;
    flex-direction: column;
    border-radius: 0.6rem;
    border: 1px solid rgba(211, 194, 158, 0.25);
    background: rgba(24, 18, 11, 0.97);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.6);
    overflow: hidden;
}

.game-menu-item {
    display: flex;
    flex-direction: column;
    gap: 0.12rem;
    text-align: left;
    padding: 0.5rem 0.7rem;
    background: none;
    border: none;
    border-bottom: 1px solid rgba(211, 194, 158, 0.12);
    color: #e2d6bf;
    cursor: pointer;
}
.game-menu-item:last-child { border-bottom: none; }
.game-menu-item:hover:not(:disabled) { background: rgba(211, 194, 158, 0.12); }
.game-menu-item:disabled { opacity: 0.4; cursor: default; }
.game-menu-item--danger .game-menu-item-label { color: #e79a92; }

.game-menu-item-label { font-weight: 600; font-size: 0.92rem; }
.game-menu-item-hint { font-size: 0.72rem; color: #b3a486; line-height: 1.3; }

.game-menu-confirm {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.5rem 0.7rem;
    background: rgba(120, 40, 44, 0.28);
}
.game-menu-confirm-btns { display: flex; gap: 0.4rem; }

.tl-btn--danger {
    font-weight: 600;
    color: #180d0d;
    background: #d08078;
    border-color: #d08078;
    transition: filter 0.15s ease;
}
.tl-btn--danger:not(:disabled):hover { background: #d08078; filter: brightness(1.08); }
