/* Design tokens — spacing / radii / font sizes. Theme-independent (don't
   change between light/dark). Use these in new CSS instead of hard-coded
   px values so the visual system stays consistent. Existing rules
   throughout this file haven't been backported yet; convert opportunistically. */
:root {
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;
    --radius-sm: 6px;
    --radius-md: 12px;
    --radius-lg: 14px;
    --radius-pill: 999px;
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-md: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.25rem;
    --text-2xl: 1.5rem;
    --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
    --shadow-md: 0 4px 12px rgba(0,0,0,0.10);
    --shadow-lg: 0 8px 24px rgba(0,0,0,0.15);
    --z-base: 1;
    --z-overlay: 100;
    --z-popover: 200;
    --z-toast: 300;
    --z-flip: 9999;
}

/* Light tokens are the default; dark tokens activate when JS sets
   data-theme="dark" on <html>. The theme JS resolves 'auto' → matches OS
   preference at load + re-resolves on prefers-color-scheme change, so the
   actual data-theme attribute is always exactly "light" or "dark". */
:root {
    --bg: #f5f3ef;
    --fg: #1a1a1a;
    --accent: #c54a3a;
    --muted: #5a5a5a;
    --border: rgba(0,0,0,0.18);
    --hover: rgba(0,0,0,0.06);
    --selected: rgba(197,74,58,0.12);
    /* Card-table felt — a touch darker than --bg so cards stand out on it,
       with a faintly warmer hue suggesting parchment / aged felt. */
    --felt: #ede8dc;
    --felt-edge: rgba(80,55,30,0.10);
    --felt-stitch: rgba(80,55,30,0.22);
    --card-shadow: 0 1px 2px rgba(60,40,20,0.12), 0 2px 6px rgba(60,40,20,0.05);
    /* Glassmorphism tokens (light). The bg is intentionally low-opacity
       (~38%) so the card-rain + cycle gradient + page behind reads
       THROUGH each panel as soft texture; the backdrop-filter blur +
       saturate compensates for the see-through and keeps text legible.
       The border + top-edge highlight + outer shadow give the panel the
       "lifted off the page" presence Apple uses. Tuned per-theme so
       both halves of the toggle feel like the same material. */
    --glass-bg: rgba(245, 243, 239, 0.38);
    --glass-border: rgba(255, 255, 255, 0.55);
    --glass-highlight: rgba(255, 255, 255, 0.65);
    --glass-shadow: 0 8px 32px rgba(20, 16, 12, 0.10), 0 2px 6px rgba(20, 16, 12, 0.06);
    --glass-blur: blur(18px) saturate(1.6);
    color-scheme: light;
}
:root[data-theme="dark"] {
    --bg: #0d0d0d;
    --fg: #e8e6e2;
    --accent: #f0a060;
    --muted: #888;
    --border: rgba(255,255,255,0.18);
    --hover: rgba(255,255,255,0.06);
    --selected: rgba(240,160,96,0.18);
    /* Dark-mode felt: slightly LIGHTER than --bg so cards (which sit on
       --bg) read as recessed wells against a faintly illuminated felt. */
    --felt: #181614;
    --felt-edge: rgba(255,220,170,0.05);
    --felt-stitch: rgba(255,220,170,0.10);
    --card-shadow: 0 1px 2px rgba(0,0,0,0.5), 0 3px 8px rgba(0,0,0,0.35);
    --glass-bg: rgba(18, 18, 22, 0.42);
    --glass-border: rgba(255, 255, 255, 0.12);
    --glass-highlight: rgba(255, 255, 255, 0.16);
    --glass-shadow: 0 10px 32px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.30);
    --glass-blur: blur(18px) saturate(1.7);
    color-scheme: dark;
}

/* Site-wide glass utility. Applied to .mode-card / .identity-card /
   .popover / etc. via class, OR via the variables directly when a
   panel needs custom border-radius / padding while keeping the
   material the same. Pairing strategy:
     - background: --glass-bg            (low-opacity tint)
     - backdrop-filter: --glass-blur     (blur + saturate)
     - border: 1px solid --glass-border  (the "edge" of glass)
     - box-shadow: outer + inset top    (lift + light-on-rim) */
.glass {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    box-shadow:
        var(--glass-shadow),
        inset 0 1px 0 var(--glass-highlight);
}

* { box-sizing: border-box; }

html, body {
    margin: 0; padding: 0;
    min-height: 100vh;
    background: var(--bg);
    color: var(--fg);
    font-family: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 14px;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;   /* prevent transform-based card-throw from inducing horizontal scroll */
}

main {
    max-width: 1100px;
    margin: 0 auto;
    padding: 2rem 1rem 4rem;
    position: relative;
    z-index: 1;
    overflow-x: clip;
}

/* (starfield removed — unreliable on mobile, replaced by card rain alone) */

/* ---- Unified nav (reusable header for any app under jakegonsalves.com) ---- */

.unified-nav {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.5rem 0.8rem;
    margin: -2rem -1rem 1.5rem;     /* extends to viewport edges within `main` */
    border-bottom: 1px solid var(--border);
    font-size: 0.85rem;
    background: var(--hover);
    position: sticky;
    top: 0;
    z-index: 50;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
.unified-nav a, .unified-nav button {
    background: none;
    border: 0;
    padding: 0.2rem 0.4rem;
    color: var(--muted);
    text-decoration: none;
    font-family: inherit;
    font-size: inherit;
    cursor: pointer;
    transition: color 0.15s;
}
.unified-nav a:hover, .unified-nav button:hover { color: var(--fg); }
.unified-nav .nav-current {
    color: var(--fg);
    font-weight: 600;
    margin-right: auto;
}

/* Breadcrumb segments: ← jakegonsalves.com / crib / ABCD
   Action buttons (theme/leaderboard/rules) sit on the right; the first
   .nav-action has margin-left:auto to push them apart from the crumbs. */
.unified-nav .nav-back {
    font-size: 1.05em;
    color: var(--muted);
    padding: 0.1rem 0.45rem;
    border-radius: 4px;
    line-height: 1;
}
.unified-nav .nav-back:hover { color: var(--accent); background: var(--bg); }
.unified-nav .nav-segment {
    color: var(--muted);
    font-weight: 500;
}
.unified-nav .nav-segment:hover { color: var(--fg); }
/* Tiny build-version tag — sits next to /crib so a deploy is visible at a
   glance. Lighter + smaller + monospace so it reads as metadata, not nav. */
.unified-nav .nav-version {
    color: var(--muted);
    font-size: 0.7em;
    opacity: 0.6;
    letter-spacing: 0.04em;
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    margin-left: -0.3rem;
    user-select: text;
}
.unified-nav .nav-version:empty { display: none; }
.unified-nav .nav-room {
    color: var(--fg);
    font-weight: 700;
    letter-spacing: 0.04em;
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.unified-nav .nav-room.hidden { display: none; }
.unified-nav #nav-leaderboard-btn { margin-left: auto; }
/* Icon-only nav buttons (e.g. 🔗 quick links). Compact so they don't
   crowd the labeled actions but stay tappable. */
.unified-nav .nav-icon-only {
    padding: 0.2rem 0.45rem;
    font-size: 1.05em;
    line-height: 1;
}

/* Mobile: collapse labeled actions to icon-only, drop chrome below the
   minimum useful set, tighten gaps. The breadcrumb (← / crib) and core
   actions (theme, leaderboard, share, rules) stay reachable end-to-end. */
@media (max-width: 700px) {
    .unified-nav {
        gap: 0.4rem;
        padding: 0.4rem 0.55rem;
        margin: -2rem -1rem 1rem;
        font-size: 0.82rem;
    }
    .unified-nav .nav-action-label { display: none; }
    .unified-nav .nav-action {
        padding: 0.35rem 0.5rem;
        font-size: 1.05em;
        line-height: 1;
        min-width: 2rem;       /* finger-friendly tap target */
        text-align: center;
    }
    .unified-nav .nav-home { font-size: 0.78em; }
    .unified-nav .nav-segment { font-size: 0.92em; }
    .unified-nav #nav-leaderboard-btn { margin-left: auto; }
}
@media (max-width: 420px) {
    /* On phone-narrow viewports the .com home link adds clutter without
       earning its keep — the back arrow already affords returning. */
    .unified-nav .nav-home { display: none; }
    .unified-nav .nav-version { display: none; }
    .unified-nav { padding: 0.35rem 0.4rem; gap: 0.3rem; }
}

/* ---- Popover modal (rules) ---- */

.popover-overlay {
    position: fixed;
    inset: 0;
    /* Dim + blur the page behind so the glass popover sits on a
       legible bed (the popover material itself is see-through, so
       without this the underlying UI would shine through too
       chaotically). Lighter scrim than the prior 0.55 so a hint of
       the card-rain still reads through the glass. */
    background: rgba(0,0,0,0.32);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 200;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 4rem 1rem;
    overflow-y: auto;
    animation: fade-in 0.18s ease-out;
}
.popover {
    /* Glass panel — sits above the dimmed/blurred .popover-overlay so
       the card-rain reads as a soft texture through the material,
       not a solid block. Same --glass-* tokens as .mode-card so the
       whole site reads as one consistent piece of frosted material. */
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-highlight);
    border-radius: 14px;
    max-width: 760px;
    width: 100%;
    padding: 1.75rem 2rem 2rem;
    position: relative;
    animation: pop-in 0.22s cubic-bezier(0.34, 1.4, 0.64, 1);
}
@keyframes pop-in {
    from { opacity: 0; transform: translateY(-12px) scale(0.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.popover h2 {
    margin: 0 0 1rem;
    font-size: 1.1rem;
    color: var(--accent);
    letter-spacing: 0.05em;
}
.popover-close {
    position: absolute;
    top: 0.5rem;
    right: 0.7rem;
    background: none;
    border: 0;
    color: var(--muted);
    font-size: 1.5rem;
    cursor: pointer;
    line-height: 1;
    padding: 0.2rem 0.5rem;
}
.popover-close:hover { color: var(--fg); }

.rules-block { margin-bottom: 1.25rem; }
.rules-block h3 {
    margin: 0 0 0.4rem;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.08em;
    text-transform: lowercase;
}
.rules-table {
    margin: 0;
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--fg);
}

/* ============================================================
   "How to play" popover — tabbed layout with friendlier copy.
   ============================================================ */

.popover.rules-popover { max-width: 720px; }
.rules-intro { margin: 0 0 0.7rem; font-size: 0.95em; }
.rules-tabs {
    display: flex;
    gap: 0.35rem;
    flex-wrap: wrap;
    border-bottom: 1px dashed var(--border);
    padding-bottom: 0.5rem;
    margin-bottom: 0.9rem;
}
.rules-tab {
    background: transparent;
    color: var(--muted);
    border: 1px solid transparent;
    padding: 0.32rem 0.7rem;
    cursor: pointer;
    font: inherit;
    border-radius: 4px;
    font-size: 0.88em;
}
.rules-tab:hover { color: var(--fg); }
.rules-tab.on {
    color: var(--fg);
    border-color: var(--accent);
    background: var(--hover);
}
.rules-stage { display: block; }
.rules-stage.hidden { display: none; }
.rules-stage h4 {
    margin: 0.75rem 0 0.3rem;
    font-size: 0.85rem;
    color: var(--accent);
    letter-spacing: 0.05em;
    text-transform: lowercase;
}
.rules-stage h4:first-child { margin-top: 0; }
.rules-foot { margin: 0.65rem 0 0; font-size: 0.83em; line-height: 1.45; }

/* Round flow numbered list — explicit step circles so we override the
   default <ol> numbering (otherwise you see "1. 1", "2. 2", …). */
.rules-flow {
    list-style: none !important;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    counter-reset: rules-flow;
}
.rules-flow > li::marker { content: ''; }   /* belt-and-suspenders for UAs that ignore list-style:none on <ol> */
.rules-flow li {
    display: grid;
    grid-template-columns: 2.2rem 1fr;
    align-items: start;
    gap: 0.7rem;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--border);
    background: var(--hover);
}
.rules-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.2rem;
    height: 2.2rem;
    border-radius: 50%;
    background: var(--accent);
    color: var(--bg);
    font-weight: 700;
    font-size: 1.05rem;
}
.rules-flow h4 {
    margin: 0 0 0.2rem;
    font-size: 0.95rem;
    color: var(--fg);
    text-transform: none;
    letter-spacing: 0;
}
.rules-flow p { margin: 0; font-size: 0.88em; color: var(--fg); line-height: 1.45; }

/* Visual scoring grid — each row pairs a pts pill with the actual cards
   that would score the rule, plus a short label + description. Cards
   are full renderCardHtml frames scaled down via font-size so they keep
   the ASCII look without dominating the popover. */
.scoring-intro { margin: 0 0 0.6rem; font-size: 0.82em; }
.scoring-grid {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin: 0 0 0.7rem;
}
.scoring-row {
    display: grid;
    grid-template-columns: auto auto 1fr;
    gap: 0.7rem;
    align-items: center;
    padding: 0.5rem 0.6rem;
    background: var(--hover);
    border: 1px solid var(--border);
    border-radius: 4px;
}
.scoring-row.no-cards { grid-template-columns: auto 1fr; }
.scoring-row.scoring-hero {
    border-color: var(--accent);
    background: linear-gradient(135deg, var(--hover) 0%, rgba(232,176,74,0.08) 100%);
}
.scoring-pts {
    flex: 0 0 auto;
    font-weight: 700;
    color: var(--accent);
    background: var(--bg);
    border: 1px solid var(--accent);
    padding: 0.15rem 0.5rem;
    border-radius: 4px;
    font-variant-numeric: tabular-nums;
    min-width: 2.4rem;
    text-align: center;
    font-size: 0.9em;
    white-space: nowrap;
    align-self: start;
}
.scoring-row.scoring-hero .scoring-pts {
    background: var(--accent);
    color: var(--bg);
}
.scoring-cards {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    flex-wrap: wrap;
}
.scoring-cards .mini-card {
    line-height: 1.05;
    transform-origin: center;
}
.scoring-cards .mini-card pre {
    margin: 0;
    font-family: inherit;
    font-size: 0.42em;
    line-height: 1.1;
    color: var(--fg);
    white-space: pre;
}
.scoring-cards .mini-card.red pre { color: var(--accent); }
.scoring-cards .mini-card.just-played {
    animation: scoring-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.scoring-cards .mini-card.starter pre {
    color: var(--accent);
    filter: brightness(1.05);
}
.scoring-cards .mini-card.starter {
    border-bottom: 1px dashed var(--accent);
}
@keyframes scoring-pop {
    0%   { transform: scale(0.85) translateY(-4px); opacity: 0; }
    60%  { transform: scale(1.05) translateY(0);     opacity: 1; }
    100% { transform: scale(1)    translateY(0);     opacity: 1; }
}
.scoring-sep {
    color: var(--muted);
    font-weight: 600;
    padding: 0 0.15rem;
    font-size: 0.85em;
}
.scoring-total {
    margin-left: 0.4rem;
    font-weight: 700;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
    font-size: 0.9em;
}
.scoring-text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}
.scoring-label {
    font-weight: 600;
    color: var(--fg);
    font-size: 0.9em;
}
.scoring-desc {
    font-size: 0.78em;
    color: var(--muted);
    line-height: 1.4;
}
@media (max-width: 600px) {
    .scoring-row {
        grid-template-columns: auto 1fr;
        gap: 0.4rem 0.55rem;
        padding: 0.45rem 0.5rem;
    }
    .scoring-cards { grid-column: 1 / -1; order: 3; }
    .scoring-text  { grid-column: 2; }
    .scoring-pts   { grid-column: 1; align-self: center; }
    .scoring-row.no-cards { grid-template-columns: auto 1fr; }
    .scoring-cards .mini-card pre { font-size: 0.5em; }
}

.rules-tips {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.rules-tips li {
    padding: 0.45rem 0.7rem;
    border-left: 3px solid var(--accent);
    background: var(--hover);
    font-size: 0.92em;
    line-height: 1.45;
}

@media (max-width: 600px) {
    .rules-flow li { grid-template-columns: 1.7rem 1fr; gap: 0.5rem; padding: 0.4rem 0.5rem; }
    .rules-step { width: 1.7rem; height: 1.7rem; font-size: 0.9rem; }
}
.rules-examples {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.rules-example {
    border-top: 1px dashed var(--border);
    padding-top: 0.75rem;
}
.rules-example h4 {
    margin: 0 0 0.25rem;
    font-size: 0.92rem;
    font-weight: 600;
}
.rules-example .ex-desc {
    margin: 0 0 0.5rem;
    color: var(--muted);
    font-size: 0.85em;
}
.rules-example .ex-cards {
    display: flex;
    gap: 0.4rem;
    align-items: flex-start;
    flex-wrap: wrap;
}
.rules-example .ex-cards .stack-card {
    animation: card-throw 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
    animation-fill-mode: both;
}
.rules-example .ex-cards .stack-card:nth-child(1) { animation-delay: 0ms;   }
.rules-example .ex-cards .stack-card:nth-child(2) { animation-delay: 90ms;  }
.rules-example .ex-cards .stack-card:nth-child(3) { animation-delay: 180ms; }
.rules-example .ex-cards .stack-card:nth-child(4) { animation-delay: 270ms; }
.rules-example .ex-cards .stack-card:nth-child(5) { animation-delay: 360ms; }
.rules-example .ex-pts {
    margin-top: 0.25rem;
    font-weight: 600;
    color: var(--accent);
}

/* ---- Hints toggle + banner ---- */

.hint-btn.on {
    color: var(--accent);
    border-color: var(--accent);
}
.hint-banner {
    margin: 0.5rem 0 0.75rem;
    padding: 0.4rem 0.7rem;
    border-left: 3px solid var(--accent);
    background: var(--hover);
    font-size: 0.88em;
    line-height: 1.5;
}
.hint-banner.hidden { display: none; }
.hint-banner .hint-label {
    color: var(--accent);
    font-weight: 600;
    margin-right: 0.4rem;
}

.card-button.recommended {
    border-color: var(--accent);
    box-shadow: 0 0 14px rgba(197, 74, 58, 0.4);
    animation: hint-glow 1.6s ease-in-out infinite;
}
.card-button.recommended::after {
    content: '★';
    position: absolute;
    top: 2px;
    right: 4px;
    color: var(--accent);
    font-size: 0.85em;
    pointer-events: none;
}
.card-button { position: relative; }
@keyframes hint-glow {
    0%, 100% { box-shadow: 0 0 8px rgba(197,74,58,0.35); }
    50%      { box-shadow: 0 0 18px rgba(197,74,58,0.65); }
}
:root[data-theme="dark"] .card-button.recommended {
    box-shadow: 0 0 14px rgba(240,160,96,0.45);
    animation-name: hint-glow-dark;
}
@keyframes hint-glow-dark {
    0%, 100% { box-shadow: 0 0 8px rgba(240,160,96,0.35); }
    50%      { box-shadow: 0 0 18px rgba(240,160,96,0.65); }
}

/* ---- Leaderboard popover ---- */

.lb-tabs { display: flex; gap: 0.3rem; margin: 0.5rem 0 1rem; border-bottom: 1px solid var(--border); }
.lb-tab {
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    padding: 0.4rem 0.8rem;
    color: var(--muted);
    font-family: inherit;
    font-size: inherit;
    font-weight: 600;
    cursor: pointer;
}
.lb-tab:hover { color: var(--fg); background: transparent; }
.lb-tab.on { color: var(--fg); border-bottom-color: var(--accent); }

/* Time-range chips below the leaderboard tabs (all-time / year / month). */
.lb-range {
    display: flex;
    gap: 0.3rem;
    margin: 0 0 0.7rem;
    flex-wrap: wrap;
}
.lb-range-btn {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--muted);
    font-family: inherit;
    font-size: 0.8em;
    font-weight: 600;
    padding: 0.18rem 0.55rem;
    cursor: pointer;
    letter-spacing: 0.03em;
}
.lb-range-btn:hover { color: var(--fg); }
.lb-range-btn.on { color: var(--bg); background: var(--accent); border-color: var(--accent); }
.lb-row {
    display: grid;
    grid-template-columns: 2ch 2ch 1fr auto auto auto;
    gap: 0.6rem;
    align-items: center;
    padding: 0.35rem 0;
    border-bottom: 1px dashed var(--border);
    font-size: 0.92em;
}
.lb-row.me { background: var(--selected); padding-left: 0.4rem; }
.lb-rank { color: var(--muted); text-align: right; }
.lb-rank.gold   { color: #f5c542; font-weight: 700; }
.lb-rank.silver { color: #c8c8c8; font-weight: 700; }
.lb-rank.bronze { color: #c8884a; font-weight: 700; }
.lb-pts { font-weight: 600; color: var(--accent); }
.lb-wl  { color: var(--muted); font-size: 0.88em; }
.lb-extra { color: var(--muted); font-size: 0.85em; text-align: right; min-width: 6ch; }
.lb-empty { color: var(--muted); padding: 1rem 0; text-align: center; }

/* Score breakdown — shown under the user's own .lb-row. One compact
   line listing where their points came from (per-tier + bonuses).
   Subtler typography than the row itself; treat it as a footnote. */
.lb-breakdown {
    font-size: 0.78em;
    color: var(--muted);
    padding: 0.2rem 0 0.5rem 4ch;
    border-bottom: 1px dashed var(--border);
    line-height: 1.45;
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.5rem;
    align-items: baseline;
}
.lb-breakdown .bd-prefix { color: var(--muted); }
.lb-breakdown .bd-line { white-space: nowrap; }
.lb-breakdown .bd-line strong { color: var(--fg); font-weight: 600; margin-left: 0.15rem; }
.lb-breakdown .bd-dot { color: var(--muted); opacity: 0.5; }

/* Phase B — claimed-name marker. Subtle green checkmark next to the
   display name. Tooltip explains. */
.lb-claimed {
    color: #6bc46d;
    font-size: 0.82em;
    margin-left: 0.25rem;
    vertical-align: 1px;
    cursor: help;
}

/* Top-of-leaderboard CTA visible to anonymous users whose row is
   unclaimed. One-line nudge — not a popup or banner. */
.lb-claim-cta {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.6rem;
    margin: 0.6rem 0 0.4rem;
    border: 1px dashed var(--accent);
    border-radius: 8px;
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    font-size: 0.86em;
}
.lb-claim-cta.hidden { display: none; }
.lb-claim-cta .lcc-text { flex: 1; color: var(--fg); }
.lb-claim-cta .lcc-btn { white-space: nowrap; }
/* "You're good" affirmative variant — green theme, disabled button. */
.lb-claim-cta.claimed-ok {
    border-color: #6bc46d;
    background: color-mix(in srgb, #6bc46d 8%, transparent);
}
.lb-claim-cta.claimed-ok .lcc-btn {
    border-color: #6bc46d;
    color: #6bc46d;
    cursor: default;
    opacity: 0.85;
}
.lb-claim-cta.claimed-ok .lcc-btn:hover { background: transparent; }

/* ============================================================
   Identity modal — claim + sign-in. Centered glass card over a
   dimmed backdrop. The same DOM is reused for both flows, the
   text + which fields show toggles via JS.
   ============================================================ */
.identity-modal {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: grid;
    place-items: center;
}
.identity-modal.hidden { display: none; }
.identity-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.identity-modal-card {
    position: relative;
    width: min(420px, 92vw);
    padding: 1.4rem 1.5rem 1.2rem;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border, var(--border));
    border-radius: 14px;
    backdrop-filter: blur(18px) saturate(1.6);
    -webkit-backdrop-filter: blur(18px) saturate(1.6);
    box-shadow: 0 10px 40px rgba(0,0,0,0.5);
}
.identity-modal-card h3 { margin: 0 0 0.6rem; }
.identity-modal-body { color: var(--muted); margin: 0 0 1rem; line-height: 1.4; font-size: 0.95em; }
.im-field { display: block; margin-bottom: 0.8rem; }
.im-field.hidden { display: none; }
.im-field-label { display: block; font-size: 0.82em; color: var(--muted); margin-bottom: 0.25rem; }
.im-field input {
    width: 100%;
    padding: 0.55rem 0.7rem;
    font-size: 1.1rem;
    font-variant-numeric: tabular-nums;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--fg);
    letter-spacing: 0.18em;
}
.im-field input:focus {
    outline: 1px solid var(--accent);
    outline-offset: 1px;
}
.identity-modal-error {
    color: #ff7a6a;
    font-size: 0.86em;
    margin: 0.4rem 0;
}
.identity-modal-error.hidden { display: none; }
.identity-modal-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 1rem;
}
.identity-modal-actions button.primary {
    padding: 0.55rem 1rem;
    font-weight: 600;
}
.identity-modal-foot {
    margin: 0.8rem 0 0;
    font-size: 0.78em;
    line-height: 1.35;
}
.im-recovery-display {
    font-family: var(--mono, monospace);
    font-size: 1.4rem;
    text-align: center;
    padding: 0.7rem;
    margin: 0.5rem 0 1rem;
    border: 1px solid var(--accent);
    border-radius: 8px;
    background: var(--bg);
    letter-spacing: 0.06em;
    user-select: all;
}

/* WebAuthn block — Touch ID button + "or" divider above the PIN field.
   Only shown when the platform reports a user-verifying authenticator
   (Touch ID / Face ID / Windows Hello) is available. */
/* Unified sign-in method list: one passkey primary + email/QR secondary rows,
   all sharing .im-method so the three methods read as one coherent picker. */
.im-methods { display: flex; flex-direction: column; gap: 0.5rem; }
.im-webauthn.hidden { display: none; }
.im-method {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.72rem 0.85rem;
    font: inherit;
    font-size: 0.98rem;
    text-align: left;
    background: color-mix(in srgb, var(--fg) 4%, transparent);
    color: var(--fg);
    border: 1px solid var(--glass-border, var(--border, rgba(255,255,255,0.16)));
    border-radius: 10px;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s, transform 0.05s;
}
.im-method:hover { border-color: var(--accent); background: color-mix(in srgb, var(--fg) 8%, transparent); }
.im-method:active { transform: translateY(1px); }
.im-method.hidden { display: none; }
.im-method-icon { font-size: 1.2em; line-height: 1; width: 1.4em; text-align: center; }
.im-method-label { flex: 1; font-weight: 500; }
.im-method-caret { color: var(--muted); font-size: 1.15em; transition: transform 0.15s; }
.im-method[aria-expanded="true"] .im-method-caret { transform: rotate(90deg); }
.im-method--primary {
    background: var(--accent);
    color: var(--bg);
    border-color: var(--accent);
    font-weight: 600;
}
.im-method--primary:hover { filter: brightness(1.08); background: var(--accent); }
.im-or {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--muted);
    font-size: 0.72em;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    margin: 0.1rem 0;
}
.im-or::before, .im-or::after { content: ""; flex: 1; height: 1px; background: var(--border, rgba(255,255,255,0.14)); }
.im-or.hidden { display: none; }

/* Model B — friction warning inside the claim modal. Soft amber, not
   alarming. Surfaces "you're already signed in as X" before they create
   a parallel identity. */
.im-warn {
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
    color: var(--fg);
    padding: 0.55rem 0.7rem;
    border-radius: 8px;
    font-size: 0.86em;
    line-height: 1.4;
    margin: 0 0 0.8rem;
}
.im-warn.hidden { display: none; }
.im-warn strong { font-weight: 600; }

/* Model C — "link to my current credentials" button. Same visual weight
   as the Touch ID button but in the meadow accent so they're clearly
   distinguishable. */
.im-link { margin-bottom: 0.8rem; }
.im-link.hidden { display: none; }
.im-link-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.65rem 1rem;
    font-size: 0.95rem;
    font-weight: 500;
    background: transparent;
    color: var(--fg);
    border: 1px solid var(--accent);
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.15s, transform 0.05s;
}
.im-link-btn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.im-link-btn:active { transform: translateY(1px); }
.im-link-icon { font-size: 1.05em; }

/* Email OTP block — the inline-expanded content under the "Email a code" row */
.im-email {
    padding: 0.55rem 0.2rem 0.2rem 0.7rem;
    margin: -0.15rem 0 0.05rem;
    border-left: 2px solid color-mix(in srgb, var(--accent) 35%, transparent);
}
.im-email.hidden { display: none; }
.im-email-btn {
    display: block;
    width: 100%;
    padding: 0.6rem 1rem;
    font-size: 0.92rem;
    font-weight: 500;
    background: transparent;
    color: var(--fg);
    border: 1px solid var(--border, rgba(255,255,255,0.18));
    border-radius: 8px;
    cursor: pointer;
    margin-top: 0.4rem;
    transition: background 0.15s, transform 0.05s;
}
.im-email-btn:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); }
.im-email-btn:active { transform: translateY(1px); }
.im-email-resend {
    font-size: 0.78em;
    color: var(--muted);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0.2rem 0;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.im-email-hint { font-size: 0.8em; margin: 0.25rem 0 0.5rem; }
.im-recovery-toggle {
    display: block;
    width: 100%;
    margin-top: 0.6rem;
    padding: 0.4rem;
    background: none;
    border: none;
    color: var(--muted);
    font-size: 0.8em;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.im-recovery-toggle.hidden { display: none; }

/* Email section in the identity panel */
.ip-email-section {
    margin: 0.8rem 0 0.4rem;
    padding-top: 0.8rem;
    border-top: 1px solid var(--border, rgba(255,255,255,0.12));
    font-size: 0.85rem;
}
.ip-email-section.hidden { display: none; }
.ip-email-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.ip-email-label { flex: none; }
.ip-email-value { flex: 1; color: var(--fg); word-break: break-all; }
.ip-email-edit {
    font-size: 0.78em;
    color: var(--muted);
    background: none;
    border: none;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
    padding: 0;
}
.ip-email-edit-row,
.ip-email-otp-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.5rem;
    flex-wrap: wrap;
}
.ip-email-edit-row.hidden,
.ip-email-otp-row.hidden { display: none; }
.ip-email-edit-row input,
.ip-email-otp-row input {
    flex: 1;
    min-width: 0;
    padding: 0.35rem 0.5rem;
    background: var(--glass-bg, rgba(255,255,255,0.08));
    border: 1px solid var(--border, rgba(255,255,255,0.18));
    border-radius: 6px;
    color: var(--fg);
    font-size: 0.88rem;
    font-family: inherit;
}

/* Q1 — name-input claim button + hint. Lives next to the name field in
   the identity editor; only visible when the typed name is claimable. */
.name-input-wrap {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex: 1;
}
.name-input-wrap input { flex: 1; }
.name-claim-btn {
    width: 2rem;
    height: 2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    border: 1px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--fg);
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.15s, transform 0.05s;
}
.name-claim-btn:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.name-claim-btn:active { transform: translateY(1px); }
.name-claim-btn.hidden { display: none; }
/* "Good to go" state — visible but disabled with a clear confirmation
   styling. Tells the user the name is theirs and they're signed in. */
.name-claim-btn.claimed-ok {
    cursor: default;
    border-color: #6bc46d;
    background: color-mix(in srgb, #6bc46d 18%, transparent);
    color: #6bc46d;
}
.name-claim-btn.claimed-ok:hover { background: color-mix(in srgb, #6bc46d 18%, transparent); }

.name-claim-hint {
    font-size: 0.78em;
    margin: 0.2rem 0 0 0;
    padding: 0.3rem 0.55rem;
    border-radius: 6px;
    line-height: 1.35;
    color: var(--muted);
}
.name-claim-hint.hidden { display: none; }
.name-claim-hint.warn {
    color: var(--fg);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
}
.name-claim-hint.ok {
    color: #6bc46d;
    background: color-mix(in srgb, #6bc46d 10%, transparent);
    border: 1px solid color-mix(in srgb, #6bc46d 35%, transparent);
}

/* ============================================================
   Identity chip — permanent floating widget showing current
   persona + lock state. Bottom-right on desktop, bottom-left
   on mobile (avoiding the mobile chat tray fab). Default is a
   small avatar+lock circle; hover expands into a name pill.
   Click opens the identity panel.
   ============================================================ */
.identity-chip {
    position: fixed;
    bottom: 1rem;
    right: 1rem;
    z-index: 9500;
    display: inline-flex;
    align-items: center;
    gap: 0;
    padding: 0.25rem 0.4rem;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border, var(--border));
    border-radius: 999px;
    backdrop-filter: blur(18px) saturate(1.6);
    -webkit-backdrop-filter: blur(18px) saturate(1.6);
    color: var(--fg);
    cursor: pointer;
    font: inherit;
    transition: max-width 0.22s ease, padding 0.22s ease, background 0.15s;
    overflow: hidden;
    max-width: 60px;        /* collapsed: avatar + lock fit, name hidden */
    white-space: nowrap;
    box-shadow: 0 4px 14px rgba(0,0,0,0.35);
}
.identity-chip.hidden { display: none; }
.identity-chip:hover,
.identity-chip:focus-visible {
    max-width: 260px;
    padding-right: 0.7rem;
    background: color-mix(in srgb, var(--accent) 12%, var(--glass-bg));
}
.identity-chip .ic-avatar {
    font-size: 1.3rem;
    line-height: 1;
    flex-shrink: 0;
}
.identity-chip .ic-lock {
    font-size: 0.85rem;
    margin-left: -0.15rem;
    flex-shrink: 0;
}
.identity-chip .ic-text {
    display: inline-flex;
    flex-direction: column;
    margin-left: 0.45rem;
    line-height: 1.15;
    opacity: 0;
    transition: opacity 0.18s 0.04s ease;
}
.identity-chip:hover .ic-text,
.identity-chip:focus-visible .ic-text { opacity: 1; }
.identity-chip .ic-name {
    font-size: 0.86rem;
    font-weight: 600;
    max-width: 12ch;
    overflow: hidden;
    text-overflow: ellipsis;
}
.identity-chip .ic-state {
    font-size: 0.7rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
@media (max-width: 700px) {
    .identity-chip {
        right: auto;
        left: 0.75rem;
        bottom: 0.75rem;
        max-width: 56px;
    }
    /* No hover on touch — tap to expand once (click handler opens panel directly). */
    .identity-chip:active { max-width: 240px; }
}

/* ============================================================
   Identity panel — slide-in from the right on desktop, full-
   screen modal on mobile. Owns ALL identity actions: claim,
   sign in/out, switch persona, manage personas.
   ============================================================ */
.identity-panel {
    position: fixed;
    inset: 0;
    z-index: 9600;
    display: grid;
    place-items: stretch;
}
.identity-panel.hidden { display: none; }
.identity-panel .ip-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0.5);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}
.identity-panel .ip-card {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(360px, 92vw);
    padding: 1.2rem 1.3rem;
    background: var(--glass-bg);
    border-left: 1px solid var(--glass-border, var(--border));
    backdrop-filter: blur(18px) saturate(1.6);
    -webkit-backdrop-filter: blur(18px) saturate(1.6);
    box-shadow: -10px 0 30px rgba(0,0,0,0.4);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    animation: ip-slide-in 0.22s ease-out;
}
@keyframes ip-slide-in {
    from { transform: translateX(20px); opacity: 0; }
    to   { transform: translateX(0); opacity: 1; }
}
.identity-panel .ip-close {
    position: absolute;
    top: 0.5rem;
    right: 0.6rem;
    background: transparent;
    color: var(--muted);
    border: none;
    font-size: 1.4rem;
    cursor: pointer;
    padding: 0.2rem 0.5rem;
}
.identity-panel .ip-title { margin: 0 0 0.2rem; font-size: 1rem; }
.identity-panel .ip-header {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.4rem 0;
}
.identity-panel .ip-avatar { font-size: 2rem; }
.identity-panel .ip-info {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.identity-panel .ip-name { font-size: 1.1rem; font-weight: 600; }
.identity-panel .ip-state {
    font-size: 0.78rem;
    color: var(--muted);
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}
.identity-panel .ip-stats {
    display: flex;
    gap: 0.9rem;
    padding: 0.5rem 0;
    border-top: 1px dashed var(--border);
    border-bottom: 1px dashed var(--border);
    font-variant-numeric: tabular-nums;
}
.identity-panel .ip-stat { font-size: 0.86rem; color: var(--muted); }
.identity-panel .ip-stat strong {
    color: var(--fg);
    font-weight: 600;
    margin-right: 0.2rem;
}
.identity-panel .ip-actions {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.identity-panel .ip-btn {
    display: block;
    width: 100%;
    padding: 0.55rem 0.8rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--fg);
    text-align: left;
    cursor: pointer;
    font-size: 0.92rem;
    transition: background 0.12s, border-color 0.12s;
}
.identity-panel .ip-btn:hover { background: color-mix(in srgb, var(--accent) 8%, var(--bg)); }
.identity-panel .ip-btn.primary {
    background: var(--accent);
    color: var(--bg);
    border-color: var(--accent);
    font-weight: 600;
}
.identity-panel .ip-btn.primary:hover { filter: brightness(1.08); }
.identity-panel .ip-btn.ghost {
    border-style: dashed;
    color: var(--muted);
}
.identity-panel .ip-btn.danger {
    color: #d6564a;
    border-color: color-mix(in srgb, #d6564a 45%, transparent);
}

/* QR device pairing — rendered inside the sign-in modal (#im-pair). */
.im-pair.hidden { display: none; }
.im-pair-back {
    background: none; border: 0; color: var(--muted);
    font: inherit; font-size: 0.85em; cursor: pointer;
    padding: 0.1rem 0; margin-bottom: 0.5rem;
}
.im-pair-back:hover { color: var(--fg); }
.im-pair-steps {
    display: flex; gap: 0.5rem; justify-content: center;
    margin-bottom: 0.7rem; font-size: 0.66rem; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--muted);
}
.im-pair-step { opacity: 0.5; }
.im-pair-step::before { content: "○ "; }
.im-pair-step.active { opacity: 1; color: var(--accent); font-weight: 600; }
.im-pair-step.active::before { content: "● "; }
.im-pair-step.done::before { content: "✓ "; }
.im-pair-body { text-align: center; }
.im-pair-headline { font-weight: 600; font-size: 0.95rem; margin-bottom: 0.4rem; }
.im-pair-linkrow { margin: 0.5rem 0 0.2rem; display: flex; gap: 0.6rem; justify-content: center; align-items: center; }
.im-pair-copy {
    background: none; border: 1px solid var(--border, rgba(255,255,255,0.18));
    border-radius: 8px; color: inherit; font: inherit; font-size: 0.8rem;
    padding: 0.3rem 0.6rem; cursor: pointer;
}
.im-pair-copy:hover { border-color: var(--accent); }
.im-pair-flip { margin-top: 0.5rem !important; font-size: 0.85rem; }

/* QR + code rendering (rendered into #im-pair) */
.ip-qr {
    width: 200px; max-width: 80%; margin: 0.2rem auto 0.5rem;
    background: #fff;            /* QR needs a light quiet-zone to scan */
    padding: 8px; border-radius: 6px;
}
.ip-qr svg { display: block; width: 100%; height: auto; }
.ip-qr-hint { font-size: 0.78rem; line-height: 1.45; margin-bottom: 0.3rem; color: var(--muted); }
.ip-qr-link { font-size: 0.8rem; color: var(--accent); }
.ip-pair-code {
    font-family: var(--mono, monospace); font-size: 2.4rem; font-weight: 700;
    letter-spacing: 0.35em; text-indent: 0.35em;   /* offset trailing tracking → optically centred */
    color: var(--accent); margin: 0.4rem 0 0.5rem;
}
.ip-pair-input {
    width: 5.5em; margin: 0.3rem auto 0.5rem; display: block; text-align: center;
    font-family: var(--mono, monospace); font-size: 1.6rem;
    letter-spacing: 0.3em; text-indent: 0.3em; padding: 0.4rem 0.3rem;
    border-radius: 8px; border: 1px solid var(--border, rgba(255,255,255,0.18));
    background: color-mix(in srgb, var(--fg) 5%, transparent); color: inherit;
}
.ip-pair-input:focus { outline: 1px solid var(--accent); outline-offset: 1px; }
.ip-pair-err { font-size: 0.78rem; color: #e0746a; margin-bottom: 0.3rem; }
.ip-pair-err.hidden { display: none; }
.ip-scan-video {
    width: 100%; max-width: 260px; aspect-ratio: 1 / 1; object-fit: cover;
    margin: 0.2rem auto 0.5rem; display: block; border-radius: 10px; background: #000;
}
/* Pairing action buttons inside the modal */
#im-pair .ip-btn {
    display: block; width: 100%; margin: 0.4rem 0 0; padding: 0.6rem 0.85rem;
    border-radius: 10px; border: 1px solid var(--glass-border, var(--border, rgba(255,255,255,0.16)));
    background: color-mix(in srgb, var(--fg) 4%, transparent);
    color: var(--fg); font: inherit; font-size: 0.95rem; cursor: pointer;
}
#im-pair .ip-btn:hover { border-color: var(--accent); }
#im-pair .ip-btn.primary { background: var(--accent); color: var(--bg); border-color: var(--accent); font-weight: 600; }
#im-pair .ip-btn.ghost { background: none; color: var(--muted); }
#im-pair .ip-btn.ghost:hover { color: var(--fg); }

/* Passkey management list */
.identity-panel .ip-passkeys { margin: 0.2rem 0 0.4rem; }
.identity-panel .ip-passkeys-title {
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-bottom: 0.3rem;
}
.identity-panel .ip-passkey-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--border, rgba(255,255,255,0.14));
    border-radius: 6px;
    margin-bottom: 0.3rem;
    font-size: 0.85rem;
}
.identity-panel .ip-passkey-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.identity-panel .ip-passkey-date { font-size: 0.72rem; flex: none; }
.identity-panel .ip-passkey-remove {
    flex: none;
    width: 1.5rem; height: 1.5rem;
    line-height: 1;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--muted);
    font-size: 1.1rem;
    cursor: pointer;
}
.identity-panel .ip-passkey-remove:hover { color: #d6564a; background: color-mix(in srgb, #d6564a 12%, transparent); }
.identity-panel .ip-btn.danger:hover {
    background: color-mix(in srgb, #d6564a 12%, var(--bg));
}
.identity-panel .ip-status-ok {
    padding: 0.55rem 0.8rem;
    background: color-mix(in srgb, #6bc46d 12%, transparent);
    border: 1px solid color-mix(in srgb, #6bc46d 40%, transparent);
    color: var(--fg);
    border-radius: 8px;
    font-size: 0.88rem;
    line-height: 1.4;
    margin-bottom: 0.2rem;
}
.identity-panel .ip-status-ok strong { font-weight: 600; }

/* Preferences — theme + visual toggles, relocated from the top nav. */
.identity-panel .ip-prefs {
    margin-top: 0.9rem;
    padding-top: 0.8rem;
    border-top: 1px solid var(--border, rgba(255,255,255,0.12));
}
.identity-panel .ip-prefs-title {
    margin: 0 0 0.5rem;
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--muted);
}
.identity-panel .ip-pref-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.5rem;
}
.identity-panel .ip-pref-label { font-size: 0.92rem; color: var(--fg); }
.identity-panel .ip-pref-control {
    min-width: 6.5rem;
    padding: 0.5rem 0.8rem;
    border: 1px solid var(--border, rgba(255,255,255,0.2));
    border-radius: 8px;
    background: var(--glass-bg, rgba(255,255,255,0.06));
    color: var(--fg);
    font-family: inherit;
    font-size: 0.9rem;
    text-align: center;
    cursor: pointer;
    transition: filter 0.15s, border-color 0.15s;
}
.identity-panel .ip-pref-control:hover { filter: brightness(1.1); }
.identity-panel .ip-pref-control.on { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.identity-panel .ip-prefs-note { margin: 0.3rem 0 0; font-size: 0.74rem; line-height: 1.4; }

.identity-panel .ip-delete-confirm { margin: 0.4rem 0 0.2rem; }
.identity-panel .ip-delete-confirm.hidden { display: none; }
.identity-panel .ip-delete-hint, .identity-panel #ip-delete-hint { font-size: 0.78rem; margin: 0 0 0.4rem; line-height: 1.4; }
.identity-panel .ip-delete-row { display: flex; gap: 0.4rem; }
.identity-panel #ip-delete-otp {
    flex: 1; min-width: 0;
    padding: 0.4rem 0.55rem;
    background: var(--glass-bg, rgba(255,255,255,0.08));
    border: 1px solid color-mix(in srgb, #d6564a 45%, transparent);
    border-radius: 6px;
    color: var(--fg);
    font-family: inherit;
    font-size: 0.9rem;
}
.identity-panel #ip-delete-confirm-btn.danger {
    background: #d6342b;
    color: #fff;
    border: 0;
    border-radius: 6px;
    padding: 0.4rem 0.7rem;
    cursor: pointer;
    white-space: nowrap;
}

.identity-panel .ip-footer {
    margin: 0.7rem 0 0;
    font-size: 0.74rem;
    line-height: 1.4;
}
@media (max-width: 700px) {
    .identity-panel .ip-card {
        width: 100vw;
        max-width: 100vw;
        animation-name: ip-slide-up;
    }
    @keyframes ip-slide-up {
        from { transform: translateY(20px); opacity: 0; }
        to   { transform: translateY(0); opacity: 1; }
    }
}
.lb-footer { font-size: 0.78em; margin-top: 1rem; }
#lb-month { font-size: 0.7em; font-weight: 400; margin-left: 0.5rem; }

/* ---- Bot vs human stats (leaderboard popover "🤖 bots" tab) ---- */
/* One card-style row per AI tier, focal on the win-rate bar. Kept
   visually distinct from the human leaderboard rows so it can't be
   confused with the rankings users actually compete on. */
.bs-row {
    display: grid;
    grid-template-columns: 3rem 1fr;
    align-items: center;
    gap: 0.8rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--border, rgba(0,0,0,0.12));
    border-radius: 8px;
    background: var(--hover, rgba(255,255,255,0.04));
    margin-bottom: 0.5rem;
}
.bs-avatar { font-size: 2rem; text-align: center; line-height: 1; }
.bs-body { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
.bs-head { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.bs-name { font-weight: 700; }
.bs-note { font-size: 0.78em; }
.bs-bar {
    position: relative;
    height: 8px;
    border-radius: 4px;
    background: rgba(0,0,0,0.18);
    overflow: hidden;
    box-shadow: inset 0 1px 1px rgba(0,0,0,0.2);
}
.bs-bar-fill {
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, var(--accent), #e8b842);
    border-radius: 4px;
    transition: width 600ms cubic-bezier(0.34, 1.4, 0.64, 1);
}
.bs-stats {
    display: flex;
    gap: 0.85rem;
    flex-wrap: wrap;
    font-size: 0.82em;
    color: var(--muted);
}
.bs-wins   { color: var(--accent); font-weight: 600; }
.bs-losses { font-weight: 500; }
.bs-rate   { font-weight: 600; }
.bs-total  { margin-left: auto; }

/* ---- Game-log feed (social) ---- */

/* Sticky-ish controls row: sort toggle + hashtag-style badge filters. */
.feed-controls {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-bottom: 0.6rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px dashed var(--border);
}
.feed-sort {
    display: flex;
    gap: 0.3rem;
}
.feed-sort-btn {
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.25rem 0.55rem;
    font: inherit;
    font-size: 0.82em;
    color: var(--muted);
    cursor: pointer;
    transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.feed-sort-btn:hover { color: var(--fg); }
.feed-sort-btn.on {
    color: var(--fg);
    border-color: var(--accent);
    background: var(--hover);
}
.feed-filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    align-items: center;
}
.feed-filter-chip {
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.15rem 0.55rem;
    font: inherit;
    font-size: 1em;
    cursor: pointer;
    line-height: 1;
    opacity: 0.55;
    transition: opacity 0.15s, transform 0.1s, background 0.15s, border-color 0.15s;
}
.feed-filter-chip:hover { opacity: 0.9; }
.feed-filter-chip.on {
    opacity: 1;
    background: var(--hover);
    border-color: var(--accent);
    transform: scale(1.05);
}
.feed-filter-clear {
    background: transparent;
    border: 0;
    color: var(--muted);
    font: inherit;
    font-size: 0.78em;
    cursor: pointer;
    padding: 0.15rem 0.3rem;
    text-decoration: underline dotted;
}
.feed-filter-clear:hover { color: var(--fg); }

.feed-entry {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.6rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    margin-bottom: 0.5rem;
    background: var(--hover);
    animation: feed-row-in 0.4s ease-out;
}
/* Games where you played get a subtle accent border so they stand out
   in the feed — quick visual cue to find your own history. */
.feed-entry.you-played {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent) inset;
    background: linear-gradient(90deg,
        rgba(197,74,58,0.06) 0%,
        var(--hover) 25%,
        var(--hover) 100%);
}
:root[data-theme="dark"] .feed-entry.you-played {
    background: linear-gradient(90deg,
        rgba(240,160,96,0.10) 0%,
        var(--hover) 25%,
        var(--hover) 100%);
}
@keyframes feed-row-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0);    }
}
.feed-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.feed-players {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.feed-winner, .feed-loser {
    display: inline-flex;
    align-items: baseline;
    gap: 0.25rem;
    font-weight: 600;
}
.feed-loser { opacity: 0.7; }
.feed-avatar { font-size: 1.15em; line-height: 1; }
.feed-vs {
    color: var(--muted);
    font-size: 0.82em;
    font-style: italic;
}
/* Streak chip next to a winner's name when they're on a 2+ win run.
   Tiny gold pill — shows momentum at a glance. */
.feed-streak {
    display: inline-block;
    margin-left: 0.3rem;
    padding: 0 0.4rem;
    border-radius: 999px;
    font-size: 0.78em;
    font-weight: 700;
    background: linear-gradient(135deg, rgba(232,184,66,0.28), rgba(232,98,58,0.22));
    border: 1px solid rgba(232,184,66,0.55);
    color: var(--fg);
}
.feed-score {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    font-size: 1.05em;
    color: var(--accent);
    white-space: nowrap;
}
.feed-score-sep { color: var(--muted); margin: 0 0.15em; }

.feed-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: baseline;
    font-size: 0.78em;
}
.feed-kind, .feed-ago { color: var(--muted); letter-spacing: 0.03em; }
.feed-ago::before { content: '· '; opacity: 0.5; }
.feed-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.08rem 0.45rem;
    border-radius: 999px;
    background: rgba(0,0,0,0.05);
    border: 1px solid var(--border);
    font-size: 0.85em;
    font-weight: 600;
    color: var(--fg);
}
:root[data-theme="dark"] .feed-badge { background: rgba(255,255,255,0.06); }
.feed-badge.gold {
    background: linear-gradient(135deg, rgba(232,184,66,0.22), rgba(232,98,58,0.18));
    border-color: rgba(232,184,66,0.55);
}
.feed-badge.perfect {
    background: linear-gradient(135deg, rgba(180,140,255,0.22), rgba(120,200,255,0.18));
    border-color: rgba(180,140,255,0.55);
}

/* Reaction picker — emoji + count. Click to bump; an optimistic count
   tick pops the button. Live reactions from other tabs flow in via
   game_log_update broadcasts. */
.feed-reactions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
}
.feed-react {
    display: inline-flex;
    align-items: center;
    gap: 0.18rem;
    padding: 0.22rem 0.5rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--fg);
    font: inherit;
    font-size: 0.92em;
    cursor: pointer;
    transition: background 0.15s, transform 0.1s;
    line-height: 1;
}
.feed-react:hover { background: var(--hover); }
.feed-react:active { transform: scale(0.94); }
.feed-react-count {
    font-size: 0.78em;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
    min-width: 1.2ch;
    text-align: right;
}
.feed-react.react-pop { animation: feed-react-pop 0.45s cubic-bezier(0.34, 1.6, 0.64, 1); }
@keyframes feed-react-pop {
    0%   { transform: scale(1); }
    35%  { transform: scale(1.18); }
    100% { transform: scale(1); }
}

/* ---- Confetti on win ---- */

#confetti-root {
    position: fixed;
    inset: 0;
    z-index: 300;
    pointer-events: none;
    overflow: hidden;
}
.confetti-piece {
    position: absolute;
    top: -16px;
    width: 8px;
    height: 14px;
    opacity: 0.9;
    animation: confetti-fall linear forwards;
}
@keyframes confetti-fall {
    0%   { transform: translateY(-20px) rotate(0deg);   opacity: 1; }
    80%  { opacity: 1; }
    100% { transform: translateY(110vh) rotate(720deg); opacity: 0; }
}

/* ---- Game-result toast ---- */

#result-toast {
    position: fixed;
    top: 30%;
    left: 50%;
    transform: translate(-50%, 0);
    z-index: 310;
    padding: 1.5rem 2rem;
    border: 2px solid var(--accent);
    background: var(--bg);
    text-align: center;
    pointer-events: none;
    opacity: 0;
}
#result-toast.show {
    animation: toast-in 3.6s ease-out forwards;
}
@keyframes toast-in {
    0%   { opacity: 0; transform: translate(-50%, 14px) scale(0.85); }
    15%  { opacity: 1; transform: translate(-50%, 0) scale(1.04); }
    80%  { opacity: 1; transform: translate(-50%, 0) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -12px) scale(0.98); }
}
#result-toast .result-headline {
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--accent);
    margin-bottom: 0.3rem;
}
#result-toast .result-detail {
    color: var(--fg);
    font-size: 0.92em;
}
#result-toast.loss { border-color: var(--muted); }
#result-toast.loss .result-headline { color: var(--muted); }

.screen { display: block; }
.hidden { display: none !important; }

pre {
    margin: 0.5rem 0;
    font-family: inherit;
    line-height: 1.15;
    white-space: pre;
    overflow-x: auto;
}

/* ============================================================
   Victory celebration popover — fires on GAME_OVER.

   Choreography (see victory.js for the trigger timing): backdrop scrim
   fades first, sun-rays start rotating behind everything, avatar pops
   in with overshoot, trumpets fly in and shake, confetti rains, then
   action buttons settle. Loser variant: same layout but dim glow, no
   shake, no trumpet flourish. Spectator: neutral copy, no first-person
   "you win" framing.

   Sun-rays + shake are disabled under prefers-reduced-motion (the popover
   still shows, just static).
   ============================================================ */
.victory-popover {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.7rem;
    pointer-events: auto;
    text-align: center;
    overflow: hidden;
}
.victory-popover.hidden { display: none; }
.victory-scrim {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: -2;
    animation: victory-scrim-in 0.35s ease-out;
}
:root[data-theme="dark"] .victory-scrim { background: rgba(0, 0, 0, 0.7); }
@keyframes victory-scrim-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.victory-rays {
    position: absolute;
    width: 200vmax;
    height: 200vmax;
    top: 50%;
    left: 50%;
    z-index: -1;
    transform-origin: center;
    background: conic-gradient(
        from 0deg,
        rgba(232, 184, 66, 0.18) 0deg,
        transparent 12deg,
        rgba(232, 184, 66, 0.18) 24deg,
        transparent 36deg,
        rgba(232, 184, 66, 0.18) 48deg,
        transparent 60deg,
        rgba(232, 184, 66, 0.18) 72deg,
        transparent 84deg,
        rgba(232, 184, 66, 0.18) 96deg,
        transparent 108deg,
        rgba(232, 184, 66, 0.18) 120deg,
        transparent 132deg,
        rgba(232, 184, 66, 0.18) 144deg,
        transparent 156deg,
        rgba(232, 184, 66, 0.18) 168deg,
        transparent 180deg,
        rgba(232, 184, 66, 0.18) 192deg,
        transparent 204deg,
        rgba(232, 184, 66, 0.18) 216deg,
        transparent 228deg,
        rgba(232, 184, 66, 0.18) 240deg,
        transparent 252deg,
        rgba(232, 184, 66, 0.18) 264deg,
        transparent 276deg,
        rgba(232, 184, 66, 0.18) 288deg,
        transparent 300deg,
        rgba(232, 184, 66, 0.18) 312deg,
        transparent 324deg,
        rgba(232, 184, 66, 0.18) 336deg,
        transparent 348deg
    );
    -webkit-mask: radial-gradient(circle, black 0%, black 25%, transparent 60%);
            mask: radial-gradient(circle, black 0%, black 25%, transparent 60%);
    animation: victory-rays-spin 28s linear infinite,
               victory-rays-in   0.6s ease-out;
}
@keyframes victory-rays-spin {
    from { transform: translate(-50%, -50%) rotate(0deg); }
    to   { transform: translate(-50%, -50%) rotate(360deg); }
}
@keyframes victory-rays-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
/* Loser variant: rays are present but very dim. */
.victory-popover.loss .victory-rays { opacity: 0.25; }
.victory-popover.spectator .victory-rays { opacity: 0.4; }

.victory-close {
    position: absolute;
    top: 1rem;
    right: 1.2rem;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    background: transparent;
    border: 1px solid rgba(255,255,255,0.4);
    color: rgba(255,255,255,0.85);
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
    z-index: 1;
}
.victory-close:hover { background: rgba(255,255,255,0.12); color: #fff; }

.victory-card {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1.4rem;
    animation: victory-pop 0.7s cubic-bezier(0.34, 1.6, 0.64, 1) both;
    animation-delay: 0.2s;
}
.victory-avatar-wrap {
    display: flex;
    align-items: center;
    gap: 0.45rem;
}
.victory-avatar {
    /* Header avatar is the celebratory icon, but the FOCAL element is now
       the scoreboard below. Header sizes scaled down ~45% so the table
       reads as the page's content and the header reads as a flourish. */
    font-size: clamp(3.4rem, 11vw, 6.6rem);
    line-height: 1;
    filter: drop-shadow(0 6px 24px rgba(232,184,66,0.65));
}
.victory-laurel-l, .victory-laurel-r {
    font-size: clamp(1.8rem, 5.4vw, 3rem);
    line-height: 1;
    opacity: 0.88;
    filter: drop-shadow(0 3px 8px rgba(60,120,40,0.45));
}
.victory-laurel-r { transform: scaleX(-1); }    /* mirror so leaves face inward */
.victory-trumpets {
    font-size: clamp(2rem, 6vw, 3.2rem);
    line-height: 1;
    filter: drop-shadow(0 3px 8px rgba(232,184,66,0.55));
    animation: victory-trumpet-shake 1.4s ease-in-out 0.9s infinite;
}
.victory-trumpets-l { transform-origin: bottom right; animation-delay: 0.9s; }
.victory-trumpets-r {
    transform-origin: bottom left;
    transform: scaleX(-1);
    animation-name: victory-trumpet-shake-r;
    animation-delay: 1.05s;
}
@keyframes victory-trumpet-shake {
    0%,100% { transform: rotate(0deg);  }
    25%     { transform: rotate(-7deg); }
    50%     { transform: rotate(0deg);  }
    75%     { transform: rotate(7deg);  }
}
@keyframes victory-trumpet-shake-r {
    0%,100% { transform: scaleX(-1) rotate(0deg);  }
    25%     { transform: scaleX(-1) rotate(-7deg); }
    50%     { transform: scaleX(-1) rotate(0deg);  }
    75%     { transform: scaleX(-1) rotate(7deg);  }
}
.victory-popover.loss .victory-trumpets,
.victory-popover.loss .victory-laurel-l,
.victory-popover.loss .victory-laurel-r {
    display: none;
}
.victory-popover.loss .victory-avatar { opacity: 0.7; filter: grayscale(0.4) drop-shadow(0 2px 8px rgba(0,0,0,0.4)); }
.victory-popover.spectator .victory-trumpets { animation: none; opacity: 0.7; }

@keyframes victory-pop {
    0%   { opacity: 0; transform: scale(0.55) translateY(20px); }
    65%  { opacity: 1; transform: scale(1.07) translateY(-4px); }
    100% { opacity: 1; transform: scale(1)    translateY(0);    }
}

.victory-headline {
    color: #fff;
    font-size: clamp(1.55rem, 4.6vw, 2.6rem);
    font-weight: 700;
    margin: 0;
    letter-spacing: 0.04em;
    text-shadow: 0 2px 12px rgba(0,0,0,0.55);
    animation: victory-fade-up 0.5s ease-out both;
    animation-delay: 0.55s;
}
.victory-popover.loss .victory-headline { color: rgba(255,255,255,0.85); font-weight: 600; }
/* Inline review chip — small pill directly under the headline so the
   user can hop into the game breakdown straight from the win moment. */
.victory-review-top {
    margin: 0.25rem 0 0;
    padding: 0.32rem 0.85rem;
    background: rgba(255,255,255,0.10);
    border: 1px solid rgba(255,255,255,0.30);
    color: #fff;
    font: inherit;
    font-size: 0.92em;
    font-weight: 600;
    border-radius: 999px;
    cursor: pointer;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: background 0.15s, border-color 0.15s, transform 0.1s;
    animation: victory-fade-up 0.5s ease-out both;
    animation-delay: 0.95s;
}
.victory-review-top:hover { background: rgba(255,255,255,0.20); border-color: rgba(255,255,255,0.55); }
.victory-review-top:active { transform: translateY(1px); }
.victory-review-top.hidden { display: none; }
.victory-name {
    color: var(--accent);
    font-size: clamp(1.05rem, 2.4vw, 1.35rem);
    font-weight: 600;
    animation: victory-fade-up 0.5s ease-out both;
    animation-delay: 0.75s;
}
.victory-score {
    /* +50% width budget so the now-larger rows have room to breathe. */
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    width: min(880px, 94vw);
    margin: 0.5rem auto 0;
    animation: victory-fade-up 0.5s ease-out both;
    animation-delay: 0.85s;
}
.vp-row {
    /* Scoreboard is now the FOCAL element — bumped row font-size from 1.05em
       to 1.6em, widened columns + gap. Reaction emoji also scales with the
       row so the corner mount stays readable instead of pinhead-tiny. */
    display: grid;
    grid-template-columns: 2.4rem 4.2rem 1fr minmax(8rem, 1.6fr) auto;
    align-items: center;
    gap: 1rem;
    padding: 0.9rem 1.3rem;
    background: rgba(255,255,255,0.10);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 10px;
    color: #fff;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    font-size: 1.6em;
}
.vp-row.winner {
    background: linear-gradient(135deg, rgba(232,184,66,0.30), rgba(232,98,58,0.22));
    border-color: rgba(232,184,66,0.6);
    box-shadow: 0 2px 12px rgba(232,184,66,0.25);
}
.vp-row.you .vp-name { color: var(--accent); font-weight: 700; }
.vp-rank { text-align: center; font-size: 0.95em; line-height: 1; }
.vp-avatar {
    position: relative;
    font-size: 1.6em;
    line-height: 1;
    text-align: center;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.35));
}
.vp-reaction {
    /* Bumped from 0.6em → 0.8em (relative to the now-bigger .vp-avatar).
       At the bigger row size this lands around 1.3em of row, big enough to
       read at a glance instead of squinting. */
    position: absolute;
    bottom: -0.4em;
    right: -0.45em;
    font-size: 0.8em;
    line-height: 1;
    filter: drop-shadow(0 1px 1px rgba(0,0,0,0.4));
}
/* Subtle wiggle for loser reactions — adds life without being mean. */
.vp-row.loser .vp-reaction { animation: vp-react-wobble 2.2s ease-in-out infinite; }
.vp-row.winner .vp-reaction {
    animation: vp-react-sparkle 1.6s ease-in-out infinite;
}
@keyframes vp-react-wobble {
    0%, 100% { transform: rotate(0deg) translateY(0); }
    35%      { transform: rotate(-12deg) translateY(-1px); }
    65%      { transform: rotate(8deg) translateY(0); }
}
@keyframes vp-react-sparkle {
    0%, 100% { transform: scale(1) rotate(0); opacity: 1; }
    50%      { transform: scale(1.25) rotate(15deg); opacity: 0.85; }
}
.vp-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.vp-bar {
    position: relative;
    height: 10px;
    background: rgba(0,0,0,0.30);
    border-radius: 5px;
    overflow: hidden;
    box-shadow: inset 0 1px 1px rgba(0,0,0,0.3);
}
.vp-bar-fill {
    position: absolute;
    inset: 0;
    background: rgba(255,255,255,0.55);
    border-radius: 3px;
    transition: width 900ms cubic-bezier(0.34, 1.4, 0.64, 1);
}
.vp-row.winner .vp-bar-fill {
    background: linear-gradient(90deg, var(--accent), #e8b842);
    box-shadow: 0 0 8px rgba(232,184,66,0.55);
}
.vp-score {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    font-size: 1em;
    white-space: nowrap;
}
.vp-score-out {
    opacity: 0.55;
    font-weight: 400;
    font-size: 0.78em;
    margin-left: 0.1em;
}
@media (prefers-reduced-motion: reduce) {
    .vp-row.loser .vp-reaction,
    .vp-row.winner .vp-reaction { animation: none; }
    .vp-bar-fill { transition: none; }
}
.victory-badges {
    display: flex;
    flex-wrap: wrap;
    gap: 0.55rem;
    justify-content: center;
    max-width: 92vw;
    animation: victory-fade-up 0.5s ease-out both;
    animation-delay: 1.0s;
}
.victory-badge {
    background: rgba(255,255,255,0.1);
    border: 1px solid rgba(255,255,255,0.25);
    color: #fff;
    padding: 0.45rem 1rem;
    border-radius: 999px;
    font-size: 1em;
    font-weight: 600;
    letter-spacing: 0.04em;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.victory-badge.gold {
    background: linear-gradient(135deg, rgba(232,184,66,0.35), rgba(232,98,58,0.3));
    border-color: rgba(232,184,66,0.6);
}
.victory-badge.perfect {
    background: linear-gradient(135deg, rgba(180,140,255,0.35), rgba(120,200,255,0.3));
    border-color: rgba(180,140,255,0.6);
}

/* Hover tooltip on badges with a computed `detail` (e.g. PERFECT 29 shows
   the round + cards + cut that earned it). Pure-CSS pop using ::after on
   .has-tip so we don't need extra DOM. Plain `title` attr is the fallback
   on touch / long-press / screenreader. */
.victory-badge.has-tip {
    position: relative;
    cursor: help;
}
.victory-badge.has-tip::after {
    content: attr(data-tip);
    position: absolute;
    left: 50%;
    bottom: calc(100% + 8px);
    transform: translateX(-50%);
    background: rgba(20, 14, 32, 0.96);
    color: #fff;
    border: 1px solid rgba(255,255,255,0.25);
    border-radius: 6px;
    padding: 0.5rem 0.75rem;
    font-size: 0.78em;
    font-weight: 500;
    letter-spacing: 0;
    line-height: 1.35;
    white-space: normal;
    width: max-content;
    max-width: min(340px, 78vw);
    text-align: center;
    box-shadow: 0 6px 24px rgba(0,0,0,0.4);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease 0s;
    z-index: 10;
}
.victory-badge.has-tip::before {
    content: '';
    position: absolute;
    left: 50%;
    bottom: calc(100% + 2px);
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-top-color: rgba(20, 14, 32, 0.96);
    opacity: 0;
    transition: opacity 0.18s ease 0s;
    pointer-events: none;
    z-index: 10;
}
.victory-badge.has-tip:hover::after,
.victory-badge.has-tip:focus-visible::after,
.victory-badge.has-tip:hover::before,
.victory-badge.has-tip:focus-visible::before {
    opacity: 1;
}
.victory-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.9rem;
    justify-content: center;
    margin-top: 0.9rem;
    animation: victory-fade-up 0.5s ease-out both;
    animation-delay: 1.15s;
}
.victory-actions button {
    background: rgba(255,255,255,0.12);
    border: 1px solid rgba(255,255,255,0.35);
    color: #fff;
    padding: 0.9rem 1.6rem;
    border-radius: 8px;
    font: inherit;
    font-size: 1.15em;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, transform 0.1s;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.victory-actions button:hover { background: rgba(255,255,255,0.22); border-color: rgba(255,255,255,0.55); }
.victory-actions button:active { transform: translateY(1px); }
.victory-actions button.primary {
    background: linear-gradient(135deg, var(--accent), #e8623a);
    border-color: var(--accent);
    color: #fff;
    box-shadow: 0 4px 14px rgba(232,98,58,0.5);
}
.victory-actions button.primary:hover { filter: brightness(1.1); }
.victory-actions button.tertiary {
    background: transparent;
    border-color: rgba(255,255,255,0.25);
    color: rgba(255,255,255,0.75);
}

@keyframes victory-fade-up {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0);   }
}

@media (prefers-reduced-motion: reduce) {
    .victory-rays { animation: victory-rays-in 0.6s ease-out; }
    .victory-trumpets, .victory-trumpets-r { animation: none; }
    .victory-card { animation-duration: 0.3s; animation-timing-function: ease-out; }
    .victory-headline, .victory-name, .victory-score, .victory-badges, .victory-actions {
        animation-duration: 0.25s;
        animation-delay: 0.05s !important;
    }
}

.title {
    color: var(--accent);
    font-weight: 600;
    margin: 0 0 0.5rem;
    font-size: clamp(0.7rem, 2.4vw, 1rem);
    transform-origin: center;
    /* Static ASCII content — never needs scrolling. Without this the
       global `pre { overflow-x: auto }` triggers a phantom horizontal
       scrollbar on first load if the fallback font (pre-JetBrains-Mono)
       is wider than monospace expects. body's overflow-x: hidden keeps
       any rare visual overflow from inducing page scroll. */
    overflow: visible;
}
.title.title-waving {
    animation: title-wave 1.4s ease-in-out;
}
@keyframes title-wave {
    0%   { transform: translateY(0) scale(1);   filter: none; }
    25%  { transform: translateY(-4px) scale(1.02); filter: drop-shadow(0 0 8px rgba(197,74,58,0.5)); }
    55%  { transform: translateY(2px) scale(0.99); }
    100% { transform: translateY(0) scale(1);   filter: none; }
}
.tagline { color: var(--muted); margin: 0 0 1.5rem; }

h2 {
    font-weight: 600;
    font-size: 1rem;
    margin: 0 0 1rem;
    display: flex;
    gap: 0.5rem;
    align-items: baseline;
    flex-wrap: wrap;
}
h2 code {
    background: var(--hover);
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--border);
    letter-spacing: 0.2em;
}

.row {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin: 0.75rem 0;
    flex-wrap: wrap;
}
.row label { color: var(--muted); min-width: 4rem; }

.actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin: 0.75rem 0;
    flex-wrap: wrap;
}
.sep { color: var(--muted); }

input[type="text"], input:not([type]), input[type="checkbox"] {
    accent-color: var(--accent);
}

input[type="text"], input:not([type]) {
    background: var(--bg);
    color: var(--fg);
    font-family: inherit;
    font-size: inherit;
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--border);
    outline: none;
    min-width: 0;
}

input:focus { border-color: var(--fg); }

#code-input {
    text-transform: uppercase;
    letter-spacing: 0.25em;
    text-align: center;
    padding: 0.55rem 0.6rem;
    font-size: 1rem;
}

button {
    background: transparent;
    color: var(--fg);
    font-family: inherit;
    font-size: inherit;
    padding: 0.45rem 0.9rem;
    border: 1px solid currentColor;
    cursor: pointer;
    font-weight: 600;
    transition: background 0.12s, color 0.12s;
}
button:hover:not(:disabled) {
    background: var(--fg);
    color: var(--bg);
}
button:disabled { opacity: 0.35; cursor: not-allowed; }
button.small {
    background: transparent;
    color: var(--muted);
    border: 1px solid var(--border);
    font-weight: 400;
    padding: 0.2rem 0.5rem;
    font-size: 0.85em;
}
button.small:hover:not(:disabled) { background: var(--hover); color: var(--fg); }

.error { color: var(--accent); min-height: 1.2em; margin: 0.5rem 0; }
.muted { color: var(--muted); }

.rules { margin-top: 2rem; color: var(--muted); }
.rules summary { cursor: pointer; }
.rules pre { color: var(--muted); font-size: 0.85em; margin-top: 0.5rem; }

#player-list { margin: 1rem 0; }

/* The peg board is the game's permanent anchor — score tracks per player,
   visible every render. Full rectangle frame + min-height reserved for up
   to 4 players, so the board's footprint stays constant from deal to
   game-over and doesn't reflow the rest of the page as scores accumulate. */
#board {
    border: 2px solid var(--accent);
    border-radius: 6px;
    background: linear-gradient(180deg, var(--hover) 0%, transparent 50%, var(--hover) 100%);
    padding: 0.9rem 0.7rem;
    margin: 1rem 0;
    font-size: clamp(0.65rem, 1.5vw, 0.9rem);
    line-height: 1.35;
    min-height: 5.2rem;
    box-shadow: inset 0 0 16px rgba(0,0,0,0.06);
    transition: box-shadow 0.4s ease;
}
:root[data-theme="dark"] #board {
    box-shadow: inset 0 0 16px rgba(0,0,0,0.35), 0 1px 0 rgba(255,255,255,0.04);
}
#board.score-pulse {
    animation: board-score-pulse 0.7s ease-out;
}
@keyframes board-score-pulse {
    0%   { box-shadow: inset 0 0 0 rgba(197,74,58,0); }
    35%  { box-shadow: inset 0 0 24px rgba(197,74,58,0.25); }
    100% { box-shadow: inset 0 0 0 rgba(197,74,58,0); }
}

#phase-banner {
    font-weight: 600;
    font-size: 1.1em;
    color: var(--accent);
}
#phase-banner .phase-round {
    display: inline-block;
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.08em;
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--accent);
    color: var(--accent);
    background: var(--hover);
    margin-right: 0.4rem;
    vertical-align: 0.1em;
    text-transform: uppercase;
}
#phase-banner .phase-step {
    opacity: 0.7;
    font-weight: 500;
    letter-spacing: 0.06em;
}

/* Big inline "▶ resume" button — appears right next to the phase banner
   text whenever the room is paused (typically pause-each-round in
   watch-bots). Glows so the user can find it without hunting for the
   small pause button in the replay-controls. */
.phase-resume {
    display: inline-block;
    margin-left: 0.6rem;
    padding: 0.25rem 0.85rem;
    font-family: inherit;
    font-size: 0.85em;
    font-weight: 700;
    letter-spacing: 0.05em;
    background: var(--accent);
    color: var(--bg);
    border: 0;
    cursor: pointer;
    text-transform: uppercase;
    vertical-align: 0.1em;
    animation: phase-resume-glow 1.6s ease-in-out infinite;
}
.phase-resume:hover { filter: brightness(1.1); }
@keyframes phase-resume-glow {
    0%, 100% { box-shadow: 0 0 0 rgba(197,74,58,0); }
    50%      { box-shadow: 0 0 18px rgba(197,74,58,0.55); }
}
@media (prefers-color-scheme: dark) {
    @keyframes phase-resume-glow {
        0%, 100% { box-shadow: 0 0 0 rgba(240,160,96,0); }
        50%      { box-shadow: 0 0 18px rgba(240,160,96,0.65); }
    }
}

/* Sticky game header keeps the phase banner + actions visible while scrolling
   chat / hands. Layered above board/stack content via z-index. */
.game-header {
    position: sticky;
    top: 0;
    z-index: 30;
    /* Transparent so the play surface reads continuously underneath as
       you scroll. A faint bottom border still separates it from the
       content below; the page bg shows through. */
    background: transparent;
    padding: 0.45rem 0.1rem 0.5rem;
    border-bottom: 1px dashed var(--border);
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
    margin: 0.5rem 0 1rem;
}

#play-area, #hand-display, #score-log {
    margin: 0.75rem 0;
}

.card-row {
    display: flex;
    flex-wrap: nowrap;
    gap: 0.4rem;
    align-items: flex-start;
    margin: 0.75rem 0;
    overflow-x: auto;
    overflow-y: visible;
    padding: 4px 2px 6px;
    scrollbar-width: thin;
}
.card-row::-webkit-scrollbar { height: 6px; }
.card-row::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

.bigsuit {
    display: inline-block;
    transform: scale(1.9);
    transform-origin: center;
    line-height: 1;
    vertical-align: baseline;
}

.card-button {
    background: transparent;
    color: var(--fg);
    border: 1px solid var(--border);
    padding: 0.25rem 0.45rem;
    cursor: pointer;
    font-family: inherit;
    font-weight: 400;
    white-space: pre;
    line-height: 1.1;
    font-size: 0.95em;
    transition: background 0.1s, border 0.1s, transform 0.05s;
}
.card-button:hover:not(.disabled) { background: var(--hover); }
.card-button:active:not(.disabled) { transform: translateY(1px); }
.card-button.selected { background: var(--selected); border-color: var(--accent); }
.card-button.disabled { opacity: 0.35; cursor: not-allowed; }
.card-button.red { color: var(--accent); }

#score-log {
    color: var(--muted);
    font-size: 0.88em;
    border-top: 1px dashed var(--border);
    padding-top: 0.75rem;
}

.sim-section {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px dashed var(--border);
    opacity: 0.85;
}
.sim-heading {
    color: var(--muted);
    font-size: 0.85em;
    font-weight: 400;
    margin: 0 0 0.5rem;
    text-transform: lowercase;
    letter-spacing: 0.1em;
}
#sim {
    color: var(--muted);
    font-size: clamp(0.65rem, 1vw, 0.85rem);
    line-height: 1.3;
}

/* ---- Lobby top row: title + demo far-right ---- */

.lobby-top {
    display: flex;
    align-items: flex-start;
    gap: 2rem;
    margin-bottom: 1rem;
}
.lobby-title-block {
    flex: 1 1 auto;
    min-width: 0;
    position: relative;   /* anchor for the .beta-tag overlay */
}
.lobby-title-block .title { margin: 0 0 0.5rem; }
.lobby-title-block .tagline { margin: 0 0 0.5rem; }

/* Beta build badge — a small red tag that overlaps the title. Hidden by
   default; shown only on beta hosts (body.is-beta, set by JS). Reusable
   convention: any beta view can drop a <span class="beta-tag">beta</span>
   and it appears only on beta.* / localhost. */
.beta-tag {
    display: none;
    position: absolute;
    top: -0.35rem;
    left: 0;
    z-index: 5;
    transform: rotate(-7deg);
    padding: 0.08rem 0.4rem;
    background: #d6342b;
    color: #fff;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    border-radius: 3px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.35);
    pointer-events: none;
}
body.is-beta .beta-tag { display: inline-block; }
.lobby-demo {
    flex: 0 0 320px;
    max-width: 360px;
    margin-left: auto;
    opacity: 0.92;
    overflow: visible;
    position: relative;
}
.lobby-demo #sim {
    position: relative;
    align-items: flex-end;
    font-size: clamp(0.7rem, 1.05vw, 0.95rem);
    min-height: 9em;
}
#sim-stack {
    display: flex;
    gap: 0.3rem;
    flex-wrap: wrap;
    justify-content: flex-end;
    min-height: 7em;
}
.sim-floaters {
    position: absolute;
    pointer-events: none;
    top: 0; left: 0; right: 0; bottom: 0;
    overflow: hidden;
}
.sim-float {
    position: absolute;
    /* Stack vertically when multiple floaters spawn close together. JS sets
       --float-stack to the count of currently-alive siblings at spawn time,
       and we offset bottom by 1.6em per stack slot so they don't overlap. */
    bottom: calc(2.5em + var(--float-stack, 0) * 1.6em);
    font-weight: 700;
    color: var(--accent);
    text-shadow: 0 0 8px rgba(197,74,58,0.4);
    font-size: 1.1em;
    animation: sim-float-up 1.6s ease-out forwards;
    pointer-events: none;
    white-space: nowrap;
}
@keyframes sim-float-up {
    0%   { transform: translateY(0)   scale(0.7); opacity: 0; }
    18%  { transform: translateY(-6px) scale(1.1); opacity: 1; }
    65%  { transform: translateY(-40px) scale(1);  opacity: 1; }
    100% { transform: translateY(-80px) scale(0.95); opacity: 0; }
}
.sim-summary {
    color: var(--muted);
    font-size: 0.85em;
    text-align: right;
    margin-top: 0.4rem;
    min-height: 1.2em;
    opacity: 0;
    transition: opacity 0.3s;
}
.sim-summary.show { opacity: 1; }

/* Compact hand rows below the lobby sim — first-initial of each player
   + their remaining cards as tiny rank+suit chips. Side-by-side so both
   players' hands read as a single horizontal line under the sim. */
.sim-hands {
    margin-top: 0.4rem;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.25rem 0.8rem;
    justify-content: flex-end;
    font-size: 0.75em;
    opacity: 0.75;
}
.sim-hand-row {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    color: var(--fg);
    white-space: nowrap;
}
.sim-hand-row.active { opacity: 1; font-weight: 600; }
.sim-hand-label {
    text-transform: uppercase;
    letter-spacing: 0.06em;
    opacity: 0.65;
}
.sim-hand-cards { display: inline-flex; gap: 0.25rem; }
.sim-hand-chip {
    display: inline-block;
    padding: 0 0.22rem;
    border: 1px solid currentColor;
    border-radius: 2px;
    line-height: 1.15;
    opacity: 0.8;
}
.sim-hand-chip.red { color: var(--accent); }
.sim-hand-chip.played {
    opacity: 0.25;
    text-decoration: line-through;
    text-decoration-thickness: 1px;
}

/* sim-card: same look as stack-card but explicit for the demo container */
#sim-stack .sim-card {
    display: inline-block;
    transform-origin: center top;
    line-height: 1.1;
}
#sim-stack .sim-card pre {
    margin: 0;
    font-family: inherit;
    line-height: 1.1;
    color: var(--fg);
}
#sim-stack .sim-card.red pre { color: var(--accent); }
#sim-stack .sim-card.just-played {
    animation: card-throw 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (max-width: 880px) {
    .lobby-top { flex-direction: column; gap: 0.5rem; }
    .lobby-demo { margin-left: 0; flex-basis: auto; max-width: 100%; }
    .lobby-demo-heading { text-align: left; }
    .lobby-demo #sim { align-items: flex-start; }
}

/* ---- Card rain (behind the top bar) ---- */

.card-rain {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: clamp(140px, 20vh, 240px);
    pointer-events: none;
    overflow: hidden;
    z-index: 0;
    user-select: none;
    -webkit-mask-image: linear-gradient(to bottom, black 0%, black 70%, transparent 100%);
            mask-image: linear-gradient(to bottom, black 0%, black 70%, transparent 100%);
}
body.no-rain .card-rain { display: none; }
.card-rain .rain-card {
    position: absolute;
    transform-origin: center center;
    opacity: 0;
    animation: rain-fade 6.8s ease-in-out forwards;
    color: var(--fg);
    line-height: 1.1;
}
.card-rain .rain-card pre {
    margin: 0;
    font-family: inherit;
    line-height: 1.08;
}
.card-rain .rain-card.red { color: var(--accent); }

@keyframes rain-fade {
    0%   { opacity: 0; }
    18%  { opacity: 0.22; }
    72%  { opacity: 0.22; }
    100% { opacity: 0; }
}

/* ---- Stars toggle ---- */

/* (stars toggle removed along with starfield) */

/* ---- Replay transport bar ----
   Two-row layout that separates state-display from action affordances:
     Row 1: status indicator (live/paused/reviewing) + the context-aware
            primary action (pause/play OR "back to live")
     Row 2: prev/next step buttons flanking a dotted scrubber that shows
            every round-end position with the current spot highlighted.
   Each row stands alone, so the "what" and the "how" are never tangled. */

.replay-controls {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.45rem 0.7rem 0.55rem;
    background: var(--hover);
    border: 1px solid var(--border);
    margin: 0.4rem 0 0.6rem;
    font-size: 0.88em;
}
.replay-controls .rw-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}
.replay-controls .rw-row-top { justify-content: space-between; }
.rw-status {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    font-size: 0.95em;
    flex: 1;
    min-width: 0;
}
.rw-status.reviewing { color: var(--muted); }
.rw-status.paused    { color: var(--fg); }
.rw-status.live      { color: var(--accent); }
.rw-dot {
    display: inline-block;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    flex-shrink: 0;
}
.rw-dot-live   { background: var(--accent); animation: rw-pulse 1.4s ease-in-out infinite; }
.rw-dot-paused { background: var(--fg); opacity: 0.55; }
.rw-dot-review { background: var(--muted); }
@keyframes rw-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(197,74,58,0.0); }
    50%      { box-shadow: 0 0 0 5px rgba(197,74,58,0.18); }
}
.replay-controls button {
    background: transparent;
    color: var(--fg);
    border: 1px solid var(--border);
    padding: 0.2rem 0.55rem;
    font-family: inherit;
    font-size: 0.95em;
    cursor: pointer;
    font-weight: 400;
}
.replay-controls button:hover:not(:disabled) { background: var(--bg); }
.replay-controls button:disabled { opacity: 0.35; cursor: not-allowed; }
.replay-controls .rw-step { padding: 0.18rem 0.5rem; line-height: 1; }

/* Context-aware primary action — pause/play when at live, "back to live"
   when scrubbed. They're sized similarly so the layout doesn't jump. */
.replay-controls .rw-pause,
.replay-controls .rw-back {
    border-color: var(--accent);
    color: var(--accent);
    font-weight: 600;
    padding: 0.25rem 0.75rem;
    flex-shrink: 0;
}
.replay-controls .rw-pause:hover:not(:disabled),
.replay-controls .rw-back:hover:not(:disabled) {
    background: var(--accent);
    color: var(--bg);
}
.replay-controls .rw-back { animation: rw-back-pulse 2.2s ease-in-out infinite; }
@keyframes rw-back-pulse {
    0%, 100% { box-shadow: 0 0 0 rgba(197,74,58,0); }
    50%      { box-shadow: 0 0 14px rgba(197,74,58,0.35); }
}

/* Scrubber — flexible-width track with N round-end dots + a "live" dot at
   the rightmost end. Clicking any dot jumps to that round end (or live). */
.rw-scrubber {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 0;
    position: relative;
    padding: 0.4rem 0.25rem;
    cursor: pointer;
    min-width: 0;
}
.rw-scrubber::before {
    content: '';
    position: absolute;
    left: 0.25rem;
    right: 0.25rem;
    top: 50%;
    height: 2px;
    background: var(--border);
    pointer-events: none;
}
.rw-tick {
    flex: 1 1 0;
    height: 1.1rem;
    background: transparent;
    border: 0;
    padding: 0;
    cursor: pointer;
    position: relative;
    z-index: 1;
}
.rw-tick::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: var(--border);
    border: 1px solid var(--border);
    transition: transform 0.15s, background 0.15s, border-color 0.15s;
}
.rw-tick:hover::after {
    transform: translate(-50%, -50%) scale(1.3);
    background: var(--muted);
}
.rw-tick.is-current::after {
    background: var(--accent);
    border-color: var(--accent);
    transform: translate(-50%, -50%) scale(1.45);
}
.rw-tick.is-live::after {
    background: var(--bg);
    border: 2px solid var(--accent);
    width: 0.65rem;
    height: 0.65rem;
}
.rw-tick.is-live.is-current::after {
    background: var(--accent);
    animation: rw-pulse 1.4s ease-in-out infinite;
}

.rw-back.hidden, .rw-pause.hidden { display: none; }

/* Per-turn step controls — secondary row beneath the round scrubber.
   The "play" button toggles auto-walk; selecting a speed changes interval. */
.replay-controls .rw-row-actions {
    gap: 0.4rem;
    border-top: 1px dashed var(--border);
    padding-top: 0.35rem;
    flex-wrap: wrap;
}
.rw-step-label {
    font-size: 0.85em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.replay-controls .rw-speed {
    background: transparent;
    color: var(--fg);
    border: 1px solid var(--border);
    font: inherit;
    padding: 0.16rem 0.3rem;
    cursor: pointer;
    margin-left: auto;
}
#rw-act-play[data-mode="playing"] {
    color: var(--bg);
    background: var(--accent);
    border-color: var(--accent);
    /* Pulse at the auto-walk tempo — JS writes --tempo-ms to <html> from
       the speed dropdown (2000ms slow / 1100ms normal / 500ms fast).
       The keyframe expands a box-shadow halo every tick so the cadence
       is visible on the button itself, not just felt in the snapshot
       advance. */
    animation: rw-tempo-pulse var(--tempo-ms, 1.1s) ease-in-out infinite;
}
@keyframes rw-tempo-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(197,74,58,0); }
    50%      { box-shadow: 0 0 0 8px rgba(197,74,58,0.32); }
}
/* When the dropdown changes but auto-walk isn't running, briefly preview
   the new tempo so the user sees that the dropdown does something. */
#rw-act-play.tempo-preview {
    animation: rw-tempo-preview var(--tempo-ms, 1.1s) ease-in-out 1;
    border-color: var(--accent);
    color: var(--accent);
}
@keyframes rw-tempo-preview {
    0%, 100% { box-shadow: 0 0 0 0 rgba(197,74,58,0); }
    50%      { box-shadow: 0 0 0 6px rgba(197,74,58,0.4); }
}
@media (prefers-reduced-motion: reduce) {
    #rw-act-play[data-mode="playing"],
    #rw-act-play.tempo-preview { animation: none; }
}
.rw-pause-round {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.88em;
    color: var(--muted);
    border: 1px solid var(--border);
    padding: 0.16rem 0.5rem;
    cursor: pointer;
    user-select: none;
}
.rw-pause-round:hover { color: var(--fg); }
.rw-pause-round input { margin: 0; cursor: pointer; }
.rw-pause-round.hidden { display: none; }

/* ---- Pause: float + dim + bot decision ---- */

body.game-paused .stack-card {
    animation: paused-float 2.6s ease-in-out infinite alternate;
}
body.game-paused .stack-card:nth-child(2n)  { animation-delay: -0.7s; }
body.game-paused .stack-card:nth-child(3n)  { animation-delay: -1.4s; }
body.game-paused .stack-card:nth-child(4n)  { animation-delay: -2.0s; }

@keyframes paused-float {
    from { transform: translateY(0); }
    to   { transform: translateY(-5px); }
}

#phase-banner.paused-banner::after {
    content: ' (paused)';
    color: var(--accent);
    font-weight: 600;
}

.bot-decision {
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    background: var(--hover);
    padding: 0.6rem 0.85rem;
    margin: 0.6rem 0;
    font-size: 0.88em;
    animation: fade-in 0.3s ease-out;
}
.bot-decision .bd-header {
    font-weight: 600;
    color: var(--accent);
    margin-bottom: 0.3rem;
}
.bot-decision .bd-mode {
    color: var(--muted);
    margin-bottom: 0.3rem;
}
.bot-decision .bd-list {
    margin: 0.3rem 0 0;
    padding: 0;
    list-style: none;
    font-family: inherit;
}
.bot-decision .bd-list li {
    padding: 0.15rem 0;
    color: var(--muted);
}
.bot-decision .bd-list li.bd-pick {
    color: var(--fg);
    font-weight: 700;
}
.bot-decision .bd-list li.bd-pick::before {
    content: '★ ';
    color: var(--accent);
}
.bot-decision .bd-list li .bd-pts {
    color: var(--accent);
    font-weight: 600;
}
.bot-decision code {
    background: var(--bg);
    border: 1px solid var(--border);
    padding: 0.05rem 0.4rem;
    font-family: inherit;
    letter-spacing: 0.04em;
}
.bot-decision .bd-help {
    margin-top: 0.4rem;
    color: var(--muted);
    font-size: 0.85em;
}
.bd-reasons { margin-left: 0.4em; }
.bd-good     { color: var(--accent); }
.bd-bad      { color: #c54a3a; opacity: 0.85; }
.bd-neutral  { color: var(--muted); }
:root[data-theme="dark"] .bd-bad { color: #f06fa0; }
.bot-decision .bd-history {
    margin: 0.3rem 0 0.6rem;
    padding-left: 1.4rem;
    font-family: inherit;
}
.bot-decision .bd-history li { padding: 0.1rem 0; }
.bot-decision .bd-details { margin-top: 0.4rem; }
.bot-decision .bd-details summary { cursor: pointer; }

/* ---- "how to play" inline link ---- */

.link-inline {
    background: none;
    border: 0;
    padding: 0;
    color: var(--accent);
    font-family: inherit;
    font-size: inherit;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-style: dotted;
    text-underline-offset: 2px;
    transition: opacity 0.15s;
}
.link-inline:hover { opacity: 0.7; background: none; color: var(--accent); }

/* ---- Inline leaderboard (lobby bottom) ---- */

.lobby-leaderboard {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
    border-top: 1px dashed var(--border);
}
.ll-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
}
.ll-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.05em;
}
#ll-month { font-size: 0.78em; font-weight: 400; margin-left: 0.5rem; }
.ll-tabs {
    display: flex;
    gap: 0.3rem;
    border-bottom: 1px solid var(--border);
}
.ll-tab {
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    padding: 0.3rem 0.6rem;
    color: var(--muted);
    font-family: inherit;
    font-size: 0.88em;
    font-weight: 600;
    cursor: pointer;
}
.ll-tab:hover { color: var(--fg); background: transparent; }
.ll-tab.on { color: var(--fg); border-bottom-color: var(--accent); }
.ll-footer { font-size: 0.78em; margin-top: 0.75rem; }
.lb-content-inline { min-height: 4em; }

/* Compact preview: "full leaderboard →" entry point + a gap row + a
   create-account nudge when the viewer isn't ranked. */
.ll-open-full {
    background: none;
    border: 0;
    color: var(--accent);
    font-family: inherit;
    font-size: 0.85em;
    font-weight: 600;
    cursor: pointer;
    padding: 0.2rem 0;
    white-space: nowrap;
}
.ll-open-full:hover { text-decoration: underline; }
.lb-preview-gap {
    text-align: center;
    color: var(--muted);
    line-height: 0.8;
    padding: 0.15rem 0;
    letter-spacing: 0.2em;
}
.lb-preview-note {
    margin-top: 0.4rem;
    font-size: 0.8em;
    cursor: pointer;
}
.lb-preview-note:hover { color: var(--fg); text-decoration: underline; }

/* ---- Animated total + 31 effect ---- */

.stack-total {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    position: relative;       /* above the stitched ::before */
    z-index: 1;
    margin-top: 0.3rem;
    padding: 0.15rem 0.5rem;
    background: rgba(0,0,0,0.05);
    border-radius: 4px;
    width: fit-content;
}
:root[data-theme="dark"] .stack-total {
    background: rgba(255,255,255,0.05);
}
.peg-total {
    font-weight: 700;
    font-size: 1.2em;
    color: var(--muted);
    transition: color 0.4s;
    display: inline-block;
}
.peg-total.tier-cool    { color: var(--muted); }
.peg-total.tier-warming { color: #c89a64; }
.peg-total.tier-warm    { color: #e8a040; }
.peg-total.tier-hot     { color: #e8623a; font-weight: 800; }
.peg-total.tier-31 {
    color: var(--accent);
    animation: count-31-flash 0.7s ease-out;
}
@keyframes count-31-flash {
    0%   { transform: scale(1);   text-shadow: none; }
    25%  { transform: scale(1.7); text-shadow: 0 0 26px var(--accent); }
    60%  { transform: scale(1.15); text-shadow: 0 0 14px var(--accent); }
    100% { transform: scale(1);   text-shadow: none; }
}
.peg-total.tier-15 {
    color: #f5c542;
    animation: count-15-blip 0.45s ease-out;
}
@keyframes count-15-blip {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.3); }
    100% { transform: scale(1); }
}

/* ---- Pause button states ---- */

/* Leave-room button: prominent, distinct */
.gh-divider {
    width: 1px;
    align-self: stretch;
    background: var(--border);
    margin: 0 0.2rem;
}
/* Leave button — accent-coloured outline, slightly larger than other
   header buttons so it reads as the primary exit action without
   shouting. Hover swaps to a solid accent fill. */
.leave-prominent {
    color: var(--accent) !important;
    border-color: var(--accent) !important;
    border-width: 1.5px !important;
    background: var(--hover) !important;
    font-weight: 700 !important;
    padding: 0.35rem 0.85rem !important;
    letter-spacing: 0.03em;
}
.leave-prominent:hover:not(:disabled) {
    background: var(--accent) !important;
    color: var(--bg) !important;
    box-shadow: 0 0 12px rgba(197,74,58,0.4);
}

/* (Legacy .rw-pause and .rw-sep rules removed — replaced by the new
   two-row replay-controls block above.) */

/* ---- Mode stack (lobby) ---- */

.mode-stack {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    margin: 1.25rem 0;
}
.mode-card {
    border: 1px solid var(--glass-border);
    padding: 1rem 1.1rem;
    /* True glass — see-through enough that the card-rain + cycle
       gradient read through the panel as soft texture, not a
       muted backdrop. Tokens live on :root so light/dark both feel
       like the same material. */
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-highlight);
    transition: border-color 0.15s, background 0.2s, transform 0.15s;
    border-radius: 12px;
}
.mode-card:hover { border-color: currentColor; }
.mode-card-row {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    flex-wrap: wrap;
}
.mode-card-text { flex: 1 1 220px; min-width: 0; }
.mode-card-actions {
    display: flex;
    gap: 0.4rem;
    align-items: center;
    flex-wrap: wrap;
}
/* AI mode-card layout: three "vs" buttons in one row, watch-bots full-width
   below. Keeps related actions grouped + avoids the awkward 3-then-1 wrap
   we had on mobile when watch-bots flowed onto its own line. */
.ai-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 0.35rem;
}
.ai-vs-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.35rem;
}
.ai-vs-row .share-pair,
.ai-vs-row button { width: 100%; }
.ai-actions .share-pair-wide,
.ai-actions .watch-btn { width: 100%; }
.mp-entry-actions { gap: 0.5rem; }

/* Share-pair: a play button + a small share-tag button sitting flush
   together. The share button is the rightmost "icon-only" cap so it
   doesn't crowd the primary action. */
.share-pair {
    display: inline-flex;
    align-items: stretch;
    gap: 0;
}
.share-pair > button:first-child { flex: 1 1 auto; border-right: 0 !important; }
.share-tag {
    flex: 0 0 auto;
    background: transparent;
    color: var(--muted);
    border: 1px solid var(--border);
    border-left: 0;
    padding: 0 0.45rem;
    font-size: 0.9em;
    cursor: pointer;
    font-family: inherit;
    transition: color 0.15s, background 0.15s;
}
.share-tag:hover {
    color: var(--accent);
    background: var(--hover);
}

/* Direct-links tab inside the how-to-play popover. */
.rules-shares {
    list-style: none;
    padding: 0;
    margin: 0 0 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}
.rules-shares li {
    display: grid;
    grid-template-columns: minmax(120px, auto) 1fr auto;
    gap: 0.55rem;
    align-items: center;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--border);
    background: var(--hover);
}
.rs-mode { font-weight: 600; }
.rs-url {
    color: var(--muted);
    font-size: 0.82em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    user-select: all;          /* triple-click selects, easy manual copy too */
}
.rs-share {
    background: var(--accent);
    color: var(--bg);
    border: 0;
    padding: 0.35rem 0.6rem;
    font-family: inherit;
    font-size: 0.85em;
    font-weight: 600;
    cursor: pointer;
    letter-spacing: 0.02em;
}
.rs-share:hover { filter: brightness(1.1); }
.rules-shares-note { font-size: 0.85em; margin-top: 0.6rem; }
@media (max-width: 600px) {
    .rules-shares li { grid-template-columns: 1fr auto; gap: 0.3rem; }
    .rs-mode { grid-column: 1 / -1; }
    .rs-url { grid-column: 1 / -1; font-size: 0.78em; }
    .rs-share { grid-column: 2; grid-row: 1; }
}
.mode-title {
    margin: 0 0 0.2rem;
    font-weight: 600;
    font-size: 1rem;
    letter-spacing: 0.05em;
}
.mode-blurb { color: var(--muted); font-size: 0.85em; margin: 0; }
.mode-blurb strong { color: var(--fg); font-weight: 600; }

.join-or { font-size: 0.85em; padding: 0 0.2rem; }
.join-row {
    display: flex;
    gap: 0.4rem;
    align-items: stretch;
}
.join-row input { width: 6.5rem; }
.join-row button { white-space: nowrap; padding: 0.45rem 0.9rem; }

/* ---- Room (expanded mp card) ---- */

.mp-room-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    padding-bottom: 0.7rem;
    margin-bottom: 0.8rem;
    border-bottom: 1px dashed var(--border);
}
.mp-room-header .mode-title { margin: 0; flex: 1; }
.mp-room-header code {
    background: var(--hover);
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--border);
    letter-spacing: 0.2em;
    font-size: 0.9em;
    margin-left: 0.3rem;
}
.mp-room-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }

/* ---- Player cards (grid) ---- */

.player-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.6rem;
    margin: 0.5rem 0 1rem;
}
.player-card {
    border: 1px solid var(--border);
    padding: 0.7rem 0.85rem;
    background: var(--bg);
    position: relative;
    transition: border 0.15s, transform 0.15s;
    min-height: 5.5em;
}
.player-card.has-player { border-color: currentColor; }
.player-card.is-me { background: var(--selected); border-color: var(--accent); }
.player-card.empty {
    color: var(--muted);
    border-style: dashed;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.88em;
}
.player-card.empty::before { content: '+ '; opacity: 0.6; }
.pc-header {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin-bottom: 0.3rem;
}
.pc-avatar { font-size: 1.45rem; line-height: 1; }
.pc-name { font-weight: 700; font-size: 1rem; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-host { color: var(--accent); }
.pc-hints {
    cursor: help;
    font-size: 1.05em;
    line-height: 1;
    animation: hints-bob 1.8s ease-in-out infinite;
}
@keyframes hints-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-2px); }
}
.pc-frog {
    cursor: help;
    font-size: 1.05em;
    line-height: 1;
    animation: frog-hop 1.1s ease-in-out infinite;
}
@keyframes frog-hop {
    0%, 60%, 100% { transform: translateY(0) rotate(0deg); }
    20%           { transform: translateY(-4px) rotate(-8deg); }
    40%           { transform: translateY(0) rotate(4deg); }
}

/* Hints-ineligible warning under the hint toggle */
#hint-warn {
    display: block;
    margin-top: 0.4rem;
    font-size: 0.78em;
    color: var(--accent);
    background: rgba(197,74,58,0.08);
    border: 1px dashed var(--accent);
    padding: 0.3rem 0.5rem;
    line-height: 1.35;
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity 0.2s, transform 0.2s;
    pointer-events: none;
    max-width: 280px;
}
#hint-warn.show { opacity: 1; transform: translateY(0); }
:root[data-theme="dark"] #hint-warn { background: rgba(240,160,96,0.08); }

/* Frog mode: dance the entire board a little */
body.has-frog #board::before {
    content: '🐸';
    position: absolute;
    top: -1.2rem;
    right: -0.4rem;
    font-size: 1.4rem;
    animation: frog-hop 0.9s ease-in-out infinite;
    pointer-events: none;
}
body.has-frog #board { position: relative; }

.pc-tag {
    font-size: 0.72em;
    color: var(--muted);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border: 1px solid var(--border);
    padding: 0.05rem 0.3rem;
}
.pc-meta {
    display: flex;
    gap: 0.5rem;
    font-size: 0.78em;
    color: var(--muted);
    margin-bottom: 0.25rem;
    flex-wrap: wrap;
}
.pc-rank.gold   { color: #f5c542; font-weight: 700; }
.pc-rank.silver { color: #c8c8c8; font-weight: 700; }
.pc-rank.bronze { color: #c8884a; font-weight: 700; }
.pc-rank { color: var(--accent); font-weight: 600; }
.pc-badges {
    font-size: 1.05em;
    line-height: 1;
    display: flex;
    gap: 0.18rem;
    flex-wrap: wrap;
}
.pc-badges .pc-badge { cursor: help; }
.player-card.talking { border-color: var(--accent); box-shadow: 0 0 12px rgba(197,74,58,0.3); }
.player-card.talking .pc-name::after {
    content: ' ♪';
    color: var(--accent);
    animation: pulse 0.7s ease-in-out infinite;
}
.player-card.disconnected .pc-name { opacity: 0.5; text-decoration: line-through; }

/* Spectator badge in header */
.spectator-tag {
    display: inline-block;
    padding: 0.1rem 0.4rem;
    border: 1px solid var(--accent);
    color: var(--accent);
    font-size: 0.72em;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-left: 0.5rem;
}

/* ---- Chat panel ---- */

.chat-panel {
    margin-top: 2rem;
    border-top: 1px dashed var(--border);
    padding-top: 1rem;
}
.chat-messages {
    max-height: 220px;
    min-height: 80px;
    overflow-y: auto;
    border: 1px solid var(--border);
    padding: 0.5rem 0.75rem;
    font-size: 0.88em;
    line-height: 1.4;
    margin-bottom: 0.5rem;
    background: var(--hover);
}
.chat-msg { margin: 0.15rem 0; }
.chat-msg .chat-name { font-weight: 600; margin-right: 0.4em; }
.chat-msg .chat-time { color: var(--muted); font-size: 0.85em; margin-right: 0.4em; }
.chat-msg.chat-sys {
    color: var(--muted);
    font-style: italic;
    font-size: 0.92em;
    padding: 0.05rem 0;
    opacity: 0.85;
}
.chat-msg.chat-sys .chat-sys-text { font-style: italic; }
.chat-input { display: flex; gap: 0.4rem; }
.chat-input input { flex: 1; min-width: 0; }

/* ---- Voice button states ---- */

.voice-btn { transition: color 0.15s, border-color 0.15s; }
.voice-btn.on {
    color: var(--accent);
    border-color: var(--accent);
}
.voice-btn.on::before {
    content: '● ';
    animation: pulse 1.5s ease-in-out infinite;
}
.voice-talking::after {
    content: ' ♪';
    color: var(--accent);
    animation: pulse 0.8s ease-in-out infinite;
}

/* Voice flagged off via FLAGS.voice_enabled — greyed, no-pointer, no animation. */
body.voice-disabled .voice-btn {
    opacity: 0.45;
    cursor: not-allowed;
    border-color: var(--muted);
    color: var(--muted);
}
body.voice-disabled .voice-btn:hover { background: transparent; }
body.voice-disabled .voice-btn.on,
body.voice-disabled .voice-btn::before,
body.voice-disabled .voice-talking::after { content: none; animation: none; }

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

/* ---- Animations: card hover + active player + score popup ---- */

.card-button {
    transition: transform 0.12s ease, background 0.1s, border 0.1s, opacity 0.15s;
}
.card-button:hover:not(.disabled) { transform: translateY(-4px); background: var(--hover); }
.card-button.selected { transform: translateY(-2px); }
.card-button.just-played {
    animation: card-pop 0.5s ease-out;
}
/* Discard "poof" — card lifts, twirls, shrinks, fades. Used by
   discardPoof() for cards leaving the hand WITHOUT being played
   (discards to the crib, hand-clears between rounds). */
.discard-poof {
    animation: discard-poof 0.72s cubic-bezier(0.4, 0, 0.7, 1) forwards;
    transform-origin: center;
}
@keyframes discard-poof {
    0%   { transform: scale(1)    rotate(0deg)   translateY(0);      opacity: 1; }
    20%  { transform: scale(1.08) rotate(-4deg)  translateY(-6px);   opacity: 1; }
    100% { transform: scale(0.18) rotate(220deg) translateY(56px);   opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .discard-poof { animation: discard-poof-fade 0.3s ease-out forwards; }
}
@keyframes discard-poof-fade {
    0%   { opacity: 1; }
    100% { opacity: 0; }
}
@keyframes card-pop {
    0% { transform: scale(0.85) translateY(-8px); opacity: 0; }
    60% { transform: scale(1.05) translateY(-2px); opacity: 1; }
    100% { transform: scale(1) translateY(0); opacity: 1; }
}

#phase-banner.your-turn {
    animation: phase-pulse 1.5s ease-in-out infinite;
}
@keyframes phase-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

#score-popup {
    position: fixed;
    top: 32%;
    left: 50%;
    transform: translate(-50%, 0);
    font-size: clamp(1.4rem, 4vw, 2.4rem);
    font-weight: 600;
    color: var(--accent);
    pointer-events: none;
    opacity: 0;
    z-index: 100;
    text-shadow: 0 2px 24px var(--bg);
    white-space: nowrap;
}
#score-popup.show {
    animation: popup 1.8s ease-out;
}

#counting-area:empty { display: none; }
#counting-area { margin: 0; }

/* ============================================================
   Game core layout: peg board + pegging arena on the left,
   scoreboard on the right, side-by-side INSIDE the central
   content column (not edge-anchored). This keeps the page
   centred and visually balanced — the pair reads as one unit.
   Hands / buttons / chat continue full-width below.
   On narrow screens the row collapses to a single column,
   preserving peg-board → scoreboard → pegging-arena order.
   ============================================================ */
.game-board-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) clamp(260px, 26vw, 360px);
    gap: 1rem;
    align-items: start;
    margin: 0.5rem 0;
}
.game-board-col {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}
.game-board-row > #counting-area {
    position: sticky;
    top: 0.5rem;
    align-self: start;
    max-height: calc(100vh - 1rem);
    overflow-y: auto;
}
@media (max-width: 1100px) {
    /* Collapse the sub-grid into a flex column. `display: contents`
       on .game-board-col unboxes the column so its children (board
       + play-row) become flex items of .game-board-row alongside
       the scoreboard — letting CSS `order` give us:
         peg board → scoreboard → pegging arena
       on phones and tablets. */
    .game-board-row {
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
    }
    .game-board-col { display: contents; }
    .game-board-col > #board       { order: 1; }
    .game-board-row > #counting-area {
        order: 2;
        position: static;
        max-height: none;
        overflow: visible;
    }
    .game-board-col > .play-row    { order: 3; }
}

/* Mobile: hide the ASCII peg board entirely. The race-track widget
   above it already visualises player progress more compactly, and
   stacking both eats too much vertical space before the user sees
   their cards. */
@media (max-width: 700px) {
    #board { display: none !important; }
    /* On touch the per-runner hover chip is unreadable; the
       #race-scoreboard row below the track surfaces every score
       persistently. Suppress the floating chip on mobile entirely
       (keep the +N delta floater — that's a separate element that
       still pops on score changes). */
    .race-runner-score { display: none; }
}

/* Mobile-only compact horizontal scoreboard. Sits directly under
   #race-track and lists every player's current score, sorted desc so
   the leader sits on the left. Hidden on desktop where the ASCII peg
   board + hover chips already cover it. */
.race-scoreboard {
    display: none;
}
.race-scoreboard.hidden { display: none !important; }
@media (max-width: 700px) {
    .race-scoreboard {
        display: flex;
        flex-wrap: wrap;
        gap: 0.3rem 0.5rem;
        padding: 0.35rem 0.55rem;
        margin: 0.25rem 0 0.4rem;
        font-variant-numeric: tabular-nums;
        font-size: 0.78rem;
    }
}
.race-scoreboard .sb-entry {
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
    padding: 0.18rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--glass-bg);
    backdrop-filter: blur(18px) saturate(1.6);
    -webkit-backdrop-filter: blur(18px) saturate(1.6);
}
.race-scoreboard .sb-entry.you {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent) inset;
}
.race-scoreboard .sb-entry.active::after {
    content: '●';
    color: var(--accent);
    font-size: 0.7em;
    margin-left: 0.1rem;
    animation: race-runner-bump 1.2s ease-in-out infinite;
}
.race-scoreboard .sb-entry.skunked {
    opacity: 0.7;
    border-style: dashed;
}
.race-scoreboard .sb-avatar { font-size: 0.9rem; }
.race-scoreboard .sb-name {
    max-width: 7rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.race-scoreboard .sb-score {
    font-weight: 600;
    color: var(--fg);
}

/* Mobile compact scoreboard — only during DEALING/DISCARDING/PEGGING
   (count-running). Flow per-player rows into a horizontal chip strip
   instead of vertical rows. Counting/round_over breakdown stays
   vertical (cards + combos need the room). */
@media (max-width: 700px) {
    #counting-area.count-running .count-rows-running {
        display: flex;
        flex-wrap: wrap;
        gap: 0.4rem 0.6rem;
    }
    #counting-area.count-running .count-row-mini {
        display: inline-flex;
        flex-direction: row;
        align-items: baseline;
        gap: 0.25rem 0.4rem;
        padding: 0.25rem 0.55rem;
        border: 1px solid var(--border);
        border-bottom: 1px solid var(--border);
        border-radius: 6px;
        font-size: 0.88em;
        flex: 0 1 auto;
    }
    #counting-area.count-running .count-row-mini.active {
        border-left: 2px solid var(--accent);
        padding-left: 0.45rem;
    }
    /* Pegging log stays as a multi-column block; constrain on mobile
       so it doesn't overflow the scoreboard. */
    .peg-log-list { columns: 9ch auto; max-height: 14em; }
}
@keyframes popup {
    0%   { opacity: 0; transform: translate(-50%, 12px) scale(0.7); }
    18%  { opacity: 1; transform: translate(-50%, -16px) scale(1.12); }
    60%  { opacity: 1; transform: translate(-50%, -56px) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -120px) scale(0.95); }
}

#voice-audio-sink { position: absolute; left: -9999px; }

/* Pegging stack with full-size cards + slide-in animation */

/* ============================================================
   Race-track widget — horizontal score race toward 121.

   Reads game state directly: each runner's left position is score/121.
   Slides smoothly via CSS transition when score changes (no FLIP needed —
   the transform animates from current to new on each render). Skunk
   zones (<90, <60) shaded in red. Leader gets a 👑 crown badge. The
   widget is purely declarative: renderRaceTrack(state) re-positions
   runners on every render and they animate to the new position.
   ============================================================ */
.race-track {
    position: relative;
    height: 3.2rem;
    margin: 0.4rem 0 0.6rem;
    padding: 0 1.6rem;
    user-select: none;
}
.race-track.hidden { display: none; }
.race-track-line {
    position: absolute;
    left: 1.6rem; right: 1.6rem;
    top: 50%;
    height: 2px;
    background: var(--border);
    border-radius: 1px;
    transform: translateY(-50%);
}
.race-zone {
    position: absolute;
    top: 50%;
    height: 8px;
    transform: translateY(-50%);
    border-radius: 4px;
    pointer-events: none;
}
.race-zone-doubleskunk {
    left: 1.6rem;
    width: calc((100% - 3.2rem) * 60 / 121);
    background: rgba(255, 80, 80, 0.18);
}
.race-zone-skunk {
    left: 1.6rem;
    width: calc((100% - 3.2rem) * 90 / 121);
    background: rgba(255, 160, 80, 0.10);
}
.race-mark {
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    font-size: 0.62em;
    color: var(--muted);
    letter-spacing: 0.04em;
    pointer-events: none;
}
.race-mark span {
    display: inline-block;
    padding: 0.05rem 0.3rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
}
.race-mark-60  { left: calc(1.6rem + (100% - 3.2rem) *  60 / 121); }
.race-mark-90  { left: calc(1.6rem + (100% - 3.2rem) *  90 / 121); }
.race-mark-121 { left: calc(100% - 1.6rem); }
.race-mark-121 span { background: linear-gradient(135deg, rgba(232,184,66,0.25), transparent); font-size: 1.1em; }

.race-runners {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.race-runner {
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    /* Smooth slide on score-change; opacity fades when this runner gets
       layered underneath a more-active one. */
    transition: left 700ms cubic-bezier(0.34, 1.4, 0.64, 1),
                opacity 350ms ease,
                filter 350ms ease;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.05rem;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}
/* Underneath runners fade so the top one reads. The accent runner ("you")
   has a higher floor so you can always spot yourself even when buried. */
.race-runner.layer-1 { opacity: 0.55; }
.race-runner.layer-2 { opacity: 0.32; filter: blur(0.3px); }
.race-runner.layer-3 { opacity: 0.22; filter: blur(0.5px); }
.race-runner.you.layer-1 { opacity: 0.75; }
.race-runner.you.layer-2 { opacity: 0.6; }
.race-runner.you.layer-3 { opacity: 0.5; }
/* Hover any runner to bring it forward — handy when several have clustered
   to similar scores and you want to read one that's underneath. */
.race-runner:hover {
    opacity: 1 !important;
    filter: none !important;
    z-index: 99 !important;
}
.race-runner:hover .race-runner-name { color: var(--accent); }
/* Active-turn marker — a small pulsing dot above the runner whose turn it
   is. Replaces the "who's up" hint that the layer ordering implies. */
.race-runner.active::after {
    content: '';
    position: absolute;
    top: -0.7rem;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 6px var(--accent);
    animation: race-active-pulse 1.4s ease-in-out infinite;
}
@keyframes race-active-pulse {
    0%,100% { transform: scale(0.85); opacity: 0.7; }
    50%     { transform: scale(1.15); opacity: 1;   }
}
/* Brief "+N" floater above a runner who just scored. Created and removed
   by renderRaceTrack — fades up and out over 1.6s. */
.race-runner-delta {
    position: absolute;
    top: -1.6rem;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--accent);
    background: var(--bg);
    border: 1px solid var(--accent);
    border-radius: 8px;
    padding: 0.05rem 0.4rem;
    white-space: nowrap;
    animation: race-delta-float 1.6s ease-out forwards;
    pointer-events: none;
}
@keyframes race-delta-float {
    0%   { transform: translateY(6px); opacity: 0; }
    20%  { transform: translateY(0);   opacity: 1; }
    80%  { transform: translateY(-14px); opacity: 1; }
    100% { transform: translateY(-22px); opacity: 0; }
}
.race-runner-avatar {
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.25));
    transition: transform 200ms ease;
}
.race-runner.leader .race-runner-avatar { transform: scale(1.15); }
.race-runner.leader::before {
    content: '👑';
    position: absolute;
    top: -1.1rem;
    font-size: 0.85rem;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.25));
}
.race-runner.skunked .race-runner-avatar {
    filter: drop-shadow(0 1px 2px rgba(255,80,80,0.5)) grayscale(0.4);
    animation: race-runner-worried 1.4s ease-in-out infinite;
}
@keyframes race-runner-worried {
    0%,100% { transform: translateY(0); }
    50%     { transform: translateY(1px); }
}
.race-runner-name {
    font-size: 0.55rem;
    color: var(--muted);
    letter-spacing: 0.03em;
    white-space: nowrap;
    max-width: 6rem;
    overflow: hidden;
    text-overflow: ellipsis;
}
.race-runner.you .race-runner-name { color: var(--accent); font-weight: 600; }
.race-runner-score {
    position: absolute;
    top: -1.7rem;
    font-size: 0.62rem;
    font-variant-numeric: tabular-nums;
    color: var(--fg);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.05rem 0.3rem;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 220ms ease;
}
.race-runner:hover .race-runner-score,
.race-runner.recent-score .race-runner-score { opacity: 1; }
@keyframes race-runner-bump {
    0%   { transform: translate(-50%, -50%) scale(1); }
    50%  { transform: translate(-50%, -50%) scale(1.25); }
    100% { transform: translate(-50%, -50%) scale(1); }
}
.race-runner.bump { animation: race-runner-bump 0.6s ease-out; }
@media (prefers-reduced-motion: reduce) {
    .race-runner { transition: none; }
    .race-runner.skunked .race-runner-avatar { animation: none; }
    .race-runner.bump { animation: none; }
}

/* The pegging area is the heart of the play surface — a sunken "table well"
   that grounds the live stack + completed piles + starter as one cohesive
   playing field. Felt-style background with stitched inner border and a
   faint diagonal weave, all CSS — keeps the ASCII card-frame aesthetic but
   makes the area feel like an actual cribbage board, not just an HTML row. */
#pegging-area {
    position: relative;
    margin: 0.75rem 0 1rem;
    /* Extra top + bottom padding gives rotated seq-pile cards + their
       shadow + the seq-tag enough room to sit inside the felt without
       crossing the stitched border. */
    padding: 1.1rem 1.1rem 1.4rem;
    /* Reserve enough vertical footprint that the felt stays the same size
       through all phases: pre-game / dealing / discarding / pegging /
       counting. Stops the layout from popping when the stack drains or
       comes back, which previously made the "starter & pegging" label
       and the surrounding UI flicker. */
    min-height: 9rem;
    /* `overflow: clip` is like `hidden` but doesn't create a new scrolling
       context. Cards/clones that exceed the felt's bounds (e.g. an entry
       keyframe overshooting, a wide pile on a small viewport) get trimmed
       at the rounded corner rather than spilling. FLIP clones live on
       <body> with position:fixed so this doesn't affect them. */
    overflow: clip;
    background: var(--felt);
    /* Crosshatch suggesting felt weave — barely-visible diagonal stitching */
    background-image:
        repeating-linear-gradient(45deg,
            transparent 0 4px,
            var(--felt-edge) 4px 5px),
        repeating-linear-gradient(-45deg,
            transparent 0 4px,
            var(--felt-edge) 4px 5px),
        radial-gradient(ellipse 70% 100% at 50% 0%,
            rgba(0,0,0,0.06), transparent 75%);
    background-blend-mode: multiply, multiply, normal;
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow:
        inset 0 0 28px var(--felt-edge),
        inset 0 1px 0 rgba(255,255,255,0.05),
        0 1px 2px rgba(0,0,0,0.08);
}
/* Stitched inner border — a classic card-table detail. Dashed line one
   step in from the edge, low contrast so it reads as embroidered. */
#pegging-area::before {
    content: '';
    position: absolute;
    inset: 5px;
    border: 1px dashed var(--felt-stitch);
    border-radius: 6px;
    pointer-events: none;
}
.pegging-label {
    margin-bottom: 0.45rem;
    font-size: 0.78em;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    position: relative;     /* sit above the ::before stitch line */
}
.stack-cards {
    display: flex;
    gap: 0.4rem;
    align-items: flex-start;
    flex-wrap: nowrap;
    min-height: 0;
}
.stack-card {
    display: inline-block;
    transform-origin: center top;
    background: var(--bg);    /* opaque so overlapping cards don't bleed through */
    position: relative;       /* sit above #pegging-area::before stitch border */
    /* Layered shadow for a "thrown on felt" look — short contact shadow
       + softer ambient shadow. Larger radius reads as a real card with
       rounded corners, not just a div with a border. */
    box-shadow:
        0 1px 1px rgba(60,40,20,0.18),
        0 3px 10px rgba(60,40,20,0.10),
        0 6px 20px rgba(60,40,20,0.05);
    border-radius: 6px;
    /* Inline CSS vars (set by JS in renderPeggingArea) give each played
       card a tiny deterministic rotation + lateral offset. Reads as
       "thrown onto the table" rather than precisely placed. */
    transform: translate(var(--play-dx, 0), 0) rotate(var(--play-rot, 0deg));
}
.stack-card pre {
    margin: 0;
    font-family: inherit;
    line-height: 1.15;
    color: var(--fg);
}
.stack-card.red pre { color: var(--accent); }
.stack-card.just-played {
    animation: card-throw 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes card-throw {
    /* Start within the felt's top padding — pre-fix this was -32px which
       briefly pushed the card above the play-surface's top edge during
       the throw, looking like the card border bled past the felt. */
    0% {
        transform: translateY(-14px) translateX(6px) rotate(-5deg) scale(1.04);
        opacity: 0;
    }
    55% {
        transform: translateY(2px) translateX(-1px) rotate(1deg) scale(1);
        opacity: 1;
    }
    100% {
        transform: translateY(0) translateX(0) rotate(0deg) scale(1);
        opacity: 1;
    }
}
.stack-total {
    margin-top: 0.5rem;
    font-size: 0.92em;
}

/* Starter + pegging on one row. The starter card sits as the round anchor;
   the pegging block (completed piles + live stack) flexes to its right. */
.play-row {
    display: flex;
    align-items: flex-start;
    gap: 0.9rem;
    margin: 0.5rem 0;
    overflow: visible;
}
.play-row #starter-area { flex: 0 0 auto; margin: 0; }
.play-row #pegging-area { flex: 1 1 auto; margin: 0; min-width: 0; }

/* The starter card now lives inside .stack-completed (first child), so it
   shares a flex row with the completed piles. Margin keeps it visually
   separated from the first pile. */
#starter-area.starter-anchor {
    flex: 0 0 auto;
    margin: 0 0.4rem 0 0;
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
}
.starter-label {
    font-size: 0.65em;
    color: var(--muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-family: inherit;
}
.starter-card {
    /* The starter card is the cut card planted at the head of each round.
       Opaque bg + heavier layered shadow than live-stack cards so it
       reads as fixed/embedded rather than in-play. */
    background: var(--bg);
    color: var(--fg);
    margin: 0;
    padding: 0;
    line-height: 1.15;
    font-family: inherit;
    border-radius: 6px;
    box-shadow:
        0 1px 1px rgba(60,40,20,0.22),
        0 3px 8px rgba(60,40,20,0.14),
        0 6px 18px rgba(60,40,20,0.06);
    overflow: hidden;
}
.starter-card.red { color: var(--accent); }

/* The crib pile: face-down discards accumulating in the dealer's crib.
   Sits next to the starter, before any completed pegging sequences, so
   the visual order is "starter → crib pile → completed pegging piles →
   live stack". Each discard layers a face-down card on top with a
   slight stagger so the eye reads it as a small messy stack, not a
   single card. Hidden when empty. */
/* Far-right card deck — visual stack of face-down cards that lives at
   the end of the pegging-arc row (starter → crib pile → seq piles →
   live stack → deck). Each .deck-card is absolutely positioned with a
   --i index so JS can grow/shrink the stack without rebuilding the DOM.
   The deck is decorative + supports a round-start fly-out animation. */
#card-deck-area.card-deck-anchor {
    flex: 0 0 auto;
    margin-left: auto;     /* push to far right of the .stack-row */
    margin-right: 0.2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    position: relative;
    z-index: 1;
}
.card-deck-label {
    font-size: 0.65em;
    color: var(--muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    order: 2;              /* below the stack */
}
#card-deck {
    position: relative;
    width: 7ch;
    height: 5.6em;
    line-height: 1.15;
    cursor: default;
    transition: transform 0.25s ease;
}
#card-deck.deck-shuffling { animation: deck-shuffle 1s ease-in-out; }
@keyframes deck-shuffle {
    0%, 100% { transform: rotate(0deg); }
    20%      { transform: rotate(-3deg) translateY(-2px); }
    50%      { transform: rotate(3deg)  translateY(0); }
    80%      { transform: rotate(-2deg) translateY(-1px); }
}
.deck-card {
    position: absolute;
    top: 0;
    left: 0;
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--muted);
    line-height: 1.15;
    font-family: inherit;
    border-radius: 6px;
    box-shadow:
        0 1px 1px rgba(60,40,20,0.18),
        0 2px 4px rgba(60,40,20,0.10);
    /* Each card stacked slightly up & right of the one below it so the
       column reads as a deck rather than a single card. --i is set by
       JS (0 = bottom of pile, larger = closer to the top). */
    --i: 0;
    transform: translate(calc(var(--i) * 1.5px), calc(var(--i) * -1.2px));
}
/* Fly-out: one card jumps out of the deck in a randomized arc and
   fades. CSS vars set per-spawn give each card a unique trajectory. */
.deck-card.deck-fly {
    pointer-events: none;
    /* Slowed ~50% — round-start choreography breathes more now. */
    animation: deck-fly 1s cubic-bezier(0.34, 1.35, 0.64, 1) forwards;
    z-index: 99998;
}
@keyframes deck-fly {
    /* Wider arc: cards travel further, spin more, and end smaller so
       the screen-wide storm reads as cards scattering across the table
       rather than a small puff over the deck. */
    0%   { transform: translate(0, 0) rotate(0deg) scale(1);   opacity: 1; }
    25%  { transform: translate(calc(var(--fly-x) * 0.35), calc(var(--fly-y) * 0.35)) rotate(calc(var(--fly-r) * 0.4)) scale(1.05); opacity: 1; }
    75%  { transform: translate(calc(var(--fly-x) * 0.92), calc(var(--fly-y) * 0.92)) rotate(calc(var(--fly-r) * 0.9)) scale(0.95); opacity: 1; }
    100% { transform: translate(var(--fly-x), var(--fly-y)) rotate(var(--fly-r)) scale(0.55); opacity: 0; }
}
/* Shuffle ghost: arcs out from the deck (riffle split) then snaps
   back to the deck origin. Tethered motion — distinct from the deal
   storm (which leaves) and the collect (which arrives). */
.deck-card.deck-shuffle-ghost {
    pointer-events: none;
    /* Shuffle is 2x slower than its original cadence — it's the
       deliberate pacing beat before the deal, so a longer riffle
       reads as "the deck is being properly shuffled." */
    animation: deck-shuffle-arc 1.1s cubic-bezier(0.4, 0, 0.6, 1) forwards;
    z-index: 99996;
}
@keyframes deck-shuffle-arc {
    0%   { transform: translate(0, 0) rotate(0deg); opacity: 1; }
    45%  { transform: translate(var(--shuf-x), var(--shuf-y)) rotate(var(--shuf-r)); opacity: 0.95; }
    100% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
}
/* Inverse: ghost cards from across the screen collapse onto the deck. */
.deck-card.deck-collect {
    pointer-events: none;
    animation: deck-collect 1.05s cubic-bezier(0.5, 0, 0.7, 1.05) forwards;
    z-index: 99997;
    opacity: 0;
}
@keyframes deck-collect {
    0%   { transform: translate(0, 0) rotate(0deg) scale(0.85); opacity: 0; }
    20%  { transform: translate(0, 0) rotate(0deg) scale(1);    opacity: 0.9; }
    100% { transform: translate(var(--collect-x), var(--collect-y)) rotate(var(--collect-r)) scale(0.6); opacity: 0; }
}
@keyframes deck-fade { from { opacity: 1; } to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
    #card-deck.deck-shuffling { animation: none; }
    .deck-card.deck-fly,
    .deck-card.deck-collect,
    .deck-card.deck-shuffle-ghost { animation: deck-fade 0.25s ease-out forwards; }
}

/* ============================================================
   Round-start "blackout": while body.cards-dealing is active,
   the hand UI is held offscreen so the consolidate → shuffle →
   deal animations get their pacing moment. Once the choreography
   completes, the class is removed and the hands fade in.
   Starter card + crib pile also hold so we don't show round-N+1
   layout details before the deal lands.
   ============================================================ */
body.cards-dealing #card-actions,
body.cards-dealing #button-actions,
body.cards-dealing #bot-hands,
body.cards-dealing #starter-area,
body.cards-dealing #crib-pile-area {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.05s linear;
}
body:not(.cards-dealing) #card-actions,
body:not(.cards-dealing) #button-actions,
body:not(.cards-dealing) #bot-hands,
body:not(.cards-dealing) #starter-area,
body:not(.cards-dealing) #crib-pile-area {
    transition: opacity 0.55s ease;
}

/* Per-card pop-in during the deal cascade. The hand containers are
   already visible (cards-dealing removed); individual card elements
   have a `backwards`-fill keyframe so they start at opacity 0 and
   animate in at their own staggered delay — appearing as the storm
   cards rain over the play area. */
body.cards-popping #card-actions .card-button,
body.cards-popping #bot-hands .bh-card {
    /* Each card's pop-in is ~50% longer than the prior pass too, and
       the staggers are stretched to match the slower deal cascade. */
    animation: card-pop-in 0.7s cubic-bezier(0.34, 1.5, 0.64, 1) backwards;
}
body.cards-popping #card-actions .card-button:nth-child(1) { animation-delay: 100ms; }
body.cards-popping #card-actions .card-button:nth-child(2) { animation-delay: 220ms; }
body.cards-popping #card-actions .card-button:nth-child(3) { animation-delay: 340ms; }
body.cards-popping #card-actions .card-button:nth-child(4) { animation-delay: 460ms; }
body.cards-popping #card-actions .card-button:nth-child(5) { animation-delay: 580ms; }
body.cards-popping #card-actions .card-button:nth-child(6) { animation-delay: 700ms; }
body.cards-popping #bot-hands .bh-card:nth-child(1) { animation-delay: 70ms;  }
body.cards-popping #bot-hands .bh-card:nth-child(2) { animation-delay: 190ms; }
body.cards-popping #bot-hands .bh-card:nth-child(3) { animation-delay: 310ms; }
body.cards-popping #bot-hands .bh-card:nth-child(4) { animation-delay: 430ms; }
body.cards-popping #bot-hands .bh-card:nth-child(5) { animation-delay: 550ms; }
body.cards-popping #bot-hands .bh-card:nth-child(6) { animation-delay: 670ms; }
@keyframes card-pop-in {
    0%   { opacity: 0; transform: translateY(28px) rotate(-10deg) scale(0.5); }
    65%  { opacity: 1; transform: translateY(-3px) rotate(2deg)   scale(1.06); }
    100% { opacity: 1; transform: translateY(0)    rotate(0)      scale(1); }
}
@keyframes card-pop-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
    body.cards-popping #card-actions .card-button,
    body.cards-popping #bot-hands .bh-card { animation: card-pop-fade 0.25s ease-out backwards; }
}

#crib-pile-area.crib-pile-anchor {
    flex: 0 0 auto;
    margin: 0 0.4rem 0 0;
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
}
#crib-pile-area:empty { display: none; }
.crib-pile-label {
    font-size: 0.65em;
    color: var(--muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-family: inherit;
}
.crib-pile-stack {
    position: relative;
    /* Reserve width for one card; layered cards are absolutely positioned
       so the stack stays compact instead of growing horizontally. */
    width: 7ch;
    height: 5.6em;
    line-height: 1.15;
}
.crib-pile-card {
    position: absolute;
    top: 0;
    left: 0;
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--muted);
    line-height: 1.15;
    font-family: inherit;
    border-radius: 6px;
    box-shadow:
        0 1px 1px rgba(60,40,20,0.22),
        0 3px 8px rgba(60,40,20,0.14);
    /* Each subsequent card nudges down + right + rotates slightly so the
       pile reads as messy-but-deliberate. The --i CSS var is set by JS
       per card (0..n). */
    --i: 0;
    transform: translate(calc(var(--i) * 2px), calc(var(--i) * 1.5px)) rotate(calc(var(--i) * 1.5deg - 1.5deg));
    transition: transform 0.25s ease;
}
.crib-pile-card.crib-pile-in {
    animation: crib-pile-drop 0.32s cubic-bezier(0.34, 1.4, 0.64, 1);
}
@keyframes crib-pile-drop {
    0%   { transform: translate(calc(var(--i) * 2px - 18px), calc(var(--i) * 1.5px - 26px)) rotate(calc(var(--i) * 1.5deg + 10deg)); opacity: 0; }
    60%  { opacity: 1; }
    100% { transform: translate(calc(var(--i) * 2px), calc(var(--i) * 1.5px)) rotate(calc(var(--i) * 1.5deg - 1.5deg)); opacity: 1; }
}

/* On portrait mobile, the play surface becomes two rows:
     Row 1 — starter card + all completed piles (.stack-completed)
     Row 2 — the live spread of the current pegging sequence (.stack-cards)
   When a sequence completes server-side, the new pile is appended to
   .stack-completed and animates in from below (slide-up keyframe), and
   .stack-cards starts fresh on row 2 with the next sequence. */
@media (orientation: portrait) and (max-width: 700px) {
    .play-row { flex-direction: column; gap: 0.3rem; }
    .play-row #pegging-area { width: 100%; }
    .stack-row {
        flex-wrap: wrap;
        gap: 0.4rem;
    }
    .stack-completed { flex: 1 1 100%; }    /* full first row */
    .stack-cards     { flex: 1 1 100%; }    /* full second row */
    /* New pile slides UP from where the live stack was (i.e. row 2 → row 1)
       on mobile portrait — directionally matches the visual flow. */
    .seq-pile-in { animation-name: card-slide-up-in; }
}
@keyframes card-slide-up-in {
    0%   { transform: translateY(28px) scale(0.95); opacity: 0; }
    60%  { transform: translateY(-2px) scale(1);    opacity: 1; }
    100% { transform: translateY(0)    scale(1);    opacity: 1; }
}

/* The pegging area is a row of [completed piles][current stack]. Overflow
   visible everywhere so the FLIP card animations don't get clipped. The
   gap between completed piles and the live stack is intentionally small —
   one pile-width apart — so the trail reads as a continuous arc. */
.stack-row {
    display: flex;
    align-items: flex-start;
    gap: 0.3rem;
    overflow: visible;
    padding: 8px 2px;
    position: relative;       /* sit above #pegging-area::before stitch border */
    z-index: 1;
}
.stack-completed {
    display: flex;
    align-items: flex-start;
    gap: 0.25rem;
    flex-wrap: wrap;
    position: relative;
    z-index: 1;
}
.stack-completed:empty { display: none; }

/* Messy pile of full-size cards stacked HORIZONTALLY with heavy overlap so
   a 5-card sequence takes ~2 card widths. Each card has an opaque
   background so cards on top obscure those below. Deterministic rotation
   + offset vars come from JS so re-renders don't shuffle the look. */
.seq-pile {
    display: inline-flex;
    align-items: flex-start;
    padding: 0 0 1rem 0;
    position: relative;
}
.seq-card-full {
    display: inline-block;
    vertical-align: top;            /* kill baseline-row whitespace */
    /* Pin width to the ASCII frame exactly (7 monospace columns) and zero
       all padding so the inline-block box is the visible card and nothing
       else — no invisible padding to steal a column of border on overlap. */
    width: 7ch;
    padding: 0;
    transform: translate(var(--seq-dx, 0), var(--seq-dy, 0)) rotate(var(--seq-rot, 0deg));
    /* -6ch overlap → exactly 1ch of the lower card peeks out, which is
       the `│`/`┌` border column. The visible edge IS the card's border. */
    margin-left: -6ch;
    background: var(--bg);
    transition: transform 0.2s ease, margin-left 0.2s ease;
    box-shadow:
        0 1px 1px rgba(60,40,20,0.18),
        0 3px 10px rgba(60,40,20,0.10);
    border-radius: 6px;
    position: relative;             /* sit above #pegging-area::before */
}
.seq-card-full:first-child { margin-left: 0; }
.seq-card-full pre {
    display: block;                 /* tighter box than default inline */
    margin: 0;
    padding: 0;
    width: 7ch;                     /* match container; no flex to wider widths */
    font-family: inherit;
    line-height: 1.1;
    color: var(--fg);
}
.seq-card-full.red pre { color: var(--accent); }
/* Hover spreads the pile so every card is fully readable. */
.seq-pile:hover .seq-card-full { margin-left: -0.5ch; }
/* The "31" / "GO" tag under each completed pile is now a small token —
   rounded, slightly elevated, reads as a wooden marker on the felt. */
.seq-tag {
    position: absolute;
    bottom: -2px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.7em;
    color: var(--fg);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: var(--bg);
    padding: 0.05rem 0.45rem;
    border: 1px solid var(--border);
    border-radius: 10px;
    white-space: nowrap;
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
    z-index: 2;
}
/* Lay the completed piles + live stack out as one horizontal row so the
   pegging arc reads start → pile-1 → pile-2 → live (spread) → */
.stack-completed { align-items: flex-start; }

/* Card containers — visible overflow so the animating clone overlays the
   destination without clipping at the parent edge. */
.stack-cards, .bh-cards, .stack-card, .bh-card, .seq-pile, .stack-row, .stack-completed { overflow: visible; }
.stack-cards { padding: 8px 2px; }
.bh-cards { scrollbar-width: none; padding: 6px 2px; }
.bh-cards::-webkit-scrollbar { display: none; }
.card-row { overflow-y: visible; padding-top: 8px; padding-bottom: 8px; }

/* The actual scrollbar-flash bug we kept chasing came from the <pre> tag
   inside each card. Pre defaults to overflow:visible, but with `bigsuit`
   doing a transform:scale(1.9) on the suit glyph, the visual content can
   exceed the pre's box, and certain UAs (notably some WebKit builds when
   the parent has will-change/3D transforms) render a scrollbar on the pre.
   Force overflow:clip everywhere — pre content is static, you never need
   to scroll inside a card. #starter-area is a <pre> itself so include it. */
#starter-area,
.stack-card pre, .bh-card pre, .seq-card-full pre,
.card-button, .card-button pre,
.rain-card pre, .sim-card pre {
    overflow: clip;
    scrollbar-width: none;
}
#starter-area::-webkit-scrollbar,
.stack-card pre::-webkit-scrollbar,
.bh-card pre::-webkit-scrollbar,
.seq-card-full pre::-webkit-scrollbar,
.card-button::-webkit-scrollbar,
.card-button pre::-webkit-scrollbar,
.rain-card pre::-webkit-scrollbar,
.sim-card pre::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
}

/* Vertical-only margin on cards. The earlier `margin: 2px 0` shorthand
   was zeroing margin-left, which silently wiped out the .seq-card-full
   `-6em` overlap rule — that's why the piles looked barely stacked
   instead of like a real stack of cards. Use longhand to keep horizontal
   margins intact. */
.stack-card, .bh-card, .seq-card-full {
    margin-top: 2px;
    margin-bottom: 2px;
}

/* Final safety net: kill horizontal scrollbar at the viewport. Vertical
   scrolling is intentional (long games scroll); horizontal would only
   appear from animation artifacts. */
html, body { overflow-x: hidden; }

/* Invite toast — shown briefly when you create/join an empty room so the
   first thing you see is a clear way to bring friends in. Pulses on the
   room-code chip too. */
.invite-toast {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.7rem;
    margin: 0.4rem 0 0.6rem;
    background: var(--hover);
    border: 1px dashed var(--accent);
    font-size: 0.9em;
    position: relative;
    animation: invite-slide 0.28s ease-out;
}
.invite-toast.hidden { display: none; }
.invite-toast-text { flex: 1; }
.invite-toast-close {
    background: transparent;
    border: 0;
    color: var(--muted);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 0.3rem;
}
@keyframes invite-slide {
    0%   { opacity: 0; transform: translateY(-4px); }
    100% { opacity: 1; transform: translateY(0); }
}
#room-code-display { animation: room-pulse 1.8s ease-in-out 2; }
@keyframes room-pulse {
    0%, 100% { box-shadow: 0 0 0 rgba(197,74,58,0); }
    50%      { box-shadow: 0 0 10px rgba(197,74,58,0.45); }
}

/* "you deal" inline tag on the phase banner */
#phase-banner .phase-deal {
    display: inline-block;
    font-size: 0.7em;
    color: var(--muted);
    border: 1px solid var(--border);
    padding: 0.02rem 0.35rem;
    margin-left: 0.4rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: 0.1em;
}

/* Self row in the hands panel (rendered face-up at the top of the list). */
.bot-hand.self {
    border-color: var(--accent);
    background: var(--hover);
}
.bot-hand.self .bh-name { font-weight: 700; }

/* Reveal toggle (training opt-in to see AI hands) */
#reveal-toggle {
    color: var(--muted);
    border-color: var(--border);
}
#reveal-toggle.on {
    color: var(--accent);
    border-color: var(--accent);
}
#reveal-toggle.disabled,
#reveal-toggle:disabled {
    opacity: 0.45;
    cursor: not-allowed;
    text-decoration: line-through;
}
#reveal-toggle.disabled:hover { background: transparent; }
#reveal-toggle.hidden { display: none; }

/* Idle indicators */
.bh-idle {
    margin-left: 0.4rem;
    font-size: 0.72em;
    letter-spacing: 0.03em;
    color: var(--muted);
    animation: bh-idle-fade 0.4s ease-out;
}
.bh-idle-idle { color: var(--accent); opacity: 0.85; }
@keyframes bh-idle-fade {
    0%   { opacity: 0; transform: translateY(-2px); }
    100% { opacity: 1; transform: translateY(0); }
}

/* The pegging stack itself: stop reserving min-height that creates dead
   space during early sequences, since we now anchor it at the top of the
   game view directly under the sticky banner. */
.stack-cards { min-height: 0; padding: 4px 0 6px; }

/* (peg-compact was removed — the layout used to collapse for short stacks,
   which made the "starter & pegging" label flicker in and out and the
   total jump from below to inline as cards accumulated. The play surface
   is now a single stable layout: label on top, cards row, total below.) */

/* ---- Identity (avatar + color) picker ---- */

.identity-row { align-items: flex-start; }
.identity-row label { padding-top: 0.4rem; }
.picker-grid {
    display: flex;
    gap: 0.3rem;
    flex-wrap: wrap;
    flex: 1;
    min-width: 0;
}
.avatar-btn, .color-btn {
    background: transparent;
    border: 1px solid var(--border);
    cursor: pointer;
    font-family: inherit;
    transition: border-color 0.15s, transform 0.1s, background 0.1s;
    padding: 0.3rem 0.55rem;
    color: var(--fg);
}
.avatar-btn { font-size: 1.25rem; line-height: 1; }
.color-btn  { font-size: 0.82em; font-weight: 600; letter-spacing: 0.03em; }
.avatar-btn:hover, .color-btn:hover { border-color: var(--fg); transform: translateY(-2px); }
.avatar-btn.selected {
    border-color: var(--accent);
    background: var(--selected);
}
.color-btn.selected {
    border-color: var(--accent);
    /* No background fill — would override animated gradients (rainbow / aurora) */
    box-shadow: 0 0 0 1px var(--accent) inset;
}

/* ---- Player color classes (used in player list, chat, banner, log) ---- */

.c-ember  { color: #e8623a; }
.c-lagoon { color: #3da9c5; }
.c-meadow { color: #6bc46d; }
.c-plum   { color: #b56bff; }
.c-sun    { color: #e8b842; }
.c-rose   { color: #f06fa0; }
.c-frost  { color: #88c8f0; }
.c-sand   { color: #c89a64; }

.c-rainbow {
    background: linear-gradient(90deg, #e8623a, #e8b842, #6bc46d, #3da9c5, #b56bff, #f06fa0, #e8623a) !important;
    background-size: 300% 100% !important;
    -webkit-background-clip: text !important;
    background-clip: text !important;
    -webkit-text-fill-color: transparent !important;
    color: transparent !important;
    animation: rainbow-shift 4s linear infinite;
}
@keyframes rainbow-shift {
    0%   { background-position: 0% 50%; }
    100% { background-position: 300% 50%; }
}

.c-shimmer {
    color: #ffd86b !important;
    text-shadow: 0 0 6px rgba(255, 216, 107, 0.45);
    animation: shimmer-pulse 1.8s ease-in-out infinite;
}
@keyframes shimmer-pulse {
    0%, 100% { opacity: 1;   text-shadow: 0 0 6px rgba(255,216,107,0.45); }
    50%      { opacity: 0.65; text-shadow: 0 0 14px rgba(255,216,107,0.75); }
}

.c-aurora {
    background: linear-gradient(90deg, #6bc46d, #3da9c5, #b56bff, #3da9c5, #6bc46d) !important;
    background-size: 250% 100% !important;
    -webkit-background-clip: text !important;
    background-clip: text !important;
    -webkit-text-fill-color: transparent !important;
    color: transparent !important;
    animation: aurora-flow 5s ease-in-out infinite;
}

/* Grandma's fire — animated flicker red ↔ orange ↔ gold */
.c-fire {
    color: #ff5511 !important;
    text-shadow: 0 0 8px rgba(255, 80, 0, 0.55);
    animation: fire-flicker 1.1s ease-in-out infinite;
    font-weight: 700;
}
@keyframes fire-flicker {
    0%, 100% {
        color: #ff5511 !important;
        text-shadow: 0 0 8px rgba(255, 80, 0, 0.55);
    }
    32% {
        color: #ffaa00 !important;
        text-shadow: 0 0 14px rgba(255, 130, 0, 0.75);
    }
    58% {
        color: #ff3300 !important;
        text-shadow: 0 0 10px rgba(255, 50, 0, 0.7);
    }
    78% {
        color: #ffcc40 !important;
        text-shadow: 0 0 12px rgba(255, 180, 0, 0.65);
    }
}
.grandma-btn {
    border-color: #ff5511 !important;
    transition: box-shadow 0.2s, transform 0.1s;
}
.grandma-btn:hover:not(:disabled) {
    box-shadow: 0 0 18px rgba(255, 100, 0, 0.55), 0 0 6px rgba(255, 200, 50, 0.4) inset;
    transform: translateY(-1px);
}
/* Lex (hard tier) — meadow accent on the button, subtle glow vs Glady's
   fiery one. Signals "strong but cool-headed." */
.hard-btn {
    border-color: #6bc46d !important;
    transition: box-shadow 0.2s, transform 0.1s;
}
.hard-btn:hover:not(:disabled) {
    box-shadow: 0 0 14px rgba(107, 196, 109, 0.5), 0 0 6px rgba(180, 230, 180, 0.35) inset;
    transform: translateY(-1px);
}
@keyframes aurora-flow {
    0%, 100% { background-position: 0%   50%; }
    50%      { background-position: 100% 50%; }
}

/* ---- Callout banners (rejoin, focused-join) ---- */

.callout-banner {
    border: 1px solid var(--accent);
    background: var(--selected);
    padding: 0.9rem 1.1rem;
    margin: 1.25rem 0;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.callout-banner .cb-text { flex: 1; min-width: 12em; }
.callout-banner code {
    background: var(--bg);
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--border);
    letter-spacing: 0.2em;
    font-size: 0.95em;
    margin: 0 0.2rem;
}
.callout-banner .cb-primary {
    background: var(--accent);
    color: var(--bg);
    border-color: var(--accent);
    font-weight: 700;
}
.callout-banner .cb-primary:hover:not(:disabled) {
    background: var(--bg);
    color: var(--accent);
}

.focused-join-banner {
    flex-direction: column;
    align-items: flex-start;
    padding: 1.1rem 1.25rem;
}
.focused-join-banner .fj-title { margin: 0; font-size: 1.05rem; font-weight: 700; }
.focused-join-banner .fj-blurb { margin: 0.2rem 0 0.4rem; font-size: 0.88em; }
.focused-join-banner .cb-primary { align-self: stretch; padding: 0.55rem 1rem; font-size: 1rem; }

/* ---- Identity card (lobby) ---- */

/* Compact identity shortcut — a single slim row that opens the identity panel
   (the name/avatar/color editor lives there now). Same glass material as
   .mode-card so the lobby still reads as one consistent frosted piece. */
.identity-card {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    margin: 1.25rem 0;
    padding: 0.6rem 1.1rem;
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-highlight);
    border: 1px solid var(--glass-border);
    border-radius: 12px;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
.identity-card:hover { border-color: var(--accent); background: var(--hover); }
.idcard-label { font-size: 0.85em; }
.idcard-preview {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 1.1rem;
    font-weight: 600;
}
.idcard-edit {
    margin-left: auto;
    font-size: 0.82em;
    color: var(--muted);
    white-space: nowrap;
}
.identity-card:hover .idcard-edit { color: var(--accent); }
#id-prev-avatar { font-size: 1.4rem; line-height: 1; }
.id-prev-anon { font-style: italic; font-weight: 500; opacity: 0.75; }

/* Profile editor inside the identity panel */
.ip-profile { margin: 0.2rem 0 0.6rem; }
.ip-profile-status {
    font-size: 0.8rem;
    line-height: 1.4;
    margin: 0 0 0.6rem;
    padding: 0.45rem 0.6rem;
    border-radius: 8px;
    border: 1px dashed var(--border, rgba(255,255,255,0.18));
    color: var(--muted);
}
.ip-profile-status.saved {
    border-style: solid;
    border-color: color-mix(in srgb, var(--accent) 45%, transparent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--fg);
}
.ip-field { margin: 0.5rem 0; }
.ip-field > label {
    display: block;
    font-size: 0.78em;
    color: var(--muted);
    margin-bottom: 0.25rem;
}
.ip-saved-flash {
    font-size: 0.74em;
    color: var(--accent);
    opacity: 0;
    transition: opacity 0.2s ease;
    white-space: nowrap;
    align-self: center;
}
.ip-saved-flash.show { opacity: 1; }
.ip-profile #name-input {
    width: 100%;
    padding: 0.5rem 0.65rem;
    border-radius: 8px;
    border: 1px solid var(--border, rgba(255,255,255,0.18));
    background: color-mix(in srgb, var(--fg) 5%, transparent);
    color: inherit;
    font: inherit;
}
.ip-profile #name-input:focus { outline: 1px solid var(--accent); outline-offset: 1px; }

/* Corner X button on player cards. Same icon, dual meaning by context:
   - On someone else's card (host only) → kick that player
   - On your own card → leave the room (with confirmation)
   The self-leave variant gets a slight tonal tweak so the meaning isn't
   ambiguous when you hover over your own card. */
.player-card .pc-kick {
    margin-left: auto;
    background: transparent;
    color: var(--muted);
    border: 1px solid transparent;
    border-radius: 4px;
    width: 1.5rem;
    height: 1.5rem;
    line-height: 1;
    font-size: 1rem;
    font-weight: 700;
    cursor: pointer;
    padding: 0;
    flex-shrink: 0;
}
.player-card .pc-kick:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: rgba(197,74,58,0.08);
}
.player-card .pc-leave-self::after {
    /* discoverability hint that hovers visible just under the button */
    content: 'leave';
    position: absolute;
    right: 0.6rem;
    top: 1.9rem;
    font-size: 0.65em;
    color: var(--muted);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.player-card .pc-leave-self:hover::after { opacity: 1; }
.player-card.is-me { position: relative; }

/* Hidden YouTube iframe for the frog easter egg — kept offscreen + tiny. */
#frog-tune {
    position: fixed;
    width: 1px;
    height: 1px;
    bottom: 0;
    right: 0;
    opacity: 0.01;
    pointer-events: none;
    overflow: hidden;
    z-index: -1;
}
#frog-tune iframe { width: 320px; height: 180px; border: 0; }

/* Frog cascade — 🐸 emojis rain from the top while the easter egg is active.
   Container is a full-viewport overlay above the content but below modals.
   Per-frog vars (--frog-drift, --frog-spin) randomize sway + rotation. */
#frog-rain {
    position: fixed;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 9000;
}
.frog-drop {
    position: absolute;
    top: -3rem;
    line-height: 1;
    user-select: none;
    will-change: transform, opacity;
    animation-name: frog-fall;
    animation-timing-function: linear;
    animation-iteration-count: 1;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.25));
}
@keyframes frog-fall {
    0%   { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 0; }
    8%   { opacity: 1; }
    90%  { opacity: 1; }
    100% {
        transform: translate3d(var(--frog-drift, 0), 110vh, 0) rotate(var(--frog-spin, 0deg));
        opacity: 0;
    }
}
@media (prefers-reduced-motion: reduce) {
    .frog-drop { animation: none; display: none; }
}

/* ---- Talking indicator ---- */

.who-talking {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.6rem;
    margin: 0.5rem 0;
    border-left: 3px solid var(--accent);
    background: var(--hover);
    font-size: 0.88em;
    animation: fade-in 0.2s ease-out;
}
@keyframes fade-in {
    from { opacity: 0; transform: translateY(-3px); }
    to   { opacity: 1; transform: translateY(0); }
}
.who-talking .who-item {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}
.who-talking .who-item::after {
    content: '♪';
    color: var(--accent);
    animation: pulse 0.7s ease-in-out infinite;
    margin-left: 0.1rem;
}

.player-row.talking .player-name::after {
    content: ' ♪';
    color: var(--accent);
    animation: pulse 0.7s ease-in-out infinite;
}

.voice-btn.voice-talking,
.voice-btn.on.voice-talking {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--selected);
}

.game-header-actions { display: flex; gap: 0.5rem; }

.player.voice-talking { color: var(--accent); }

/* HTML player list rows */
.player-list { margin: 1rem 0; font-family: inherit; line-height: 1.45; }
.player-row { display: flex; gap: 0.5rem; align-items: center; padding: 0.1rem 0; }
.player-row .host-mark { color: var(--accent); width: 1ch; text-align: center; }
.player-row .player-num { color: var(--muted); width: 2ch; }
.player-row .player-avatar { font-size: 1.1em; }
.player-row .player-name { font-weight: 600; }
.player-row .player-tag { color: var(--muted); font-size: 0.85em; }
.player-row.disconnected .player-name { opacity: 0.5; text-decoration: line-through; }

/* ============================================================
   Mobile responsive — under 700px viewport
   ============================================================ */

@media (max-width: 700px) {
    main { padding: 0.6rem 0.75rem 3rem; }

    /* Unified nav */
    .unified-nav {
        flex-wrap: wrap;
        gap: 0.35rem;
        padding: 0.4rem 0.55rem;
        margin: -0.6rem -0.75rem 0.4rem;
        font-size: 0.82rem;
    }
    .unified-nav .nav-current { font-size: 0.95em; margin-right: auto; }
    .unified-nav button, .unified-nav a { padding: 0.2rem 0.4rem; font-size: 0.92em; }

    /* Lobby header — tighter to claw back vertical space above first content.
       The big gap previously visible on mobile was #sim's fixed min-height
       reserving ~6em of space even when no cards were animating. Setting
       min-height: 0 here lets the demo collapse to actual content height. */
    .lobby-top { gap: 0.4rem; margin-bottom: 0.35rem; }
    .lobby-title-block .title { margin: 0 0 0.2rem; }
    .lobby-title-block .tagline { margin: 0 0 0.2rem; }
    .title { font-size: clamp(0.55rem, 2vw, 0.85rem); }
    .tagline { font-size: 0.85rem; margin: 0.1rem 0; }
    /* On phones the sim demo wastes vertical real estate — the user is here
       to play, not watch a demo. Drop it entirely under 700px. */
    .lobby-demo { display: none; }

    /* Identity card (compact shortcut) */
    .identity-card { margin: 0.55rem 0; padding: 0.5rem 0.85rem; }
    .idcard-preview { font-size: 1rem; }
    .picker-grid { gap: 0.25rem; }
    .avatar-btn { font-size: 1.15rem; padding: 0.25rem 0.45rem; line-height: 1; }
    .color-btn { font-size: 0.78em; padding: 0.25rem 0.5rem; }

    /* Mode cards — pull mode-title/blurb closer to action buttons.
       display:contents flattens the .mode-card-text wrapper so the row's
       gap controls spacing uniformly between title, blurb, and actions. */
    .mode-stack { gap: 0.45rem; }
    .mode-card { padding: 0.65rem 0.85rem; }
    .mode-card-row { flex-direction: column; align-items: stretch; gap: 0.35rem; }
    .mode-card-text { display: contents; }
    .mode-title { margin: 0; font-size: 0.95rem; }
    .mode-blurb { margin: 0; font-size: 0.78em; line-height: 1.35; }
    .mode-card-actions { flex-wrap: wrap; gap: 0.4rem; }
    /* Mobile touch targets: bump every mode-card action button to the
       44pt-min recommendation. Applied to both regular buttons AND
       `.small` (which the mode cards use for tighter desktop layout
       — on phones that's too small to tap reliably). */
    .mode-card-actions button,
    .mode-card-actions button.small {
        flex: 1 1 auto;
        min-height: 44px;
        padding: 0.7rem 1rem;
        font-size: 0.95rem;
    }
    .ai-vs-row button,
    .ai-vs-row .share-pair button {
        min-height: 44px;
        padding: 0.7rem 0.6rem;
        font-size: 0.92rem;
    }
    .watch-btn { font-size: 1rem; font-weight: 600; }
    /* Primary lobby actions (create room / start game) get the same
       touch-target treatment so the host doesn't fumble the launch. */
    #create-btn, #join-btn, #start-btn {
        min-height: 44px;
        padding: 0.7rem 1.1rem;
        font-size: 1rem;
    }
    .mp-entry-actions { flex-direction: column; align-items: stretch; }
    .join-or { display: none; }
    .join-row { width: 100%; }
    .join-row input { flex: 1; }

    /* Multiplayer in-room */
    .mp-room-header { gap: 0.4rem; }
    .mp-room-header .mode-title { font-size: 0.92rem; }
    .mp-room-header code { font-size: 0.85em; padding: 0.05rem 0.4rem; }
    .mp-room-actions { gap: 0.3rem; flex-wrap: wrap; }
    .player-cards { grid-template-columns: 1fr; gap: 0.4rem; }
    .player-card { min-height: auto; padding: 0.6rem 0.75rem; }

    /* Game header — let actions wrap to a second row */
    .game-header { flex-direction: column; align-items: flex-start; gap: 0.45rem; }
    .game-header-actions {
        width: 100%;
        flex-wrap: wrap;
        gap: 0.3rem;
        justify-content: flex-end;
    }
    .leave-prominent { margin-left: auto; }
    .gh-divider { display: none; }
    .game-header-actions button.small { padding: 0.3rem 0.55rem; font-size: 0.8em; }

    /* Replay transport — tighter on mobile */
    .replay-controls { padding: 0.4rem 0.55rem; font-size: 0.8em; gap: 0.3rem; }
    .replay-controls button { padding: 0.22rem 0.5rem; font-size: 0.95em; }
    .replay-controls .rw-step { padding: 0.16rem 0.42rem; }
    .rw-status { font-size: 0.9em; }
    .rw-tick { height: 1.4rem; }  /* bigger tap target */

    /* Board — smaller font + horizontal scroll fallback.
       JS reduces WIDTH from 60→30 cells at the same breakpoint so it fits. */
    #board {
        font-size: clamp(0.58rem, 2.1vw, 0.82rem);
        line-height: 1.2;
        overflow-x: auto;
        padding: 0.7rem 0;
    }

    /* Pegging / starter / counting */
    #starter-area, #counting-area { font-size: 0.82em; }
    .stack-card pre { font-size: 0.78em; line-height: 1.1; }
    .stack-cards { gap: 0.3rem; min-height: 0; }

    /* Card buttons (hand) — fit 4 per row on most phones */
    .card-row { gap: 0.3rem; }
    .card-button { padding: 0.25rem 0.35rem; font-size: 0.82em; line-height: 1.08; }

    /* Chat + voice */
    .chat-messages { max-height: 160px; min-height: 56px; font-size: 0.82em; padding: 0.4rem 0.55rem; }
    .chat-input { gap: 0.3rem; }
    .chat-input input {
        font-size: 16px;     /* prevents iOS auto-zoom on focus */
        padding: 0.4rem 0.55rem;
    }

    /* Score, hints, bot decision */
    #score-popup { font-size: clamp(1.05rem, 4vw, 1.6rem); top: 22%; }
    .hint-banner { font-size: 0.82em; padding: 0.35rem 0.6rem; }
    .bot-decision { font-size: 0.8em; padding: 0.5rem 0.65rem; }
    .bot-decision .bd-list, .bot-decision .bd-history { padding-left: 1.1rem; }
    #score-log { font-size: 0.8em; }

    /* Inline leaderboard */
    .lobby-leaderboard { margin-top: 1.5rem; padding-top: 1rem; }
    .ll-header { flex-direction: column; align-items: flex-start; gap: 0.4rem; }
    .ll-tabs { width: 100%; }
    .lb-row {
        grid-template-columns: 2ch 2ch 1fr auto auto;
        gap: 0.25rem;
        font-size: 0.82em;
        padding: 0.3rem 0;
    }
    .lb-extra { display: none; }

    /* Popovers */
    .popover-overlay { padding: 1rem 0.5rem; }
    .popover { padding: 1.25rem 1.1rem 1.4rem; max-width: 95vw; max-height: 88vh; overflow-y: auto; }
    .popover h2 { font-size: 0.95rem; }
    .rules-table { font-size: 0.68rem; line-height: 1.4; }
    .rules-example h4 { font-size: 0.85rem; }
    .rules-example .ex-cards { gap: 0.3rem; }
    .rules-example .ex-cards .stack-card pre { font-size: 0.8em; }

    /* Decorative on mobile too — card rain stays behind the UI in
       the top third of the viewport. pointer-events: none + low
       z-index keeps it from blocking taps; the mask fade bottoms
       out before the play area so cards don't crowd the content. */
    .card-rain {
        display: block;
        height: 35vh;
        max-height: 280px;
    }
    .perfect-29-demo, #title-card-pop { display: none; }
}

/* Even tighter for the smallest phones */
@media (max-width: 380px) {
    main { padding: 0.85rem 0.55rem 2.5rem; }
    .title { font-size: clamp(0.48rem, 2.4vw, 0.7rem); }
    .avatar-btn { padding: 0.2rem 0.35rem; }
    .color-btn { font-size: 0.72em; padding: 0.2rem 0.4rem; }
    .card-button { font-size: 0.75em; padding: 0.2rem 0.3rem; }
    .stack-card pre { font-size: 0.7em; }
    #board { font-size: clamp(0.5rem, 1.9vw, 0.72rem); }
}

/* ============================================================
   Mobile chat tray.

   Desktop keeps the existing inline .chat-panel boxes (they live in
   the mp-room view and the game screen). On phones we hide those
   boxes — they ate too much vertical real estate — and surface chat
   via a floating button + slide-up bottom sheet. Single shared sheet
   (its data-chat-output / data-chat-form match the broadcast
   selectors, so messages flow in unchanged).
   ============================================================ */

.mobile-chat-fab {
    position: fixed;
    right: 0.9rem;
    bottom: 0.9rem;
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    border: 0;
    background: var(--accent);
    color: var(--bg);
    font-size: 1.4rem;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0,0,0,0.3);
    z-index: 8500;
    display: none;
    align-items: center;
    justify-content: center;
}
.mobile-chat-fab.hidden { display: none; }
.mobile-chat-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 1.2rem;
    height: 1.2rem;
    padding: 0 0.3rem;
    border-radius: 0.65rem;
    background: var(--fg);
    color: var(--bg);
    font-size: 0.7rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.mobile-chat-badge.hidden { display: none; }

.mobile-chat-tray {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    background: var(--bg);
    border-top: 1px solid var(--accent);
    box-shadow: 0 -8px 24px rgba(0,0,0,0.3);
    z-index: 8600;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    animation: mobile-chat-slide 0.25s ease-out;
}
.mobile-chat-tray.hidden { display: none; }
@keyframes mobile-chat-slide {
    0%   { transform: translateY(100%); }
    100% { transform: translateY(0); }
}
.mobile-chat-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.55rem 0.9rem;
    border-bottom: 1px dashed var(--border);
}
.mobile-chat-title { font-weight: 700; letter-spacing: 0.03em; }
.mobile-chat-close {
    background: transparent;
    border: 0;
    color: var(--muted);
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 0.4rem;
}
.mobile-chat-close:hover { color: var(--fg); }
.mobile-chat-tray .chat-messages {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.5rem 0.9rem;
    min-height: 30vh;
    max-height: 50vh;
}
.mobile-chat-tray .chat-input {
    display: flex;
    gap: 0.4rem;
    padding: 0.55rem 0.7rem 0.7rem;
    border-top: 1px dashed var(--border);
}
.mobile-chat-tray .chat-input input {
    flex: 1;
    padding: 0.55rem 0.7rem;
    font-size: 16px;   /* prevents iOS zoom on focus */
    font-family: inherit;
}

/* Mobile voice button — lives in the tray header so chat + voice share
   one comms tray. When voice is active, the FAB itself swaps to a mic
   glyph (via ::after) so the user knows the call is live without
   reopening the tray. */
.mobile-voice-btn {
    margin-left: auto;
    margin-right: 0.4rem;
    font-family: inherit;
    font-size: 0.85em;
    padding: 0.35rem 0.7rem;
    background: transparent;
    color: var(--muted);
    border: 1px solid var(--border);
    cursor: pointer;
}
.mobile-voice-btn:hover { color: var(--fg); }
.mobile-voice-btn.on {
    color: var(--bg);
    background: var(--accent);
    border-color: var(--accent);
}
.mobile-voice-btn.voice-talking::after { content: ' ●'; color: rgba(255,255,255,0.85); }

/* Inline mic-error strip under the tray header. Hidden when empty
   (the JS sets textContent; CSS :empty hides the box so there's no
   stray border). Subtle warning tint so it reads as a hint, not an
   error scream — the user can still type chat below it. */
.mobile-voice-error {
    margin: 0.4rem 0.6rem 0;
    padding: 0.4rem 0.6rem;
    font-size: 0.78rem;
    line-height: 1.35;
    color: var(--accent);
    background: rgba(197, 74, 58, 0.10);
    border: 1px solid rgba(197, 74, 58, 0.30);
    border-radius: 6px;
}
.mobile-voice-error:empty { display: none; }
:root[data-theme="dark"] .mobile-voice-error {
    background: rgba(240, 160, 96, 0.10);
    border-color: rgba(240, 160, 96, 0.30);
}

/* When voice is on, the FAB shows a mic indicator so the on-call state
   is visible without opening the tray. */
.mobile-chat-fab.voice-on::before {
    content: '🎙';
    position: absolute;
    top: -8px;
    left: -8px;
    width: 1.4rem;
    height: 1.4rem;
    background: var(--accent);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}

@media (max-width: 700px) {
    .mobile-chat-fab { display: flex; }
    /* Desktop chat panels would still render but offscreen would be wasted;
       hide them. The tray + FAB are the chat surface on mobile. */
    .chat-panel { display: none !important; }
}
@media (min-width: 701px) {
    /* Tray + FAB are mobile-only — never show on desktop even if JS sets
       the show class accidentally. */
    .mobile-chat-fab, .mobile-chat-tray { display: none !important; }
}

/* Landscape phone — claw back vertical space */
@media (max-width: 920px) and (orientation: landscape) and (max-height: 500px) {
    main { padding: 0.6rem 0.75rem 1.5rem; }
    .lobby-top { flex-direction: row; gap: 1rem; }
    .lobby-demo { max-width: 220px; flex: 0 0 220px; }
    .unified-nav { margin: -0.6rem -0.75rem 0.6rem; }
    .game-header { flex-direction: row; align-items: center; }
    .game-header-actions { width: auto; }
    .chat-messages { max-height: 100px; }
    #board { font-size: clamp(0.55rem, 1.6vw, 0.78rem); }
}

/* ===========================================================
   Lobby sim: nowrap + horizontal scroll so the demo never
   pushes the page height around when a long pegging sequence
   builds up. Allow the demo container to widen a bit too so
   typical sequences fit before scrolling kicks in.
   =========================================================== */

#sim-stack {
    flex-wrap: nowrap;
    overflow: hidden;          /* clip cards instead of showing scrollbars; demo loops anyway */
    align-items: flex-start;
    scrollbar-width: none;
}
#sim-stack::-webkit-scrollbar { display: none; }
#sim { overflow: hidden; }     /* belt-and-suspenders: parent also clips */
.lobby-demo { flex-basis: clamp(220px, 32vw, 360px); max-width: 100%; }

/* ===========================================================
   Bot hands panel (watch-bots): a row per bot showing their
   full hand. The played card animates out of here into the
   stack via JS FLIP. We use the same .bigsuit-scaled cards
   so visual continuity is preserved.
   =========================================================== */

/* Toolbar sitting right above the bot-hands rows — host for the
   reveal toggle so it lives where the user is looking when they want
   to flip the active-bot reveal mode. Right-aligned, transparent. */
.bot-hands-bar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 0.4rem;
    margin: 0.55rem 0 0.1rem;
    min-height: 0;
}
.bot-hands-bar:empty,
.bot-hands-bar > .hidden { display: none; }
.bot-hands-bar > .reveal-btn.hidden { display: none; }
.bot-hands {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    margin: 0.1rem 0 0.4rem;
}
.bot-hand {
    display: grid;
    grid-template-columns: minmax(7.5rem, max-content) 1fr;
    align-items: start;
    gap: 0.55rem;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: rgba(0,0,0,0.04);
    transition: border-color 0.2s, background 0.2s;
}
:root[data-theme="dark"] .bot-hand { background: rgba(255,255,255,0.03); }
.bot-hand.active {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px rgba(197,74,58,0.22), 0 0 22px rgba(197,74,58,0.18);
    background: rgba(197,74,58,0.04);
}
.bot-hand.active .bh-avatar {
    transform: scale(1.18);
    transform-origin: left center;
    filter: drop-shadow(0 1px 3px rgba(0,0,0,0.25));
    transition: transform 0.3s cubic-bezier(0.34,1.45,0.64,1);
}
.bot-hand .bh-avatar { transition: transform 0.3s ease; }
/* One-shot pulse when the active slot moves to this row (JS toggles the
   class). Gets the eye following the turn from row to row. */
.bot-hand.active-arrive {
    animation: bot-hand-arrive 0.7s ease-out;
}

/* Glady is peeking. Subtle floating eye-chip near her name when she's
   the active player in PEGGING — implies "she's reading the table" without
   exposing the cheating mechanic outright. */
.bot-hand.peeking .bh-head::after {
    content: '👁 peeking';
    display: inline-block;
    margin-left: 0.5rem;
    font-size: 0.7em;
    letter-spacing: 0.05em;
    color: var(--accent);
    background: rgba(197,74,58,0.08);
    border: 1px solid var(--accent);
    padding: 0.04rem 0.4rem;
    text-transform: lowercase;
    animation: peek-blink 2.6s ease-in-out infinite;
    vertical-align: 0.1em;
}
@keyframes peek-blink {
    0%, 90%, 100% { opacity: 1; transform: translateY(0); }
    45%, 55%      { opacity: 0.35; transform: translateY(-1px); }
}
@media (prefers-reduced-motion: reduce) {
    .bot-hand.peeking .bh-head::after { animation: none; }
}
@keyframes bot-hand-arrive {
    0%   { box-shadow: 0 0 0 0    rgba(197,74,58,0.55); }
    100% { box-shadow: 0 0 0 22px rgba(197,74,58,0); }
}
@media (prefers-reduced-motion: reduce) {
    .bot-hand.active-arrive { animation: none; }
    .bot-hand.active .bh-avatar { transform: none; }
}
.bh-round-pts {
    margin-left: 0.4rem;
    font-size: 0.75em;
    letter-spacing: 0.04em;
    color: var(--accent);
    font-weight: 700;
    border: 1px solid var(--accent);
    padding: 0.04rem 0.35rem;
    text-transform: uppercase;
}
.bh-round-pts.hidden { display: none; }

/* Inline bot decision panel — lives inside the active bot's row so the
   explanation reads beside the player making the call. Replaces the
   standalone #bot-decision during PEGGING. */
.bh-decision {
    grid-column: 1 / -1;
    margin-top: 0.4rem;
    padding-top: 0.4rem;
    border-top: 1px dashed var(--border);
    font-size: 0.9em;
    color: var(--fg);
}
.bh-decision:empty { display: none; }
.bh-decision .bd-header,
.bh-decision .bd-mode,
.bh-decision .bd-help { margin: 0.25rem 0; }
.bh-decision .bd-help { color: var(--muted); font-size: 0.85em; }
.bh-decision .bd-list { margin: 0.2rem 0; padding-left: 1.1rem; }
.bh-decision .bd-list li { padding: 0.08rem 0; }
.bh-head {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    align-items: flex-start;
    min-width: 0;
}
.bh-avatar { font-size: 1.3rem; line-height: 1; }
.bh-name {
    font-weight: 700;
    font-size: 0.92rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.bh-active {
    font-size: 0.7em;
    color: var(--accent);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0;
    transition: opacity 0.15s;
}
.bot-hand.active .bh-active { opacity: 1; }
.bot-hand.dealer .bh-avatar::after {
    content: ' D';
    color: var(--accent);
    font-size: 0.65em;
    font-weight: 700;
    vertical-align: super;
}
.bh-cards {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    gap: 0.35rem;
    padding: 2px 2px 5px;
    scrollbar-width: thin;
}
.bh-cards::-webkit-scrollbar { height: 5px; }
.bh-cards::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.bh-card {
    flex: 0 0 auto;
    display: inline-block;
    line-height: 1.1;
    animation: bh-card-in 0.45s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.bh-card pre {
    margin: 0;
    font-family: inherit;
    line-height: 1.1;
    color: var(--fg);
}
.bh-card.red pre { color: var(--accent); }
@keyframes bh-card-in {
    0% { transform: translateY(-8px) scale(0.85); opacity: 0; }
    100% { transform: translateY(0) scale(1); opacity: 1; }
}

/* Face-down opponent card — same frame as a face-up card but with a
   subtle cross-hatched pattern. We tint slightly so it reads as "not yours"
   at a glance. The card count is also shown in the row header. */
.bh-card.bh-back pre {
    color: var(--muted);
    letter-spacing: 0;
}
.bot-hand.facedown { opacity: 0.92; }
.bot-hand.facedown .bh-cards { gap: 0.18rem; }  /* tighter — they're informational, not interactive */
.bot-hand.facedown .bh-card { animation-duration: 0.3s; }

/* One-shot flip keyframe on the cards container when the row's mode
   changes (e.g. spectator's active-only reveal moves to a new bot —
   the prior bot's cards flip face-down, the new one's flip face-up).
   `transform-style: preserve-3d` + `perspective` on the parent give
   the rotation real depth instead of just a 2D squash. */
.bh-cards { transform-style: preserve-3d; }
.bot-hand { perspective: 600px; }
.bh-cards.bh-cards-flip {
    animation: bh-cards-flip 0.72s cubic-bezier(0.34, 1.35, 0.64, 1);
    transform-origin: center center;
}
@keyframes bh-cards-flip {
    /* 3D flip on the Y axis (paired with `perspective: 600px` on the
       parent .bot-hand). A 2D `rotate()` made the wide bot-hand row
       sweep through a wide arc — the cards furthest from center
       travelled the longest path and the whole row read as
       "spinning around a wide invisible pivot" instead of flipping
       in place. rotateY keeps the row's CENTER fixed and folds the
       cards through depth like flipping a physical card, so the
       rotation reads as "around the image itself". */
    0%   { transform: rotateY(0deg)   scale(1);    filter: brightness(0.9); }
    50%  { transform: rotateY(180deg) scale(0.92); opacity: 0.55; filter: brightness(1.35); }
    100% { transform: rotateY(360deg) scale(1);   filter: brightness(1); }
}
@keyframes bh-cards-fade {
    0% { opacity: 0; } 100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .bh-cards.bh-cards-flip { animation: bh-cards-fade 0.2s ease-out; }
}
.bh-count {
    font-size: 0.72em;
    color: var(--muted);
    letter-spacing: 0.04em;
    margin-left: 0.4rem;
}

/* ===========================================================
   My-turn rainbow glow — multiplayer-only cue that the table
   is waiting on YOU. Subtle border-pulse around the viewport.
   =========================================================== */

/* Rainbow viewport-edge glow on your turn. Two body classes share the
   same overlay; my-turn shows in any game and ramps up from invisible
   over ~45s (JS writes --turn-intensity 0→1), mp-my-turn adds extra
   intensity since other humans are actively waiting. The keyframe
   modulates filter brightness so the pulse rhythm stays visible at all
   intensity levels. */
/* Viewport-edge rainbow glow on your turn.
   `--turn-intensity` (0 → 1, written by JS to <html>, see
   manageTurnIntensity) drives both edge opacity AND the inner-glow
   box-shadow that fades inward from the viewport sides — so the cue
   feels like the screen edges are pulsing rather than a thin border
   being drawn.
   The overlay is ALWAYS rendered (not class-gated) so opacity can
   smoothly transition on/off rather than the pseudo-element popping
   in and out of existence. Intensity ramps linearly from 0 → 1 over
   ~60s of stalling — quick turns barely show, long stalls escalate.
   `body.mp-my-turn` (multiplayer with other humans waiting) layers a
   stronger inner glow on top. */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 9100;
    /* Thinner border (was 6px) so the edge cue reads as a hint, not
       a frame around the page. Paired with a smaller inset shadow
       so the inner falloff stays proportional. The point of this
       glow is the page hum + escalation — the focal "act on THIS"
       prompt now lives on .cta-pulse attached to the action element. */
    border: 3px solid transparent;
    border-image: linear-gradient(45deg, #ff5e3a, #ffb84a, #44e08a, #4ad7ff, #b46cff, #ff5e9b) 1;
    box-shadow:
        inset 0 0 16px rgba(255, 94, 58, calc(var(--turn-intensity, 0) * 0.28)),
        inset 0 0 40px rgba(74, 215, 255, calc(var(--turn-intensity, 0) * 0.14));
    opacity: var(--turn-intensity, 0);
    transition: opacity 0.6s ease, box-shadow 0.6s ease;
    animation: mp-turn-pulse 2.4s ease-in-out infinite;
}
body.mp-my-turn::before {
    box-shadow:
        inset 0 0 20px rgba(255, 94, 58, calc(var(--turn-intensity, 0) * 0.42)),
        inset 0 0 50px rgba(180, 108, 255, calc(var(--turn-intensity, 0) * 0.22));
}
@keyframes mp-turn-pulse {
    0%, 100% { filter: brightness(0.85); }
    50%      { filter: brightness(1.25); }
}
@media (prefers-reduced-motion: reduce) {
    body::before { animation: none; transition: opacity 0.6s linear; }
}

/* ===========================================================
   .cta-pulse — thin rainbow attention ring around the FOCAL
   action element. Layered ::before sits behind the host with a
   blurred animated rainbow gradient that's intentionally tight
   (2-3px halo) so it draws the eye without competing with the
   element's own contents. Applied to:
     - #watch-bots-btn (always — "try this fun thing" prompt)
     - lobby start button when canStart (set by JS)
     - action buttons during a stalled turn (set by JS)
   =========================================================== */
.cta-pulse {
    position: relative;
    isolation: isolate;
}
.cta-pulse::before {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: inherit;
    background: linear-gradient(
        45deg,
        #ff5e3a, #ffb84a, #44e08a, #4ad7ff, #b46cff, #ff5e9b, #ff5e3a
    );
    background-size: 300% 300%;
    z-index: -1;
    filter: blur(4px);
    opacity: 0.75;
    animation:
        rainbow-shift 4s linear infinite,
        cta-pulse-breath 2.4s ease-in-out infinite;
    pointer-events: none;
}
/* On dark themes the gradient reads too bright against muted UI;
   knock it down slightly so it stays a hint, not a billboard. */
:root[data-theme="dark"] .cta-pulse::before { opacity: 0.6; filter: blur(5px); }
@keyframes cta-pulse-breath {
    0%, 100% { opacity: 0.42; transform: scale(1); }
    50%      { opacity: 0.85; transform: scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
    .cta-pulse::before {
        animation: none;
        opacity: 0.55;
    }
}

/* ===========================================================
   Game-start splash. Fires on lobby→game transition. Two-stage
   reveal: "ready" scale-in (0-35%) → "begin!" pop + accent
   flash (50-85%) → fade-out (90-100%). The outer overlay dims
   the page so the splash reads as a beat, not a banner.
   =========================================================== */
.game-start-splash {
    position: fixed;
    inset: 0;
    z-index: 9000;        /* below the viewport rainbow border (9100) */
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    background: rgba(0, 0, 0, 0.35);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.game-start-splash.hidden { display: none; }
.game-start-splash.show { animation: gs-overlay 1.6s ease-out forwards; }
@keyframes gs-overlay {
    0%, 85% { opacity: 1; }
    100%    { opacity: 0; }
}
.gs-card {
    padding: 2.2rem 3rem;
    border-radius: 18px;
    text-align: center;
    min-width: 240px;
    font-family: inherit;
    color: var(--fg);
}
.gs-ready,
.gs-begin {
    font-size: clamp(2.4rem, 8vw, 4.4rem);
    font-weight: 800;
    letter-spacing: 0.08em;
    line-height: 1;
    text-transform: lowercase;
}
.gs-ready {
    animation: gs-ready-in 1.6s ease-out forwards;
}
.gs-begin {
    color: var(--accent);
    animation: gs-begin-in 1.6s ease-out forwards;
}
@keyframes gs-ready-in {
    0%   { opacity: 0; transform: scale(0.6); }
    18%  { opacity: 1; transform: scale(1.05); }
    35%  { opacity: 1; transform: scale(1); }
    50%  { opacity: 0; transform: scale(0.85); height: 0; margin: 0; }
    100% { opacity: 0; transform: scale(0.85); height: 0; margin: 0; }
}
@keyframes gs-begin-in {
    0%, 45% { opacity: 0; transform: scale(0.5); }
    62%     { opacity: 1; transform: scale(1.3); }
    80%     { opacity: 1; transform: scale(1); }
    100%    { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
    .game-start-splash.show { animation: gs-overlay 0.8s linear forwards; }
    .gs-ready, .gs-begin { animation: none; }
    .gs-ready { display: none; }
}

/* ============================================================
   Card-action slide-in animations.

   Across the play surface, every newly-rendered card uses the same
   easeOutBack-style entrance so card additions read as a transition
   (slide+fade in from above) rather than a pop. Used by:
     - completed pegging piles when a sequence ends
     - the trailing pegging stack when it collapses at round-end
     - counting result rows
     - the crib reveal cards
*/
@keyframes card-slide-in {
    0%   { transform: translateY(-12px) scale(0.95); opacity: 0; }
    60%  { transform: translateY(2px)   scale(1);    opacity: 1; }
    100% { transform: translateY(0)     scale(1);    opacity: 1; }
}

/* Brand-new completed pile (just collapsed from the live stack). */
.seq-pile-in {
    animation: card-slide-in 0.45s cubic-bezier(0.34, 1.45, 0.64, 1) backwards;
}

/* Counting / crib slide-in. Each row uses --row-i (set by JS) to
   stagger entry; the .count-pts span has an EXTRA delay so the
   row's cards + breakdown land first, and the score "credits" in
   a moment later. Reads as cards-revealed → score awarded. */
.count-row-in {
    animation: card-slide-in 0.4s cubic-bezier(0.34, 1.45, 0.64, 1) backwards;
    animation-delay: calc(var(--row-i, 0) * 0.12s);
}
.count-row-in .count-pts {
    animation: count-pts-credit 0.5s cubic-bezier(0.2, 1.6, 0.5, 1) backwards;
    animation-delay: calc(var(--row-i, 0) * 0.12s + 0.5s);
    display: inline-block;
}
@keyframes count-pts-credit {
    0%   { opacity: 0; transform: scale(0.4); }
    60%  { opacity: 1; transform: scale(1.25); }
    100% { opacity: 1; transform: scale(1); }
}
.count-crib-cards .count-row-in:nth-child(1) { animation-delay: 0.40s; }
.count-crib-cards .count-row-in:nth-child(2) { animation-delay: 0.46s; }
.count-crib-cards .count-row-in:nth-child(3) { animation-delay: 0.52s; }
.count-crib-cards .count-row-in:nth-child(4) { animation-delay: 0.58s; }
.count-crib-cards .count-row-in:nth-child(5) { animation-delay: 0.64s; }
@media (prefers-reduced-motion: reduce) {
    .seq-pile-in, .count-row-in { animation: none; }
    .count-row-in .count-pts { animation: none; }
}

/* Pegging log inside the scoreboard during PEGGING — running list of
   plays this round, one row per card played. Stays compact: avatar,
   card chip (rank+suit), running total. */
.peg-log {
    margin-top: 0.7rem;
    padding-top: 0.5rem;
    border-top: 1px dashed var(--border);
}
.peg-log-head {
    font-size: 0.7em;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 0.3rem;
}
.peg-log-list {
    list-style: none;
    margin: 0;
    padding: 0;
    /* When the round's play list grows long, wrap into multiple
       sub-columns inside the scoreboard instead of forcing the user
       to scroll. column-width sets the desired column size; the
       browser fits as many as the panel's content-box allows. */
    columns: 11ch auto;
    column-gap: 0.7rem;
    max-height: 28em;
    overflow-y: auto;
}
.peg-log-row {
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    page-break-inside: avoid;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin-bottom: 0.18rem;
}
.peg-log-row {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    font-size: 0.82em;
}
.peg-log-av { font-size: 1em; }
.peg-log-card {
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 0 0.25rem;
    font-weight: 600;
    background: var(--bg);
    color: var(--fg);
}
.peg-log-card.red { color: var(--accent); border-color: var(--accent); }
.peg-log-total {
    font-variant-numeric: tabular-nums;
    font-size: 0.86em;
}

/* Counting-area visual layout. The block surfaces the round-winner,
   each player's hand (or the crib) as compact chips, and the combo
   breakdown ("fifteen-2, pair, run of 3"). All theme-aware via vars so
   it picks up light/dark/cycle automatically. */
#counting-area.count-rich {
    background: var(--hover);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.7rem 0.9rem;
    margin: 0.25rem 0 0.5rem;
}
.count-summary {
    font-size: 0.95rem;
    margin-bottom: 0.55rem;
    padding-bottom: 0.45rem;
    border-bottom: 1px dashed var(--border);
    letter-spacing: 0.02em;
}
.count-summary-label {
    text-transform: uppercase;
    font-size: 0.72em;
    letter-spacing: 0.08em;
    margin-right: 0.4rem;
}
.count-summary strong { color: var(--accent); }
.count-rows { list-style: none; padding: 0; margin: 0; }
/* Each row carries:
     [who] [tag] [pts] [round]
     [cards.............................]
     [desc..............................]
   `pts` is this hand/crib's contribution; `round` (only on the
   player's LAST counting row) is the FULL round delta — bold +
   larger so it reads as the main number, with the +N pts chip
   showing what that row contributed. */
.count-row {
    display: grid;
    grid-template-columns: 1fr auto auto auto;
    grid-template-areas:
        "who   tag   pts   round"
        "cards cards cards cards"
        "desc  desc  desc  desc";
    gap: 0.15rem 0.6rem;
    padding: 0.4rem 0.4rem;
    border-bottom: 1px dotted var(--border);
    align-items: baseline;
}
.count-row:last-child { border-bottom: 0; }
.count-row.count-lead {
    background: rgba(197,74,58,0.07);
    border-left: 2px solid var(--accent);
    padding-left: 0.55rem;
    border-radius: 4px;
}
@media (prefers-color-scheme: dark) {
    .count-row.count-lead { background: rgba(240,160,96,0.07); }
}
.count-who { grid-area: who; font-weight: 600; }
.count-tag {
    grid-area: tag;
    font-size: 0.72em;
    text-transform: uppercase;
    color: var(--muted);
    letter-spacing: 0.05em;
    border: 1px solid var(--border);
    padding: 0.04rem 0.35rem;
    border-radius: 3px;
}
.count-pts {
    grid-area: pts;
    font-weight: 600;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
    font-size: 0.92em;
    min-width: 2.4ch;
    text-align: right;
    opacity: 0.85;
}
.count-pts-round {
    grid-area: round;
    font-weight: 800;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
    font-size: 1.2em;
    text-align: right;
    letter-spacing: 0.02em;
    margin-left: 0.4rem;
    animation: count-pts-credit 0.6s cubic-bezier(0.2, 1.6, 0.5, 1) backwards;
    animation-delay: calc(var(--row-i, 0) * 0.12s + 0.7s);
}
.count-cards {
    grid-area: cards;
    display: flex;
    gap: 0.25rem;
    flex-wrap: wrap;
    align-items: center;
    padding-top: 0.15rem;
}
.count-cards-sep {
    font-size: 0.72em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 0 0.15rem 0 0.3rem;
}
.count-card {
    display: inline-block;
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    font-size: 0.82em;
    line-height: 1.2;
    padding: 0.1rem 0.35rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg);
    color: var(--fg);
    white-space: nowrap;
}
.count-card.red { color: var(--accent); border-color: var(--accent); }
.count-desc {
    grid-area: desc;
    font-size: 0.85em;
    padding-left: 0.1rem;
    padding-top: 0.2rem;
}
.count-desc strong { color: var(--accent); font-weight: 700; }

/* Round-totals footer — final climax after the per-row breakdown
   animates in. Each player's FULL round delta (pegging + hand +
   crib) appears at its own stagger delay, so the panel climaxes on
   "who actually scored more this round". */
.count-totals {
    list-style: none;
    padding: 0;
    margin: 0.6rem 0 0;
    border-top: 1px dashed var(--border);
    padding-top: 0.5rem;
}
.count-totals-row {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.3rem 0.4rem;
    font-size: 1.05em;
}
.count-totals-row + .count-totals-row { border-top: 1px dotted var(--border); }
.count-totals-row.count-lead {
    background: rgba(197,74,58,0.07);
    border-left: 2px solid var(--accent);
    padding-left: 0.55rem;
    border-radius: 4px;
}
:root[data-theme="dark"] .count-totals-row.count-lead { background: rgba(240,160,96,0.07); }
.count-totals-who { font-weight: 700; }
.count-totals-tag {
    font-size: 0.7em;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* Running scoreboard (DISCARDING / PEGGING) — compact per-player rows
   with avatar, name, total score, this-round delta. Smaller than the
   count-rich rows since the panel is narrow on wide screens. */
.count-running .count-summary {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.6rem;
}
.count-running-phase {
    font-size: 0.78em;
    text-transform: lowercase;
    letter-spacing: 0.04em;
}
.count-rows-running { list-style: none; padding: 0; margin: 0; }
.count-row-mini {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    align-items: baseline;
    gap: 0.35rem 0.55rem;
    padding: 0.32rem 0.4rem;
    border-bottom: 1px dotted var(--border);
    font-size: 0.92em;
}
.count-row-mini:last-child { border-bottom: 0; }
.count-row-mini.active {
    background: rgba(197, 74, 58, 0.07);
    border-left: 2px solid var(--accent);
    padding-left: 0.55rem;
    border-radius: 4px;
}
:root[data-theme="dark"] .count-row-mini.active { background: rgba(240,160,96,0.07); }
.count-row-mini.you .count-mini-name { color: var(--accent); font-weight: 700; }
.count-mini-avatar { font-size: 1.1em; line-height: 1; }
.count-mini-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}
.count-dealer {
    font-size: 0.7em;
    margin-left: 0.3rem;
    border: 1px solid currentColor;
    padding: 0 0.2rem;
    border-radius: 2px;
    vertical-align: 0.1em;
    letter-spacing: 0.06em;
}
.count-mini-score {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--accent);
}
.count-mini-out { opacity: 0.5; font-weight: 400; font-size: 0.78em; margin-left: 0.1em; }
.count-mini-delta {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--accent);
    min-width: 2.4ch;
    text-align: right;
}
.count-mini-delta.empty { color: var(--muted); font-weight: 400; }
.count-crib {
    margin-top: 0.7rem;
    padding-top: 0.5rem;
    border-top: 1px dashed var(--border);
}
.count-crib-head {
    font-size: 0.85em;
    letter-spacing: 0.04em;
    margin-bottom: 0.35rem;
}
.count-crib-cards {
    display: flex;
    gap: 0.35rem;
    flex-wrap: wrap;
}
.count-crib-card { line-height: 1.1; }
.count-crib-card pre { margin: 0; line-height: 1.1; }
.count-crib-card.red pre { color: var(--accent); }

/* Floating hand: when it's your turn, the playable card buttons gently
   bob up and down so the action area visually invites a play. Each card
   gets a small staggered delay so they move slightly out of phase. */
#card-actions.your-turn .card-button:not(.disabled) {
    animation: card-bob 2.2s ease-in-out infinite;
}
#card-actions.your-turn .card-button:nth-child(2) { animation-delay: 0.10s; }
#card-actions.your-turn .card-button:nth-child(3) { animation-delay: 0.20s; }
#card-actions.your-turn .card-button:nth-child(4) { animation-delay: 0.30s; }
#card-actions.your-turn .card-button:nth-child(5) { animation-delay: 0.40s; }
#card-actions.your-turn .card-button:nth-child(6) { animation-delay: 0.50s; }
@keyframes card-bob {
    /* Halved from 4px to 2px — was reading as nervous twitching; now
       it's a subtle breath. The hover lift stays at 6px so the
       interactive cue still feels distinct. */
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-2px); }
}
/* Hover override stays sharper than the bob so an interactive lift is
   still distinguishable from the idle float. */
#card-actions.your-turn .card-button:hover:not(.disabled) {
    animation: none;
    transform: translateY(-6px);
}
@media (prefers-reduced-motion: reduce) {
    #card-actions.your-turn .card-button { animation: none; }
}

/* ===========================================================
   Review launcher (post-game button) + popover (summary + rounds).
   =========================================================== */

.review-launcher {
    display: flex;
    justify-content: center;
    margin: 0.5rem 0 0.8rem;
}
.review-launcher .cb-primary {
    font-size: 1rem;
    padding: 0.55rem 1.1rem;
    animation: rv-launch-pulse 2.4s ease-in-out infinite;
}
@keyframes rv-launch-pulse {
    0%, 100% { box-shadow: 0 0 0 rgba(197,74,58,0); }
    50%      { box-shadow: 0 0 18px rgba(197,74,58,0.35); }
}

body.lock-scroll { overflow: hidden; }

.popover.review-popover { max-width: 720px; }
.review-tabs {
    display: flex;
    gap: 0.4rem;
    margin: 0.4rem 0 0.9rem;
    border-bottom: 1px dashed var(--border);
    padding-bottom: 0.5rem;
}
.review-tab {
    background: transparent;
    color: var(--muted);
    border: 1px solid transparent;
    padding: 0.35rem 0.7rem;
    cursor: pointer;
    font: inherit;
    border-radius: 4px;
}
.review-tab:hover { color: var(--fg); }
.review-tab.on {
    color: var(--fg);
    border-color: var(--accent);
    background: var(--hover);
}
.review-stage { display: block; }
.review-stage.hidden { display: none; }

/* Summary stage */
.rv-headline { text-align: center; margin-bottom: 0.6rem; }
.rv-win {
    font-size: 1.2rem;
    font-weight: 700;
    color: var(--accent);
    letter-spacing: 0.03em;
}
.rv-skunk { color: var(--fg); font-weight: 700; }
.rv-rounds-count { font-size: 0.85em; margin-top: 0.2rem; }
.rv-spark {
    width: 100%;
    margin: 0.4rem 0 0.8rem;
    background: var(--hover);
    padding: 0.6rem 0.4rem;
    border: 1px dashed var(--border);
}
.rv-spark svg { width: 100%; height: auto; display: block; }
.rv-spark .rv-line { fill: none; stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; }
.rv-spark .rv-winline { stroke: var(--accent); stroke-dasharray: 4 4; opacity: 0.55; }
.rv-spark .rv-grid    { stroke: var(--border); opacity: 0.5; }
.rv-spark .rv-axislabel { fill: var(--muted); font-size: 9px; font-family: inherit; }
.rv-spark .rv-name    { font-size: 11px; font-weight: 700; font-family: inherit; }
.rv-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.45rem 0.85rem;
    margin: 0.5rem 0 0.7rem;
}
.rv-stat {
    border: 1px solid var(--border);
    padding: 0.45rem 0.6rem;
    background: var(--hover);
}
.rv-stat-k {
    font-size: 0.7em;
    color: var(--muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.rv-stat-v { font-size: 0.95em; margin-top: 0.15rem; }
.rv-actions { display: flex; justify-content: center; margin-top: 0.6rem; }

/* Round browser */
.rv-rounds-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px dashed var(--border);
    margin-bottom: 0.7rem;
}
.rv-pos { font-weight: 700; color: var(--fg); }
.rv-rmeta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.8rem;
    align-items: center;
    padding-bottom: 0.5rem;
    border-bottom: 1px dotted var(--border);
    margin-bottom: 0.6rem;
}
.rv-h3 { font-size: 0.85em; color: var(--muted); margin: 0.6rem 0 0.3rem; letter-spacing: 0.04em; text-transform: uppercase; }
.rv-hands {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.5rem;
}
.rv-hand {
    border: 1px solid var(--border);
    padding: 0.45rem 0.6rem;
    background: var(--hover);
}
.rv-hand.rv-crib { border-style: dashed; border-color: var(--accent); }
.rv-hand-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.3rem;
    font-weight: 700;
    font-size: 0.92rem;
}
.rv-cards { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.rv-card {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    border: 1px solid var(--border);
    padding: 0.1rem 0.3rem 0.15rem;
    min-width: 1.6rem;
    background: var(--bg);
    font-weight: 600;
    line-height: 1.1;
}
.rv-card.red { color: var(--accent); border-color: var(--accent); }
.rv-rank { font-size: 0.85em; }
.rv-suit { font-size: 1.05em; }

.rv-peg {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem 0.45rem;
    padding: 0.4rem;
    background: var(--hover);
    border: 1px dashed var(--border);
}
.rv-peg-step {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    border: 1px solid var(--border);
    padding: 0.05rem 0.25rem;
    background: var(--bg);
}
.rv-peg-av { font-size: 0.95em; line-height: 1; }
.rv-peg-tot {
    font-size: 0.75em;
    color: var(--muted);
    margin-left: 0.15rem;
}
.rv-peg-reset {
    width: 1.2em;
    text-align: center;
    color: var(--accent);
    font-weight: 700;
}

.rv-count {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.rv-count li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.25rem 0.45rem;
    border-bottom: 1px dotted var(--border);
}
.rv-count li:last-child { border-bottom: 0; }
.rv-tag {
    font-size: 0.68em;
    text-transform: uppercase;
    color: var(--muted);
    border: 1px solid var(--border);
    padding: 0.02rem 0.3rem;
    letter-spacing: 0.05em;
}
.rv-bd { font-size: 0.85em; }
.rv-running {
    margin-top: 0.55rem;
    padding-top: 0.35rem;
    border-top: 1px dashed var(--border);
    font-size: 0.85em;
}

@media (max-width: 600px) {
    .popover.review-popover { padding: 1rem; }
    .review-tabs { flex-wrap: wrap; }
    .rv-spark { padding: 0.4rem 0.2rem; }
    .rv-hands { grid-template-columns: 1fr; }
    .rv-stats { grid-template-columns: 1fr; }
    .bot-hand {
        grid-template-columns: 1fr;
        gap: 0.25rem;
    }
    .bh-head { flex-direction: row; align-items: center; gap: 0.4rem; }
    .bh-name { font-size: 0.85rem; }
    .bh-card pre { font-size: 0.65rem; line-height: 1.05; }
}
