/* V3Labs Shell — the persistent lobby chrome.
   Stage, top nav, wings, bottom bar, panel host. */

/* ═══ Hide every scrollbar across the shell ═══
   Pages still scroll — only the visible bar is suppressed. Covers the html
   / body / panel body / any *-overflow:auto element. WebKit needs the
   pseudo-element rule; Firefox uses scrollbar-width; legacy IE/Edge uses
   -ms-overflow-style. */
* {
    scrollbar-width: none;
    -ms-overflow-style: none;
}
*::-webkit-scrollbar {
    width: 0;
    height: 0;
    display: none;
}

/* ═══ Root body in shell context ═══ */
html, body.lobby-body {
    margin: 0;
    padding: 0;
    background: var(--surf-0);
    color: var(--ink-0);
    min-height: 100vh;
    overflow-x: hidden;
    /* Legacy /styles.css sets `html { scrollbar-gutter: stable; }` which
       reserves ~15px on the right for a vertical scrollbar even when none
       is needed, leaving the bottom bar and nav visibly short of the right
       edge. The shell handles its own scrolling inside panels, so let the
       gutter collapse when there's no scrollbar. */
    scrollbar-gutter: auto;
    font-family: var(--font-display);
    -webkit-font-smoothing: antialiased;
}

body.lobby-body.body-locked { overflow: hidden; }

/* ═══ Cycling lobby backdrop ═══
   Direct child of <body>, position:fixed with inset:0 — fills the full
   viewport, edge to edge, regardless of .lobby-root's max-width or
   overflow context. z-index:0 + DOM order (bg before .lobby-root) keeps
   it painting behind lobby-root via source order, while still ABOVE
   body's opaque var(--surf-0) (which would swallow a negative z-index).
   .lobby-root is transparent so the bg shows through in the stage row;
   the chrome bands (ticker / nav / bar) keep their own opaque backgrounds.
*/
.lobby-bg {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
}
.lobby-bg__layer {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center center;
    background-repeat: no-repeat;
    opacity: 0;
    transition: opacity 1100ms cubic-bezier(0.22, 0.61, 0.36, 1);
    will-change: opacity;
    transform: translateZ(0); /* GPU-promote the layer for steady cross-fades */
}
.lobby-bg__layer.is-active { opacity: 1; }

/* ═══ Root layout ═══ */
.lobby-root {
    position: relative;
    display: grid;
    grid-template-rows: var(--ticker-h) var(--nav-h) 1fr var(--bar-h);
    /* Lock to viewport height so the header AND footer are always
       visible together on a desktop — the stage row (1fr) shrinks
       to fit whatever's left between them. */
    height: 100vh;
    height: 100dvh;
    max-width: 100vw;
    overflow: hidden;
    isolation: isolate;
}

/* ═══ Ticker (reduced from legacy 36px → 28px, 80% opacity) ═══ */
.lobby-ticker {
    position: relative;
    overflow: hidden;
    /* Transparent so .lobby-bg shows behind, but a thin dark scrim
       keeps the ticker text legible against any backdrop. */
    background: linear-gradient(to bottom, rgba(0,0,0,0.55), rgba(0,0,0,0.35));
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border-bottom: 1px solid var(--rule);
    z-index: var(--z-chrome);
    display: flex;
    align-items: center;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.12em;
    color: var(--ink-2);
    white-space: nowrap;
}
.lobby-ticker__track {
    display: flex;
    gap: 48px;
    padding-left: 48px;
    animation: lobby-ticker-scroll 60s linear infinite;
    will-change: transform;
}
.lobby-ticker__item { display: inline-flex; align-items: center; gap: 10px; }
.lobby-ticker__item strong { color: var(--ink-0); font-weight: 600; letter-spacing: 0.2em; }
.lobby-ticker__item .up   { color: var(--ok); }
.lobby-ticker__item .down { color: var(--err); }
@keyframes lobby-ticker-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}

/* ═══ Top nav ═══ */
.lobby-nav {
    display: flex;
    align-items: center;
    gap: 32px;
    padding: 0 32px;
    border-bottom: 1px solid var(--rule);
    /* Transparent so .lobby-bg shows behind; subtle dark scrim + blur
       preserves contrast for the brand, tabs and wallet chip. */
    background: linear-gradient(to bottom, rgba(0,0,0,0.50), rgba(0,0,0,0.20));
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: var(--z-nav);
    height: var(--nav-h);
}

.lobby-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-family: var(--font-display);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--ink-0);
    text-decoration: none;
    flex-shrink: 0;
}
.lobby-brand img { width: 22px; height: 22px; }
.lobby-brand:hover { color: var(--acc-cyan); }

.lobby-tabs {
    display: flex;
    align-items: center;
    gap: 2px;
    flex: 1;
    overflow-x: auto;
    scrollbar-width: none;
}
.lobby-tabs::-webkit-scrollbar { display: none; }

.lobby-tab {
    position: relative;
    padding: 10px 18px;
    background: transparent;
    border: 0;
    color: var(--ink-3);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--ls-nav);
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--dur-sm) var(--ease-out);
    text-decoration: none;
    white-space: nowrap;
}
.lobby-tab:hover { color: var(--ink-0); }
.lobby-tab[aria-current="true"] {
    color: var(--acc-cyan);
}
.lobby-tab[aria-current="true"]::after {
    content: "";
    position: absolute;
    left: 18px; right: 18px; bottom: -1px;
    height: 2px;
    background: var(--acc-gradient);
    box-shadow: var(--glow-cyan-sm);
}
.lobby-tab:focus-visible {
    outline: none;
    color: var(--acc-cyan);
    box-shadow: inset 0 0 0 1px rgba(6, 182, 212, 0.4);
    border-radius: var(--r-1);
}

.lobby-nav__end {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-shrink: 0;
}

/* Hamburger toggle is hidden on desktop; ≤768px media block below shows it.
   When shown, give it a visible scrim + border so it reads against the
   image backdrop (legacy .mobile-menu-toggle is transparent + 10% white). */
#lobby-menu-toggle { display: none; }
#lobby-menu-toggle.mobile-menu-toggle {
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid rgba(255, 255, 255, 0.35);
    color: #fff;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
#lobby-menu-toggle.mobile-menu-toggle svg {
    stroke: #fff;
    width: 22px;
    height: 22px;
}

/* Wallet chip */
/* Unified Connect chip — opens the master-list popover that absorbed
   the old LLM/Steam/link chips. Same chip footprint as wallet so the
   header rhythm doesn't change. */
.lobby-connect {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    margin-right: 8px;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    background: transparent;
    color: var(--ink-1);
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    cursor: pointer;
    position: relative;
    transition: border-color var(--dur-sm), color var(--dur-sm), box-shadow var(--dur-sm);
}
.lobby-connect:hover {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.5);
    box-shadow: var(--glow-cyan-sm);
}
.lobby-connect[data-linked-count]:not([data-linked-count="0"]) {
    color: var(--ink-0);
    border-color: rgba(6, 182, 212, 0.45);
}
.lobby-connect[aria-expanded="true"] {
    color: var(--ink-0);
    border-color: var(--acc-cyan);
    box-shadow: var(--glow-cyan-sm);
}
.lobby-connect__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--acc-cyan);
    color: #0a0e18;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
}

/* Hide the legacy LLM/Steam/link wrappers entirely so the new Connect
   chip stands alone. Markup stays so the old JS hooks (which still
   manage state in the background) keep firing. */
.lobby-chip-wrap--legacy { display: none !important; }

/* ── Connections popover (master list of linkable platforms) ──── */

.lobby-connect-pop {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    width: min(360px, 90vw);
    max-height: 78vh;
    overflow-y: auto;
    z-index: 1100;
    background: linear-gradient(180deg, rgba(15, 20, 32, 0.96) 0%, rgba(8, 12, 22, 0.96) 100%);
    border: 1px solid rgba(6, 182, 212, 0.18);
    border-radius: 12px;
    box-shadow:
        0 22px 70px -14px rgba(0, 0, 0, 0.6),
        0 1px 0 0 rgba(255, 255, 255, 0.04) inset;
    backdrop-filter: blur(14px) saturate(140%);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    padding: 12px 12px 12px;
    color: var(--ink-0);
}
.lobby-connect-pop[hidden] { display: none; }

.connect-master__head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 4px 6px 10px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    margin-bottom: 10px;
}
.connect-master__title {
    font-family: var(--font-display);
    font-size: 13px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--ink-0);
}
.connect-master__count {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--acc-cyan);
}
.connect-master__group-label {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-3);
    padding: 10px 6px 6px;
}

/* Search input — sits directly under the wallet row. The brains list
   is collapsed to the top 3 + any wallet-linked providers by default;
   typing here un-hides everything that matches across both sections. */
.connect-master__search {
    position: relative;
    margin: 10px 0 6px;
}
.connect-master__search-icon {
    position: absolute;
    left: 10px; top: 50%;
    transform: translateY(-50%);
    width: 13px; height: 13px;
    color: var(--ink-3);
    pointer-events: none;
}
.connect-master__search-input {
    width: 100%;
    height: 32px;
    padding: 0 10px 0 28px;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    color: var(--ink-0);
    font-family: inherit;
    font-size: 12.5px;
    box-sizing: border-box;
    transition: border-color 120ms, background 120ms;
}
.connect-master__search-input::placeholder { color: var(--ink-3); }
.connect-master__search-input:focus {
    outline: none;
    border-color: rgba(6, 182, 212, 0.45);
    background: rgba(6, 182, 212, 0.04);
}

/* "+ N more brains — search to find" hint surfaced under the
   collapsed AI Brains list. Inline text + a tiny CTA that focuses
   the search input on click. */
.connect-master__hint {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 10px;
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-3);
    border: 1px dashed rgba(255, 255, 255, 0.08);
    border-radius: 6px;
    background: transparent;
}
.connect-master__hint-cta {
    background: transparent;
    border: 0;
    color: var(--acc-cyan);
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
    padding: 2px 0;
}
.connect-master__hint-cta:hover { text-decoration: underline; }
.connect-master__group {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.connect-master__row {
    all: unset;
    display: grid;
    grid-template-columns: 28px 1fr auto;
    align-items: center;
    gap: 10px;
    padding: 10px 10px;
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 8px;
    cursor: pointer;
    transition: border-color 120ms, background 120ms, transform 120ms;
}
.connect-master__row--no-action {
    grid-template-columns: 28px 1fr;
}
.connect-master__row:hover:not(.connect-master__row--soon) {
    background: rgba(6, 182, 212, 0.04);
    border-color: rgba(6, 182, 212, 0.30);
    transform: translateY(-1px);
}
.connect-master__row--linked {
    border-color: rgba(6, 182, 212, 0.35);
    background: rgba(6, 182, 212, 0.05);
}
.connect-master__row--gated   { opacity: 0.55; }
.connect-master__row--soon    { opacity: 0.45; cursor: default; }
.connect-master__row-glyph {
    width: 28px; height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-1);
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 6px;
}
.connect-master__row-glyph > svg { width: 70%; height: 70%; }
.connect-master__row-letter {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 13px;
    color: var(--ink-2);
}
.connect-master__row-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.connect-master__row-title {
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-0);
    line-height: 1.2;
}
.connect-master__row-meta {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.10em;
    text-transform: uppercase;
    color: var(--ink-3);
    line-height: 1.2;
}
.connect-master__row-action {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    padding: 5px 10px;
    border-radius: 4px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    color: var(--ink-2);
    transition: color 120ms, border-color 120ms, background 120ms;
}
.connect-master__row:hover:not(.connect-master__row--soon) .connect-master__row-action {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.55);
    background: rgba(6, 182, 212, 0.08);
}
.connect-master__row--linked .connect-master__row-action {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.4);
    background: rgba(6, 182, 212, 0.06);
}
/* Wallet row (when not yet linked) — keep the "Start here" label as
   plain text, no pill box. */
.connect-master__row--idle[data-row="wallet"] .connect-master__row-action {
    border: 0;
    background: transparent;
    padding: 0;
    color: var(--ink-2);
}
.connect-master__row[data-row="wallet"]:hover .connect-master__row-action {
    color: var(--acc-cyan);
    background: transparent;
    border-color: transparent;
}

.connect-master__sso {
    margin-top: 14px;
    padding: 12px 12px 14px;
    border: 1px dashed rgba(6, 182, 212, 0.35);
    border-radius: 8px;
    background: rgba(6, 182, 212, 0.03);
}
.connect-master__sso-title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 12px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-0);
}
.connect-master__sso-meta {
    margin-top: 4px;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-2);
}
.connect-master__sso-cta {
    margin-top: 10px;
    background: var(--acc-cyan);
    color: #0a0e18;
    border: 0;
    border-radius: 4px;
    padding: 6px 12px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    cursor: pointer;
}
.connect-master__sso-cta:hover { filter: brightness(1.1); }

.connect-master__foot {
    margin-top: 12px;
    padding: 10px 6px 4px;
    border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.connect-master__foot-hint {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.12em;
    color: var(--ink-3);
    line-height: 1.4;
}

.lobby-wallet {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    background: transparent;
    color: var(--ink-1);
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    cursor: pointer;
    transition: border-color var(--dur-sm), color var(--dur-sm), box-shadow var(--dur-sm);
}
.lobby-wallet:hover {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.5);
    box-shadow: var(--glow-cyan-sm);
}
.lobby-wallet[data-connected="true"] { color: var(--ink-0); }
.lobby-wallet[data-connected="true"] .lobby-wallet__addr { color: var(--acc-cyan); }

/* LLM brain chip — sits between Steam and Wallet. Mirrors the wallet
   chip's shape; Claude-purple accent when connected, amber when the
   active provider is unreachable. This is the third identity: Wallet
   = Web3, Steam = games, LLM = intelligence (the brain that drives
   JarJar's CharacterRecipe). */
.lobby-llm {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    margin-right: 8px;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    background: transparent;
    color: var(--ink-2);
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    cursor: pointer;
    transition: border-color var(--dur-sm), color var(--dur-sm), box-shadow var(--dur-sm);
}
.lobby-llm:hover {
    color: var(--acc-blue, #3b82f6);
    border-color: rgba(59, 130, 246, 0.55);
    box-shadow: 0 0 12px rgba(59, 130, 246, 0.22);
}
.lobby-llm[data-connected="true"] {
    color: var(--acc-blue, #3b82f6);
    border-color: rgba(59, 130, 246, 0.55);
    box-shadow: 0 0 10px rgba(59, 130, 246, 0.16);
}
.lobby-llm[data-connected="true"] .lobby-llm__label {
    color: var(--acc-blue, #3b82f6);
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-transform: none;
}
.lobby-llm[data-connected="error"] {
    color: #fbbf24;
    border-color: rgba(251, 191, 36, 0.55);
    box-shadow: 0 0 10px rgba(251, 191, 36, 0.18);
}
.lobby-llm[data-connected="error"] .lobby-llm__label { color: #fbbf24; }

/* Steam chip — sits to the LEFT of the wallet chip. Same shape as
   .lobby-wallet for symmetry; switches to a Steam-blue accent when
   the user is signed in. Avatar replaces the steam logo when present. */
.lobby-steam {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    margin-right: 8px;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    background: transparent;
    color: var(--ink-2);
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    cursor: pointer;
    transition: border-color var(--dur-sm), color var(--dur-sm), box-shadow var(--dur-sm);
}
.lobby-steam:hover {
    color: #66c0f4;                  /* Steam brand cyan */
    border-color: rgba(102, 192, 244, 0.55);
    box-shadow: 0 0 12px rgba(102, 192, 244, 0.22);
}
.lobby-steam__icon { flex-shrink: 0; color: currentColor; }
/* Avatar slot is no longer used — we keep the Steam logo at all times so
   the chip stays branded (signed-out + signed-in) instead of swapping to a
   profile avatar that can fall back to a "?" placeholder. */
.lobby-steam__avatar { display: none !important; }
.lobby-steam[data-linked="true"] {
    color: var(--ink-0);
    border-color: rgba(102, 192, 244, 0.55);
    box-shadow: 0 0 10px rgba(102, 192, 244, 0.16);
}
.lobby-steam[data-linked="true"] .lobby-steam__icon { color: #66c0f4; }
.lobby-steam[data-linked="true"] .lobby-steam__label {
    color: #66c0f4;
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-transform: none;
    letter-spacing: 0.04em;
}

/* Chip popover (Steam + Wallet) — toast-styled mini dropdown that drops
   below the chip and offers a single "Sign out / Disconnect" action.
   Replaces the browser-native confirm() dialog. Same surface treatment
   as .lobby-toast for visual continuity. */
.lobby-chip-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
}
/* Chip popover (Steam + Wallet) — single-row Sign Out card. Identical
   markup + style for both chips so the user gets the same affordance no
   matter which identity they're managing. Slides under the chip with
   the same animation toasts use. */
.lobby-chip-pop {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    background: #0a0e1a;
    border: 1px solid var(--rule-strong, rgba(255,255,255,0.12));
    border-radius: var(--r-2, 10px);
    box-shadow: 0 10px 26px rgba(0,0,0,0.55);
    overflow: hidden;
    z-index: 90;
    animation: hud-rise 180ms var(--ease-out, ease-out) both;
}
.lobby-chip-pop__btn {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 14px;
    background: transparent;
    color: var(--ink-1, rgba(255,255,255,0.82));
    border: 0;
    font-family: var(--font-mono);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
    min-width: 130px;
    transition: color var(--dur-sm), background var(--dur-sm);
}
.lobby-chip-pop__btn + .lobby-chip-pop__btn {
    border-top: 1px solid var(--rule, rgba(255,255,255,0.06));
}
.lobby-chip-pop__btn svg {
    color: var(--ink-3, rgba(255,255,255,0.55));
    transition: color var(--dur-sm);
}
.lobby-chip-pop__btn:hover {
    color: #fff;
    background: rgba(255,255,255,0.04);
}
.lobby-chip-pop__btn:hover svg { color: var(--ink-0, #fff); }
/* The destructive Sign-Out row gets a red treatment on hover so it
   reads as the heavier action vs the neutral Profile row. */
.lobby-chip-pop__btn[data-action]:hover { background: rgba(239, 68, 68, 0.18); }
.lobby-chip-pop__btn[data-action]:hover svg { color: var(--err, #ef4444); }

/* Link icon (⇄) — sits between the Steam chip and the wallet chip.
   Bridges the two identities: when both are signed in, click signs a
   message and inserts a steam_links row tying them together. When
   already linked, the icon turns cyan and opens a small popover. */
.lobby-link-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
}
.lobby-link {
    width: 22px;
    height: 22px;
    margin: 0 3px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    color: var(--ink-3, rgba(255,255,255,0.55));
    border: 1px solid var(--rule, rgba(255,255,255,0.10));
    border-radius: 6px;
    cursor: pointer;
    transition: color var(--dur-sm), border-color var(--dur-sm), background var(--dur-sm), box-shadow var(--dur-sm), transform var(--dur-sm);
}
.lobby-link svg { width: 11px; height: 11px; }
.lobby-link:hover { color: var(--ink-1, rgba(255,255,255,0.78)); }
.lobby-link[data-state="ready"] {
    color: var(--acc-cyan, #06b6d4);
    border-color: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 10px rgba(6,182,212,0.28);
    animation: lobby-link-pulse 2s ease-in-out infinite;
}
.lobby-link[data-state="linked"] {
    color: var(--ok, #22c55e);
    border-color: var(--ok, #22c55e);
    background: rgba(34,197,94,0.08);
}
@keyframes lobby-link-pulse {
    0%, 100% { box-shadow: 0 0 8px rgba(6,182,212,0.22); }
    50%      { box-shadow: 0 0 14px rgba(6,182,212,0.45); }
}

/* Popover — drops down under the ⇄ icon, lists linked wallets with
   inline unlink × buttons. Uses the same panel surface tokens as the
   rest of the lobby chrome. */
.lobby-link__popover {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    min-width: 260px;
    max-width: 320px;
    padding: 12px;
    border-radius: 10px;
    background: rgba(6, 9, 18, 0.96);
    border: 1px solid var(--rule-strong, rgba(255,255,255,0.12));
    box-shadow: 0 12px 32px rgba(0,0,0,0.55);
    backdrop-filter: blur(10px);
    z-index: 90;
}
.lobby-link__head {
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--ink-2, rgba(255,255,255,0.66));
    margin-bottom: 10px;
}
.lobby-link__list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 10px;
}
.lobby-link__row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--rule, rgba(255,255,255,0.06));
    border-radius: 6px;
}
.lobby-link__addr {
    flex: 1;
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--ink-0, #fff);
    letter-spacing: 0.04em;
}
.lobby-link__active {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--acc-cyan, #06b6d4);
    padding: 2px 6px;
    border: 1px solid currentColor;
    border-radius: 999px;
}
.lobby-link__unlink {
    width: 22px;
    height: 22px;
    border-radius: 6px;
    background: transparent;
    border: 1px solid var(--rule-strong, rgba(255,255,255,0.12));
    color: var(--ink-3, rgba(255,255,255,0.55));
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    transition: color var(--dur-sm), border-color var(--dur-sm);
}
.lobby-link__unlink:hover { color: var(--err, #ef4444); border-color: var(--err, #ef4444); }
.lobby-link__add {
    width: 100%;
    padding: 8px;
    margin-bottom: 8px;
    background: var(--acc-soft, rgba(6,182,212,0.12));
    color: var(--acc-cyan, #06b6d4);
    border: 1px solid var(--acc-cyan, #06b6d4);
    border-radius: 6px;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background var(--dur-sm), color var(--dur-sm);
}
.lobby-link__add:hover { background: var(--acc-cyan, #06b6d4); color: #000; }
.lobby-link__empty {
    padding: 12px 8px;
    text-align: center;
    font-size: 11.5px;
    color: var(--ink-3, rgba(255,255,255,0.55));
}
.lobby-link__footnote {
    font-size: 10.5px;
    line-height: 1.5;
    color: var(--ink-3, rgba(255,255,255,0.55));
    text-align: center;
}

/* ═══ Stage area ═══ */
.lobby-stage-wrap {
    position: relative;
    display: grid;
    /* Three columns — left wing, stage, and an empty right column
       that mirrors the left wing's width so the stage stays
       optically centered in the viewport. The right wing is
       position: fixed (overlay), so this third column has no
       content; it exists purely to balance the grid so the
       model + chat input centre on the visible page rather than
       drifting right toward the missing column. */
    grid-template-columns: var(--wing-w) 1fr var(--wing-w);
    /* Row fills the wrap so the inner stage uses the full vertical
       space allotted to it by lobby-root's 1fr row — without this,
       the auto row sizes to content and leaves a third of the page
       empty below. */
    grid-template-rows: 1fr;
    gap: var(--sp-5);
    padding: var(--sp-5) var(--sp-6);
    min-height: 0;
    overflow: hidden;
    z-index: var(--z-stage);
}
/* Right wing — transparent overlay on the upper-right. The arrow
   button (.lobby-stage__nav--next, see #lobby-stage-next) toggles
   `.lobby-wing--collapsed` so the bubble dock + title strip + panel
   slide off-stage to a small sliver at the right edge. */

.lobby-wing--right {
    /* Pulled out of the grid flow so the stage area keeps its full
       width and the wing floats over the right side of the viewport.
       Width is a global CSS variable (defined on :root below) so the
       arrow toggle button (.lobby-stage__nav--next) can read the same
       value and stick to the wing's left edge as it resizes. The
       resize handle (app/ui/right-wing-resize.js) writes the variable
       to document.documentElement so every reader sees changes.

       Flex column so the tab strip (mounted at top by wing-tabs-mount.js)
       sits above a panel area that fills the remaining wing height. */
    position: fixed;
    top: var(--wing-right-top);
    left: var(--wing-right-left);
    right: auto;                     /* left-anchored so the user can drag it anywhere */
    bottom: auto;
    width: var(--wing-right-width);
    min-width: 280px;
    max-width: min(80vw, 1100px);
    height: var(--wing-right-height);
    min-height: 240px;
    max-height: calc(100vh - 32px);
    z-index: var(--z-overlay, 40);
    background: transparent;
    border: 0;
    padding: 0;
    overflow: visible;
    display: flex;
    flex-direction: column;
    animation: none;
    pointer-events: none;
}
/* Make the home-game-slider container fill the wing height below the
 * tab strip so the IDE-chat panel grows to fit. */
.lobby-wing--right .home-game-slider {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.lobby-wing--right .home-game-slider__panel--chatlog {
    flex: 1 1 auto;
    height: auto;            /* override the 72vh cap set globally */
    min-height: 0;
}
.lobby-wing--right > * { pointer-events: auto; }

/* Strip every drop of chrome from the slider when it lives in the
   right-wing overlay. No card background, no head separator, just
   text. The tab strip keeps the cyan underline indicator. */
.lobby-wing--right .home-game-slider {
    gap: 0;
    max-width: none;
    width: 100%;
}
.lobby-wing--right .home-game-slider__head {
    border-bottom: 0;
    padding: 0;
    margin: 0;
}
.lobby-wing--right .home-game-slider__tabs {
    justify-content: flex-start;
    padding: 14px 12px 8px;
    gap: 22px;
}
.lobby-wing--right .home-game-slider__tab {
    flex: 0 0 auto;            /* size to content; no equal-share split */
    padding: 2px 0 6px;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.14em;
    color: #ffffff;            /* plain white titles per UX call */
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
}
.lobby-wing--right .home-game-slider__tab:hover:not(.is-disabled):not(.is-active) {
    color: #ffffff;
    text-shadow: 0 0 8px rgba(255, 255, 255, 0.4), 0 1px 4px rgba(0, 0, 0, 0.6);
}
.lobby-wing--right .home-game-slider__tab.is-active {
    color: #ffffff;
    text-shadow: 0 0 12px rgba(6, 182, 212, 0.6), 0 1px 4px rgba(0, 0, 0, 0.6);
}

/* Chat Log tab content is the bubble dock — hide the right-wing's own
   chat-log panel entirely so it doesn't overlap the bubbles. */
.lobby-wing--right [data-tab-panel="chatlog"] { display: none !important; }

/* To-Do, Topics, and Activity panels become free-floating widgets
   when active. No card background; just the list + headings on a
   transparent backdrop. */
.lobby-wing--right [data-tab-panel="todo"],
.lobby-wing--right [data-tab-panel="topics"],
.lobby-wing--right [data-tab-panel="activity"] {
    background: transparent;
    padding: 16px 8px 0;
}
.lobby-wing--right [data-tab-panel="todo"][hidden],
.lobby-wing--right [data-tab-panel="topics"][hidden],
.lobby-wing--right [data-tab-panel="activity"][hidden] {
    display: none !important;
}
.lobby-wing--right .chat-log,
.lobby-wing--right .chat-log__head {
    background: transparent;
    border: 0;
    padding: 0;
}
/* The Activity panel is the IDE-chat surface — it's the only thing
 * visible in the wing now (tab strip hidden), so it gets the full
 * IDE chrome: dark plate, border on all sides, fully rounded corners. */
.lobby-wing--right [data-tab-panel="activity"] {
    background: transparent;
    padding: 0;
}
.lobby-wing--right [data-tab-panel="activity"] .chat-log {
    background: linear-gradient(180deg, rgba(13, 17, 23, 0.92), rgba(8, 11, 16, 0.92));
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
    padding: 0;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
    overflow: hidden;
    /* Whole container is the drag handle for moving the wing — click
     * + hold anywhere on it (header, borders, empty state) to drag.
     * Resize handles + the message list override below. */
    cursor: grab;
}
.lobby-wing--right [data-tab-panel="activity"] .chat-log .activity__list {
    cursor: auto;       /* messages = scroll + text-select, not drag */
}
.lobby-wing--right.is-moving [data-tab-panel="activity"] .chat-log,
.lobby-wing--right.is-moving [data-tab-panel="activity"] .chat-log * {
    cursor: grabbing !important;
}
/* Restore the chat-log header on the Activity panel — its styling
 * was stripped by the generic "topics/todo free-floating" rule
 * earlier. The header doubles as a DRAG HANDLE — click + hold
 * anywhere on it to move the entire wing around the viewport. */
.lobby-wing--right [data-tab-panel="activity"] .chat-log__head {
    display: flex !important;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: rgba(255, 255, 255, 0.02);
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    cursor: grab;
    user-select: none;
}
.lobby-wing--right.is-moving [data-tab-panel="activity"] .chat-log__head {
    cursor: grabbing;
    background: rgba(6, 182, 212, 0.06);
}

/* Hide the bubble dock when the user is on a non-Chat-Log tab —
   they're reading lists, not conversation, and the bubble column
   would visually fight the list. */
/* To-Do and Topics replace the bubble column with their lists. The
   Activity pane IS the bubble dock — chat replies and proactive ticks
   show as bubble items there, so we keep the dock visible. */
body[data-active-pane="todo"]   .lobby-stage__bubble,
body[data-active-pane="topics"] .lobby-stage__bubble {
    display: none !important;
}

/* The right wing's old per-tab strip (Chat Log / To-Do / Topics) is
   permanently hidden — the bubble titles over the chat bubble are the
   only tab UI now. The wing still holds the tab PANELS (driven by the
   bubble titles' active-pane state), just without its own header. */
.lobby-wing--right .home-game-slider__head { display: none !important; }

/* When Topics or To-Do is active, the right wing slides down to the
   bubble column's vertical position and hides everything except the
   active panel — recents block etc. step out of the way so the panel
   reads as a single overlay column, not a stacking sidebar. */
body[data-active-pane="todo"]   .lobby-wing--right,
body[data-active-pane="topics"] .lobby-wing--right {
    /* Panel content starts at the same vertical position as the
       bubble dock (top: 30vh) so Activity, Topics, and To-Do all
       align below the title strip (top: 22vh) with the same gap. */
    top: 30vh;
}
/* On To-Do / Topics tabs, hide everything in the wing except the
 * panels container AND the tab strip — the user needs the tabs to
 * switch back to Activity. The bubble-titles strip is mounted into
 * the wing by wing-tabs-mount.js as its first child. */
body[data-active-pane="todo"]   .lobby-wing--right > *:not(.home-game-slider):not(.lobby-stage__bubble-titles),
body[data-active-pane="topics"] .lobby-wing--right > *:not(.home-game-slider):not(.lobby-stage__bubble-titles) {
    display: none !important;
}
body[data-active-pane="todo"]   .lobby-wing--right .home-game-slider__panel--chatlog,
body[data-active-pane="topics"] .lobby-wing--right .home-game-slider__panel--chatlog {
    height: min(54vh, 500px);
    max-height: calc(100vh - 30vh - 14vh);   /* end before the chat input bar */
}

/* Floating-modules style for Topics + To-Do panels inside the right
   wing — strip every container chrome (background, border, shadow,
   rounded corners, head bar) so each topic / todo row reads as its
   own free-floating card on the page. The bubble titles strip is
   the only "chrome" the user sees; everything below is just items. */
.lobby-wing--right [data-tab-panel="topics"] .chat-log,
.lobby-wing--right [data-tab-panel="todo"]   .chat-log {
    background: transparent !important;
    border: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    overflow: visible !important;
    padding: 0;
}
.lobby-wing--right [data-tab-panel="topics"] .chat-log__head,
.lobby-wing--right [data-tab-panel="todo"]   .chat-log__head {
    display: none !important;
}
.lobby-wing--right [data-tab-panel="topics"] .chat-log__empty,
.lobby-wing--right [data-tab-panel="todo"]   .chat-log__empty {
    background: transparent;
    border: 0;
    padding: 8px 0;
}
/* The /addtopic and /todo hint buttons sit above their lists — let
   them keep their inline-card look but with a hint of transparency
   so they read as floating affordances, not headers. */
.lobby-wing--right [data-tab-panel="topics"] .todo__hint,
.lobby-wing--right [data-tab-panel="todo"]   .todo__hint {
    margin-bottom: 8px;
}

.lobby-wing {
    position: relative;             /* anchor for .lobby-stage__slots */
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--sp-5);
    min-width: 0;
    overflow: hidden;
    animation: hud-breath 7s ease-in-out infinite;
    transition: opacity 220ms ease, transform 280ms cubic-bezier(0.4, 0, 0.2, 1);
}
.lobby-wing--left  { align-items: flex-end; text-align: right; }
.lobby-wing--right { align-items: flex-start; text-align: left; }
/* Re-assert the right wing's overlay positioning AFTER `.lobby-wing`
   (above) declared `position: relative` for the left wing's slot
   anchor. We only re-assert `position: fixed` here — top/left/right
   are driven by --wing-right-* variables in the main rule earlier
   (so drag-to-move + 8-handle resize work). Re-declaring top/right
   here was clobbering those vars and freezing the wing in place. */
.lobby-wing--right {
    position: fixed;
}
/* Collapsed wing: fade + slide off-stage to its own side. The parent
   grid column also collapses to 0 (above) so the center model fills
   the freed space. */
.lobby-wing--collapsed {
    opacity: 0;
    pointer-events: none;
}
.lobby-wing--left.lobby-wing--collapsed  { transform: translateX(-30px); }
/* Right wing slides ENTIRELY off the right edge of the viewport when
 * closed — translate by its own width + the 16px right offset so no
 * sliver remains. Reopens at the same width/height (variables on
 * :root, persisted to localStorage). */
.lobby-wing--right.lobby-wing--collapsed { transform: translateX(calc(100% + 16px)); }

/* Hover-peek removed — the arrow toggle is the SINGLE control for
 * the wing's visibility. Previously, hovering the collapsed wing
 * (or peeking the bubble dock) would auto-uncollapse it, which made
 * it feel like a separate hover-triggered sidebar. Now the wing only
 * opens/closes when the user clicks the arrow. The rules below are
 * kept as a no-op selector that never matches, so any downstream
 * `:has()` polyfills don't fall over. */
body:has(.nope-no-such-element) .lobby-wing--right.lobby-wing--collapsed,
body:has(.nope-no-such-element) .lobby-wing--right.lobby-wing--collapsed {
    opacity: 1;
    pointer-events: auto;
    transform: translateX(0);
}

/* Arrow buttons that toggle wing visibility. The data-wing-collapsed
   attribute (set by stage.js) just flips the chevron direction so the
   button reads as "open this wing" when its wing is hidden, and "close
   this wing" when its wing is shown. */
/* Chevron flips horizontally when its wing is collapsed so the
   button reads as "open this wing" (chevron now points TOWARD the
   wing being shown), versus the default "close this wing" state. */
.lobby-stage__nav svg { transition: transform 200ms ease; }
.lobby-stage__nav[data-wing-collapsed="true"] svg { transform: scaleX(-1); }

/* ─── Game slider (right wing) ──────────────────────────────────────
   Replaces the old Quick Actions block (Browse Market / Play / Create
   / Host buttons). Vertical snap-scroll of GAME profile cards, one
   card per viewport. Signed out → top featured games. Signed in →
   games linked to the currently-centered DexHero. Tap a card → game
   detail page. Wraps invisibly at the end so it feels endless. */
.home-game-slider {
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 100%;
    max-width: 320px;
}
.home-game-slider__head {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 4px 2px 8px;
    border-bottom: 1px solid rgba(102, 192, 244, 0.18);
    margin-bottom: 10px;
}

/* Tab strip — horizontal row of section labels. The active tab gets
   a sliding underline (the ::after-like ::indicator) whose position
   is set by JS via custom properties on the indicator element, so
   the underline animates smoothly between tabs and supports new
   tabs added later without CSS changes. */
.home-game-slider__tabs {
    position: relative;
    display: flex;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.home-game-slider__tabs::-webkit-scrollbar { display: none; }

.home-game-slider__tab {
    all: unset;
    /* Share the row evenly — three tabs split the right wing into
       thirds. flex:1 + min-width:0 lets the tabs shrink past their
       intrinsic content when the wing is narrow. text-align centers
       the label inside its share. */
    flex: 1 1 0;
    min-width: 0;
    padding: 8px 10px 10px;
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: 0.10em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.40));
    cursor: pointer;
    transition: color var(--dur-sm);
    white-space: nowrap;
    text-align: center;
}
.home-game-slider__tab:hover:not(.is-disabled):not(.is-active) {
    color: var(--ink-1, rgba(255, 255, 255, 0.80));
}
.home-game-slider__tab.is-active {
    color: var(--acc-cyan, #06b6d4);
    text-shadow: 0 0 10px rgba(6, 182, 212, 0.28);
}
.home-game-slider__tab.is-disabled {
    color: var(--ink-3, rgba(255, 255, 255, 0.30));
    cursor: not-allowed;
    opacity: 0.6;
}

.home-game-slider__tab-indicator {
    position: absolute;
    bottom: 0;
    left: var(--tab-indicator-left, 0);
    width: var(--tab-indicator-width, 0);
    height: 2px;
    background: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 12px rgba(6, 182, 212, 0.45);
    border-radius: 1px;
    pointer-events: none;
    transition: left 260ms cubic-bezier(0.4, 0, 0.2, 1),
                width 260ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Contextual subtitle line — shows which DexHero is currently centered
   on the stage + its price. Sits below the tabs so it can react to
   stage-subject changes without disrupting the tab strip's layout. */
.home-game-slider__context {
    display: flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: nowrap;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    min-height: 16px;   /* reserve baseline so layout doesn't jump when populated */
}
.home-game-slider__context-hero {
    color: var(--acc-cyan, #06b6d4);
    text-shadow: 0 0 12px rgba(6, 182, 212, 0.28);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 700;
}
.home-game-slider__context-sep {
    color: var(--ink-3, rgba(255, 255, 255, 0.35));
    font-weight: 400;
}
.home-game-slider__context-price {
    font-family: var(--font-mono);
    color: var(--ink-0, #fff);
    font-weight: 700;
}
.home-game-slider__context:empty,
.home-game-slider__context:not(:has([data-slider-hero]:not(:empty))) {
    display: none;
}

/* Placeholder panels for tabs that aren't built out yet. */
.home-game-slider__panel--placeholder {
    padding: 32px 12px;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.40));
}
.home-game-slider__title {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: nowrap;
    white-space: nowrap;
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink-0, #fff);
}
.home-game-slider__title-prefix {
    color: var(--ink-1, rgba(255, 255, 255, 0.88));
}
.home-game-slider__title-sep {
    color: var(--ink-3, rgba(255, 255, 255, 0.35));
    font-weight: 400;
}
.home-game-slider__title-hero {
    color: var(--acc-cyan, #06b6d4);
    text-shadow: 0 0 12px rgba(6, 182, 212, 0.35);
    font-weight: 700;
}
.home-game-slider__title-price {
    margin-left: 8px;
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--ink-0, #fff);
}
.home-game-slider__snap {
    scroll-snap-type: y mandatory;
    overflow-y: auto;
    overscroll-behavior: contain;
    scroll-behavior: smooth;
    height: min(72vh, 580px);
    scrollbar-width: none;
}
.home-game-slider__snap::-webkit-scrollbar { display: none; }
.home-game-slider__placeholder {
    padding: 24px 12px;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.45));
    line-height: 1.6;
}

/* Card: a vertical mini Steam game-detail page. Hero header (image +
   scrim + brackets + eyebrow + title + byline) sits on top; below it a
   body with genres, short description, screenshots strip, and a play
   CTA. Mirrors the layout of /#/game/<appid> for visual continuity. */
.home-game-slider__card {
    position: relative;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    /* Content-sized — the card ends right under the PLAY button. A
       generous gap between cards makes it clear the next hero peek
       belongs to a different game, not the same one. */
    margin-bottom: 28px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: 2px;
    border: 1px solid rgba(102, 192, 244, 0.15);
    background: linear-gradient(180deg, rgba(6, 9, 18, 0.94) 0%, rgba(10, 14, 24, 0.96) 100%);
    box-shadow:
        inset 0 0 80px rgba(0, 0, 0, 0.5),
        0 0 24px rgba(6, 182, 212, 0.08);
    cursor: pointer;
    transition: border-color var(--dur-sm), box-shadow var(--dur-sm);
}
.home-game-slider__card:hover {
    border-color: rgba(102, 192, 244, 0.45);
    box-shadow:
        inset 0 0 80px rgba(0, 0, 0, 0.5),
        0 0 24px rgba(6, 182, 212, 0.22);
}

/* ── Hero header ─── */
.home-game-slider__hero {
    position: relative;
    width: 100%;
    aspect-ratio: 460/215;       /* Steam header_image native ratio */
    overflow: hidden;
    flex-shrink: 0;
    background: linear-gradient(135deg, rgba(6, 9, 18, 0.95), rgba(6, 9, 18, 0.8));
}
.home-game-slider__hero-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: brightness(0.85);
}
/* Trailer video sits on top of the still cover image (declared later
   in the DOM so it naturally stacks above with no z-index needed).
   preload="none" + the data-src trick means no bytes are fetched until
   pointerenter; once playing the cover image is invisible underneath. */
.home-game-slider__hero-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    pointer-events: none;
    transition: opacity 220ms ease;
}
.home-game-slider__card:hover .home-game-slider__hero-video[data-has-video="1"] {
    opacity: 1;
}
.home-game-slider__hero-img--ph {
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-weight: 700;
    font-size: clamp(32px, 9vw, 72px);
    color: var(--ink-3, rgba(255, 255, 255, 0.40));
    background: linear-gradient(135deg, rgba(6, 182, 212, 0.08), rgba(168, 85, 247, 0.05));
}
.home-game-slider__hero-scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg,
        rgba(0, 0, 0, 0) 0%,
        rgba(0, 0, 0, 0.35) 55%,
        rgba(6, 9, 18, 0.92) 100%);
    pointer-events: none;
}
.home-game-slider__hero-overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 14px 16px 14px;
    z-index: 2;
}
.home-game-slider__name {
    margin: 0;
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 22px;
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -0.01em;
    color: #fff;
    text-shadow:
        0 1px 2px rgba(0, 0, 0, 0.8),
        0 0 24px rgba(0, 0, 0, 0.5);
}
.home-game-slider__byline {
    margin-top: 5px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.04em;
    color: rgba(255, 255, 255, 0.78);
}
.home-game-slider__byline:empty { display: none; }

/* ── Body (below hero) ─── */
/* The screenshot strip lives in its own body row below the hero.
   Collapsed (max-height: 0 + opacity: 0) at rest so the card is just
   the hero; on card-hover the body expands and the strip slides into
   view. The hero size doesn't change — the card grows downward. */
.home-game-slider__body {
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    padding: 0 12px;
    transition: max-height 240ms ease, opacity 200ms ease, padding 240ms ease;
}
.home-game-slider__card:hover .home-game-slider__body {
    max-height: 110px;
    opacity: 1;
    padding: 10px 12px 12px;
}
/* Footer is no longer emitted by _renderGameCard — keep the class
   no-op'd so any leftover stylesheet ref doesn't paint a phantom row. */
.home-game-slider__footer { display: none; }

/* Local hpd-divider rule (host-play-hud.css owns the canonical one but
   it isn't loaded on the lobby). Same shape — small mono "ABOUT" /
   "SCREENSHOTS" tag between two thin lines, mirroring the dividers on
   the actual game-detail page. */
.home-game-slider__card .hpd-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 4px 0 2px;
}
.home-game-slider__card .hpd-divider__line {
    flex: 1;
    height: 1px;
    background: rgba(255, 255, 255, 0.08);
}
.home-game-slider__card .hpd-divider__tag {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
    padding: 3px 8px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 1px;
    background: rgba(255, 255, 255, 0.02);
}

/* Stats line — players · DexHeroes · price. Small uppercase mono,
   matches the rest of the lobby's meta typography. Sits just above
   the About divider. */
.home-game-slider__stats {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 4px;
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
}
.home-game-slider__stat {
    color: var(--ink-1, rgba(255, 255, 255, 0.82));
}
.home-game-slider__stat-sep {
    color: var(--ink-3, rgba(255, 255, 255, 0.30));
}

/* About toggle — clickable divider that expands the description.
   Mirrors game-detail.css `.td-about__toggle` behavior. */
.home-game-slider__about-section {
    margin: 0;
}
.home-game-slider__about-toggle {
    all: unset;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    cursor: pointer;
}
.home-game-slider__about-toggle:focus-visible {
    outline: 1px solid var(--acc-cyan, #06b6d4);
    outline-offset: 2px;
}
.home-game-slider__about-toggle:hover .hpd-divider__tag {
    color: var(--ink-0, #fff);
    border-color: rgba(102, 192, 244, 0.45);
    box-shadow: 0 0 12px rgba(6, 182, 212, 0.18);
}
.home-game-slider__chev {
    display: inline-block;
    margin-left: 6px;
    color: var(--acc-cyan, #06b6d4);
    transition: transform var(--dur-sm, 0.18s);
    font-size: 10px;
}
.home-game-slider__about-section--expanded .home-game-slider__chev {
    transform: rotate(180deg);
}
.home-game-slider__about {
    margin: 8px 0 0;
    font-family: var(--font-mono);
    font-size: 11.5px;
    line-height: 1.6;
    letter-spacing: 0.01em;
    color: var(--ink-1, rgba(255, 255, 255, 0.72));
    /* Collapsed by default — only the divider line is visible until the
       user clicks the About toggle. */
    display: none;
}
.home-game-slider__about-section--expanded .home-game-slider__about {
    display: block;
}

.home-game-slider__shots {
    /* Horizontal screenshot strip inside the body row. Wheel events get
       translated to horizontal scroll by the home.js handler; the
       overscroll-behavior here keeps that wheel from leaking out to
       the page once the strip hits its left/right edge. */
    display: flex;
    gap: 6px;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: thin;
    scrollbar-color: rgba(102, 192, 244, 0.3) transparent;
}
.home-game-slider__shots:empty { display: none; }
.home-game-slider__shots::-webkit-scrollbar { height: 3px; }
.home-game-slider__shots::-webkit-scrollbar-track { background: transparent; }
.home-game-slider__shots::-webkit-scrollbar-thumb { background: rgba(102, 192, 244, 0.3); border-radius: 2px; }
.home-game-slider__shot {
    flex: 0 0 auto;
    width: 132px;
    aspect-ratio: 16/9;
    overflow: hidden;
    border-radius: 2px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    scroll-snap-align: start;
    transition: border-color var(--dur-sm), transform var(--dur-sm);
}
.home-game-slider__shot:hover {
    border-color: rgba(102, 192, 244, 0.55);
    transform: translateY(-1px);
}
.home-game-slider__shot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.home-game-slider__cta {
    all: unset;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 14px 18px;
    font-family: var(--font-mono);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.26em;
    text-transform: uppercase;
    color: var(--acc-cyan, #06b6d4);
    background: transparent;
    border: 1px solid rgba(102, 192, 244, 0.55);
    border-radius: 2px;
    cursor: pointer;
    transition: background var(--dur-sm), border-color var(--dur-sm), color var(--dur-sm), box-shadow var(--dur-sm);
}
.home-game-slider__cta:hover {
    color: #fff;
    background: rgba(6, 182, 212, 0.10);
    border-color: rgba(102, 192, 244, 0.95);
    box-shadow: 0 0 18px rgba(6, 182, 212, 0.22);
}

/* Local copy of the shared HUD eyebrow pill (with LED). Scoped to the
   slider card so we don't have to pull host-play-hud.css onto the lobby. */
/* Footer bar at the bottom of each card — separates the screenshot
   strip from the player-count + LIVE LED meta so neither competes
   with the game title in the hero overlay. */
.home-game-slider__footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding: 10px 16px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    background: rgba(0, 0, 0, 0.25);
}
.home-game-slider__card .hpd-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 4px 10px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.78);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 1px;
    background: rgba(0, 0, 0, 0.55);
}
/* Live-players + LIVE LED chip pinned to the bottom-right of the hero
   so it stays visible at all times (resting AND on hover) without
   competing for space with the title or byline. The hero is already
   position: relative; this absolute child anchors against it. */
.home-game-slider__card .home-game-slider__live {
    position: absolute;
    right: 12px;
    bottom: 12px;
    z-index: 3;
    backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.home-game-slider__card .hpd-eyebrow__led {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 6px var(--acc-cyan, #06b6d4);
    animation: hpd-led-pulse-slider 2.4s ease-in-out infinite;
}
.home-game-slider__card .hpd-eyebrow__led--ok      { background: #22c55e; box-shadow: 0 0 6px #22c55e; }
.home-game-slider__card .hpd-eyebrow__led--warn    { background: #f59e0b; box-shadow: 0 0 6px #f59e0b; }
.home-game-slider__card .hpd-eyebrow__led--standby { background: #a855f7; box-shadow: 0 0 6px #a855f7; }
@keyframes hpd-led-pulse-slider {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.45; }
}

.lobby-stage {
    position: relative;
    display: flex;
    /* Align toward the bottom so the model sits closer to the footer
       and uses the available vertical space, rather than floating in
       the middle of a tall 1fr row. */
    align-items: flex-end;
    justify-content: center;
    min-height: 0;
    min-width: 0;
    height: 100%;
    padding-bottom: var(--sp-3);
}

/* Stage background vignette */
.lobby-stage::before {
    content: "";
    position: absolute;
    inset: -5%;
    background: radial-gradient(circle at center,
        rgba(6, 182, 212, 0.14) 0%,
        rgba(59, 130, 246, 0.06) 30%,
        transparent 65%);
    filter: blur(40px);
    z-index: 0;
    pointer-events: none;
    transition: opacity var(--dur-lg) var(--ease-out);
}

/* Platform disc under subject */
.lobby-stage::after {
    content: "";
    position: absolute;
    bottom: 18%;
    left: 50%;
    transform: translateX(-50%);
    width: clamp(240px, 28vw, 420px);
    height: 32px;
    border-radius: 50%;
    background: radial-gradient(ellipse at center,
        rgba(6, 182, 212, 0.4) 0%,
        rgba(6, 182, 212, 0.08) 40%,
        transparent 75%);
    filter: blur(6px);
    z-index: 0;
    pointer-events: none;
}

.lobby-stage[data-stage-mode="dim"]::before { opacity: 0.2; }
.lobby-stage[data-stage-mode="dim"]::after  { opacity: 0.2; }
.lobby-stage[data-stage-mode="dim"] .lobby-carousel,
.lobby-stage[data-stage-mode="dim"] .lobby-stage__solo { opacity: 0.18; filter: grayscale(0.7); }

/* ── Idle mode: horizontal carousel ribbon ────────────────────── */
.lobby-carousel {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    z-index: 1;
    /* Lock the visible carousel band to 3 cards max at any screen size.
       3 cards × 300px base = 900px content area. The padding scales with
       viewport so at wide screens (>= 1500px) the calc dominates and the
       center band stays capped at 920px; at narrow widths the 8vw
       fallback keeps cards from hugging the chrome. */
    padding: 0 max(8vw, calc(50% - 460px));
    scroll-behavior: auto;            /* JS manages smooth motion */
    /* No native scroll-snap-type — it fights the JS momentum loop
       (every frame the loop sets scrollLeft, mandatory snap pulls it
       back). We snap to the nearest card from JS once momentum dies. */
    transition: opacity var(--dur-lg) var(--ease-out);
    cursor: grab;
    user-select: none;
    -webkit-user-select: none;
    touch-action: pan-x;              /* allow horizontal swipe, prevent v-scroll hijack */
    /* Base card width. JS scale envelope tops out at 1.20 — modest zoom
       so the source thumb stays close to native (sharp) while the
       proximity falloff still drops adjacent cards to ~0.40 so they
       read as supporting previews to the centered focus card. */
    --card-w: 300px;
}
.lobby-carousel::-webkit-scrollbar { display: none; }
.lobby-carousel[data-dragging="true"] { cursor: grabbing; scroll-behavior: auto; }
.lobby-carousel[data-dragging="true"] .lobby-carousel__item {
    transition: none;                 /* freeze scale during drag for snappy feel */
    pointer-events: none;             /* prevent accidental nav on drag-release */
}

.lobby-stage[data-stage-mode="context"] .lobby-carousel { opacity: 0; pointer-events: none; }
.lobby-stage[data-stage-mode="context"] .lobby-stage__solo,
.lobby-stage[data-stage-mode="idle"]    .lobby-stage__solo { opacity: 1; pointer-events: auto; }

.lobby-carousel__item {
    position: relative;
    flex-shrink: 0;
    width: var(--card-w, 300px);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    will-change: transform, opacity;
    /* Tighter spring — scale/opacity transitions feel mechanical and
       responsive instead of floaty. Faster curve, anticipatory ease. */
    transition: transform 160ms cubic-bezier(0.34, 1.2, 0.64, 1),
                opacity   160ms ease-out;
    /* scale/opacity driven by JS via CSS custom properties */
    transform: scale(var(--card-s, 0.5));
    opacity:   var(--card-o, 0.3);
}
.lobby-carousel__item + .lobby-carousel__item { margin-left: -18px; } /* slight overlap so centered item feels "ahead" */

.lobby-carousel__subject {
    position: relative;
    width: var(--card-w, 300px);
    height: var(--card-w, 300px);
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border-radius: var(--r-2);
    overflow: visible;
}
.lobby-carousel__subject .featured-hero-sprite {
    width: 100%;
    height: 100%;
    transform: scale(1);
}
.lobby-carousel__subject img,
.lobby-carousel__subject model-viewer {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    background: transparent;
    --poster-color: transparent;
}
/* Hide the <model-viewer> built-in progress bar in every scope it might render.
   model-viewer exposes --progress-bar-height / --progress-bar-color on the host,
   and the bar itself is the `default-progress-bar` shadow part. Belt + braces. */
model-viewer {
    --progress-bar-height: 0px;
    --progress-bar-color: transparent;
    --progress-mask: transparent;
}
model-viewer::part(default-progress-bar),
model-viewer::part(default-progress-mask) {
    display: none !important;
    height: 0 !important;
    opacity: 0 !important;
}
.lobby-carousel__subject .lobby-stage__subject-letter {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: clamp(56px, 9vw, 120px);
    color: var(--ink-2);
    line-height: 1;
    text-transform: uppercase;
}

.lobby-carousel__label {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
    min-width: 0;
    max-width: 100%;
    padding: 0 8px;
    margin-top: 8px;
}
/* Character nameplate — engineering display type with cyan side
   brackets so it reads as a roster callout, not a crypto ticker.
   The brackets are pure ::before/::after pseudo content (no extra DOM)
   and grow slightly when the card is the focal/center subject. */
.lobby-carousel__name {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 19px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: none;
    color: var(--ink-0, #fff);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    padding: 4px 0;
    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.65),
                 0 0 22px rgba(6, 182, 212, 0.28);
}
.lobby-carousel__name::before,
.lobby-carousel__name::after {
    content: "";
    flex-shrink: 0;
    width: 14px;
    height: 1px;
    background: linear-gradient(90deg,
        transparent 0%,
        var(--acc-cyan, #06b6d4) 100%);
    box-shadow: 0 0 6px rgba(6, 182, 212, 0.55);
    transition: width 220ms var(--ease-out);
}
.lobby-carousel__name::after {
    background: linear-gradient(90deg,
        var(--acc-cyan, #06b6d4) 0%,
        transparent 100%);
}
.lobby-carousel__item[data-center="true"] .lobby-carousel__name::before,
.lobby-carousel__item[data-center="true"] .lobby-carousel__name::after {
    width: 28px;
}
.lobby-carousel__item:hover .lobby-carousel__name {
    color: var(--acc-cyan, #06b6d4);
}

/* Gamified stat pills under the nameplate — players + games counts.
   Small, monospace, neutral by default; the centered card lifts them
   into a brighter accent so the focal hero's stats pop. The pills use
   subtle dot-prefix separators so they read as a coordinated badge row
   instead of disconnected chips. */
.lobby-carousel__stats {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
    margin-top: 2px;
    transition: color 220ms var(--ease-out);
}
.lobby-carousel__stat {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-variant-numeric: tabular-nums;
}
.lobby-carousel__stat svg {
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
    flex-shrink: 0;
    transition: color 220ms var(--ease-out);
}
.lobby-carousel__stat + .lobby-carousel__stat {
    padding-left: 10px;
    position: relative;
}
.lobby-carousel__stat + .lobby-carousel__stat::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.22);
    transform: translateY(-50%);
}
/* Centered card: stats brighten to white + icons go cyan so the focal
   hero's adoption/breadth pops over neighbors. */
.lobby-carousel__item[data-center="true"] .lobby-carousel__stats {
    color: var(--ink-0, #fff);
}
.lobby-carousel__item[data-center="true"] .lobby-carousel__stat svg {
    color: var(--acc-cyan, #06b6d4);
}
.lobby-carousel__item:hover {
    filter: drop-shadow(0 0 24px rgba(6, 182, 212, 0.35));
}

/* ── Context mode: single solo subject (token detail, game, create preview) ── */
.lobby-stage__solo {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Anchor the model + nameplate + chat stack toward the bottom of
       the stage so the lobby uses its full vertical space and the
       content sits closer to the footer instead of floating mid-row. */
    justify-content: flex-end;
    gap: 18px;
    padding-bottom: var(--sp-3);
    z-index: 1;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-lg) var(--ease-out);
}
.lobby-stage__subject {
    position: relative;
    /* Bigger render box so animated models (walk-in-place, idle bobs)
       don't clip at the edges. The camera-orbit radius in stage-subject.js
       is bumped in proportion so the model itself stays at the same
       visual size — the extra container area becomes pure breathing room
       around the resting silhouette. */
    /* Scale with the smaller of width / height so the stage fits any
       desktop viewport without scrolling, but never shrinks below 380px
       so the model stays prominent on common 1280×720+ desktops. */
    width: clamp(380px, min(42vw, 62vh), 560px);
    height: clamp(380px, min(42vw, 62vh), 560px);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: opacity var(--dur-lg) var(--ease-out),
                transform var(--dur-lg) var(--ease-out);
}
.lobby-stage__subject[data-swapping="true"] { opacity: 0; transform: scale(0.96); }

/* Publish-module panel — Phase 2 of the Agent Store roadmap.
   Lean, one-screen creator form. Reuses hud-input + hud-btn primitives
   so the visual treatment matches every other workshop write surface.
   Custom rules below cover the four widgets that don't have a perfect
   primitive: category chip row, label text, divider rule, error line. */
.pm-chips {
    display: flex; gap: 8px; flex-wrap: wrap;
}
.pm-chip {
    flex: 1 1 auto;
    padding: 10px 14px;
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: var(--r-1);
    color: var(--ink-2);
    font-family: var(--font-display);
    font-size: 12px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    cursor: pointer;
    transition: border-color 120ms, color 120ms, background 120ms;
}
.pm-chip:hover { color: var(--ink-0); border-color: rgba(255, 255, 255, 0.25); }
.pm-chip.is-active {
    color: var(--ink-0);
    border-color: var(--acc-cyan, #22d3ee);
    background: rgba(34, 211, 238, 0.08);
}
.pm-label {
    display: block;
    font-family: var(--font-display);
    font-size: 10.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: 8px;
}
.pm-input { width: 100%; }
.pm-textarea {
    width: 100%;
    min-height: 120px;
    resize: vertical;
    font-family: inherit;
    line-height: 1.5;
}
.pm-emoji {
    width: 88px;
    font-size: 24px;
    text-align: center;
}
.pm-rule {
    border: 0;
    height: 1px;
    background: rgba(255, 255, 255, 0.08);
    margin: 0;
}
.pm-error {
    text-align: center;
    min-height: 16px;
    margin-top: 8px;
    color: var(--acc-red, #ef4444);
}

/* Equipped body-outfit overlay — Phase 7 of the Agent Store roadmap.
   Single emoji/glyph element floating just above the centered hero's
   head. Tracks the body driver's head-sway CSS vars so it visibly
   follows the body's idle sway / nod / talk_bob.

   Top offset is tuned to land roughly at the crown of Truffle's mesh
   at the default clamp() container size — model-viewer's camera-orbit
   keeps the silhouette consistent, so a fixed offset works without
   per-hero tuning. */
.lobby-stage__outfit-overlay {
    position: absolute;
    left: 50%;
    top: 12%;
    transform: translate(-50%, 0)
        translate(var(--dh-head-sway-x, 0px), var(--dh-head-sway-y, 0px));
    font-size: clamp(48px, 6vw, 96px);
    line-height: 1;
    pointer-events: none;
    z-index: 4;
    filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.35));
    transition: opacity var(--dur-md) var(--ease-out);
}

.lobby-stage__subject img,
.lobby-stage__subject model-viewer {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    background: transparent;
    --poster-color: transparent;
}
.lobby-stage__subject-letter {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: clamp(72px, 10vw, 140px);
    color: var(--ink-2);
    line-height: 1;
    text-transform: uppercase;
}

/* Nameplate — sits below the single subject in idle mode. Shows the
   DexHero name (with cyan side-brackets via existing .lobby-carousel__name
   styling), the players + games stat pills, and a dots-row indicator
   showing position in the ranked list. Hidden in context mode since the
   panel itself owns the title/info there. */
.lobby-stage__nameplate {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 0 12px;
    pointer-events: none;
}
.lobby-stage[data-stage-mode="context"] .lobby-stage__nameplate { display: none; }

/* 4-slot character picker pinned to the bottom-right of the lobby
   stage's model area. Replaces the carousel-arrow stepping flow —
   each slot is a square button showing the DexHero's sprite or
   image. Click → stage swaps to that subject. Empty slots stay
   visible but dimmed so the grid is always 4-wide. */
.lobby-stage__slots {
    /* Desktop: absolutely anchored to the bottom-right of the left
       wing. Both wings share a grid row whose height is dictated by
       the right wing's tall game-slider content, so the left wing
       has empty space at its bottom — slots fill that gap, landing
       visually level with the bottom of the right-side game slider.
       align-items: flex-end on the wing already right-aligns content
       to the wing's right edge; `right: 0` matches that edge so the
       slot picker lines up with the Create DexHero button above.
       Mobile (≤960px) drops back to in-flow centered placement
       because the wings collapse to a single stacked column then. */
    position: absolute;
    right: 0;
    bottom: 0;
    display: grid;
    grid-template-columns: repeat(4, 56px);
    gap: 8px;
    z-index: 2;
}
@media (max-width: 960px) {
    .lobby-stage__slots {
        position: static;
        margin-top: 12px;
        justify-self: center;
    }
}
@media (max-width: 768px) {
    .lobby-stage__slots {
        grid-template-columns: repeat(4, 48px);
        gap: 6px;
        margin-top: 10px;
    }
    .lobby-slot { width: 48px !important; height: 48px !important; }
    .lobby-slot__letter { font-size: 18px !important; }
}
@media (max-width: 480px) {
    .lobby-stage__slots {
        grid-template-columns: repeat(4, 44px);
        gap: 6px;
    }
    .lobby-slot { width: 44px !important; height: 44px !important; }
}
.lobby-stage[data-stage-mode="context"] .lobby-stage__slots { display: none; }

.lobby-slot {
    width: 56px;
    height: 56px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    background: rgba(6, 9, 18, 0.78);
    border-radius: 4px;
    padding: 0;
    overflow: hidden;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: border-color 160ms ease, transform 120ms ease, box-shadow 160ms ease;
}
.lobby-slot:hover:not(:disabled) {
    border-color: rgba(102, 192, 244, 0.55);
    transform: translateY(-1px);
    box-shadow: 0 4px 14px rgba(6, 182, 212, 0.12);
}
.lobby-slot.is-active {
    border-color: rgba(102, 192, 244, 0.85);
    box-shadow: 0 0 0 1px rgba(102, 192, 244, 0.55), 0 0 14px rgba(6, 182, 212, 0.22);
}
.lobby-slot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.lobby-slot__letter {
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-weight: 700;
    font-size: 22px;
    color: var(--ink-2, rgba(255, 255, 255, 0.6));
}
.lobby-slot--empty {
    cursor: default;
    opacity: 0.32;
    border-style: dashed;
}

.lobby-stage__dots {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
}
.lobby-stage__dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--ink-4, rgba(255, 255, 255, 0.22));
    transition: width 220ms var(--ease-out),
                background 220ms var(--ease-out),
                box-shadow 220ms var(--ease-out);
    display: inline-block;
}
.lobby-stage__dot--active {
    width: 18px;
    border-radius: 3px;
    background: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 8px rgba(6, 182, 212, 0.45);
}

/* ═══ LLM Connect modal — bring your own brain ═══
   Center-stage modal opened by the header "Connect Brain" chip. Vertical
   list of provider cards; clicking a card accordion-expands to show its
   key input (or endpoint dropdown for the local provider).

   Design tokens from styles/tokens.css. The wallet-connect modal at
   components/modals.js is the visual template for the card pattern. */
.llm-connect-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal, 2000);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.llm-connect-overlay__backdrop {
    position: absolute;
    inset: 0;
    background: var(--pnl-veil, rgba(0, 0, 0, 0.55));
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    opacity: 0;
    transition: opacity var(--pnl-dur-in) var(--pnl-ease);
}
.llm-connect-overlay[data-state="open"] .llm-connect-overlay__backdrop {
    opacity: 1;
}
.llm-connect-overlay[data-state="closing"] .llm-connect-overlay__backdrop {
    opacity: 0;
    transition: opacity var(--pnl-dur-out) var(--pnl-ease);
}

.llm-connect {
    position: relative;
    width: 440px;
    max-width: 100%;
    max-height: min(720px, calc(100vh - 64px));
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: linear-gradient(180deg,
        rgba(0, 0, 0, 0.94) 0%,
        rgba(5, 6, 10, 0.97) 100%);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-2);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow:
        0 48px 96px rgba(0, 0, 0, 0.65),
        0 0 32px rgba(6, 182, 212, 0.18),
        inset 0 1px 0 rgba(255, 255, 255, 0.04);
    color: var(--ink-0);
    font-family: var(--font-display);
    transform: scale(0.97) translateY(8px);
    opacity: 0;
    transition: transform var(--pnl-dur-in) var(--pnl-ease),
                opacity var(--pnl-dur-in) var(--pnl-ease);
}
.llm-connect-overlay[data-state="open"] .llm-connect {
    transform: scale(1) translateY(0);
    opacity: 1;
}
.llm-connect-overlay[data-state="closing"] .llm-connect {
    transform: scale(0.98) translateY(4px);
    opacity: 0;
    transition: transform var(--pnl-dur-out) var(--pnl-ease),
                opacity var(--pnl-dur-out) var(--pnl-ease);
}

/* Head */
.llm-connect__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 20px 22px 16px;
    border-bottom: 1px solid var(--rule);
    flex-shrink: 0;
}
.llm-connect__head-lead {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}
.llm-connect__head-eyebrow {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--acc-cyan);
}
.llm-connect__head-title {
    margin: 0;
    font-size: 19px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--ink-0);
    letter-spacing: -0.01em;
}
.llm-connect__close {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid var(--rule-strong);
    border-radius: 50%;
    color: var(--ink-2);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    padding: 0 0 1px;
    flex-shrink: 0;
    transition: color var(--dur-sm), border-color var(--dur-sm), box-shadow var(--dur-sm);
}
.llm-connect__close:hover {
    color: var(--err);
    border-color: rgba(239, 68, 68, 0.6);
    box-shadow: 0 0 12px rgba(239, 68, 68, 0.22);
}
.llm-connect__close:focus-visible {
    outline: none;
    color: var(--acc-cyan);
    border-color: var(--acc-cyan);
    box-shadow: var(--glow-cyan-sm);
}

/* Card list */
.llm-connect__list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px 16px;
    overflow-y: auto;
    overflow-x: hidden;
    flex: 1;
    min-height: 0;
    scrollbar-width: thin;
    scrollbar-color: rgba(6, 182, 212, 0.25) transparent;
}
.llm-connect__list::-webkit-scrollbar { width: 6px; }
.llm-connect__list::-webkit-scrollbar-track { background: transparent; }
.llm-connect__list::-webkit-scrollbar-thumb { background: rgba(6, 182, 212, 0.25); border-radius: 3px; }

/* Provider card */
.llm-connect__card {
    border: 1px solid var(--rule);
    border-radius: var(--r-2);
    background: rgba(255, 255, 255, 0.02);
    overflow: hidden;
    transition: border-color var(--dur-sm), background var(--dur-sm), box-shadow var(--dur-sm);
}
.llm-connect__card:hover { border-color: rgba(255, 255, 255, 0.16); }
.llm-connect__card.is-connected {
    border-color: rgba(34, 197, 94, 0.32);
    background: linear-gradient(180deg, rgba(34, 197, 94, 0.04), rgba(255, 255, 255, 0.02));
}
.llm-connect__card.is-expanded {
    border-color: rgba(6, 182, 212, 0.55);
    background: rgba(6, 182, 212, 0.04);
    box-shadow: var(--glow-cyan-sm);
}
.llm-connect__card.is-expanded.is-connected {
    border-color: rgba(34, 197, 94, 0.55);
    box-shadow: 0 0 18px rgba(34, 197, 94, 0.16);
}

/* Card head (clickable row) */
.llm-connect__card-head {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: transparent;
    border: 0;
    color: inherit;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    transition: background var(--dur-xs);
}
.llm-connect__card-head:hover {
    background: rgba(255, 255, 255, 0.025);
}
.llm-connect__card-head:focus-visible {
    outline: none;
    background: rgba(6, 182, 212, 0.06);
}

.llm-connect__monogram {
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--rule);
    border-radius: var(--r-2);
    color: var(--ink-1);
    flex-shrink: 0;
    transition: color var(--dur-xs), border-color var(--dur-xs), background var(--dur-xs);
}
.llm-connect__card:hover .llm-connect__monogram,
.llm-connect__card.is-expanded .llm-connect__monogram {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.40);
    background: rgba(6, 182, 212, 0.08);
}
.llm-connect__card.is-connected .llm-connect__monogram {
    color: var(--ok);
    border-color: rgba(34, 197, 94, 0.45);
    background: rgba(34, 197, 94, 0.06);
}
.llm-connect__monogram svg { width: 22px; height: 22px; }

.llm-connect__card-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.llm-connect__card-name {
    color: var(--ink-0);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.llm-connect__card-tag {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Status pill (right side of head) */
.llm-connect__status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    font-family: var(--font-mono);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    flex-shrink: 0;
    transition: color var(--dur-xs), border-color var(--dur-xs), background var(--dur-xs);
}
.llm-connect__status--idle {
    color: var(--ink-2);
}
.llm-connect__card:hover .llm-connect__status--idle {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.45);
}
.llm-connect__status--connected {
    color: var(--ok);
    border-color: rgba(34, 197, 94, 0.45);
    background: rgba(34, 197, 94, 0.06);
}
.llm-connect__status-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ok);
    box-shadow: 0 0 6px rgba(34, 197, 94, 0.7);
}

.llm-connect__chev {
    color: var(--ink-3);
    font-size: 12px;
    line-height: 1;
    flex-shrink: 0;
    transition: transform var(--dur-sm) var(--ease-out), color var(--dur-sm);
}
.llm-connect__card.is-expanded .llm-connect__chev {
    transform: rotate(180deg);
    color: var(--acc-cyan);
}

/* Card body (accordion content) */
.llm-connect__card-body {
    padding: 4px 14px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    border-top: 1px solid var(--rule);
    margin-top: -1px;
    animation: llm-connect-expand var(--dur-sm) var(--ease-out);
}
@keyframes llm-connect-expand {
    from { opacity: 0; transform: translateY(-2px); }
    to   { opacity: 1; transform: translateY(0); }
}

.llm-connect__row {
    display: flex;
    align-items: stretch;
    gap: 8px;
    margin-top: 8px;
}
.llm-connect__input {
    flex: 1;
    min-width: 0;
    padding: 10px 12px;
    font-family: var(--font-mono);
    font-size: 12.5px;
    color: var(--ink-0);
    background: rgba(0, 0, 0, 0.50);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    outline: none;
    transition: border-color var(--dur-sm), box-shadow var(--dur-sm), background var(--dur-sm);
}
.llm-connect__input::placeholder { color: var(--ink-3); }
.llm-connect__input:focus {
    border-color: rgba(6, 182, 212, 0.55);
    background: rgba(0, 0, 0, 0.60);
    box-shadow: var(--glow-cyan-sm);
}
.llm-connect__select {
    width: 100%;
    padding: 10px 12px;
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: 0.04em;
    color: var(--ink-0);
    background: rgba(0, 0, 0, 0.50);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    outline: none;
    cursor: pointer;
    transition: border-color var(--dur-sm), box-shadow var(--dur-sm);
}
.llm-connect__select:focus {
    border-color: rgba(6, 182, 212, 0.55);
    box-shadow: var(--glow-cyan-sm);
}

.llm-connect__btn {
    padding: 0 16px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    background: transparent;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    color: var(--ink-1);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--dur-sm), border-color var(--dur-sm), background var(--dur-sm), box-shadow var(--dur-sm);
}
.llm-connect__btn:hover:not(:disabled) {
    color: var(--ink-0);
    border-color: rgba(6, 182, 212, 0.5);
}
.llm-connect__btn:focus-visible {
    outline: none;
    color: var(--ink-0);
    border-color: var(--acc-cyan);
    box-shadow: var(--glow-cyan-sm);
}
.llm-connect__btn:disabled { opacity: 0.4; cursor: not-allowed; }
.llm-connect__btn--primary {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.55);
}
.llm-connect__btn--primary:hover:not(:disabled) {
    background: rgba(6, 182, 212, 0.12);
    box-shadow: var(--glow-cyan-sm);
}
.llm-connect__btn--ghost { color: var(--ink-2); border-color: var(--rule); }
.llm-connect__btn--ghost:hover:not(:disabled) {
    color: var(--err);
    border-color: rgba(239, 68, 68, 0.5);
}

.llm-connect__masked {
    flex: 1;
    min-width: 0;
    padding: 10px 12px;
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: 0.06em;
    color: var(--ink-1);
    background: rgba(0, 0, 0, 0.40);
    border: 1px solid var(--rule);
    border-radius: var(--r-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.llm-connect__hint {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.12em;
    color: var(--ink-3);
    margin: 0;
    line-height: 1.5;
    transition: color var(--dur-sm);
}
.llm-connect__hint a {
    color: var(--acc-cyan);
    text-decoration: none;
    transition: color var(--dur-sm);
}
.llm-connect__hint a:hover { color: var(--ink-0); text-decoration: underline; }
.llm-connect__hint.is-err { color: var(--warn); }

/* Foot */
.llm-connect__foot {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 22px;
    border-top: 1px solid var(--rule);
    background: rgba(0, 0, 0, 0.4);
    flex-shrink: 0;
}
.llm-connect__foot-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--acc-cyan);
    box-shadow: 0 0 8px rgba(6, 182, 212, 0.6);
    flex-shrink: 0;
}
.llm-connect__foot-text {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-3);
    line-height: 1.5;
}

@media (max-width: 540px) {
    .llm-connect-overlay { padding: 12px; }
    .llm-connect { max-height: calc(100vh - 24px); }
    .llm-connect__head { padding: 16px 16px 12px; }
    .llm-connect__foot { padding: 12px 16px; }
}

/* ═══ Brain picker (workshop chapter: BRAIN) ═══
   Popover that opens when the user clicks the BRAIN annotation label.
   Built dynamically by app/ui/brain-picker.js and appended to <body> so
   the document.elementFromPoint hit-tests don't get clipped by ancestor
   transforms on the stage. Owner-gated Save button. */
.brain-picker {
    position: absolute;
    width: 340px;
    max-width: calc(100vw - 24px);
    z-index: 1100;
    background: rgba(6, 9, 18, 0.96);
    border: 1px solid rgba(102, 192, 244, 0.55);
    border-radius: 4px;
    box-shadow:
        0 12px 36px rgba(0, 0, 0, 0.55),
        0 0 24px rgba(6, 182, 212, 0.20);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    color: var(--ink-0, #fff);
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    animation: hud-rise 200ms var(--ease-out, ease-out);
}
.brain-picker__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    border-bottom: 1px solid rgba(102, 192, 244, 0.20);
}
.brain-picker__title {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--acc-cyan, #06b6d4);
}
.brain-picker__close {
    all: unset;
    cursor: pointer;
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
    transition: color var(--dur-sm);
}
.brain-picker__close:hover { color: #fff; }

.brain-picker__body { padding: 12px 14px; }
.brain-picker__loading,
.brain-picker__error {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
    padding: 8px 4px;
}
.brain-picker__error {
    color: #f59e0b;
    margin-bottom: 8px;
    border-left: 2px solid #f59e0b;
    padding-left: 10px;
}

.brain-picker__sub { margin-bottom: 10px; }
.brain-picker__owner-badge {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
    padding: 3px 8px;
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 1px;
}
.brain-picker__owner-badge--you {
    color: var(--acc-cyan, #06b6d4);
    border-color: rgba(102, 192, 244, 0.55);
    background: rgba(6, 182, 212, 0.08);
}

/* Account-connection row inside the brain picker — shows whether the
   current viewer has an LLM key wired up, with a quick link to Connect
   or change the key. */
.brain-picker__account {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    margin: 10px 0;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 2px;
    background: rgba(255, 255, 255, 0.02);
}
.brain-picker__account-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}
.brain-picker__account--ok .brain-picker__account-dot {
    background: #22c55e;
    box-shadow: 0 0 6px #22c55e;
}
.brain-picker__account--off .brain-picker__account-dot {
    background: var(--ink-4, rgba(255, 255, 255, 0.22));
}
.brain-picker__account-label {
    flex: 1;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.10em;
    color: var(--ink-1, rgba(255, 255, 255, 0.82));
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.brain-picker__account-link {
    all: unset;
    cursor: pointer;
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.20em;
    text-transform: uppercase;
    color: var(--acc-cyan, #06b6d4);
    padding: 4px 8px;
    border: 1px solid rgba(102, 192, 244, 0.45);
    border-radius: 1px;
    transition: background var(--dur-sm), border-color var(--dur-sm), color var(--dur-sm);
}
.brain-picker__account-link:hover {
    color: #fff;
    background: rgba(6, 182, 212, 0.14);
    border-color: rgba(102, 192, 244, 0.90);
}

.brain-picker__rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 10px;
}
.brain-picker__row {
    all: unset;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    cursor: pointer;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 2px;
    background: rgba(255, 255, 255, 0.02);
    transition: border-color var(--dur-sm), background var(--dur-sm);
}
.brain-picker__row:hover:not([aria-disabled="true"]):not([disabled]) {
    border-color: rgba(102, 192, 244, 0.45);
    background: rgba(6, 182, 212, 0.06);
}
.brain-picker__row--selected {
    border-color: rgba(102, 192, 244, 0.70);
    background: rgba(6, 182, 212, 0.08);
}
.brain-picker__row[disabled],
.brain-picker__row[aria-disabled="true"] {
    cursor: not-allowed;
    opacity: 0.55;
}
.brain-picker__row--stub { opacity: 0.5; }

.brain-picker__radio {
    flex-shrink: 0;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 1.5px solid rgba(255, 255, 255, 0.25);
    transition: background var(--dur-sm), border-color var(--dur-sm), box-shadow var(--dur-sm);
}
.brain-picker__radio.is-on { background: currentColor; box-shadow: 0 0 8px currentColor; }
.brain-picker__radio--ok     { color: #22c55e; border-color: rgba(34, 197, 94, 0.55); }
.brain-picker__radio--cyan   { color: var(--acc-cyan, #06b6d4); border-color: rgba(102, 192, 244, 0.55); }
.brain-picker__radio--violet { color: #a855f7; border-color: rgba(168, 85, 247, 0.55); }
.brain-picker__radio--off    { color: var(--ink-4, rgba(255, 255, 255, 0.22)); }

.brain-picker__row-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.brain-picker__row-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-0, #fff);
}
.brain-picker__row-meta {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255, 255, 255, 0.55));
}
.brain-picker__current {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: 9px;
    letter-spacing: 0.22em;
    color: var(--acc-cyan, #06b6d4);
    padding: 2px 6px;
    border: 1px solid rgba(102, 192, 244, 0.55);
    border-radius: 1px;
}

.brain-picker__foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding-top: 4px;
    margin-top: 6px;
    border-top: 1px solid rgba(102, 192, 244, 0.15);
    padding-top: 12px;
}
.brain-picker__btn {
    all: unset;
    cursor: pointer;
    padding: 8px 16px;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.20em;
    text-transform: uppercase;
    border-radius: 2px;
    border: 1px solid;
    transition: background var(--dur-sm), border-color var(--dur-sm), color var(--dur-sm);
}
.brain-picker__btn--ghost {
    color: var(--ink-2, rgba(255, 255, 255, 0.65));
    border-color: rgba(255, 255, 255, 0.18);
}
.brain-picker__btn--ghost:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.40);
}
.brain-picker__btn--primary {
    color: var(--acc-cyan, #06b6d4);
    border-color: rgba(102, 192, 244, 0.55);
}
.brain-picker__btn--primary:hover:not(:disabled) {
    color: #fff;
    background: rgba(6, 182, 212, 0.14);
    border-color: rgba(102, 192, 244, 0.90);
}
.brain-picker__btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* Voice + Schedule editor adornments. Both editors reuse .brain-picker
   shell + buttons; these add the textarea, preset grid, count badge,
   and the side-by-side two-column row used in the schedule modal. */
.voice-editor__presets {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    margin: 0 0 10px;
}
.voice-editor__preset {
    appearance: none;
    text-align: left;
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 6px;
    padding: 7px 9px;
    cursor: pointer;
    color: var(--ink-1, rgba(255,255,255,0.88));
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    transition: background 100ms ease, border-color 100ms ease;
}
.voice-editor__preset:hover {
    background: rgba(210, 168, 255, 0.06);
    border-color: rgba(210, 168, 255, 0.40);
}
.voice-editor__preset-name {
    font-size: 12px;
    font-weight: 700;
    color: #d2a8ff;
    letter-spacing: 0.04em;
    margin-bottom: 2px;
}
.voice-editor__preset-tag {
    font-size: 11px;
    color: var(--ink-3, rgba(255,255,255,0.45));
    line-height: 1.3;
}
.voice-editor__label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--ink-2, rgba(255,255,255,0.55));
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin: 8px 0 4px;
}
.voice-editor__label > span:first-child {
    display: inline-flex;
    align-items: baseline;
    justify-content: space-between;
}
.voice-editor__label input[type="text"],
.voice-editor__label input[type="time"],
.voice-editor__label input[type="number"],
.voice-editor__label select {
    background: rgba(0, 0, 0, 0.30);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 5px;
    padding: 6px 8px;
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--ink-0, #fff);
    outline: none;
    transition: border-color 120ms ease;
    text-transform: none;
    letter-spacing: 0;
}
.voice-editor__label input:focus,
.voice-editor__label select:focus {
    border-color: rgba(210, 168, 255, 0.55);
}
.voice-editor__count {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-3, rgba(255,255,255,0.40));
    letter-spacing: 0.04em;
    text-transform: none;
}
.voice-editor__textarea {
    width: 100%;
    box-sizing: border-box;
    background: rgba(0, 0, 0, 0.30);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 6px;
    padding: 9px 10px;
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 13px;
    line-height: 1.5;
    color: #e6edf3;
    resize: vertical;
    outline: none;
    transition: border-color 120ms ease;
    margin: 0 0 8px;
}
.voice-editor__textarea:focus {
    border-color: rgba(210, 168, 255, 0.55);
}
.voice-editor__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

/* Install JarJar modal — Phase D pair flow. Reuses .brain-picker
   shell + .brain-picker__btn buttons; these add the step layout,
   pair-message code block, paired-installs table, and the polling
   "waiting" indicator. */
.install-jarjar { width: 400px; }
.install-jarjar__step {
    display: grid;
    grid-template-columns: 22px 1fr;
    gap: 10px;
    align-items: flex-start;
    margin: 0 0 12px;
    font-size: 12.5px;
    color: var(--ink-1, rgba(255,255,255,0.88));
}
.install-jarjar__step-num {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(210, 168, 255, 0.18);
    border: 1px solid rgba(210, 168, 255, 0.45);
    color: #d2a8ff;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 22px;
}
.install-jarjar__downloads {
    display: flex;
    gap: 8px;
    margin-top: 6px;
    flex-wrap: wrap;
}
.install-jarjar__downloads a {
    appearance: none;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 5px;
    padding: 4px 10px;
    color: var(--ink-1, rgba(255,255,255,0.88));
    font-family: var(--font-mono);
    font-size: 11px;
    text-decoration: none;
    transition: border-color 100ms ease, color 100ms ease;
}
.install-jarjar__downloads a:hover {
    color: #d2a8ff;
    border-color: rgba(210, 168, 255, 0.40);
}
.install-jarjar__code {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 5px;
    padding: 6px 9px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: #e6edf3;
    overflow-x: auto;
    margin: 4px 0;
    white-space: pre;
}
.install-jarjar__row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 6px;
}
.install-jarjar__expiry {
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--ink-3, rgba(255,255,255,0.45));
    letter-spacing: 0.04em;
}
.install-jarjar__expiry--out { color: #fda4af; }
.install-jarjar__details {
    margin-top: 6px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--ink-3, rgba(255,255,255,0.45));
}
.install-jarjar__details > summary {
    cursor: pointer;
    padding: 2px 0;
}
.install-jarjar__waiting {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--ink-2, rgba(255,255,255,0.55));
    letter-spacing: 0.04em;
}
.install-jarjar__waiting-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(210, 168, 255, 0.55);
    box-shadow: 0 0 8px rgba(210, 168, 255, 0.45);
    animation: install-jarjar-pulse 1.4s ease-in-out infinite;
}
@keyframes install-jarjar-pulse {
    0%, 100% { transform: scale(0.7); opacity: 0.45; }
    50%      { transform: scale(1.0); opacity: 1; }
}
.install-jarjar__success {
    padding: 14px 12px;
    border: 1px solid rgba(126, 231, 135, 0.45);
    background: rgba(126, 231, 135, 0.06);
    border-radius: 6px;
}
.install-jarjar__success-title {
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 14px;
    font-weight: 700;
    color: #7ee787;
    margin-bottom: 4px;
}
.install-jarjar__success-sub {
    font-size: 12px;
    color: var(--ink-1, rgba(255,255,255,0.88));
    line-height: 1.45;
}

.install-jarjar__table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-mono);
    font-size: 11px;
}
.install-jarjar__table th,
.install-jarjar__table td {
    text-align: left;
    padding: 5px 6px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
    color: var(--ink-1, rgba(255,255,255,0.88));
}
.install-jarjar__table th {
    color: var(--ink-3, rgba(255,255,255,0.45));
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 9.5px;
}
.install-jarjar__status {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 3px;
}
.install-jarjar__status--active  { color: #7ee787; background: rgba(126,231,135,0.10); }
.install-jarjar__status--pending { color: #fbbf24; background: rgba(251,191,36,0.10); }
.install-jarjar__status--revoked { color: #fda4af; background: rgba(253,164,175,0.10); }
.install-jarjar__status--expired { color: var(--ink-3, rgba(255,255,255,0.45)); background: rgba(255,255,255,0.04); }
.install-jarjar__when {
    color: var(--ink-3, rgba(255,255,255,0.45));
}
.install-jarjar__revoke {
    appearance: none;
    background: transparent;
    color: #fda4af;
    border: 1px solid rgba(253, 164, 175, 0.35);
    border-radius: 4px;
    padding: 2px 7px;
    font-family: var(--font-mono);
    font-size: 10px;
    cursor: pointer;
    transition: background 100ms ease;
}
.install-jarjar__revoke:hover { background: rgba(253, 164, 175, 0.10); }
.install-jarjar__revoke:disabled { opacity: 0.45; cursor: not-allowed; }

/* Workshop stub variant — smaller popover for the not-yet-wired chapters.
   Reuses the .brain-picker shell + adds a teaser + a "Coming soon" chip. */
.workshop-stub { width: 320px; }
.workshop-stub__teaser {
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--ink-1, rgba(255, 255, 255, 0.85));
    margin-bottom: 12px;
}
.workshop-stub__meta {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-top: 10px;
    border-top: 1px solid rgba(102, 192, 244, 0.15);
}
.workshop-stub__chip {
    font-family: var(--font-mono);
    font-size: 9px;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--acc-cyan, #06b6d4);
    padding: 3px 8px;
    border: 1px solid rgba(102, 192, 244, 0.55);
    border-radius: 1px;
    background: rgba(6, 182, 212, 0.08);
}
.workshop-stub__field {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.06em;
    color: var(--ink-3, rgba(255, 255, 255, 0.50));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ═══ Annotation overlay (workshop entrance) ═══
   On hover (or tap), an SVG overlay covers the subject slot showing
   leader lines that point from anchor dots on the rig to clickable
   labels (BRAIN / VOICE / VISION / MEMORY / SKILLS / BODY). Wired by
   app/ui/stage-annotations.js. Hidden in context mode. */
/* Overlay lives inside .lobby-stage__subject and is re-attached after
   each model swap (stage-subject.js wipes the subject's innerHTML on
   render, so we re-append on every STAGE_SUBJECT in stage-annotations.js).
   Always visible — the workshop-affordance reads better as a permanent
   schematic than a hover-gated one. */
.lobby-stage__annotations {
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Starts hidden. stage-annotations.js adds `is-ready` once the
       FIRST model swap has completed (data-swapping attribute removed)
       — that prevents the annotations from flashing over an empty
       subject during page load. After ready, only visible on hover. */
    opacity: 0;
    transition: opacity 220ms var(--ease-out, ease-out);
    /* Bumped high so the model-viewer's WebGL canvas (which sometimes
       composites with elevated GPU layers) can't paint over the schematic. */
    z-index: 50;
    isolation: isolate;
}
/* Reveal only while the user hovers the model (and after the first
   model swap has loaded). Labels live inside the subject so hovering
   from model → label keeps the overlay open without flicker. */
.lobby-stage__subject:hover .lobby-stage__annotations.is-ready {
    opacity: 1;
}
/* Always hide during a model swap, even mid-hover. */
.lobby-stage__subject[data-swapping="true"] .lobby-stage__annotations {
    opacity: 0;
}
.lobby-stage[data-stage-mode="context"] .lobby-stage__annotations { display: none; }

.lobby-stage__anno-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    pointer-events: none;
}
.lobby-stage__anno-line {
    fill: none;
    stroke: var(--acc-cyan, #06b6d4);
    stroke-width: 0.4;
    stroke-dasharray: 1.4 1.0;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.75;
}
.lobby-stage__anno-dot {
    fill: var(--acc-cyan, #06b6d4);
    filter: drop-shadow(0 0 1.2px rgba(6, 182, 212, 0.9));
    pointer-events: none;   /* drag handled by the larger .anno-dot-hit beside it */
}
/* Transparent, larger circle stacked under the visible dot — gives a
   reasonable hit-target for grabbing the anchor end of a callout.
   pointer-events: auto overrides the .anno-svg's parent-level none. */
.lobby-stage__anno-dot-hit {
    fill: transparent;
    pointer-events: auto;
    cursor: grab;
    transition: fill 120ms ease;
}
.lobby-stage__anno-dot-hit:hover { fill: rgba(6, 182, 212, 0.16); }
.lobby-stage__anno-dot-hit.is-dragging { cursor: grabbing; fill: rgba(6, 182, 212, 0.28); }

.lobby-stage__anno-label {
    all: unset;
    position: absolute;
    pointer-events: auto;
    cursor: grab;
    touch-action: none;          /* let pointer events drive the drag on touch screens */
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.20em;
    text-transform: uppercase;
    color: var(--ink-1, rgba(255, 255, 255, 0.85));
    background: rgba(6, 9, 18, 0.88);
    border: 1px solid rgba(102, 192, 244, 0.45);
    border-radius: 2px;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    white-space: nowrap;
    transition: color var(--dur-sm), border-color var(--dur-sm), box-shadow var(--dur-sm), transform var(--dur-sm);
}
.lobby-stage__anno-label.is-dragging {
    cursor: grabbing;
    transition: none;            /* no hover-scale jitter while dragging */
}
.lobby-stage__anno-label:hover {
    color: #fff;
    border-color: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 14px rgba(6, 182, 212, 0.32);
    transform: translate(var(--anno-tx, 0), var(--anno-ty, 0)) scale(1.05);
}
.lobby-stage__anno-chev {
    color: var(--acc-cyan, #06b6d4);
    font-size: 9px;
}

/* Label-side variants — translate around the percent anchor so the
   label's NEAR edge sits exactly at label_at and the leader line meets
   it cleanly. The percent positions in stage-annotations.js are tuned
   so labels stay inside the subject's 0-100% box. */
.lobby-stage__anno-label--top    { --anno-tx: -50%; --anno-ty: -100%; transform: translate(-50%, -100%); }
.lobby-stage__anno-label--bottom { --anno-tx: -50%; --anno-ty: 0;     transform: translate(-50%, 0);     }
.lobby-stage__anno-label--left   { --anno-tx: -100%; --anno-ty: -50%; transform: translate(-100%, -50%); }
.lobby-stage__anno-label--right  { --anno-tx: 0;     --anno-ty: -50%; transform: translate(0, -50%);     }


/* On small viewports the overlay would crowd the model. Hide on phones — the
   workshop is desktop-first; mobile gets the chat bubble only. */
@media (max-width: 768px) {
    .lobby-stage__annotations { display: none; }
}

/* ═══ Chat with DexHero (workshop layer) ═══
   Input lives below the nameplate; speech bubble floats to the upper-
   right of the centered subject like a comic-book cloud. Hidden entirely
   while the stage is in context mode (a panel is open over the lobby). */
.lobby-stage__chat {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    max-width: 380px;
    margin-top: 14px;
    padding: 4px 6px;
    background: rgba(15, 22, 36, 0.72);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1px solid rgba(102, 192, 244, 0.30);
    border-radius: 2px;
    transition: border-color var(--dur-sm), box-shadow var(--dur-sm);
}
.lobby-stage__chat:focus-within {
    border-color: rgba(102, 192, 244, 0.70);
    box-shadow: 0 0 14px rgba(6, 182, 212, 0.20);
}
.lobby-stage[data-stage-mode="context"] .lobby-stage__chat { display: none; }
.lobby-stage__chat-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: 0;
    outline: none;
    padding: 8px 10px;
    color: var(--ink-0, #fff);
    font-family: var(--font-mono);
    font-size: 12.5px;
    letter-spacing: 0.02em;
}
.lobby-stage__chat-input::placeholder {
    color: var(--ink-3, rgba(255, 255, 255, 0.45));
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 10.5px;
}
.lobby-stage__chat-input:disabled { opacity: 0.5; cursor: not-allowed; }
.lobby-stage__chat[aria-busy="true"] .lobby-stage__chat-input { opacity: 0.6; }
.lobby-stage__chat-send {
    all: unset;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    color: var(--acc-cyan, #06b6d4);
    border: 1px solid rgba(102, 192, 244, 0.45);
    border-radius: 2px;
    cursor: pointer;
    transition: background var(--dur-sm), border-color var(--dur-sm), color var(--dur-sm);
}
.lobby-stage__chat-send:hover {
    color: #fff;
    background: rgba(6, 182, 212, 0.16);
    border-color: rgba(102, 192, 244, 0.85);
}

/* Voice mute / unmute toggle — sits between the chat input and the
   send button. Speaker icon swaps based on `aria-pressed`. */
.lobby-stage__voice-toggle {
    all: unset;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    color: var(--ink-2);
    border: 1px solid var(--rule-strong);
    border-radius: 2px;
    cursor: pointer;
    flex-shrink: 0;
    transition: color var(--dur-sm), border-color var(--dur-sm), background var(--dur-sm), box-shadow var(--dur-sm);
}
.lobby-stage__voice-toggle:hover {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.55);
}
.lobby-stage__voice-toggle:focus-visible {
    outline: none;
    color: var(--acc-cyan);
    border-color: var(--acc-cyan);
    box-shadow: var(--glow-cyan-sm);
}
.lobby-stage__voice-icon-off { display: none; }
.lobby-stage__voice-toggle[aria-pressed="true"] {
    color: var(--warn);
    border-color: rgba(251, 191, 36, 0.4);
    background: rgba(251, 191, 36, 0.06);
}
.lobby-stage__voice-toggle[aria-pressed="true"] .lobby-stage__voice-icon-on  { display: none; }
.lobby-stage__voice-toggle[aria-pressed="true"] .lobby-stage__voice-icon-off { display: inline-block; }

/* JarJar status pill — pulses cyan while the AI is actively speaking
   or driving the body. Visible at-a-glance signal that an LLM is
   wired through to puppeteer Truffle. */
.lobby-stage__jarjar-status {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 8px;
    border: 1px solid var(--rule);
    border-radius: 2px;
    font-family: var(--font-mono);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--ink-3);
    background: rgba(255, 255, 255, 0.02);
    transition: color 200ms, border-color 200ms, background 200ms, box-shadow 200ms;
    flex-shrink: 0;
}
.lobby-stage__jarjar-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ink-4);
    box-shadow: none;
    transition: background 200ms, box-shadow 200ms;
}
.lobby-stage__jarjar-status[data-state="online"] {
    color: var(--acc-cyan);
    border-color: rgba(6, 182, 212, 0.4);
    background: rgba(6, 182, 212, 0.06);
}
.lobby-stage__jarjar-status[data-state="online"] .lobby-stage__jarjar-dot {
    background: var(--acc-cyan);
    box-shadow: 0 0 8px rgba(6, 182, 212, 0.7);
}
.lobby-stage__jarjar-status[data-state="speaking"] {
    color: var(--ok);
    border-color: rgba(34, 197, 94, 0.55);
    background: rgba(34, 197, 94, 0.08);
    box-shadow: 0 0 12px rgba(34, 197, 94, 0.18);
}
.lobby-stage__jarjar-status[data-state="speaking"] .lobby-stage__jarjar-dot {
    background: var(--ok);
    box-shadow: 0 0 10px rgba(34, 197, 94, 0.9);
    animation: jarjar-pulse 1.2s ease-in-out infinite;
}
@keyframes jarjar-pulse {
    0%, 100% { transform: scale(1);   opacity: 1; }
    50%      { transform: scale(1.5); opacity: 0.6; }
}

/* Speech-bubble container — TWO positional states:
     • Default (dock)  — `left: calc(100vw - 32px)`; the stack sits
       mostly off-screen with a 32 px sliver of cyan visible at the
       right edge. Old bubbles live here.
     • Hover reveal    — `left: clamp(60vw, 66vw, calc(100vw - 320px))`;
       user hovers the sliver → whole stack slides in.
   New bubbles are BORN here too (in the flex stack), but get an
   `--at-character` transform that visually offsets them leftward to
   Truffle's head while they speak. When typing finishes, JS strips
   that class and the bubble's transform transitions back to identity,
   sliding it RIGHT into the dock with its older siblings. */
/* Title strip that sits just above the bubble dock at the right edge.
   Plain white text — clicking a title swaps body[data-active-pane]
   which drives whether the column below is showing the chat log,
   activity feed, to-do list, or topics. */
/* The bubble titles strip is moved into the right wing at runtime by
 * app/ui/wing-tabs-mount.js so it reads as the wing's tab-bar header
 * sitting flush above the active panel (Activity / To-Do / Topics).
 * Defaults below cover the brief boot window BEFORE the JS move-in
 * runs (kept hidden / off-screen). The :scope-style selector below
 * the defaults takes over once the element is inside the wing. */
.lobby-stage__bubble-titles {
    position: fixed;
    top: -9999px;                    /* hide pre-mount, no layout disruption */
    opacity: 0;
    pointer-events: none;
    display: flex;
    align-items: baseline;
}
/* Tab strip is hidden — wing shows only the activity (chat) panel.
 * Keeping the strip in the DOM (and the click handler wired) so the
 * To-Do / Topics tabs can be re-enabled later by unhiding this rule.
 * The strip's body[data-active-pane] state still defaults to 'activity'
 * so the right panel stays visible. */
.lobby-wing--right > .lobby-stage__bubble-titles {
    display: none;
}
/* Title strip is visible whenever the right wing is uncollapsed
   (the default state). The arrow toggle button on the stage
   collapses the wing — at which point the titles fade out too. */
body:has(.lobby-wing--right:not(.lobby-wing--collapsed)) .lobby-stage__bubble-titles {
    pointer-events: auto;
    opacity: 1;
    transform: translateY(0);
}
.lobby-stage__bubble-title {
    background: transparent;
    border: 0;
    color: rgba(255, 255, 255, 0.78);
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
    padding: 4px 0 6px;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.75);
    transition: color 160ms, text-shadow 160ms;
    border-bottom: 2px solid transparent;
    white-space: nowrap;
}
.lobby-stage__bubble-title:hover {
    color: #ffffff;
    text-shadow: 0 0 10px rgba(255, 255, 255, 0.45), 0 1px 4px rgba(0, 0, 0, 0.75);
}
.lobby-stage__bubble-title.is-active {
    color: #ffffff;
    border-bottom-color: var(--acc-cyan, #06b6d4);
    text-shadow: 0 0 12px rgba(6, 182, 212, 0.55), 0 1px 4px rgba(0, 0, 0, 0.75);
}

.lobby-stage__bubble {
    /* Bottom-centered dock: replies land here, stacked above the chat
     * input and visually OVER the nameplate. `column-reverse` keeps
     * the newest reply at the bottom (closest to the chat bar = the
     * user's eye-line) while older replies push upward.
     *
     * The width/height/bottom values are exposed as custom properties
     * so the titles strip can position itself in lockstep. */
    --bubble-dock-width:  clamp(300px, 88vw, 760px);
    --bubble-dock-bottom: 96px;
    --bubble-dock-max-h:  48vh;
    position: fixed;
    bottom: var(--bubble-dock-bottom);
    left: 50%;
    transform: translateX(-50%);
    width: var(--bubble-dock-width);
    max-width: 760px;
    max-height: var(--bubble-dock-max-h);
    overflow-y: auto;
    overflow-x: hidden;
    display: flex;
    flex-direction: column-reverse;
    gap: 12px;
    /* Above the nameplate so a long reply visually covers the hero
     * name (per user spec — "over the dexhero name"). */
    z-index: 42;
    transition: opacity 200ms ease;
    /* Slim scrollbar so long stacks don't burst the centered column. */
    scrollbar-width: thin;
    scrollbar-color: rgba(6, 182, 212, 0.35) transparent;
}
.lobby-stage__bubble::-webkit-scrollbar { width: 6px; }
.lobby-stage__bubble::-webkit-scrollbar-track { background: transparent; }
.lobby-stage__bubble::-webkit-scrollbar-thumb { background: rgba(6, 182, 212, 0.35); border-radius: 3px; }

/* Bubble dock is bottom-centered — no peek/dock-slide behavior
 * needed (the old right-edge dock had a sliver/peek system that's
 * obsolete with centered layout). The hover-catcher element and
 * right-edge hit-area extender are also retired for the same reason. */
.lobby-stage__hover-catch { display: none !important; }
/* Full opacity on every dock item when titles are hovered — without
   this the bubble dock looks dim because `.lobby-stage__bubble:not(:hover)`
   matches when the cursor is on the titles, not on the bubbles. */
body:has(.lobby-stage__bubble-titles:hover) .lobby-stage__bubble-item:not(.lobby-stage__bubble-item--at-character) {
    opacity: 1;
    transform: scale(1);
}

/* Each bubble in the stack — the visual chrome lives on items, not
   the container, so the container can keep overflow:visible for tails
   and stack-depth opacity. Items have a TWO-phase lifecycle:
     1. Born with `--at-character` → positioned visually at Truffle's
        mouth via transform offset; user sees the speech happen there.
     2. After typing completes, JS removes `--at-character` → transform
        transitions back to identity → bubble SLIDES from Truffle into
        its slot at the top of the right-side stack. Older items
        already in the stack get pushed down by the layout. */
.lobby-stage__bubble-item {
    position: relative;
    padding: 12px 16px 13px 16px;
    background: rgba(6, 9, 18, 0.92);
    border: 1px solid rgba(6, 182, 212, 0.35);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45), 0 0 22px rgba(6, 182, 212, 0.16);
    color: var(--ink-0, #fff);
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    font-size: 13.5px;
    line-height: 1.55;
    letter-spacing: -0.005em;
    overflow: visible;
    cursor: pointer;
    transform-origin: top right;
    animation: bubble-slot-open 360ms cubic-bezier(0.22, 1, 0.36, 1);
    transition:
        transform 620ms cubic-bezier(0.22, 1, 0.36, 1),
        opacity 320ms ease;
}

/* Born-at-character state — three transforms combined:
     1. `translate(--dh-head-sway-x, --dh-head-sway-y)` — tracks
        Truffle's head/body motion every frame (set by the body
        driver). The bubble follows him around the stage.
     2. `translateX(active_left - dock_left)` — shifts the item from
        the container's dock position over to the active position
        just right of Truffle's head.
     3. `translateY(-100%)` — `100%` in translateY resolves to the
        element's OWN current height, so this pins the bubble's BOTTOM
        at the container's top edge (26vh ≈ Truffle's mouth) and lets
        the bubble grow UPWARD as new lines wrap in. Without this the
        bubble would extend down from 26vh into his chest.
   After typing JS removes the class; transform transitions back to
   identity and the bubble slides rightward AND downward into the
   dock with its older siblings. */
.lobby-stage__bubble-item--at-character {
    /* Intrinsic width — bubble starts narrow with just the dots (~80
       px) and grows rightward as the typewriter fills in text, capped
       at 280 px where it wraps. `align-self: flex-start` overrides
       the container's default cross-axis stretch so the item doesn't
       balloon to full width while waiting for the reply. */
    align-self: flex-start;
    width: max-content;
    min-width: 80px;
    max-width: 280px;
    /* No viewport-relative translation here anymore — the speaking
       bubble lives inside `#lobby-stage-speaking-bubble`, which is
       itself positioned at the model's projected head anchor (see
       --dh-head-anchor-x/y published from dexhero-body-driver.js).
       Head sway is applied on the surrounding container instead so
       the bubble rocks with the head pixel-for-pixel.   */
    z-index: 2;
}

/* ── Speaking-bubble surface — anchored to the model's head ─────────
   The body driver projects the head bone's world position into
   viewport pixel coords every rAF and publishes them as the CSS
   variables below. This container lives at that exact spot,
   bottom-centre, so the speaking bubble visibly emanates from
   Truffle's head no matter where the model is on screen.

   Defaults (50vw / 28vh / no sway) are used until the body binds
   so a freshly-loaded page renders the bubble somewhere sane
   instead of (0,0). Once the rAF tick runs, the live values take
   over without re-paint flicker. */
.lobby-stage__speaking-bubble {
    position: fixed;
    left: var(--dh-head-anchor-x, 50vw);
    top:  var(--dh-head-anchor-y, 28vh);
    width: max-content;
    max-width: 320px;
    /* Comic-style speech-bubble offset: the balloon sits to the
       upper-right of the head, with its bottom-left corner just
       past the model's silhouette. This reads as a real speech
       bubble (pointing back at the character) instead of a label
       perfectly stacked over the head. Head sway is applied here
       so the bubble rocks with the head bone every frame.

       Geometry: `translate(35%, ...)` shifts the bubble so 35% of
       its width sits past the head anchor X. `calc(-100% + 12px)`
       lifts the bubble fully up with a small overlap so the
       bottom-left corner reads as connected to the head. */
    transform:
        translate(var(--dh-head-sway-x, 0px), var(--dh-head-sway-y, 0px))
        translate(35%, calc(-100% + 12px));
    transform-origin: bottom left;
    pointer-events: none;
    z-index: 45;
    display: flex;
    flex-direction: column;
    gap: 8px;
    /* Smooth follow even on layout reflows (resize, orientation
       changes). The pixel-by-pixel rAF updates are cheap enough that
       this transition is only visible on big jumps (body swap, etc). */
    transition: left 80ms linear, top 80ms linear;
}
.lobby-stage__speaking-bubble > * { pointer-events: auto; }
.lobby-stage__speaking-bubble[hidden] { display: none; }
/* The bubble item inside the speaking surface keeps its own little
   transform for head sway only — the container's position already
   places it at the head. */
.lobby-stage__speaking-bubble .lobby-stage__bubble-item {
    transform: none;
    margin: 0;
}
/* (Stacked dock bubbles have no tail — see :first-child rules below
   which scope the tail to `--at-character` only.) */

/* Stack depth — older bubbles dim and shrink slightly so the newest
   stands out. Doesn't apply while a bubble is still --at-character
   (it owns z-index:2 and its own transform). On hover the dimming
   clears so the user can read every bubble in the stack. */
.lobby-stage__bubble:not(:hover) .lobby-stage__bubble-item:not(.lobby-stage__bubble-item--at-character):nth-child(2) { opacity: 0.78; transform: scale(0.985); }
.lobby-stage__bubble:not(:hover) .lobby-stage__bubble-item:not(.lobby-stage__bubble-item--at-character):nth-child(3) { opacity: 0.55; transform: scale(0.97);  }
.lobby-stage__bubble:not(:hover) .lobby-stage__bubble-item:not(.lobby-stage__bubble-item--at-character):nth-child(4) { opacity: 0.35; transform: scale(0.955); }
.lobby-stage__bubble:not(:hover) .lobby-stage__bubble-item:not(.lobby-stage__bubble-item--at-character):nth-child(n+5) { opacity: 0.18; transform: scale(0.94); }

/* Slot-open keyframe — the new item's vertical space expands from 0
   to natural height so existing items below get smoothly pushed down
   instead of snapping. Runs in parallel with the at-character transform
   so the bubble visually inflates AT Truffle's mouth while the stack
   slot opens above. */
@keyframes bubble-slot-open {
    from { max-height: 0; margin-top: -16px; opacity: 0; }
    to   { max-height: 400px; margin-top: 0; opacity: 1; }
}

/* Speech-bubble tail — ONLY on the currently-speaking bubble (the
   `--at-character` one at Truffle's head). Stacked dock bubbles are
   archived speech and don't need a tail. The tail at the bubble's
   bottom-left points DOWN-LEFT back toward Truffle (who sits below-
   left of the bubble's active position). ::before is the cyan outline;
   ::after is a 1 px-smaller dark fill that leaves a continuous ring. */
.lobby-stage__bubble-item--at-character::before {
    content: '';
    position: absolute;
    bottom: -16px;
    left: 19px;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 16px 18px 0 0;
    border-color: rgba(6, 182, 212, 0.35) transparent transparent transparent;
    pointer-events: none;
}
.lobby-stage__bubble-item--at-character::after {
    content: '';
    position: absolute;
    bottom: -14px;
    left: 20px;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 14px 16px 0 0;
    border-color: rgba(6, 9, 18, 0.92) transparent transparent transparent;
    pointer-events: none;
}

.lobby-stage__bubble-body {
    display: block;
    color: var(--ink-0, #fff);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-height: 220px;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
    scrollbar-color: rgba(6, 182, 212, 0.35) transparent;
}
.lobby-stage__bubble-body::-webkit-scrollbar { width: 6px; }
.lobby-stage__bubble-body::-webkit-scrollbar-track { background: transparent; }
.lobby-stage__bubble-body::-webkit-scrollbar-thumb { background: rgba(6, 182, 212, 0.35); border-radius: 3px; }
.lobby-stage__bubble[hidden] { display: none; }
/* CTA variant — when no AI model is connected. Allows pointer events
   on the inline link so the body's link is clickable. */
.lobby-stage__bubble-item--cta { pointer-events: auto; }

/* User message variant — single-line chat-dialog entry that lands
   directly in the dock when the user submits. Distinct cyan-blue
   tint so the eye reads the column as alternating AI ↔ user lines.
   Tighter padding + smaller font + nowrap so it stays compact
   regardless of message length. */
.lobby-stage__bubble-item--user {
    background: rgba(59, 130, 246, 0.18);
    border-color: rgba(59, 130, 246, 0.45);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35), 0 0 14px rgba(59, 130, 246, 0.12);
    padding: 7px 14px 8px 14px;
    font-size: 12.5px;
}
.lobby-stage__bubble-item--user .lobby-stage__bubble-body {
    max-height: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.lobby-stage__bubble-cta {
    all: unset;
    cursor: pointer;
    display: inline;
    font-family: inherit;
    font-size: inherit;
    font-weight: 600;
    line-height: inherit;
    color: var(--acc-cyan, #06b6d4);
    border-bottom: 1px dashed rgba(102, 192, 244, 0.55);
    transition: color var(--dur-sm), border-color var(--dur-sm);
}
.lobby-stage__bubble-cta:hover {
    color: #fff;
    border-bottom-color: var(--acc-cyan, #06b6d4);
}
.lobby-stage__bubble-cta::after {
    content: ' \203A';
    margin-left: 2px;
    color: inherit;
}

.lobby-stage[data-stage-mode="context"] .lobby-stage__bubble { display: none; }

/* Quick-reply column — pairs with the docked speech bubble. Sits
   directly underneath the bubble's reveal position so the user sees
   the chips at the same time as the bubble. Tied to the bubble's
   docked / revealed state via the body-level `data-bubble-revealed`
   attribute. */
.lobby-stage__quick-reply {
    position: fixed;
    top: calc(22vh + 220px);  /* below the bubble's natural height */
    right: 0;
    left: auto;
    width: 220px;
    z-index: var(--z-overlay, 40);
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.80) 0%, rgba(5, 6, 10, 0.92) 100%);
    border: 1px solid rgba(6, 182, 212, 0.32);
    border-right: 0;
    border-top-left-radius: 12px;
    border-bottom-left-radius: 12px;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    box-shadow: -10px 0 32px rgba(0, 0, 0, 0.45);
    font-family: var(--font-display, 'Inter'), system-ui, sans-serif;
    pointer-events: auto;
    opacity: 1;
    /* Same dock pattern as the bubble — translateX pushes most of the
       chip stack off-screen by default. Only reveals when the bubble
       reveals (via the body-level data attribute below, set by JS). */
    --quick-dock-x: calc(100% - 28px);
    transform: translateX(var(--quick-dock-x)) translate(var(--dh-head-sway-x, 0px), var(--dh-head-sway-y, 0px));
    transition: transform 360ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
    animation: quick-reply-in 240ms cubic-bezier(0.22, 1, 0.36, 1);
}
body[data-bubble-revealed="true"] .lobby-stage__quick-reply,
.lobby-stage__quick-reply:hover {
    --quick-dock-x: 0px;
}
.lobby-stage__quick-reply[hidden] { display: none; }
@keyframes quick-reply-in {
    from { opacity: 0; transform: translate(var(--dh-head-sway-x, 0px), calc(var(--dh-head-sway-y, 0px) + 4px)) scale(0.96); }
    to   { opacity: 1; transform: translate(var(--dh-head-sway-x, 0px), var(--dh-head-sway-y, 0px)) scale(1); }
}
.lobby-stage__quick-reply-label {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--acc-cyan, #06b6d4);
    padding: 2px 4px 4px;
}
.lobby-stage__quick-reply-chip {
    background: rgba(6, 182, 212, 0.08);
    border: 1px solid rgba(6, 182, 212, 0.35);
    border-radius: 8px;
    color: var(--ink-0, #fff);
    font-family: var(--font-display);
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.3;
    padding: 8px 12px;
    text-align: left;
    cursor: pointer;
    transition: color 150ms, background 150ms, border-color 150ms, transform 150ms, box-shadow 150ms;
}
.lobby-stage__quick-reply-chip:hover {
    background: rgba(6, 182, 212, 0.18);
    border-color: rgba(6, 182, 212, 0.65);
    transform: translateX(-2px);
    box-shadow: 0 0 14px rgba(6, 182, 212, 0.25);
}
.lobby-stage__quick-reply-chip:focus-visible {
    outline: none;
    border-color: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 0 2px rgba(6, 182, 212, 0.35);
}
.lobby-stage[data-stage-mode="context"] .lobby-stage__quick-reply { display: none; }

/* Loading dots inside the bubble while the brain is thinking */
.lobby-stage__bubble-dots {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.lobby-stage__bubble-dots i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 6px rgba(6, 182, 212, 0.6);
    animation: lobby-stage-bubble-pulse 1.2s ease-in-out infinite;
}
.lobby-stage__bubble-dots i:nth-child(2) { animation-delay: 0.15s; }
.lobby-stage__bubble-dots i:nth-child(3) { animation-delay: 0.30s; }
@keyframes lobby-stage-bubble-pulse {
    0%, 100% { opacity: 0.30; transform: scale(0.8); }
    50%      { opacity: 1;    transform: scale(1);   }
}

/* Stage caption (solo mode only) */
.lobby-stage__caption {
    position: absolute;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-2);
    white-space: nowrap;
    z-index: 2;
    opacity: 0;
    transition: opacity var(--dur-lg) var(--ease-out);
}
.lobby-stage[data-stage-mode="context"] .lobby-stage__caption { opacity: 1; }
.lobby-stage__caption strong { color: var(--ink-0); font-weight: 600; }

/* Arrow nav (idle: scroll carousel ±1 item; hidden in context) */
.lobby-stage__nav {
    position: absolute;
    /* Anchored to the top-right of the stage area, which is the top-
       left edge of the right wing. Reads as an affordance attached to
       the panel it controls. */
    top: 16px;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    border: 1px solid rgba(102, 192, 244, 0.32);
    background: rgba(6, 9, 18, 0.55);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: var(--ink-1);
    cursor: pointer;
    opacity: 0.55;
    transition: opacity var(--dur-sm), color var(--dur-sm), border-color var(--dur-sm), transform var(--dur-sm), box-shadow var(--dur-sm);
    display: flex; align-items: center; justify-content: center;
    z-index: 4;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.42);
}
.lobby-stage__nav--prev { left: 16px; }
/* Right-wing toggle: sits attached to the LEFT edge of the right
   wing so it reads as a handle on the panel it controls. When the
   wing is collapsed the arrow slides over to the right edge to act
   as a "tap to re-open" affordance. Uses `position: fixed` so it
   anchors to the viewport regardless of the lobby-stage column. */
.lobby-stage__nav--next {
    position: fixed;
    z-index: calc(var(--z-overlay, 40) + 2);
    transition: left 240ms cubic-bezier(0.22, 1, 0.36, 1),
                right 240ms cubic-bezier(0.22, 1, 0.36, 1),
                top 240ms cubic-bezier(0.22, 1, 0.36, 1),
                opacity var(--dur-sm),
                color var(--dur-sm),
                border-color var(--dur-sm),
                transform var(--dur-sm),
                box-shadow var(--dur-sm);
}
/* WHEN WING IS OPEN — flat in-header chrome. The button sits where the
   "While you were away" sub-text was, on the right edge of the
   .chat-log__head row. No circle, no border, no backdrop blur — reads
   as a control inside the wing's title bar. The wing-geometry vars
   (--wing-right-top / --wing-right-left / --wing-right-width) keep
   it glued to the wing as the user resizes or drags. */
.lobby-stage__nav--next:not([data-wing-collapsed="true"]) {
    width: 26px;
    height: 26px;
    border-radius: 4px;
    border: 0;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
    opacity: 0.72;
    /* Vertically centered on the chat-log__head row (head padding 10px +
       its line-height ≈ 14px → center at top + 14px). */
    top: calc(var(--wing-right-top, 12px) + 14px);
    left: calc(var(--wing-right-left, calc(100vw - 320px - 16px))
             + var(--wing-right-width, 320px) - 30px);   /* 26px button + 4px inset */
}
.lobby-stage__nav--next:not([data-wing-collapsed="true"]):hover {
    background: rgba(110, 231, 255, 0.10);
    border: 0;
    transform: scale(1.06);
    box-shadow: none;
    opacity: 1;
}
/* Hide the "While you were away" sub-text while the wing is open so
   the arrow occupies its slot. The sub-text is purely decorative — the
   activity panel works the same with or without it. */
body:has(.lobby-wing--right:not(.lobby-wing--collapsed)) .chat-log__head-sub {
    display: none;
}

/* WHEN WING IS COLLAPSED — keep the floating circle-arrow tab at the
   viewport edge so the user can re-open the wing. */
.lobby-stage__nav--next[data-wing-collapsed="true"] {
    top: 22vh;
    left: auto;
    right: 16px;
}
/* While the user is drag-resizing the wing, kill the arrow's `right`
 * transition so it tracks the wing pixel-for-pixel instead of
 * lagging 240ms behind every mousemove tick. (.is-wing-resizing is
 * set on body by app/ui/right-wing-resize.js for the drag duration.) */
body.is-wing-resizing .lobby-stage__nav--next {
    transition: none;
}
.lobby-stage:hover .lobby-stage__nav { opacity: 0.9; }
.lobby-stage__nav:hover {
    color: var(--acc-cyan);
    border-color: var(--acc-cyan);
    opacity: 1;
    transform: scale(1.06);
    box-shadow: 0 6px 22px rgba(6, 182, 212, 0.36);
}
.lobby-stage__nav:active { transform: scale(0.96); }
.lobby-stage[data-stage-mode="context"] .lobby-stage__nav { display: none; }

/* ═══ Bottom bar ═══ */
.lobby-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: 0 var(--sp-6);
    height: var(--bar-h);
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border-top: 1px solid var(--rule);
    z-index: var(--z-bar);
    animation: hud-rise 500ms var(--ease-out) both;
}

.lobby-bar__chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-1);
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    color: var(--ink-1);
}
.lobby-bar__chip .hud-dot { margin-right: 2px; }

/* Live counters next to the ONLINE chip — populated by app/shell.js's
   live-stats poller. Compact + monospace so the numbers don't reflow
   the bar when they change. */
.lobby-bar__stats {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.lobby-bar__stats-dot { color: var(--acc-ok, #22c55e); margin-right: 2px; font-size: 10px; }
.lobby-bar__stats-sep { color: var(--ink-4, var(--ink-3)); margin: 0 2px; }
.lobby-bar__stats [data-stat] {
    color: var(--ink-1);
    font-variant-numeric: tabular-nums;
}
.lobby-bar__stat { display: inline; white-space: nowrap; }
/* Desktop shows the verbose label, mobile rule below swaps to the short one. */
.lobby-bar__stat-short { display: none; }

.lobby-bar__cta {
    flex: 1;
    display: flex;
    justify-content: center;
}

.lobby-bar__end {
    display: flex;
    align-items: center;
    gap: 12px;
    font-family: var(--font-mono);
    font-size: var(--fs-mono-sm);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-3);
}
.lobby-bar__end a { color: var(--ink-3); text-decoration: none; transition: color var(--dur-sm); }
.lobby-bar__end a:hover { color: var(--acc-cyan); }

/* "Changes · ON/OFF" master toggle — text-only, inherits the lobby-bar
 * link style so it sits inline with no visible container. Amber tint
 * when toggled OFF so the non-default state reads at a glance. */
.lobby-bar__changes-toggle {
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: var(--ink-3);
    cursor: pointer;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    transition: color var(--dur-sm);
}
.lobby-bar__changes-toggle:hover { color: var(--acc-cyan); }
.lobby-bar__changes-toggle[data-state="off"] {
    color: rgba(255, 200, 120, 0.85);   /* amber — defaults are showing */
}
.lobby-bar__changes-toggle[data-state="off"]:hover {
    color: rgba(255, 200, 120, 1);
}

/* ═══ Panel host ═══
   The host itself must never capture pointer events — it's a full-viewport
   overlay. Children (individual .panel elements and their veils) have their
   own pointer-events: auto, so clicks on a panel still land, but clicks on
   empty host area fall through to the shell chrome underneath. */
.lobby-panels {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: var(--z-panel);
}
.lobby-panels > * { pointer-events: auto; }

/* ═══ Toast host ═══ */
.lobby-toasts {
    position: fixed;
    top: calc(var(--ticker-h) + var(--nav-h) + 16px);
    right: 24px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    z-index: var(--z-toast);
    pointer-events: none;
}
.lobby-toast {
    pointer-events: auto;
    min-width: 240px;
    max-width: 360px;
    padding: 12px 14px;
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-2);
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: 0.04em;
    color: var(--ink-1);
    display: flex;
    align-items: center;
    gap: 10px;
    animation: hud-rise 300ms var(--ease-out) both;
}
.lobby-toast--ok    { border-color: rgba(34, 197, 94, 0.45); color: var(--ok); }
.lobby-toast--err   { border-color: rgba(239, 68, 68, 0.45); color: var(--err); }
.lobby-toast--info  { border-color: rgba(6, 182, 212, 0.45); color: var(--acc-cyan); }
.lobby-toast[data-leaving="true"] { opacity: 0; transform: translateY(-6px); transition: all var(--dur-sm); }

/* ═══ Mobile collapse ═══ */
@media (max-width: 960px) {
    .lobby-root {
        grid-template-rows: var(--ticker-h) var(--nav-h) 1fr var(--bar-h);
    }
    .lobby-nav { padding: 0 16px; gap: 16px; }
    .lobby-brand { font-size: 13px; letter-spacing: 0.22em; }
    .lobby-tabs { gap: 0; }
    .lobby-tab { padding: 10px 12px; font-size: 10px; letter-spacing: 0.25em; }
    .lobby-stage-wrap {
        grid-template-columns: 1fr;
        gap: var(--sp-5);
        padding: var(--sp-5) var(--sp-4);
    }
    .lobby-wing--left  { order: 2; align-items: center; text-align: center; }
    .lobby-stage       { order: 1; min-height: 360px; }
    .lobby-wing--right { order: 3; align-items: center; text-align: center; }
    .lobby-bar { padding: 0 12px; gap: 8px; }
    .lobby-bar__end { gap: 8px; }
    .lobby-bar__end .hide-mobile { display: none; }
    /* Mobile: swap verbose stat labels for the short variants and keep
       separators so it reads as "● 0 live · 0 srv · 0 /24h". */
    .lobby-bar__stats { font-size: 9.5px; gap: 4px; letter-spacing: 0.08em; }
    .lobby-bar__stat-full  { display: none; }
    .lobby-bar__stat-short { display: inline; }
    .lobby-wallet { padding: 6px 10px; font-size: 10px; letter-spacing: 0.14em; }
    .lobby-steam  { padding: 5px 9px;  font-size: 10px; letter-spacing: 0.14em; margin-right: 6px; }
    .lobby-llm    { padding: 5px 9px;  font-size: 10px; letter-spacing: 0.14em; margin-right: 6px; }
    .lobby-steam[data-linked="true"] .lobby-steam__label { max-width: 80px; }
    .lobby-llm[data-connected="true"] .lobby-llm__label  { max-width: 90px; }
}

@media (max-width: 560px) {
    .lobby-stage__subject {
        width: clamp(160px, 60vw, 260px);
        height: clamp(160px, 60vw, 260px);
    }
    .lobby-tab { padding: 10px 10px; }
    /* Keep "DEXHERO" visible at all widths — title is a brand anchor and
       previously folded to the logo only at ≤560px. Slightly tighter than
       the 768px rule so very narrow phones still keep title + hamburger
       on one row. */
    .lobby-brand        { font-size: 15px; letter-spacing: 0.24em; }
    .lobby-brand img    { width: 24px; height: 24px; }
    .lobby-brand span   { display: inline; }
}

/* ═══ Phone hamburger + centering safety nets ═══
   At ≤768px the desktop tab row and wallet chip yield to a hamburger-driven
   slide-out menu (reuses .mobile-menu-toggle + .mobile-nav-* CSS from
   /styles.css). The wing/stage centering rules belt-and-brace the existing
   960px collapse so phones never inherit horizontal scroll. */
@media (max-width: 768px) {
    .lobby-tabs            { display: none; }
    .lobby-wallet          { display: none; }
    .lobby-steam           { display: none; }
    .lobby-llm             { display: none; }
    .lobby-link-wrap       { display: none; }
    /* The two newer header CTAs ("DNA Feed", "Start here") were squeezing
       the hamburger off-screen on phones. Both actions are reachable from
       inside the hamburger menu (DNA Feed lands you at #/main, Connect
       Wallet is in the actions row), so hide them at this tier. */
    #lobby-main-genesis    { display: none; }
    #lobby-connect         { display: none; }
    #lobby-menu-toggle     { display: inline-flex; }
    .lobby-nav             { gap: 12px; padding: 0 12px; justify-content: space-between; }
    .lobby-nav__end        { gap: 8px; }
    /* Hamburger takes over the tab row → brand gets the freed real-estate.
       Bump size + letter-spacing so the title reads as the page anchor. */
    .lobby-brand           { font-size: 17px; letter-spacing: 0.28em; gap: 10px; }
    .lobby-brand img       { width: 26px; height: 26px; }

    .lobby-stage-wrap      { padding: var(--sp-4) var(--sp-3); gap: var(--sp-4); }
    .lobby-wing            { width: 100%; max-width: 100%; align-items: center; text-align: center; }
    .lobby-wing--left,
    .lobby-wing--right     { align-items: center; text-align: center; }
    /* Tall enough to fit the centered card after its 1.25x scale-up.
       Card layout = --card-w (≤300px) + label (~52px); scaled = ~440px.
       Without this the top/bottom of the model gets clipped on phones. */
    .lobby-stage           { width: 100%; min-width: 0; min-height: clamp(380px, 100vw, 460px); }
    .lobby-carousel        { padding: 0 6vw; }

    /* Make the centered carousel card dominate the viewport so a single
       DexHero is the focal point instead of two half-cards splitting center.
       Capped at 380px on phones so the model-viewer's native render size
       stays reasonable on lower-DPR devices. */
    .lobby-carousel__item     { --card-w: clamp(280px, 80vw, 380px); }
    .lobby-carousel__subject  { width: var(--card-w); height: var(--card-w); }
    .lobby-bar             { padding: 0 12px; }
    .lobby-bar__end        { gap: 6px; font-size: 9.5px; }

    .mobile-nav-section a[aria-current="true"] {
        color: var(--acc-cyan);
        background: rgba(6, 182, 212, 0.08);
        border-left: 2px solid var(--acc-cyan);
        padding-left: 18px;
    }
}

@media (max-width: 480px) {
    .lobby-bar__cta        { display: none; }
    .lobby-stage__caption  { font-size: 9px; }
    .lobby-carousel        { padding: 0 3vw; }

    /* Very narrow phones: drop the third stat entirely so the remaining
       two ("0 live · 0 srv") + ONLINE chip + Docs/X always fit without
       elbowing the layout off-center. (nth-child counts the dot too:
       1=dot, 2=players, 3=sep, 4=servers, 5=sep, 6=sessions.) */
    .lobby-bar__stats > :nth-child(5),
    .lobby-bar__stats > :nth-child(6) { display: none; }
}

/* ═══════════════════════════════════════════════════════════════
   Responsive right-side overlay (bubble dock + title strip + wing).
   The @media (max-width:768px) block above forces
   `.lobby-wing { width: 100%; max-width: 100% }` for the mobile
   collapse layout — that wins over `.lobby-wing--right`'s overlay
   positioning by source order. The rules below re-assert the
   overlay so the bubble dock + title strip + panel stay pinned to
   the right edge at every viewport, and the bubble/title width
   scales down so the overlay never eats half the screen on phones.
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 960px) {
    /* Right wing keeps its overlay position even when the mobile
       layout flips the rest of the wings to centered. */
    .lobby-wing.lobby-wing--right {
        position: fixed;
        top: 11vh;
        right: 12px;
        left: auto;
        width: min(280px, 38vw);
        max-width: min(280px, 38vw);
        align-items: flex-start;
        text-align: left;
        order: 0;                          /* unstacked from mobile flow */
    }
    body[data-active-pane="topics"] .lobby-wing.lobby-wing--right,
    body[data-active-pane="todo"]   .lobby-wing.lobby-wing--right {
        top: 30vh;
    }
    /* Bubble dock + title strip scale to match the wing column. */
    .lobby-stage__bubble {
        --bubble-dock-left: calc(100vw - 24px);
        --bubble-peek-left: calc(100vw - min(280px, 38vw) - 12px);
        max-width: min(280px, 38vw);
    }
    .lobby-stage__bubble-titles {
        right: 12px;
        width: min(280px, 38vw);
    }
}
@media (max-width: 600px) {
    /* On phones the overlay column gets even slimmer so the 3D
       character isn't drowned out. The title labels and bubble
       text both stay legible at this width. */
    .lobby-wing.lobby-wing--right {
        right: 8px;
        width: min(220px, 56vw);
        max-width: min(220px, 56vw);
    }
    .lobby-stage__bubble {
        --bubble-dock-left: calc(100vw - 20px);
        --bubble-peek-left: calc(100vw - min(220px, 56vw) - 8px);
        max-width: min(220px, 56vw);
    }
    .lobby-stage__bubble-titles {
        right: 8px;
        width: min(220px, 56vw);
        font-size: 11px;
    }
    .lobby-stage__bubble-title { font-size: 11px; padding: 4px 0 5px; }
}
@media (max-width: 420px) {
    /* Smallest phones — keep the overlay readable, shrink labels. */
    .lobby-wing.lobby-wing--right {
        width: min(190px, 60vw);
        max-width: min(190px, 60vw);
    }
    .lobby-stage__bubble {
        --bubble-peek-left: calc(100vw - min(190px, 60vw) - 8px);
        max-width: min(190px, 60vw);
    }
    .lobby-stage__bubble-titles {
        width: min(190px, 60vw);
        font-size: 10px;
        letter-spacing: 0.10em;
    }
    .lobby-stage__bubble-title { font-size: 10px; }
}

/* ── Brand-popover · Lobby background picker ─────────────────────
   Hover the DexHero logo + title in the top-left to open a small
   dropdown with the four preset swatches + an upload button. Same
   feature that used to live in the Profile panel; moved here for
   single-click access from any route. */
.lobby-brand-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
}
.lobby-brand-pop {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    width: 320px;
    padding: 12px;
    background: #0a0e1a;
    border: 1px solid var(--rule-strong, rgba(255,255,255,0.12));
    border-radius: var(--r-2, 10px);
    box-shadow: 0 12px 32px rgba(0,0,0,0.55);
    z-index: 90;
    animation: hud-rise 180ms var(--ease-out, ease-out) both;
}
.brand-bg__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--ink-3, rgba(255,255,255,0.55));
}
.brand-bg__reset {
    background: transparent;
    color: var(--ink-3, rgba(255,255,255,0.55));
    border: 1px solid var(--rule, rgba(255,255,255,0.06));
    border-radius: 4px;
    padding: 3px 8px;
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--dur-sm), border-color var(--dur-sm);
}
.brand-bg__reset:hover {
    color: var(--ink-0, #fff);
    border-color: var(--ink-2, rgba(255,255,255,0.5));
}
.brand-bg__swatches {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 6px;
    margin-bottom: 10px;
}
.brand-bg__swatch {
    aspect-ratio: 16 / 9;
    border: 1px solid var(--rule-strong, rgba(255,255,255,0.12));
    border-radius: 4px;
    background-size: cover;
    background-position: center;
    background-color: var(--surf-2, rgba(255,255,255,0.04));
    padding: 0;
    cursor: pointer;
    transition: border-color var(--dur-sm), box-shadow var(--dur-sm);
}
.brand-bg__swatch:hover {
    border-color: rgba(6,182,212,0.55);
    box-shadow: 0 0 8px rgba(6,182,212,0.18);
}
.brand-bg__swatch.is-selected {
    border-color: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 10px rgba(6,182,212,0.35);
}

/* Cycle swatch — first option in the picker. Shows a 2x2 quadrant of all
   four presets behind a "Cycle" label so the user sees at a glance that
   it represents the auto-rotation. Slow pulse animation hints "this is
   the moving one." Resets to default cycle when clicked. */
.brand-bg__swatch--cycle {
    position: relative;
    overflow: hidden;
    background: var(--surf-2, rgba(255,255,255,0.04));
}
.brand-bg__cycle-grid {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
}
.brand-bg__cycle-grid > span {
    background-size: cover;
    background-position: center;
    opacity: 0.85;
}
.brand-bg__cycle-label {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: #fff;
    text-shadow: 0 0 4px rgba(0,0,0,0.85), 0 1px 2px rgba(0,0,0,0.6);
    background: rgba(0,0,0,0.25);
    backdrop-filter: blur(1px);
}
.brand-bg__swatch--cycle.is-selected .brand-bg__cycle-label {
    color: var(--acc-cyan, #06b6d4);
    text-shadow: 0 0 6px rgba(6,182,212,0.6), 0 0 4px rgba(0,0,0,0.85);
}
.brand-bg__current {
    aspect-ratio: 16 / 9;
    border-radius: 4px;
    background-size: cover;
    background-position: center;
    border: 2px solid var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 10px rgba(6,182,212,0.35);
}
.brand-bg__upload-btn {
    width: 100%;
    padding: 8px 10px;
    background: transparent;
    color: var(--ink-1, rgba(255,255,255,0.78));
    border: 1px solid var(--rule-strong, rgba(255,255,255,0.12));
    border-radius: 6px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--dur-sm), border-color var(--dur-sm), background var(--dur-sm);
}
.brand-bg__upload-btn:hover {
    color: var(--acc-cyan, #06b6d4);
    border-color: var(--acc-cyan, #06b6d4);
    background: rgba(6,182,212,0.06);
}

/* ─────────────────────────────────────────────────────────────────
   Chat Log — right-wing tab. Professional AI-chat layout (Claude /
   ChatGPT / Cursor): role-tagged rows with avatars, soft user bubble
   on the right, plain assistant copy on the left, sticky header,
   auto-scrolling list, typing dots while a reply is in flight.
   ───────────────────────────────────────────────────────────────── */
/* Panel itself fills the same box every other tab uses — locked to
   the same dimensions as .home-game-slider__snap so the right wing
   never reflows when the user clicks between tabs. */
.home-game-slider__panel--chatlog {
    padding: 0;
    height: min(72vh, 580px);
    overflow: hidden;
}
/* IDE-style surface: solid dark plate with a subtle 1px border, light
   inner glow at the seams, gentle rounded corners. Inspired by the
   editor pane in VS Code / Cursor — high enough contrast that the
   message text stays legible over any wallpaper behind the right wing. */
.chat-log {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    color: var(--ink-1, rgba(255,255,255,0.88));
    font-family: var(--font-display);
    background:
        linear-gradient(180deg, rgba(13, 17, 23, 0.92), rgba(8, 11, 16, 0.92));
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 10px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.04),
        0 10px 30px rgba(0, 0, 0, 0.35);
    overflow: hidden;
}
/* Header bar reads as a single "file tab" along the top — flat, dark,
   one accent dot to indicate live session, all caps for the title. */
/* Header bar reads as a single "file tab" along the top — flat, dark,
   one accent dot to indicate live session, all caps for the title.
   margin-left:auto on the sub-label pushes it to the right while the
   ::before dot + title stay grouped on the left. */
.chat-log__head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    background: rgba(255, 255, 255, 0.02);
}
.chat-log__head::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #34d399;
    box-shadow: 0 0 8px rgba(52, 211, 153, 0.55);
    flex: 0 0 7px;
}
.chat-log__head-sub { margin-left: auto; }
.chat-log__head-title {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-2, rgba(255,255,255,0.55));
}
.chat-log__head-sub {
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-0, #fff);
    max-width: 60%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-log__list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 16px 18px 18px;
    display: flex;
    flex-direction: column;
    gap: 22px;                            /* generous turn separation */
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.18) transparent;
}
.chat-log__list::-webkit-scrollbar { width: 6px; }
.chat-log__list::-webkit-scrollbar-thumb {
    background: rgba(255,255,255,0.14);
    border-radius: 3px;
}
.chat-log__list::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.24); }

/* Industry-standard AI chat row layout — Cursor / Windsurf / Claude /
   ChatGPT. Each turn is a small block: muted role label + timestamp +
   hover-only copy button, then a sans-serif body underneath at a
   comfortable reading size. No avatars, no bubbles for assistant; a
   minimal neutral card for user.

   Stack ordering: newest message at the TOP of the list (set by
   stage-chat-log.js prepending). When a new row arrives, it slides
   in from the right (the side where the speech bubble lives on the
   stage) so the eye traces the path from Truffle's mouth into the
   transcript. Older rows naturally drift downward — eventually
   scrolling out past the bottom edge — as the stack grows. */
@keyframes chat-log-row-in {
    from {
        opacity: 0;
        transform: translateX(28px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}
.chat-log__row {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    position: relative;
    animation: chat-log-row-in 320ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* History loaded from storage on subject swap — these aren't NEW
   messages, just restored state. Animating each one would feel
   chaotic and re-trigger every reload. */
.chat-log__row--restored { animation: none; }
.chat-log__row--user {
    align-items: flex-end;
    text-align: right;
}
.chat-log__row--user .chat-log__meta { justify-content: flex-end; }

.chat-log__meta {
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--font-display);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    line-height: 1;
    color: var(--ink-2, rgba(255, 255, 255, 0.55));
}
.chat-log__role {
    color: var(--ink-2, rgba(255, 255, 255, 0.65));
    font-weight: 600;
}
.chat-log__row--assistant .chat-log__role { color: var(--ink-1, rgba(255, 255, 255, 0.86)); }
.chat-log__time {
    color: var(--ink-3, rgba(255, 255, 255, 0.38));
    font-weight: 400;
    font-size: 11px;
    font-family: var(--font-display);
}

/* Copy button — hidden until row hover. Matches the industry IDE
   chat-panel pattern (Cursor / Windsurf / VS Code Copilot Chat). */
.chat-log__copy {
    all: unset;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    margin-left: auto;
    border-radius: 4px;
    color: var(--ink-3, rgba(255, 255, 255, 0.45));
    opacity: 0;
    transition: color 150ms, background 150ms, opacity 150ms;
}
.chat-log__row--user .chat-log__copy { margin-left: 0; margin-right: auto; }
.chat-log__row:hover .chat-log__copy,
.chat-log__copy:focus-visible {
    opacity: 1;
}
.chat-log__copy:hover {
    color: var(--ink-0, #fff);
    background: rgba(255, 255, 255, 0.06);
}
.chat-log__copy--ok {
    color: var(--ok, #22c55e) !important;
    opacity: 1 !important;
}
.chat-log__copy--ok svg { display: none; }
.chat-log__copy--ok::after {
    content: '✓';
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
}

/* Body — system sans-serif (matches Claude.ai / ChatGPT / Cursor),
   14px reading size, 1.6 line-height. Mono only kicks in for fenced
   code blocks (future render pass). */
.chat-log__body {
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 400;
    line-height: 1.6;
    letter-spacing: -0.003em;
    color: var(--ink-0, #f7f9fb);
    white-space: pre-wrap;
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

/* User: subtle right-aligned neutral card. No cyan tinting — just a
   restrained surface against the panel background. */
.chat-log__row--user .chat-log__body {
    display: inline-block;
    max-width: 86%;
    text-align: left;
    color: var(--ink-1, rgba(255, 255, 255, 0.92));
    background: rgba(255, 255, 255, 0.045);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 12px;
    padding: 10px 14px;
}

/* Assistant: no bubble. Just the body — full-width, left-aligned,
   like Claude.ai / ChatGPT / Cursor. A thin left accent on hover for
   subtle delineation without permanent visual clutter. */
.chat-log__row--assistant .chat-log__body {
    padding: 0;
    color: var(--ink-0, #f7f9fb);
    border-left: 2px solid transparent;
    padding-left: 0;
    transition: border-color 200ms, padding-left 200ms;
}
.chat-log__row--assistant:hover .chat-log__body {
    border-left-color: rgba(6, 182, 212, 0.35);
    padding-left: 10px;
}

/* Error variant — tints the role label red but leaves the body
   readable on the dark plate. */
.chat-log__row--error .chat-log__role { color: #fca5a5; }

.chat-log__row--assistant.chat-log__row--typing .chat-log__body {
    padding: 0;
    border-left-color: transparent;
}

/* Phase G — live agent-step rows from a paired JarJar desktop install.
 * Visually quieter than user/assistant messages so a long autonomous run
 * doesn't drown out the actual conversation. Tool-call rows lean monospace
 * with a faint left border indicating the trace column. */
.chat-log__row--agent .chat-log__role {
    color: #c4b5fd;  /* Claude-purple — matches the brain chip */
    opacity: 0.85;
}
.chat-log__row--agent .chat-log__body--trace {
    font-size: 11.5px;
    line-height: 1.5;
    color: rgba(230, 237, 243, 0.78);
    border-left: 2px solid rgba(196, 181, 253, 0.35);
    padding: 2px 0 2px 10px;
}
.chat-log__trace-thinking {
    font-style: italic;
    color: rgba(230, 237, 243, 0.65);
}
.chat-log__trace-tool {
    color: #e6edf3;
}
.chat-log__trace-args {
    color: rgba(196, 181, 253, 0.85);
}
.chat-log__trace-result {
    color: #86efac;  /* soft green for success */
}
.chat-log__trace-result--err {
    color: #fca5a5;  /* soft red for tool error */
}
/* Header dot indicating a live agent run. The chat-log__head element sets
 * `data-agent-running="true"` while the WS is open. */
[data-tab-panel="chatlog"][data-agent-running="true"] .chat-log__head::before {
    background: #86efac;
    box-shadow: 0 0 6px rgba(134, 239, 172, 0.6);
}

/* Typing indicator — three pulsing dots, matches the bubble's. */
.chat-log__body--typing {
    padding: 4px 0 0;
}
.chat-log__dots {
    display: inline-flex;
    gap: 4px;
    align-items: center;
    height: 14px;
}
.chat-log__dots i {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: rgba(6, 182, 212, 0.75);
    animation: chat-log-dots 1.2s infinite ease-in-out both;
}
.chat-log__dots i:nth-child(2) { animation-delay: 0.15s; }
.chat-log__dots i:nth-child(3) { animation-delay: 0.30s; }
@keyframes chat-log-dots {
    0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
    40%           { transform: scale(1.0); opacity: 1; }
}

.chat-log__empty {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 32px 24px;
    color: var(--ink-3, rgba(255,255,255,0.4));
    text-align: center;
    font-family: var(--font-display);
}
.chat-log__empty[hidden] { display: none; }
.chat-log__empty-title {
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -0.005em;
    color: var(--ink-1, rgba(255, 255, 255, 0.86));
}
.chat-log__empty-sub {
    font-size: 12.5px;
    line-height: 1.55;
    max-width: 280px;
    color: var(--ink-3, rgba(255, 255, 255, 0.5));
}

/* Hide empty-state automatically when there are message rows. */
.chat-log__list:not(:empty) + .chat-log__empty { display: none; }

/* ─── History tab — session list ───
   Clickable rows, one per past chat session. Each row reads like a
   git-log entry: id on the left, last reply preview, timestamp +
   message count in the meta line. Hover lifts the row; the
   currently-centered hero's session is outlined. */
.chat-history__list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 8px 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.18) transparent;
}
.chat-history__list::-webkit-scrollbar { width: 6px; }
.chat-history__list::-webkit-scrollbar-thumb {
    background: rgba(255,255,255,0.14);
    border-radius: 3px;
}
.chat-history__row {
    appearance: none;
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 6px;
    padding: 9px 11px;
    text-align: left;
    cursor: pointer;
    color: var(--ink-1, rgba(255,255,255,0.88));
    font-family: var(--font-display);
    display: flex;
    flex-direction: column;
    gap: 4px;
    transition: background 120ms ease, border-color 120ms ease;
}
.chat-history__row:hover {
    background: rgba(255, 255, 255, 0.05);
    border-color: rgba(210, 168, 255, 0.32);
}
.chat-history__row.is-current {
    border-color: rgba(126, 231, 135, 0.45);
    background: rgba(126, 231, 135, 0.06);
}
.chat-history__row-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}
.chat-history__row-id {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #d2a8ff;
}
.chat-history__row-time {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-3, rgba(255,255,255,0.38));
}
.chat-history__row-preview {
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--ink-1, rgba(255,255,255,0.78));
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
}
.chat-history__row-meta {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-3, rgba(255,255,255,0.38));
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

/* ─── To-Do tab — IDE-style task list ───
   Click-to-prefill bar at the top of the panel: it's a hint card
   styled like the chat input, but clicking it focuses the main
   lobby chat bar below the model and seeds it with "/todo " so the
   user learns the slash command. Scrollable list below. Each row is
   a checkbox + text + delete button. Done items dim and strike. */
.todo__hint {
    appearance: none;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    background: rgba(255, 255, 255, 0.015);
    color: var(--ink-2, rgba(255,255,255,0.55));
    font-family: var(--font-mono);
    text-align: left;
    cursor: pointer;
    transition: background 120ms ease;
}
.todo__hint:hover { background: rgba(125, 211, 252, 0.06); }
.todo__hint:focus-visible { outline: 1px solid rgba(125, 211, 252, 0.55); outline-offset: -1px; }
.todo__hint-cmd {
    font-size: 12.5px;
    font-weight: 700;
    color: #7dd3fc;
    letter-spacing: 0.04em;
}
.todo__hint-arg {
    flex: 1 1 auto;
    font-size: 12.5px;
    color: var(--ink-3, rgba(255,255,255,0.38));
    font-style: italic;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.todo__hint-plus {
    flex: 0 0 24px;
    width: 24px;
    height: 24px;
    border-radius: 5px;
    background: rgba(125, 211, 252, 0.10);
    color: #7dd3fc;
    font-size: 16px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.todo__empty-cmd {
    font-family: var(--font-mono);
    color: #7dd3fc;
    background: rgba(125, 211, 252, 0.10);
    padding: 1px 5px;
    border-radius: 3px;
}

.topics__list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 8px 4px 14px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.18) transparent;
}
.topics__list::-webkit-scrollbar { width: 6px; }
.topics__list::-webkit-scrollbar-thumb {
    background: rgba(255,255,255,0.14);
    border-radius: 3px;
}

/* ─── Topics tab — cards grouping to-dos by #tag ───
   Each topic is a card with a header (name + count + optional delete)
   and a nested list of any /todo items tagged with that topic. */
.topics__card {
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.025);
    padding: 8px 10px 10px;
    margin: 0 4px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.topics__card.is-auto {
    border-style: dashed;
    border-color: rgba(125, 211, 252, 0.20);
}
.topics__card-head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.topics__card-title {
    flex: 1 1 auto;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: 12.5px;
    font-weight: 700;
    color: #7dd3fc;
    letter-spacing: 0.04em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topics__card-count {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-3, rgba(255,255,255,0.38));
    background: rgba(255, 255, 255, 0.04);
    padding: 1px 6px;
    border-radius: 3px;
}
.topics__auto-badge {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 9px;
    letter-spacing: 0.10em;
    text-transform: uppercase;
    color: rgba(125, 211, 252, 0.7);
    border: 1px solid rgba(125, 211, 252, 0.30);
    padding: 0 5px;
    border-radius: 3px;
}
.topics__card .todo__del { opacity: 0; }
.topics__card:hover .todo__del { opacity: 1; }

.topics__items {
    list-style: none;
    margin: 0;
    padding: 0 0 0 2px;
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.topics__item {
    font-family: var(--font-mono);
    font-size: 12px;
    line-height: 1.4;
    color: #e6edf3;
    padding-left: 12px;
    position: relative;
    overflow-wrap: anywhere;
}
.topics__item::before {
    content: '›';
    position: absolute;
    left: 0;
    color: rgba(125, 211, 252, 0.55);
}
.topics__item.is-done {
    text-decoration: line-through;
    color: var(--ink-3, rgba(255,255,255,0.38));
}
.topics__items-empty {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--ink-3, rgba(255,255,255,0.38));
    padding: 2px 2px 0;
}

/* ── Per-DexHero topic rows (new chat-topic manager) ───────────── */

.topics__row {
    display: grid;
    grid-template-columns: 28px 1fr auto;
    gap: 10px;
    align-items: center;
    padding: 8px 10px;
    margin: 0 4px;
    background: rgba(255,255,255,0.02);
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: var(--r-2, 6px);
    color: var(--ink-0);
    text-align: left;
    cursor: pointer;
    transition: border-color var(--dur-xs), background var(--dur-xs);
}
.topics__row:hover {
    border-color: rgba(6,182,212,0.30);
    background: rgba(6,182,212,0.04);
}
.topics__row.is-unread {
    border-color: rgba(6,182,212,0.40);
}
.topics__row-icon {
    font-size: 18px;
    line-height: 1;
    text-align: center;
}
.topics__row-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.topics__row-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-0);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topics__row-preview {
    font-size: 11.5px;
    color: var(--ink-2, rgba(255,255,255,0.55));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topics__row-meta {
    display: flex;
    align-items: center;
    gap: 6px;
}
.topics__row-time {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--ink-3, rgba(255,255,255,0.38));
    letter-spacing: 0.04em;
}
.topics__row-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--acc-cyan, #06b6d4);
    box-shadow: 0 0 6px rgba(6,182,212,0.6);
}
.topics__row-del {
    width: 18px;
    height: 18px;
    line-height: 16px;
    text-align: center;
    border-radius: 4px;
    color: var(--ink-3, rgba(255,255,255,0.38));
    opacity: 0;
    cursor: pointer;
}
.topics__row:hover .topics__row-del { opacity: 1; }
.topics__row-del:hover { color: var(--err, #ef4444); background: rgba(239,68,68,0.08); }

.todo__list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 8px 14px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.18) transparent;
}

/* ═══ Activity tab — IDE-style chat (VS Code / Claude Code feel) ═══
 *
 * Flat conversation log. Each message is a role label + content body
 * with margin between, no bubbles. User messages get a subtle dark
 * background + left border accent; assistant gets full-width markdown;
 * proactive ticks (thought/observation/rest) are muted + smaller.
 *
 * The .chat-log container that wraps the activity panel already provides
 * the dark IDE-style chrome (border, header bar, rounded corners) — see
 * the .chat-log rules earlier in this file. Here we only style the row
 * variants the right-wing-activity.js renderer emits. */

.activity__list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 16px 14px 18px;
    display: flex;
    flex-direction: column;
    gap: 16px;
    scrollbar-width: thin;
    scrollbar-color: rgba(255,255,255,0.18) transparent;
}
.activity__list::-webkit-scrollbar { width: 6px; }
.activity__list::-webkit-scrollbar-thumb {
    background: rgba(255,255,255,0.14);
    border-radius: 3px;
}

/* ─── Timeline-rail row ───
 * Every event (user message, tool call, assistant reply, proactive
 * tick) sits on a single vertical timeline. Left column is a thin
 * rail with a colored status dot; rail::before draws a 1px line down
 * to the next row so consecutive events visually connect. Mirrors the
 * Claude Code / Cursor transcript view. */
.activity__row {
    display: grid;
    grid-template-columns: 14px 1fr;
    gap: 12px;
    align-items: start;
    position: relative;
}
.activity__rail {
    position: relative;
    width: 14px;
    height: 100%;
    min-height: 18px;
    display: flex;
    justify-content: center;
    padding-top: 5px;
}
.activity__rail::before {
    /* Vertical connector line from this dot down past the row's bottom
     * into the next row's gap, then visually continuing to the next
     * dot. Hidden on the very last row. */
    content: '';
    position: absolute;
    left: 50%;
    top: 16px;
    bottom: -16px;
    width: 1px;
    background: rgba(255, 255, 255, 0.08);
    transform: translateX(-50%);
}
.activity__row:last-child .activity__rail::before { display: none; }
.activity__rail-dot {
    position: relative;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.35);
    z-index: 1;
}

/* Status-colored dots (Claude Code palette). */
.activity__row--user    .activity__rail-dot {
    background: #67e8f9;
    box-shadow: 0 0 6px rgba(103, 232, 249, 0.45);
}
.activity__row--reply   .activity__rail-dot {
    background: #34d399;
    box-shadow: 0 0 6px rgba(52, 211, 153, 0.45);
}
.activity__row--tool[data-status="running"] .activity__rail-dot {
    background: #fbbf24;
    box-shadow: 0 0 8px rgba(251, 191, 36, 0.55);
    animation: activity-dot-pulse 1.2s ease-in-out infinite;
}
.activity__row--tool[data-status="ok"]      .activity__rail-dot {
    background: #34d399;
    box-shadow: 0 0 6px rgba(52, 211, 153, 0.45);
}
.activity__row--tool[data-status="error"]   .activity__rail-dot,
.activity__row--error                       .activity__rail-dot {
    background: #f87171;
    box-shadow: 0 0 6px rgba(248, 113, 113, 0.45);
}
.activity__row--thought      .activity__rail-dot,
.activity__row--observation  .activity__rail-dot,
.activity__row--action       .activity__rail-dot,
.activity__row--rest         .activity__rail-dot {
    background: transparent;
    border: 1px solid rgba(255, 255, 255, 0.28);
    box-shadow: none;
}

@keyframes activity-dot-pulse {
    0%, 100% { transform: scale(1);   opacity: 1;   }
    50%      { transform: scale(1.4); opacity: 0.55;}
}

/* Right column — content */
.activity__row-content {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.activity__row-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
}
.activity__row-action {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-0, #fff);
    letter-spacing: 0;
}
.activity__row-time {
    font-size: 10.5px;
    color: var(--ink-3, rgba(255,255,255,0.45));
    font-family: var(--font-mono);
}
.activity__row-subtitle {
    font-size: 11.5px;
    color: var(--ink-3, rgba(255,255,255,0.55));
    line-height: 1.4;
}
.activity__row-body {
    font-size: 13px;
    line-height: 1.55;
    color: var(--ink-1, rgba(255,255,255,0.88));
    word-wrap: break-word;
    margin-top: 4px;
}
.activity__row-body > *:first-child { margin-top: 0; }
.activity__row-body > *:last-child  { margin-bottom: 0; }

/* User message body — subtle inset card. */
.activity__row--user .activity__row-body {
    background: rgba(255, 255, 255, 0.04);
    padding: 8px 12px;
    border-radius: 6px;
    white-space: pre-wrap;
}

/* IN / OUT code blocks for tool calls — IDE log style. */
.activity__io-block {
    margin-top: 6px;
    display: grid;
    grid-template-columns: 32px 1fr;
    gap: 10px;
    align-items: start;
    background: rgba(0, 0, 0, 0.40);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 4px;
    padding: 7px 10px;
}
.activity__io-label {
    font-family: var(--font-mono);
    font-size: 9.5px;
    font-weight: 600;
    color: var(--ink-3, rgba(255, 255, 255, 0.45));
    letter-spacing: 0.10em;
    text-transform: uppercase;
    padding-top: 1px;
}
.activity__io-body {
    font-family: ui-monospace, SF Mono, Menlo, Consolas, monospace;
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--ink-1, rgba(255, 255, 255, 0.85));
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 200px;
    overflow-y: auto;
}

/* Proactive ticks — muted background thoughts, not foreground conversation. */
.activity__row--thought,
.activity__row--observation,
.activity__row--action,
.activity__row--rest {
    opacity: 0.55;
}
.activity__row--rest { opacity: 0.4; }
.activity__row--thought .activity__row-body,
.activity__row--observation .activity__row-body,
.activity__row--action .activity__row-body,
.activity__row--rest .activity__row-body {
    font-size: 12px;
    font-style: italic;
    color: var(--ink-2, rgba(255,255,255,0.7));
}

/* Live (mid-stream) assistant reply — pulsing time tag. */
.activity__row--live .activity__row-time {
    color: var(--acc-cyan, #06b6d4);
    animation: bubble-md-pulse 1.4s ease-in-out infinite;
}

/* Legacy bubble dock is hidden ALWAYS — the right-wing Activity panel
 * is the single chat surface. The speaking bubble at Truffle's head
 * (separate #lobby-stage-speaking-bubble) stays — that's the "bubble
 * coming out of the dexhero" intent. */
.lobby-stage__bubble {
    display: none !important;
}

/* When the tab strip is mounted inside the wing, the panel below it
 * needs to merge visually — remove the panel's top border + top-radius
 * and hide its internal chat-log header (the tab strip IS the header). */
.lobby-wing--right > .lobby-stage__bubble-titles ~ .home-game-slider .home-game-slider__panel--chatlog {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}
.lobby-wing--right > .lobby-stage__bubble-titles ~ .home-game-slider .chat-log {
    border-top: none;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}
.lobby-wing--right > .lobby-stage__bubble-titles ~ .home-game-slider .chat-log__head {
    /* Redundant with the tab strip above — the tab strip names the
     * current panel by virtue of which tab is active. */
    display: none;
}

/* ═══ Right-wing resize handles — 8-way ═══
 * Four edge handles (n/s/e/w) and four corner handles (nw/ne/sw/se)
 * for VS-Code / Cursor-style full-direction resize. Cursors follow
 * native conventions; cyan glow on hover. JS persists to localStorage. */
.lobby-wing--right .wing-resize-handle {
    position: absolute;
    z-index: 5;
    pointer-events: auto;
    background: transparent;
}
.lobby-wing--right .wing-resize-handle::before {
    content: '';
    position: absolute;
    inset: 0;
    background: transparent;
    transition: background 140ms ease;
}
/* Edges — thin strips. Corners overlap them but win via higher z-index. */
.lobby-wing--right .wing-resize-handle--w  { top: 8px;  bottom: 8px; left: -3px;   width: 8px;  cursor: col-resize; }
.lobby-wing--right .wing-resize-handle--e  { top: 8px;  bottom: 8px; right: -3px;  width: 8px;  cursor: col-resize; }
.lobby-wing--right .wing-resize-handle--n  { left: 8px; right: 8px;  top: -3px;    height: 8px; cursor: row-resize; }
.lobby-wing--right .wing-resize-handle--s  { left: 8px; right: 8px;  bottom: -3px; height: 8px; cursor: row-resize; }
/* Corners — 14px square pads sitting on top of the edge handles. */
.lobby-wing--right .wing-resize-handle--nw { top: -4px;    left: -4px;    width: 14px; height: 14px; cursor: nwse-resize; z-index: 6; }
.lobby-wing--right .wing-resize-handle--ne { top: -4px;    right: -4px;   width: 14px; height: 14px; cursor: nesw-resize; z-index: 6; }
.lobby-wing--right .wing-resize-handle--sw { bottom: -4px; left: -4px;    width: 14px; height: 14px; cursor: nesw-resize; z-index: 6; }
.lobby-wing--right .wing-resize-handle--se { bottom: -4px; right: -4px;   width: 14px; height: 14px; cursor: nwse-resize; z-index: 6; }
/* Edge highlights — a 2px cyan strip on the inner side. */
.lobby-wing--right .wing-resize-handle--w::before { inset: 0 auto 0 3px;  width: 2px; }
.lobby-wing--right .wing-resize-handle--e::before { inset: 0 3px 0 auto;  width: 2px; }
.lobby-wing--right .wing-resize-handle--n::before { inset: 3px 0 auto 0;  height: 2px; }
.lobby-wing--right .wing-resize-handle--s::before { inset: auto 0 3px 0;  height: 2px; }
/* Corner highlights — small chevron dot. */
.lobby-wing--right .wing-resize-handle--nw::before,
.lobby-wing--right .wing-resize-handle--ne::before,
.lobby-wing--right .wing-resize-handle--sw::before,
.lobby-wing--right .wing-resize-handle--se::before {
    inset: 5px;
    border-radius: 2px;
}
.lobby-wing--right .wing-resize-handle:hover::before,
.lobby-wing--right.is-resizing .wing-resize-handle::before {
    background: rgba(6, 182, 212, 0.5);
    box-shadow: 0 0 8px rgba(6, 182, 212, 0.4);
}
/* Cursor + selection lock during any drag. Cursor follows the handle
 * the user grabbed (col / row / nwse / nesw — set on the handle). */
body.is-wing-resizing { user-select: none; }
body.is-wing-resizing iframe { pointer-events: none; }
.todo__list::-webkit-scrollbar { width: 6px; }
.todo__list::-webkit-scrollbar-thumb {
    background: rgba(255,255,255,0.14);
    border-radius: 3px;
}
.todo__row {
    display: grid;
    grid-template-columns: 22px 1fr 22px;
    gap: 8px;
    align-items: center;
    padding: 7px 8px;
    border-radius: 5px;
    transition: background 100ms ease;
}
.todo__row:hover { background: rgba(255, 255, 255, 0.035); }
.todo__row.is-done { opacity: 0.55; }

.todo__check {
    appearance: none;
    width: 18px;
    height: 18px;
    border-radius: 4px;
    border: 1px solid rgba(255, 255, 255, 0.25);
    background: rgba(0, 0, 0, 0.20);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background 100ms ease, border-color 100ms ease;
}
.todo__check:hover { border-color: rgba(125, 211, 252, 0.55); }
.todo__row.is-done .todo__check {
    background: rgba(125, 211, 252, 0.20);
    border-color: rgba(125, 211, 252, 0.55);
}
.todo__check-mark {
    font-size: 11px;
    font-weight: 800;
    color: #7dd3fc;
    line-height: 1;
}
.todo__text {
    font-family: var(--font-mono);
    font-size: 12.5px;
    line-height: 1.4;
    color: #e6edf3;
    word-wrap: break-word;
    overflow-wrap: anywhere;
}
.todo__row.is-done .todo__text {
    text-decoration: line-through;
    color: var(--ink-3, rgba(255,255,255,0.38));
}
.todo__del {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--ink-3, rgba(255,255,255,0.38));
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: opacity 100ms ease, color 100ms ease;
    padding: 0;
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.todo__row:hover .todo__del { opacity: 1; }
.todo__del:hover { color: #fda4af; }

/* ═══ Mobile responsiveness polish (3-tier contract; see tokens.css) ═══
 * Builds on the existing 960/768/600/540/480/420 blocks above. These rules
 * close the remaining overflow + reachability gaps at phone widths.
 *
 *  - .lobby-wing--right keeps its `min-width: 280px` on desktop so it stays
 *    legible while being dragged around. On phones that min wins over the
 *    `width: min(220px, 56vw)` set at the 600px tier and forces the wing
 *    to overflow the viewport. Unsetting it lets the existing width rule
 *    actually take effect.
 *  - .lobby-bar (bottom row) collapses to bare essentials on tiny phones. */
@media (max-width: 640px) {
    .lobby-wing.lobby-wing--right {
        min-width: 0;
        min-height: 0;
    }
    /* Demote the bubble dock so it isn't a draggable overlay on phones —
       the user already has the panel router for content; the floating
       dock just competes for the small viewport. */
    .lobby-wing--right.is-moving { transition: none; }
}

@media (max-width: 414px) {
    .lobby-bar {
        padding: 0 8px;
        gap: 6px;
    }
    .lobby-bar__end { gap: 4px; font-size: 9px; }
    .lobby-bar__stats { font-size: 9.5px; gap: 4px; }
    /* Tighten ticker so it doesn't visually dominate ~30% of the chrome */
    .lobby-ticker { padding: 0 8px; }
    .lobby-ticker__track { animation-duration: 60s; }
}
