/* ============================================================
   MAIN.CSS: Shared styles for Starforge Pets
   Tokens, reset, layout, nav, buttons, forms, cards, toasts, themes
   ============================================================ */

/* Self-hosted faces: font-src is 'self', no external CDNs. See
   static/fonts/README.md. Selectable UI faces live in user-fonts.css. */
@font-face {
    font-family: 'Unbounded';
    src: url("../fonts/unbounded-vf.8616dc355070.woff2") format('woff2-variations');
    font-weight: 300 800;
    font-display: swap;
}
@font-face {
    font-family: 'Space Grotesk';
    src: url("../fonts/space-grotesk-500.ab4861869c91.woff2") format('woff2');
    font-weight: 500;
    font-display: swap;
}
@font-face {
    font-family: 'Space Grotesk';
    src: url("../fonts/space-grotesk-700.f9e147a59a93.woff2") format('woff2');
    font-weight: 700;
    font-display: swap;
}
@font-face {
    font-family: 'Space Mono';
    src: url("../fonts/space-mono-400.049dfc5919c2.woff2") format('woff2');
    font-weight: 400;
    font-display: swap;
}
@font-face {
    font-family: 'Space Mono';
    src: url("../fonts/space-mono-700.e5e908997a45.woff2") format('woff2');
    font-weight: 700;
    font-display: swap;
}

:root {
    /* ---- Type roles ---- */
    --font-display: 'Unbounded', 'Space Grotesk', system-ui, sans-serif;
    --font-heading: 'Space Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-body: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --font-mono: 'Space Mono', ui-monospace, 'Cascadia Code', 'Segoe UI Mono', Consolas, monospace;
    --font-body-size: 1rem;
    --font-body-line-height: 1.55;
    --font-heading-letter-spacing: -0.01em;

    /* ---- Structural tokens (shared across every theme) ---- */
    --radius-sm: 5px;
    --radius-md: 10px;
    --radius-lg: 16px;
    --radius-xl: 22px;
    --radius-pill: 999px;

    --header-height: 62px;
    /* Height of .mobile-tabbar EXCLUDING its safe-area padding, which callers add
       themselves. Anything that anchors above the bottom navigation, or that scrolls itself
       clear of it, reads this rather than hardcoding a number that drifts when the bar
       changes. The incursion board uses it for scroll-margin-bottom.

       MEASURED IN A BROWSER, not derived from the bar's own padding: 79px at 320, 390 and
       768 with the safe-area inset reporting 0. If .mobile-tabbar's padding or font sizing
       changes, re-measure this rather than recomputing it. */
    --tabbar-height: 79px;
    --modal-overlay: rgba(6, 3, 12, 0.62);

    --shadow-sm: 0 1px 2px rgba(0,0,0,0.30);
    --shadow-md: 0 6px 20px rgba(0,0,0,0.38);
    --shadow-lg: 0 18px 48px rgba(0,0,0,0.50);

    --ease: cubic-bezier(0.22, 1, 0.36, 1);
    --dur: 0.18s;
    /* Wider companions to --dur, for new work only: the ~50 existing transition:
       declarations in this file mostly predate these and are not being retrofitted. */
    --dur-fast: 0.12s;
    --dur-slow: 0.4s;

    /* ---- Type scale (--font-size-*, not --text-*: that prefix already names the
       color tokens below, per theme). New UI only: the ~15 distinct font-size values
       already in this file were each picked by hand across dozens of feature passes,
       several legitimately dense (staff tables), and are not being retrofitted onto
       this scale. Reach for these on anything new instead of adding a 16th. */
    --font-size-xs: 0.72rem;
    --font-size-sm: 0.85rem;
    --font-size-base: 1rem;
    --font-size-lg: 1.15rem;
    --font-size-xl: 1.4rem;

    /* ---- z-index scale. Additive only: the ~40 existing z-index declarations in
       this file are untouched (several already sit at these exact values by
       convention, e.g. modals at 9990), so nothing here changes current layout.
       New overlays should reach for a name instead of a fresh guess. */
    --z-raised: 1;      /* a card/row lifting off its own surface */
    --z-sticky: 100;    /* sticky headers, in-page sticky rails */
    --z-dropdown: 200;  /* dropdowns, popovers, inline menus */
    --z-drawer: 1000;   /* the nav drawer, off-canvas panels */
    --z-modal: 9990;    /* modal dialogs */
    --z-toast: 9999;    /* toasts: must always beat a modal */
    --z-top: 10000;     /* the one or two things that must never be covered */

    /* ---- Breakpoint scale (documentation only: CSS custom properties can't be
       used inside @media conditions, so these values must be typed literally in
       each query; this is the single source of truth for which number to type).
       Three tiers, structural swaps at exactly two lines:
         --bp-sm   768px  phone → tablet: content grids go two-column (dashboard,
                          star map, create-pet workbench). Nav stays the bottom
                          tab bar + "More" drawer: content is full width here, so
                          the wider grids never fight a sidebar for space.
         --bp-md  1024px  tablet → desktop: the sidebar rail replaces the bottom
                          tab bar. Content is now sidebar + ~≥768px, which still
                          fits every two-column grid, so nothing reflows awkwardly.
       (600px is a sub-line, not structural: the header condenses; weather + name
       hide, currency pills shrink.) Component-internal card-grid reflows (3→2→1)
       may use intermediate values (480/700/etc.). Content column caps at 1100px.
       See docs/frontend-guide.md. */

    /* ============================================================
       DEFAULT PALETTE: Deep-Field Observatory (Nebula, dark)
       aubergine/petrol depths · bioluminescent teal · coral reward
       Glow is reserved for earned/actionable states only.
       ============================================================ */
    --color-primary: #37e0c0;        /* bioluminescent teal; actions, links, focus */
    --color-primary-dark: #22b89e;   /* deeper teal; hover */
    --color-violet: #7c5cff;         /* nebula mid */
    --color-reward: #ff6b8a;         /* coral; earned / reward moments only */
    --gradient-primary: linear-gradient(120deg, #37e0c0 0%, #7c5cff 52%, #ff6b8a 100%);
    --glow-reward: 0 0 0 1px rgba(255,107,138,0.42), 0 0 26px rgba(255,107,138,0.32);
    --glow-accent: 0 0 24px rgba(55,224,192,0.28);

    --bg-body: #140b1e;              /* deep aubergine */
    --bg-surface: #1e1330;           /* raised plum panel */
    --bg-subtle: #180e26;            /* recessed */
    --bg-hover: #281a3e;
    --bg-elevated: #241638;          /* dropdowns / popovers */
    --bg-cool: #0e1b28;              /* petrol; observatory / weather zones */

    --text-primary: #f3ecff;         /* starlight */
    --text-secondary: #b9aad4;
    /* Raised from #897ba8, which cleared 4.5:1 on --bg-body but sat at 4.18 on
       --bg-hover and 4.38 on --bg-elevated: i.e. it failed on exactly the raised
       surfaces muted text most often lands on (dropdowns, popovers, hovered rows).
       Every theme's token is now tuned against its OWN worst background, with a little
       headroom (~4.8) so a nested surface tint can't push an instance back under. Check
       per theme: these are set on body.theme-*, so reading them off documentElement
       returns base defaults and makes every theme look identical. */
    --text-muted: #9386af;
    --text-inverse: #140b1e;         /* text on bright accents */

    --border-color: #33244a;
    --border-subtle: #251838;

    /* Signature backdrop is now flat + a faint corner vignette (grain/nebula
       retired). Depth comes from panels, the accent-hairline motif, and
       reward-glow: not a busy background. Fixed, so it never scrolls. Light
       themes soften this to a barely-there wash. */
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 58%, rgba(4,2,12,0.28) 100%);

    /* ---- Semantic (retuned; UI always pairs these with a label/icon) ---- */
    --color-success: #35d39a;
    --color-success-ink: #35d39a;
    --color-success-bg: #0f2b23;
    --color-success-text: #7ff0ce;
    --color-success-border: #1f6a55;

    --color-error: #f2596a;
    --color-error-ink: #f2596a;
    --color-error-bg: #351019;
    --color-error-text: #ffb3bd;
    --color-error-border: #7c2230;

    --color-warning: #f2b34b;
    --color-warning-ink: #f2b34b;
    --color-warning-bg: #33260c;
    --color-warning-text: #ffe0a3;
    --color-warning-border: #8a6414;

    --color-info: #56c7e6;
    --color-info-ink: #56c7e6;
    --color-reward-ink: #ff6b8a;
    --color-info-bg: #0c2733;
    --color-info-text: #a7e2f2;
    --color-info-border: #1c5f74;

    color-scheme: dark;
}

/* ===== RESET ===== */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
    margin: 0;
    font-family: var(--font-body);
    /* Flat body with a faint fixed corner vignette for quiet depth: the
       backdrop is deliberately calm; the design's signature lives in the
       panels, hairlines, and reward-glow, not here. */
    background-color: var(--bg-body);
    background-image: var(--vignette);
    background-repeat: no-repeat;
    background-attachment: fixed;
    color: var(--text-primary);
    font-size: var(--font-body-size);
    line-height: var(--font-body-line-height);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    transition: background-color var(--dur) ease, color var(--dur) ease;
}
h1, h2, h3, h4, h5 {
    font-family: var(--font-heading);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: var(--font-heading-letter-spacing);
    color: var(--text-primary);
}

/* Selectable interface font profiles. Display and mono remain Starforge's
   authored identity; body and ordinary headings follow the Captain's choice.
   These size and rhythm values are calibration starting points, not additional
   user controls. */
body.font-system {
    --font-body-size: 1rem;
    --font-body-line-height: 1.55;
}
body.font-nunito {
    --font-body: 'Nunito', system-ui, sans-serif;
    --font-heading: 'Nunito', system-ui, sans-serif;
    --font-body-size: 1rem;
    --font-body-line-height: 1.58;
    --font-heading-letter-spacing: -0.015em;
}
body.font-lora {
    --font-body: 'Lora', Georgia, serif;
    --font-heading: 'Lora', Georgia, serif;
    --font-body-size: 1.02rem;
    --font-body-line-height: 1.62;
    --font-heading-letter-spacing: 0;
}
body.font-opendyslexic {
    --font-body: 'Open-Dyslexic', system-ui, sans-serif;
    --font-heading: 'Open-Dyslexic', system-ui, sans-serif;
    --font-body-size: 1rem;
    --font-body-line-height: 1.65;
    --font-heading-letter-spacing: 0;
}
body.font-space-grotesk {
    --font-body: 'Space Grotesk', system-ui, sans-serif;
    --font-heading: 'Space Grotesk', system-ui, sans-serif;
    --font-body-size: 1rem;
    --font-body-line-height: 1.55;
    --font-heading-letter-spacing: -0.015em;
}
body.font-exo-2 {
    --font-body: 'Exo 2', system-ui, sans-serif;
    --font-heading: 'Exo 2', system-ui, sans-serif;
    --font-body-size: 1.01rem;
    --font-body-line-height: 1.58;
    --font-heading-letter-spacing: 0;
}
body.font-quantico {
    --font-body: 'Quantico', system-ui, sans-serif;
    --font-heading: 'Quantico', system-ui, sans-serif;
    --font-body-size: 1rem;
    --font-body-line-height: 1.62;
    --font-heading-letter-spacing: 0;
}
body.font-cormorant-garamond {
    --font-body: 'Cormorant Garamond', Georgia, serif;
    --font-heading: 'Cormorant Garamond', Georgia, serif;
    --font-body-size: 1.08rem;
    --font-body-line-height: 1.58;
    --font-heading-letter-spacing: 0;
}
a { color: var(--color-primary); text-decoration: none; }
@media (hover: hover) { a:hover { text-decoration: underline; } }

/* Type-system utilities. .eyebrow + .mono give the "instrument readout" texture
   used for currencies, timers, coordinates, and section kickers. */
.eyebrow {
    display: inline-block;
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.18em;
    font-size: 0.72rem;
    color: var(--text-muted);
}
.mono, .tabular { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* Standard page-header block: replaces bare <h1> so every page reads as one
   designed surface: mono eyebrow, display title, optional right-aligned actions. */
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 24px;
}
.page-header__titles { min-width: 0; }
.page-title {
    font-family: var(--font-display);
    font-size: clamp(1.7rem, 1.15rem + 2.3vw, 2.45rem);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.04;
    margin: 6px 0 0;
}
/* A page title given tabindex="-1" as a post-swap focus target (the incursion board's
   #board-heading): same reasoning as .room-card:focus. */
.page-title:focus { outline: 2px solid var(--color-primary); outline-offset: 4px; }
.page-subtitle { color: var(--text-secondary); margin: 8px 0 0; max-width: 62ch; }
.page-header__actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
/* A consequence note sitting with a destructive header action. Takes the full row so it
   reads as a sentence under the buttons rather than a third chip beside them. */
.page-header__note { flex: 1 0 100%; margin: 0; }

/* Vertical rhythm for stacked panels/sections. */
.stack { display: flex; flex-direction: column; gap: 18px; }

/* ===== THEMES =====
   Each theme re-tints the same Deep-Field Observatory system: the type,
   layout, grain, and reward-glow language are constant; only color moves.
   Nebula (default) lives in :root above; theme-default inherits it as-is. */

/* Daylight Station (light): daytime observatory: lilac paper, petrol ink,
   teal + coral accents held over from the dark identity. */
body.theme-daylight {
    /* Darkened die-colour text so .slot-req clears 4.5:1 on this theme's --bg-subtle
       (#ece5f7); the die chips themselves keep the bright palette on their own fills. */
    --dice-red-text: #a32d16;
    --dice-green-text: #1c6b32;
    --dice-blue-text: #1b4f8f;
    --color-primary: #006b5c;
    --color-primary-dark: #005247;
    --color-violet: #5a35b5;
    --color-reward: #a52f54;
    --color-reward-ink: #a52f54;
    --color-success: #006b4f;
    --color-success-ink: #006b4f;
    --color-error: #a73540;
    --color-error-ink: #a73540;
    --color-warning: #805400;
    --color-warning-ink: #805400;
    --color-info: #00677d;
    --color-info-ink: #00677d;
    /* A light theme must redefine the whole semantic set, not only -ink. 128 rules
       read --color-*-text and only 11 read --color-*-ink, so a theme that stops at
       -ink inherits :root's dark-surface pale mint/pink/amber and paints it on white:
       measured 1.28-1.69:1 on the staff readouts, and the same on every reward,
       penalty and hint figure in the game. -bg/-border move with -text because the
       badges pair them; changing -text alone would put dark ink on :root's dark tint.
       See docs/ux-rules.md "Contrast". */
    --color-success-text: var(--color-success-ink);
    --color-success-bg: color-mix(in srgb, var(--color-success) 12%, var(--bg-surface));
    --color-success-border: color-mix(in srgb, var(--color-success) 45%, var(--border-color));
    --color-error-text: var(--color-error-ink);
    --color-error-bg: color-mix(in srgb, var(--color-error) 12%, var(--bg-surface));
    --color-error-border: color-mix(in srgb, var(--color-error) 45%, var(--border-color));
    --color-warning-text: var(--color-warning-ink);
    --color-warning-bg: color-mix(in srgb, var(--color-warning) 12%, var(--bg-surface));
    --color-warning-border: color-mix(in srgb, var(--color-warning) 45%, var(--border-color));
    --color-info-text: var(--color-info-ink);
    --color-info-bg: color-mix(in srgb, var(--color-info) 12%, var(--bg-surface));
    --color-info-border: color-mix(in srgb, var(--color-info) 45%, var(--border-color));
    --gradient-primary: linear-gradient(120deg, #006b5c 0%, #5a35b5 55%, #a52f54 100%);
    --glow-reward: 0 0 0 1px rgba(224,86,122,0.35), 0 0 20px rgba(224,86,122,0.22);
    --glow-accent: 0 0 18px rgba(14,158,134,0.18);
    --bg-body: #f2eef9;
    --bg-surface: #ffffff;
    --bg-subtle: #ece5f7;
    --bg-hover: #e4dbf3;
    --bg-elevated: #ffffff;
    --bg-cool: #e7f0f2;
    --text-primary: #1c1230;
    --text-secondary: #4b4066;
    --text-muted: #645a7d;           /* was #6d6389: 4.14 on --bg-hover */
    --text-inverse: #ffffff;
    --border-color: #ddd3ef;
    --border-subtle: #ece4f7;
    --shadow-sm: 0 1px 2px rgba(30,16,50,0.08);
    --shadow-md: 0 8px 22px rgba(30,16,50,0.10);
    --shadow-lg: 0 20px 46px rgba(30,16,50,0.14);
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 62%, rgba(28,18,48,0.06) 100%);
    color-scheme: light;
}

/* Aurora (dark): cold deep field: petrol base, aurora-teal + violet.
   Reads distinctly cooler than Nebula's plum. */
body.theme-aurora {
    --color-primary: #57e6c3;
    --color-primary-dark: #33b79a;
    --color-violet: #8f7bff;
    --color-reward: #ff7fa3;
    --color-error: #ff8a96;
    --color-error-ink: #ff8a96;
    --gradient-primary: linear-gradient(120deg, #57e6c3 0%, #8f7bff 55%, #ff7fa3 100%);
    --glow-reward: 0 0 0 1px rgba(255,127,163,0.42), 0 0 26px rgba(255,127,163,0.30);
    --glow-accent: 0 0 24px rgba(87,230,195,0.30);
    --bg-body: #071620;
    --bg-surface: #0e2532;
    --bg-subtle: #0a1c27;
    --bg-hover: #123243;
    --bg-elevated: #113040;
    --bg-cool: #0a2230;
    --text-primary: #e6f7fb;
    --text-secondary: #a3c6d3;
    --text-muted: #7ba0af;           /* already clears 4.5 everywhere (worst 4.79); left alone */
    --text-inverse: #071620;
    --border-color: #1c4155;
    --border-subtle: #143140;
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 58%, rgba(1,8,14,0.30) 100%);
    color-scheme: dark;
}

/* Comet Tail (light): dawn horizon: warm sand, coral + amber with a teal
   counter-accent so it stays in the same family as the others. */
body.theme-comet {
    /* See theme-daylight: same contrast fix against this theme's --bg-subtle (#f6e8da). */
    --dice-red-text: #a32d16;
    --dice-green-text: #1c6b32;
    --dice-blue-text: #1b4f8f;
    --color-primary: #9a330a;
    --color-primary-dark: #7f2a08;
    --color-violet: #8b2f68;
    --color-reward: #9b2c4d;
    --color-reward-ink: #9b2c4d;
    --color-success: #006b4f;
    --color-success-ink: #006b4f;
    --color-error: #a73540;
    --color-error-ink: #a73540;
    --color-warning: #805400;
    --color-warning-ink: #805400;
    --color-info: #00677d;
    --color-info-ink: #00677d;
    /* Light theme: see the note on theme-daylight's own copy of this block. */
    --color-success-text: var(--color-success-ink);
    --color-success-bg: color-mix(in srgb, var(--color-success) 12%, var(--bg-surface));
    --color-success-border: color-mix(in srgb, var(--color-success) 45%, var(--border-color));
    --color-error-text: var(--color-error-ink);
    --color-error-bg: color-mix(in srgb, var(--color-error) 12%, var(--bg-surface));
    --color-error-border: color-mix(in srgb, var(--color-error) 45%, var(--border-color));
    --color-warning-text: var(--color-warning-ink);
    --color-warning-bg: color-mix(in srgb, var(--color-warning) 12%, var(--bg-surface));
    --color-warning-border: color-mix(in srgb, var(--color-warning) 45%, var(--border-color));
    --color-info-text: var(--color-info-ink);
    --color-info-bg: color-mix(in srgb, var(--color-info) 12%, var(--bg-surface));
    --color-info-border: color-mix(in srgb, var(--color-info) 45%, var(--border-color));
    --gradient-primary: linear-gradient(120deg, #9a330a 0%, #8b2f68 55%, #0e6f70 100%);
    --glow-reward: 0 0 0 1px rgba(225,29,103,0.32), 0 0 20px rgba(225,29,103,0.22);
    --glow-accent: 0 0 18px rgba(194,65,12,0.16);
    --bg-body: #fbf2e9;
    --bg-surface: #fffaf4;
    --bg-subtle: #f6e8da;
    --bg-hover: #f2ddc9;
    --bg-elevated: #fffaf4;
    --bg-cool: #eef0e6;
    --text-primary: #2b1a12;
    --text-secondary: #6b4a37;
    --text-muted: #765a48;           /* was #8a6a54: failed on 4 of 6 backgrounds, worst 3.74 */
    --text-inverse: #fffaf4;
    --border-color: #ecd8c3;
    --border-subtle: #f4e6d6;
    --shadow-sm: 0 1px 2px rgba(60,30,12,0.08);
    --shadow-md: 0 8px 22px rgba(60,30,12,0.10);
    --shadow-lg: 0 20px 46px rgba(60,30,12,0.14);
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 62%, rgba(40,20,10,0.06) 100%);
    color-scheme: light;
}

/* Mosslight (dark): a living frontier glow for companions and discovery. */
body.theme-mosslight {
    --color-primary: #50e5bb;
    --color-primary-dark: #2ac29b;
    --color-violet: #a68cff;
    --color-reward: #f7b44a;
    --color-error: #ff8a96;
    --color-error-ink: #ff8a96;
    --gradient-primary: linear-gradient(120deg, #50e5bb 0%, #a68cff 55%, #f7b44a 100%);
    --glow-reward: 0 0 0 1px rgba(247,180,74,0.42), 0 0 26px rgba(247,180,74,0.28);
    --glow-accent: 0 0 24px rgba(80,229,187,0.28);
    --bg-body: #0c1717;
    --bg-surface: #112524;
    --bg-subtle: #0e1e1d;
    --bg-hover: #1b3734;
    --bg-elevated: #18312e;
    --bg-cool: #0e2228;
    --text-primary: #effff7;
    --text-secondary: #b7d9cc;
    --text-muted: #91b9aa;
    --text-inverse: #0c1717;
    --border-color: #2b544d;
    --border-subtle: #1d3c37;
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 58%, rgba(2,14,12,0.34) 100%);
    color-scheme: dark;
}

/* Emberwake (dark): warm signal light for dangerous routes and hard-won returns. */
body.theme-emberwake {
    --color-primary: #ffb15a;
    --color-primary-dark: #e4862a;
    --color-violet: #b78cff;
    --color-reward: #ff6f91;
    --gradient-primary: linear-gradient(120deg, #ffb15a 0%, #b78cff 55%, #ff6f91 100%);
    --glow-reward: 0 0 0 1px rgba(255,111,145,0.44), 0 0 26px rgba(255,111,145,0.30);
    --glow-accent: 0 0 24px rgba(255,177,90,0.28);
    --bg-body: #1a0f14;
    --bg-surface: #2a1720;
    --bg-subtle: #211218;
    --bg-hover: #3b1f2b;
    --bg-elevated: #341b27;
    --bg-cool: #1c1b29;
    --text-primary: #fff1eb;
    --text-secondary: #e4bdb3;
    --text-muted: #c08f89;
    --text-inverse: #1a0f14;
    --border-color: #5a2f39;
    --border-subtle: #3d222d;
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 58%, rgba(20,4,8,0.34) 100%);
    color-scheme: dark;
}

/* Surveyor (light): crisp, high-clarity instruments for focused command. */
body.theme-surveyor {
    --dice-red-text: #9f2616;
    --dice-green-text: #175c2c;
    --dice-blue-text: #17477f;
    --color-primary: #005f73;
    --color-primary-dark: #00495a;
    --color-violet: #5b21b6;
    --color-reward: #9b2c4d;
    --color-reward-ink: #9b2c4d;
    --color-success: #006b4f;
    --color-success-ink: #006b4f;
    --color-error: #a73540;
    --color-error-ink: #a73540;
    --color-warning: #805400;
    --color-warning-ink: #805400;
    --color-info: #00677d;
    --color-info-ink: #00677d;
    /* Light theme: see the note on theme-daylight's own copy of this block. */
    --color-success-text: var(--color-success-ink);
    --color-success-bg: color-mix(in srgb, var(--color-success) 12%, var(--bg-surface));
    --color-success-border: color-mix(in srgb, var(--color-success) 45%, var(--border-color));
    --color-error-text: var(--color-error-ink);
    --color-error-bg: color-mix(in srgb, var(--color-error) 12%, var(--bg-surface));
    --color-error-border: color-mix(in srgb, var(--color-error) 45%, var(--border-color));
    --color-warning-text: var(--color-warning-ink);
    --color-warning-bg: color-mix(in srgb, var(--color-warning) 12%, var(--bg-surface));
    --color-warning-border: color-mix(in srgb, var(--color-warning) 45%, var(--border-color));
    --color-info-text: var(--color-info-ink);
    --color-info-bg: color-mix(in srgb, var(--color-info) 12%, var(--bg-surface));
    --color-info-border: color-mix(in srgb, var(--color-info) 45%, var(--border-color));
    --gradient-primary: linear-gradient(120deg, #005f73 0%, #5b21b6 55%, #9b2c4d 100%);
    --glow-reward: 0 0 0 1px rgba(180,35,93,0.30), 0 0 18px rgba(180,35,93,0.16);
    --glow-accent: 0 0 16px rgba(0,95,115,0.14);
    --bg-body: #f4f7fb;
    --bg-surface: #ffffff;
    --bg-subtle: #e7eef6;
    --bg-hover: #dbe7f2;
    --bg-elevated: #ffffff;
    --bg-cool: #e4f1f3;
    --text-primary: #111827;
    --text-secondary: #374151;
    --text-muted: #4b5563;
    --text-inverse: #ffffff;
    --border-color: #9aaabd;
    --border-subtle: #d2dce7;
    --shadow-sm: 0 1px 2px rgba(15,23,42,0.08);
    --shadow-md: 0 8px 22px rgba(15,23,42,0.10);
    --shadow-lg: 0 20px 46px rgba(15,23,42,0.14);
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 62%, rgba(15,23,42,0.05) 100%);
    color-scheme: light;
}

/* Star-Touched (dark): the exact CELESTIAL_ANCHORS hues breeding rolls for
   (pets/genetics.py: violet, gold, deep blue), worn by the chrome itself.
   --color-reward is a lightened tint of the anchor blue (#2980B9), not the
   literal hex: the raw anchor fails 4.5:1 against this theme's own --bg-hover
   (worst background in every theme, see docs/ux-rules.md), so the token is
   raised per that doctrine while --gradient-primary keeps the literal three
   anchors in their genetics.py order. --color-violet is the literal anchor
   unmodified: it's exempt from the solid-token contrast rule (decorative/
   gradient use only), same as every other theme.
   This is also the one Legacy palette with its own desktop layout: see
   "Star-Touched layout" below, right after the sidebar rules it restyles.
   That layout is scoped away from Frontier, whose interface frame owns the
   desktop rail geometry. */
body.theme-star-touched {
    --color-primary: #F1C40F;
    --color-primary-dark: #cda30c;
    --color-violet: #8E44AD;
    --color-reward: #3fa0dd;
    --color-reward-ink: #3fa0dd;
    --gradient-primary: linear-gradient(120deg, #8E44AD 0%, #F1C40F 50%, #2980B9 100%);
    --glow-reward: 0 0 0 1px rgba(63,160,221,0.46), 0 0 26px rgba(63,160,221,0.30);
    --glow-accent: 0 0 24px rgba(241,196,15,0.26);
    --bg-body: #0b0716;
    --bg-surface: #150e28;
    --bg-subtle: #120c22;
    --bg-hover: #271c47;
    --bg-elevated: #1c1436;
    --bg-cool: #12102a;
    --text-primary: #f5eeff;
    --text-secondary: #b7a6d9;
    --text-muted: #9789c4;
    --text-inverse: #140b1e;
    --border-color: #362a5c;
    --border-subtle: #241a40;
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 58%, rgba(4,2,14,0.34) 100%);
    color-scheme: dark;
}

/* High Contrast (dark): a deliberately spare palette for maximum separation.
   Accent and status colors remain decorative signals; words and symbols carry
   the meaning everywhere else. */
body.theme-high-contrast {
    --color-primary: #00ffff;
    --color-primary-dark: #00cccc;
    --color-violet: #d8b4fe;
    --color-reward: #ffcc00;
    --color-reward-ink: #ffcc00;
    --gradient-primary: linear-gradient(120deg, #00ffff 0%, #d8b4fe 55%, #ffcc00 100%);
    --glow-reward: 0 0 0 2px #ffcc00;
    --glow-accent: 0 0 0 2px #00ffff;
    --bg-body: #000000;
    --bg-surface: #080808;
    --bg-subtle: #141414;
    --bg-hover: #242424;
    --bg-elevated: #101010;
    --bg-cool: #06131a;
    --text-primary: #ffffff;
    --text-secondary: #ffffff;
    --text-muted: #e6e6e6;
    --text-inverse: #000000;
    --border-color: #b8b8b8;
    --border-subtle: #707070;
    --vignette: none;
    --color-success: #00ff99;
    --color-success-ink: #00ff99;
    --color-success-text: #9affd8;
    --color-error: #ff6b7a;
    --color-error-ink: #ff6b7a;
    --color-error-text: #ffc0c8;
    --color-warning: #ffcc00;
    --color-warning-ink: #ffcc00;
    --color-warning-text: #ffe680;
    --color-info: #00d9ff;
    --color-info-ink: #00d9ff;
    --color-info-text: #a8efff;
    color-scheme: dark;
}

/* Colorblind Safe (dark): blue, orange, and magenta are kept distinct, while
   every status also has a text label or symbol in the markup. */
body.theme-colorblind-safe {
    --color-primary: #56b4e9;
    --color-primary-dark: #2c8dc4;
    --color-violet: #cc79a7;
    --color-reward: #e69f00;
    --color-reward-ink: #e69f00;
    --gradient-primary: linear-gradient(120deg, #56b4e9 0%, #cc79a7 55%, #e69f00 100%);
    --glow-reward: 0 0 0 1px rgba(230,159,0,0.62), 0 0 20px rgba(230,159,0,0.26);
    --glow-accent: 0 0 20px rgba(86,180,233,0.24);
    --bg-body: #081522;
    --bg-surface: #0e2232;
    --bg-subtle: #0a1b2a;
    --bg-hover: #15364c;
    --bg-elevated: #123047;
    --bg-cool: #0a2630;
    --text-primary: #f7fbff;
    --text-secondary: #d5e9f2;
    --text-muted: #b9d7e6;
    --text-inverse: #081522;
    --border-color: #42657a;
    --border-subtle: #29485d;
    --vignette: radial-gradient(125% 125% at 50% 32%, transparent 58%, rgba(1,8,14,0.28) 100%);
    --color-success: #25c997;
    --color-success-ink: #25c997;
    --color-success-text: #8de8c7;
    --color-error: #ff6b6b;
    --color-error-ink: #ff6b6b;
    --color-error-text: #ffb886;
    --color-warning: #e69f00;
    --color-warning-ink: #e69f00;
    --color-warning-text: #ffd77a;
    --color-info: #56b4e9;
    --color-info-text: #a8dcf5;
    color-scheme: dark;
}

/* Reactive decoration: weather can tint local surfaces, but never reassigns
   the semantic primary/text/border tokens that communicate state. Unknown
   weather keys intentionally inherit the current palette's accent. */
body {
    --flux-accent: var(--color-primary);
    --flux-wash: color-mix(in srgb, var(--flux-accent) 10%, var(--bg-cool));
}
body[data-flux="stardust-drizzle"] { --flux-accent: #60e6cf; }
body[data-flux="aurora-veil"] { --flux-accent: #b89cff; }
body[data-flux="void-fog"] { --flux-accent: #d982b5; }
body[data-flux="comet-shower"] { --flux-accent: #f3b45b; }
body[data-flux="clear-skies"] { --flux-accent: var(--color-primary); }
body[data-flux]:not([data-flux=""]) .conditions-strip {
    background: var(--flux-wash);
    border-color: color-mix(in srgb, var(--flux-accent) 42%, var(--border-subtle));
}

/* Surface dialect proof of concept: each named page gets a faint local
   material cue. The same cards, type, buttons, and state semantics remain in
   place; this is a decorative layer, not a second theme system. */
[data-surface] {
    --surface-accent: color-mix(in srgb, var(--flux-accent) 44%, var(--color-violet));
    border-radius: var(--radius-lg);
    background-color: color-mix(in srgb, var(--bg-body) 96%, var(--surface-accent));
}
[data-surface="command-post"] {
    background-image: radial-gradient(circle at 78% 4%, color-mix(in srgb, var(--surface-accent) 14%, transparent), transparent 28rem);
}
[data-surface="star-map"] {
    background-image: repeating-linear-gradient(135deg, color-mix(in srgb, var(--surface-accent) 5%, transparent) 0 1px, transparent 1px 34px);
}
/* No background-image here (unlike the other named surfaces): its radial gradient was
   centered at 18% 8%, almost exactly where the page-header eyebrow/title sit, so under a
   vivid flux accent (e.g. Aurora Veil) it read as a pale highlighted box directly behind
   the header text rather than the faint ambient wash the other surfaces manage by keeping
   their gradient's hot spot away from the header (command-post's is offset to 78% from the
   left, well clear of its left-aligned title). Keeps the shared [data-surface] tint/radius
   below, just not this page's own gradient layer. */
[data-surface="incursion"] {
    background-image: linear-gradient(110deg, color-mix(in srgb, var(--surface-accent) 7%, transparent), transparent 34%, color-mix(in srgb, var(--color-reward) 4%, transparent));
}

/* ===== LAYOUT ===== */
.page-container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 24px 20px 60px;
}

.site-header {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    gap: 18px;
    height: var(--header-height);
    padding: 0 20px;
    background: var(--bg-surface);
    background: color-mix(in srgb, var(--bg-surface) 84%, transparent);
    backdrop-filter: saturate(1.3) blur(12px);
    -webkit-backdrop-filter: saturate(1.3) blur(12px);
    border-bottom: 1px solid var(--border-color);
}

.site-brand {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.15rem;
    letter-spacing: -0.02em;
    white-space: nowrap;
    background: var(--gradient-primary);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
@media (hover: hover) { .site-brand:hover { text-decoration: none; filter: brightness(1.08); } }

.site-nav-right {
    display: flex;
    align-items: center;
    gap: 16px;
    flex: 1;
    justify-content: flex-end;
    white-space: nowrap;
}
.site-nav-right a { color: var(--text-secondary); }
@media (hover: hover) { .site-nav-right a:hover { color: var(--text-primary); text-decoration: none; } }

/* Logout is a POST form (M12); style its button to read as a nav link. */
.nav-logout-form { display: inline; margin: 0; }
.nav-logout-form button {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--text-secondary);
    cursor: pointer;
}
@media (hover: hover) { .nav-logout-form button:hover { color: var(--text-primary); } }

/* Nav currency chips (M3). */
.nav-currency {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Toast stack positioning (M1): applies whether the container is pre-rendered
   in base.html or created on demand by main.js. */
#toast-container {
    position: fixed;
    top: 20px;
    right: 20px;
    left: 20px;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 10px;
    pointer-events: none;
}

/* Hide Alpine x-cloak regions until Alpine initializes (M2). */
[x-cloak] { display: none !important; }

/* Screen-reader-only content: visually hidden but still announced (e.g.
   aria-live status text that has a visible equivalent elsewhere). */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Skip-to-content link: visible only on keyboard focus (UX12 a11y). */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 10000;
    padding: 8px 14px;
    background: var(--bg-elevated, #222);
    color: var(--text-primary, #fff);
    border-radius: 0 0 6px 0;
}
.skip-link:focus { left: 0; }

/* Honeypot field for a public form (register): unlike .sr-only, this must be
   invisible to assistive tech too, not just sighted users, or a screen-reader
   user tabbing through would land on a field with nothing telling them to
   skip it. Pair with aria-hidden="true" and tabindex="-1" on the wrapper in
   markup; the class only handles the visual half. */
.form-honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Daily-bonus streak milestone track (UX9). */
.streak-track {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    list-style: none;
    padding: 0;
    margin: 8px 0;
}
/* Two-line pip: day above, its Stardust payout below. The payout used to live only in a
   `title=` (plus an .sr-only sentence), so on touch there was no way to learn what any
   milestone paid. `height` became `min-height` for the same reason the reward line exists:
   a fixed 32px clipped the second line outright. */
.streak-milestone {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    min-height: 32px;
    padding: 3px 8px;
    border-radius: 14px;
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
    line-height: 1.15;
}
.streak-milestone__reward {
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
}
.streak-milestone.reached .streak-milestone__reward { color: inherit; opacity: 0.85; }
/* Reached pips also carry a ✓ glyph in the markup: the colour is reinforcement,
   never the signal on its own. Uses --color-reward (the earned/reward token) rather
   than the previously-hardcoded #7e57c2 fallback of an undefined --accent, which
   rendered the same off-palette purple in every palette. */
.streak-milestone.reached {
    background: color-mix(in srgb, var(--color-reward) 16%, transparent);
    border-color: color-mix(in srgb, var(--color-reward) 55%, var(--border-color));
    color: var(--color-reward);
    font-weight: 700;
}

/* Pet-card image placeholder while generation is pending (UX11): fixed size
   matches the 120x120 img so the grid doesn't jump when images arrive. */
.pet-card-image-pending {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 120px;
    height: 120px;
    border-radius: 8px;
    background: var(--bg-elevated, #2a2a2a);
    color: var(--text-secondary, #aaa);
    font-size: 0.8rem;
}

.staff-nav {
    display: flex;
    align-items: center;
    gap: 16px;
    padding-right: 16px;
    border-right: 1px solid var(--border-color);
}

.weather-chip {
    padding: 4px 10px;
    border-radius: var(--radius-lg);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    color: var(--text-secondary);
    font-size: 0.9em;
}

.site-body {
    display: flex;
    align-items: stretch;
}

.site-sidebar {
    width: 216px;
    flex-shrink: 0;
    border-right: 1px solid var(--border-subtle);
    padding: 20px 14px;
    position: sticky;
    top: var(--header-height);
    align-self: flex-start;
    /* dvh, not vh: on mobile browsers 100vh excludes the URL bar and toolbar, so a
       vh-sized scroll box runs on under the browser chrome and its bottom is
       unreachable. vh first as the fallback for anything without dvh support. */
    height: calc(100vh - var(--header-height));
    height: calc(100dvh - var(--header-height));
    overflow-y: auto;
}

.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.sidebar-nav a {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-family: var(--font-heading);
    font-weight: 500;
    font-size: 0.95rem;
    position: relative;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.sidebar-nav a .nav-ico,
.sidebar-nav-group > summary .nav-ico { width: 20px; text-align: center; font-size: 1.05rem; flex-shrink: 0; }
@media (hover: hover) { .sidebar-nav a:hover { background: var(--bg-hover); color: var(--text-primary); text-decoration: none; } }
.sidebar-nav a.is-active { background: var(--bg-hover); color: var(--text-primary); }
.sidebar-nav a:focus-visible,
.staff-subnav a:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.sidebar-nav a.is-active::before {
    content: '';
    position: absolute;
    left: -14px;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 22px;
    border-radius: 0 3px 3px 0;
    background: var(--gradient-primary);
}
.sidebar-nav-group { margin-top: 2px; }
.sidebar-nav-group > summary {
    display: flex;
    align-items: center;
    gap: 11px;
    min-height: 44px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-family: var(--font-heading);
    font-size: .95rem;
    font-weight: 600;
    cursor: pointer;
    list-style: none;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.sidebar-nav-group__label {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.15;
}
.sidebar-nav-group__hint {
    margin-top: 2px;
    color: var(--text-muted);
    font-size: .68rem;
    font-weight: 500;
    letter-spacing: .02em;
}
.sidebar-nav-group > summary::-webkit-details-marker { display: none; }
.sidebar-nav-group > summary:focus-visible {
    outline: 3px solid var(--color-primary);
    outline-offset: 2px;
}
@media (hover: hover) {
    .sidebar-nav-group > summary:hover { background: var(--bg-hover); color: var(--text-primary); }
}
.sidebar-nav-group[open] > summary { color: var(--text-primary); }
.sidebar-nav-group__chevron { margin-left: auto; color: var(--text-muted); font-size: 1.1rem; line-height: 1; transition: transform var(--dur) var(--ease); }
.sidebar-nav-group[open] .sidebar-nav-group__chevron { transform: rotate(180deg); }
.sidebar-nav-group__items { display: flex; flex-direction: column; gap: 2px; }
.sidebar-nav-group__items a { padding-left: 20px; }
.site-body > .page-container {
    flex: 1;
    min-width: 0;
}

@media (width < 1024px) {
    /* Below desktop the sidebar rail is replaced by the fixed bottom tab bar
       (+ "More" drawer). Tablets (768–1023) keep this bar but get full-width
       two-column content: see the grid queries below. */
    .site-header { gap: 12px; padding: 0 14px; }
    .site-sidebar { display: none; }
    body.is-app { padding-bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom, 0px)); }
}

/* ============================================================
   Star-Touched Legacy layout: top nav bar, desktop only (1024px+)
   The header (brand/weather/currencies/bell/avatar) is untouched: only
   `.site-sidebar` is retired for this one theme, restyled from a left rail
   into a horizontal strip under the header. This is a pure restyle of the
   EXISTING `.sidebar-nav` markup from base.html: same links, same URLs, same
   badges, same <details> groups. Below 1024px this theme is identical to
   every other theme (the sidebar is already hidden in favor of the bottom
   tab bar, per the rule just above).
   The interface-style exclusion is intentional: UserPreferences keeps the
   Legacy palette selection when a player switches to Frontier, but the
   Legacy palette's alternate layout must not reshape the Frontier shell.
   ============================================================ */
@media (min-width: 1024px) {
    body.theme-star-touched:not(.interface-frontier) .site-body { flex-direction: column; }

    body.theme-star-touched:not(.interface-frontier) .site-sidebar {
        width: 100%;
        height: auto;
        position: sticky;
        top: var(--header-height);
        z-index: 90;
        border-right: none;
        border-bottom: 1px solid var(--border-color);
        padding: 0 14px;
        overflow: visible;
        background: var(--bg-elevated);
    }

    /* flex-wrap, not overflow-x: an auto-scrolling `.sidebar-nav` would force
       its own overflow-y to auto too (a child with overflow-x != visible and
       overflow-y left visible has the latter computed to auto per spec), which
       would clip the absolutely-positioned dropdown panels below instead of
       letting them float over the page. Wrapping to a second row at narrower
       desktop widths is a duller fallback but never clips anything. */
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: stretch;
        gap: 2px;
    }

    body.theme-star-touched:not(.interface-frontier) .sidebar-nav a,
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav-group > summary {
        height: 46px;
        padding: 0 12px;
        border-radius: 0;
        border-bottom: 2px solid transparent;
        white-space: nowrap;
    }
    /* The vertical rail's left accent bar reads as a stray mark once the item
       is laid out horizontally: a bottom border under the active/open item
       takes over as the position indicator instead. */
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav a.is-active {
        background: none;
        color: var(--color-primary);
        border-bottom-color: var(--color-primary);
    }
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav a.is-active::before { content: none; }
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav-group[open] > summary { border-bottom-color: var(--color-violet); }

    body.theme-star-touched:not(.interface-frontier) .sidebar-nav-group { position: relative; margin-top: 0; }
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav-group__label { flex-direction: row; align-items: center; gap: 6px; }
    /* The "Companions" / "Frontier" sub-label has room in a vertical rail; it
       doesn't in a 46px-tall bar, and the group's own name already carries
       enough meaning without it. */
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav-group__hint { display: none; }

    /* .sidebar-nav-group (the <details>) is this panel's containing block,
       set above. See game/test_css_positioning.py. */
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav-group__items {
        position: absolute;
        top: 100%;
        left: 0;
        margin-top: 1px;
        min-width: 230px;
        padding: 6px;
        background: var(--bg-elevated);
        border: 1px solid var(--border-color);
        border-radius: 0 0 var(--radius-md) var(--radius-md);
        box-shadow: var(--shadow-lg);
        z-index: 95;
    }
    body.theme-star-touched:not(.interface-frontier) .sidebar-nav-group__items a { padding-left: 12px; border-radius: var(--radius-sm); }
}

/* ===== LANDING (anonymous first impression) ===== */
.landing-hero {
    position: relative;
    text-align: center;
    padding: clamp(56px, 9vw, 104px) 20px clamp(36px, 6vw, 60px);
    overflow: hidden;
}
/* Brand halo: the one sanctioned glow on an anonymous page: this IS the
   product's opening moment. Faint and theme-aware via the accent token. */
.landing-hero::before {
    content: '';
    position: absolute;
    inset: -30% 0 auto 0;
    height: 440px;
    background: radial-gradient(58% 100% at 50% 0%,
        color-mix(in srgb, var(--color-primary) 16%, transparent), transparent 70%);
    pointer-events: none;
    z-index: 0;
}
.landing-hero > * { position: relative; z-index: 1; }

.landing-hero .eyebrow { margin-bottom: 18px; }

.landing-showcase {
    margin: 24px auto 0;
    width: clamp(140px, 16vw, 180px);
}
.landing-showcase img {
    width: 100%;
    height: auto;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}

.landing-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: clamp(2.6rem, 1.4rem + 5.2vw, 4.6rem);
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 0.98;
    background: var(--gradient-primary);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.landing-tagline {
    color: var(--text-secondary);
    font-size: clamp(1.02rem, 0.95rem + 0.5vw, 1.25rem);
    max-width: 48ch;
    margin: 18px auto 30px;
}

.landing-actions {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 14px;
}

.landing-note { margin-top: 22px; }

/* Breed roster strip: the six species as pill chips */
.landing-breeds {
    max-width: 760px;
    margin: clamp(28px, 5vw, 44px) auto;
    text-align: center;
}
.landing-breeds .eyebrow { margin-bottom: 14px; }
.landing-breed-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
}
.landing-breed {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 16px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-pill);
    background: var(--bg-surface);
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--text-secondary);
}
.landing-breed__glyph { font-size: 1.15rem; line-height: 1; }

/* Feature grid: reuses the .panel system */
/* auto-fit rather than a 720px query stepping 1 -> 3 columns: it needs no breakpoint at
   all (the last off-scale 720px line in the file), and it picks up two columns through
   the tablet band that the hard step skipped straight past. */
.landing-features {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 16px;
    max-width: 980px;
    margin: 0 auto;
}
.landing-feature__icon { font-size: 1.8rem; line-height: 1; margin-bottom: 12px; }
.landing-feature h2 { font-family: var(--font-heading); font-size: 1.05rem; margin: 0 0 6px; }
.landing-feature p { color: var(--text-secondary); font-size: 0.9rem; margin: 0; }

/* Larger button for the hero call-to-action */
.btn-lg { padding: 13px 28px; font-size: 1.02rem; min-height: 50px; }

/* ===== WAITLIST (landing-page pre-launch email capture) ===== */
.waitlist { max-width: 620px; margin: 34px auto 0; scroll-margin-top: 24px; }
.waitlist__panel { text-align: center; padding: 30px 26px; }
.waitlist__panel--ok { border-color: var(--color-success-border); }
.waitlist__panel--error { border-color: var(--color-error-border); }
.waitlist__glyph { font-size: 2rem; line-height: 1; margin-bottom: 10px; color: var(--color-success); }
.waitlist__title { font-family: var(--font-heading); font-size: 1.15rem; margin: 6px 0 8px; }
.waitlist__blurb { color: var(--text-secondary); font-size: 0.92rem; margin: 0 0 18px; }
.waitlist__form {
    display: flex;
    /* main.css's global `form` rule is flex-direction: column; without
       overriding it here, `.waitlist__input`'s `flex: 1 1 240px` sets a
       240px *height* basis instead of width, ballooning the email field. */
    flex-direction: row;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: center;
    /* main.css sets a global max-width on `form`; the row needs the panel's
       full width or the input collapses to a stub beside the button. */
    max-width: none;
    width: 100%;
    margin: 0;
}
.waitlist__input {
    flex: 1 1 240px;
    min-width: 0;
    margin: 0;
}
.waitlist__form .btn { flex: 0 0 auto; }
.waitlist__error {
    color: var(--color-error-text);
    font-size: 0.9rem;
    margin: 0 0 14px;
}
.waitlist__fineprint {
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.5;
    margin: 16px 0 0;
}

/* ===== AUTH (login / register / password reset): light-touch card =====
   max-width is a comfortable reading measure for a handful of stacked fields,
   not a hard cap: it was 440px (blocking register's extra fields onto a
   cramped column), widened to sit alongside the site's other intermediate
   card widths (frontend-guide.md's "480px or 700px" examples) while staying
   fluid below it, so nothing narrower than the card ever gets clipped. */
.auth-card { max-width: 560px; margin: clamp(24px, 6vw, 60px) auto; }
.auth-card .eyebrow { color: var(--text-muted); }
.auth-title {
    font-family: var(--font-display);
    font-size: 1.7rem;
    letter-spacing: -0.02em;
    margin: 6px 0 16px;
}
.auth-card form { max-width: none; }
.auth-intro { color: var(--text-secondary); font-size: 0.92rem; margin: 0 0 16px; }
.auth-links { margin: 18px 0 0; display: flex; flex-direction: column; gap: 6px; font-size: 0.9rem; }
.auth-links p { margin: 0; color: var(--text-secondary); }

/* ===== BUTTONS ===== */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 20px;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-family: var(--font-heading);
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    cursor: pointer;
    min-height: 42px;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* Primary = the accent, solid. The nebula gradient is reserved for .btn-reward
   (earned / celebratory actions) so the glow stays meaningful. */
.btn-primary {
    background: var(--color-primary);
    color: var(--text-inverse);
}
@media (hover: hover) { .btn-primary:hover { background: var(--color-primary-dark); box-shadow: var(--glow-accent); } }

.btn-reward {
    background: var(--gradient-primary);
    color: var(--text-inverse);
    box-shadow: var(--glow-reward);
}
@media (hover: hover) { .btn-reward:hover { filter: brightness(1.06); } }

.btn-secondary {
    background: var(--bg-subtle);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
}
@media (hover: hover) { .btn-secondary:hover { background: var(--bg-hover); } }

.btn-danger {
    background: var(--color-error);
    color: var(--text-inverse);
}
@media (hover: hover) { .btn-danger:hover { opacity: 0.9; } }

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

@media (max-width: 768px) {
    .btn { min-height: 44px; }
}

body.compact-ui .btn { padding: 7px 14px; font-size: 0.9em; min-height: 34px; }

/* ===== FORMS ===== */
form { display: flex; flex-direction: column; gap: 14px; }
/* Opt-in narrow width for small confirm/single-field forms (rename, cancel, decline,
   allocate, etc.) — forms no longer default to a 420px cap (that used to squeeze full-
   width content like the bio editors and trade offer builders), so anything that actually
   wants the old compact width takes this class explicitly instead of inheriting it silently. */
.form-compact { max-width: 420px; }
label { display: flex; flex-direction: column; gap: 6px; font-weight: 500; color: var(--text-secondary); }

/* A checkbox label reads naturally box-then-text on one line, not stacked
   like the column labels above it (register's Alpha Agreement checkbox). */
.form-checkbox-row { flex-direction: row; align-items: flex-start; gap: 8px; font-weight: 400; }
.form-checkbox-row input[type="checkbox"] { margin-top: 3px; flex-shrink: 0; }

/* `input:not([type])` is in the list for the same reason it is in the coarse-
   pointer font floor below: a bare <input> defaults to type=text but matches
   none of the attribute selectors, so it renders as an unstyled 24px browser
   box next to styled siblings. The dressing room's "save this look" field
   shipped exactly that way. */
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="search"],
input:not([type]),
select,
textarea {
    background: var(--bg-surface);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
    font-size: 1em;
}

input:focus, select:focus, textarea:focus {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
}

/* ===== TABLES =====
   Every in-app table is a dense data table (mod/staff panels, login activity).
   They self-scroll horizontally via the display:block technique (like GitHub's
   markdown tables): the table element itself is the scroll container, so a wide
   table scrolls sideways inside its column instead of forcing the whole page to
   scroll on phones: no per-table wrapper markup required. Cells stay on one line
   (nowrap) so columns keep their shape while scrolling; opt a long free-text
   column into wrapping with class="cell-wrap". */
table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border-collapse: collapse;
    margin: 4px 0 18px;
    font-size: 0.9rem;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
th, td {
    padding: 9px 14px;
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--border-subtle);
    white-space: nowrap;
}
thead th {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.68rem;
    font-weight: 700;
    color: var(--text-muted);
    background: var(--bg-subtle);
}
/* Row-header tables (Redis panel: <th> in the first column); quieter than a
   column header, aligned as a label. */
tbody th {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    font-weight: 700;
}
tbody tr:last-child td,
tbody tr:last-child th { border-bottom: none; }
@media (hover: hover) { tbody tr:hover td { background: var(--bg-hover); } }
td a { font-weight: 600; }
/* Long free text (user agents, tracebacks, summaries) wraps instead of blowing
   the row width out: width is capped so the column stays readable. */
.cell-wrap { white-space: normal; min-width: 200px; max-width: 380px; word-break: break-word; }
table pre { white-space: pre-wrap; word-break: break-word; margin: 6px 0 0; }

/* ===== MESSAGES / TOASTS ===== */
.messages { list-style: none; margin: 0; padding: 12px 20px 0; }
.messages li {
    padding: 10px 16px;
    border-radius: var(--radius-md);
    margin-bottom: 8px;
}
.messages li.success { background: var(--color-success-bg); color: var(--color-success-text); border: 1px solid var(--color-success-border); }
.messages li.error   { background: var(--color-error-bg);   color: var(--color-error-text);   border: 1px solid var(--color-error-border); }
.messages li.info    { background: var(--color-info-bg);    color: var(--color-info-text);    border: 1px solid var(--color-info-border); }
.messages li.warning { background: var(--color-warning-bg); color: var(--color-warning-text); border: 1px solid var(--color-warning-border); }

.message-toast {
    padding: 12px 18px;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    color: var(--text-inverse);
    font-weight: 500;
    max-width: 380px;
}
.message-toast.success { background: var(--color-success); }
.message-toast.error   { background: var(--color-error); }
.message-toast.info    { background: var(--color-primary); }
.message-toast.reward  { border-left: 4px solid var(--color-warning-text); background: var(--color-warning-bg); color: var(--color-warning-text); }

/* ===== CARDS =====
   Shared surface treatment for the existing card class hooks already present
   in template markup: .pet-card, .nest-card, .expedition-card,
   .map-node-card, .faction-card, .inventory-item */
.pet-card,
.nest-card,
.expedition-card,
.map-node-card,
.faction-card,
.inventory-item {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 16px;
}

.squad-badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: var(--radius-lg);
    background: var(--color-success-bg);
    color: var(--color-success-text);
    font-size: 0.85em;
    font-weight: 600;
}

.hex-swatch {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 1px solid var(--border-color);
    margin-right: 4px;
    vertical-align: middle;
}

/* ===== BARRACKS: tile grid, toolbar, group tabs ===== */
.pet-grid-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
    margin-top: 16px;
}
@media (min-width: 768px) and (max-width: 1024px) {
    .pet-grid-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 480px) { .pet-grid-cards { grid-template-columns: minmax(0, 1fr); } }

/* Roster stat strip (sits inside #pet-grid so it refreshes with the filtered view) */
.roster-summary {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px 14px;
    margin-top: 14px;
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.roster-summary__title { color: var(--text-muted); }
.roster-summary__stats { display: flex; flex-wrap: wrap; gap: 8px 22px; margin: 0; }
.roster-summary__stat { display: flex; align-items: baseline; gap: 6px; }
.roster-summary__stat dt { font-size: 0.78rem; color: var(--text-muted); }
.roster-summary__stat dd { margin: 0; font-size: 0.95rem; font-weight: 700; color: var(--text-primary); }
.roster-summary__species { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
.roster-summary__species-chip {
    padding: 2px 10px;
    font-size: 0.8rem;
    color: var(--text-secondary);
    background: var(--bg-hover);
    border-radius: var(--radius-pill);
}
.roster-summary__species-chip span { color: var(--text-primary); font-weight: 700; }

.pet-card {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 10px;
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
@media (hover: hover) { .pet-card:hover { border-color: var(--color-primary); } }
.pet-card__link { display: block; text-align: center; color: inherit; }
@media (hover: hover) { .pet-card__link:hover { text-decoration: none; } }
.pet-card__media {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1 / 1;
    background: var(--bg-subtle);
    border-radius: var(--radius-md);
    overflow: hidden;
    margin-bottom: 10px;
}
.pet-card__media img { width: 100%; height: 100%; object-fit: contain; border-radius: var(--radius-md); }
.pet-card__name { margin: 0; font-size: 1.02rem; }
.pet-card__meta {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex-wrap: wrap;
}
.pet-card__level {
    font-size: 0.66rem;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    color: var(--text-secondary);
}
.pet-card__swatches { display: flex; justify-content: center; gap: 0; }
.pet-card__stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    padding: 8px 0;
    border-top: 1px solid var(--border-subtle);
    border-bottom: 1px solid var(--border-subtle);
}
.pet-card__stat { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.pet-card__stat-label {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.56rem;
    color: var(--text-muted);
}
.pet-card__stat-val { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 700; color: var(--text-primary); }
.pet-card__foot { display: flex; flex-direction: column; gap: 8px; margin-top: auto; align-items: stretch; }
.pet-card__foot .pet-group-manage { display: block; margin-top: 0; }
/* A card is only ~200px wide and .state-chip is nowrap by default, so a long chip
   ("🥚 Cooldown 2 days, 5 hours") spilled straight past the card border. Inside a
   card, chips wrap and stay within the content box instead; the precise timestamp
   lives in the title attribute either way. */
.pet-card > .state-chip,
.pet-card__foot .state-chip {
    align-self: center;
    max-width: 100%;
    white-space: normal;
    text-align: center;
    line-height: 1.35;
}
.stat-readout .state-chip {
    max-width: 100%;
    white-space: normal;
    overflow-wrap: anywhere;
    text-align: center;
    line-height: 1.35;
}

/* Rearrange mode (.pet-card--compact, set by _pet_card.html when is_reordering): the
   full card's mutation/tag/marketplace/busy/cooldown/sickness/rested chips, swatches,
   stats grid and squad/group/tag menus are dropped from the DOM entirely (see the
   template's own comment), so this only has to lay out what's left: image, name,
   breed, level, trait. A single wide row per pet, rather than the full card's tall
   vertical stack, is what actually lets more than one row fit on screen at once. */
.pet-card--compact {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    padding: 8px 40px 8px 12px;
}
.pet-card--compact .pet-card__link {
    display: flex;
    align-items: center;
    gap: 8px;
    text-align: left;
    flex: 1 1 auto;
    min-width: 0;
}
.pet-card--compact .pet-card__media {
    width: 40px;
    flex: 0 0 40px;
    margin-bottom: 0;
}
.pet-card--compact .pet-card__name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pet-card--compact .pet-card__meta {
    flex: 0 0 auto;
}
.pet-card--compact .trait-chip {
    flex: 0 0 auto;
}

.barracks-toolbar {
    display: flex;
    flex-direction: row;
    gap: 16px;
    flex-wrap: wrap;
    align-items: flex-end;
    margin: 16px 0;
    padding: 14px 16px;
    background: var(--bg-cool);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
}
.barracks-toolbar label {
    min-width: 160px;
    gap: 5px;
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.66rem;
    color: var(--text-muted);
}
.barracks-filter-disclosure {
    display: block;
    flex: 0 0 100%;
    width: 100%;
}
.barracks-filter-disclosure > summary { display: none; }
.barracks-toolbar__fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    flex: 0 0 100%;
    width: 100%;
    min-width: 0;
    gap: 12px;
}
.barracks-toolbar select,
.barracks-toolbar input[type="search"] { font-family: var(--font-body); text-transform: none; letter-spacing: normal; font-size: 0.95rem; color: var(--text-primary); }
.barracks-toolbar input[type="search"] { margin: 0; }
/* Result count + clear-filters sit at the end of the toolbar row, pushed right. */
.barracks-toolbar__count {
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    align-self: center;
}
.barracks-toolbar__count + .btn { align-self: center; }
.page-position { color: var(--text-muted); padding: 0 6px; }

@media (max-width: 1024px) {
    .barracks-toolbar { gap: 10px; padding: 12px; }
    .barracks-toolbar label { min-width: 140px; }
}
@media (max-width: 768px) {
    .barracks-toolbar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
    .barracks-filter-disclosure { display: block; grid-column: 1 / -1; min-width: 0; }
    .barracks-filter-disclosure > summary {
        display: flex;
        align-items: center;
        justify-content: space-between;
        min-height: 44px;
        padding: 10px 12px;
        border: 1px solid var(--border-color);
        border-radius: var(--radius-md);
        color: var(--text-primary);
        font-weight: 700;
        cursor: pointer;
        list-style: none;
    }
    .barracks-filter-disclosure > summary::-webkit-details-marker { display: none; }
    .barracks-filter-disclosure > summary::after { content: '＋'; color: var(--color-primary); }
    .barracks-filter-disclosure[open] > summary::after { content: '−'; }
    .barracks-toolbar__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding-top: 10px; flex: none; }
    .barracks-toolbar label { min-width: 0; }
    .barracks-toolbar__fields label:first-child { grid-column: auto; }
    .barracks-toolbar__count { margin-left: 0; min-width: 0; }
    .barracks-toolbar__count + .btn { justify-self: end; }
}
@media (max-width: 380px) {
    .barracks-toolbar__fields label:first-child { grid-column: 1 / -1; }
}

.pet-group-tabs {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--border-color);
    padding-bottom: 4px;
}
.pet-group-tab {
    display: inline-block;
    padding: 8px 14px;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    color: var(--text-secondary);
    font-weight: 500;
    text-decoration: none;
}
@media (hover: hover) { .pet-group-tab:hover { background: var(--bg-hover); } }
.pet-group-tab-wrap { display: flex; align-items: center; position: relative; }
.pet-group-tab-wrap.active .pet-group-tab,
a.pet-group-tab.active {
    background: var(--bg-surface);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
    border-bottom: 1px solid var(--bg-surface);
    margin-bottom: -1px;
}
.pet-group-tab-menu-btn {
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 4px 6px;
}
.pet-group-tab-menu,
.pet-group-manage-menu {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 10;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 160px;
}
.pet-group-tab-menu form { flex-direction: row; max-width: none; }
.pet-group-add-form { flex-direction: row; max-width: none; align-items: center; gap: 8px; margin-left: 4px; }
.pet-group-add-form input { margin: 0; }
/* The containing block for the "+ New Group" / "Manage Tags" panels. Without
   `position: relative` here the absolutely-positioned .pet-group-tab-menu inside
   escapes to the initial containing block and renders a full viewport-height
   below the page, at the far left: the button appears to do nothing at all.
   game/test_css_positioning.py is the guard. */
.pet-group-tab-add { display: inline-block; position: relative; }

.pet-group-manage { display: inline-block; position: relative; margin-top: 8px; }
.pet-group-checkbox { flex-direction: row; align-items: center; gap: 8px; font-weight: 400; }

/* Group tabs: icon, color accent, default marker, reorder + edit menus.
   The color rides an inset top box-shadow on the tab link itself (inline style,
   set per-group), rather than a swatch square beside the icon: box-shadow
   respects the tab's own top-rounded border-radius, so the accent reads as a
   rounded color bar along the tab's top edge instead of a second glyph. The
   icon stays aria-hidden decoration; the tab's text name is still the primary
   distinguishing label (never colour alone), per the a11y rules. */
.pet-group-tabs__sortable { display: contents; }
.pet-group-tab--colored { padding-top: 10px; }
.pet-group-tab__icon { margin-right: 4px; }
.pet-group-tab__default { color: var(--color-reward); margin-left: 4px; }
.pet-group-tab.active[aria-pressed="true"],
.pet-group-tab.active { background: var(--bg-hover); }
.pet-group-edit-row { flex-direction: row; align-items: center; gap: 6px; flex-wrap: wrap; }
.pet-group-icon-input { width: 3.2em; text-align: center; }
.pet-group-tab-reorder { display: flex; gap: 6px; }

/* Preset tag chip: the card display, the pet-detail pill, and the card's quick-add
   search box. Tags carry no per-tag colour (a fixed preset catalog, emoji-only). */
.pet-tag-chip { color: var(--text-secondary); }
.pet-tag-preset-search input { margin: 0; width: 100%; }

/* Pet detail Manage section (assign preset tags + custom tabs), lifted from the donor
   codebase's dragon-detail "Manage" panel. Two columns on wide screens, stacked on
   narrow ones; each column is its own swap target (see _pet_detail_tags/tabs.html). */
.pet-detail-manage-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
}
@media (max-width: 640px) {
    .pet-detail-manage-grid { grid-template-columns: 1fr; }
}
.pet-detail-manage-col h3 { margin: 0 0 8px; font-size: 0.95rem; }
.pet-detail-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; min-height: 1.6em; }
.pet-tag-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 6px 3px 10px;
    border-radius: var(--radius-pill);
}
.pet-tag-pill__remove {
    background: none;
    border: none;
    color: inherit;
    cursor: pointer;
    line-height: 1;
    padding: 2px 4px;
    font-size: 0.9rem;
}
/* Split deliberately: only the :hover half takes the hover guard. Wrapping the pair
   together would put :focus-visible behind (hover: hover) too and delete the keyboard
   focus colour on every touch device, which is the opposite of the point. */
.pet-tag-pill__remove:focus-visible { color: var(--color-error); }
@media (hover: hover) { .pet-tag-pill__remove:hover { color: var(--color-error); } }
.pet-detail-add-select { width: 100%; }

/* Batch multi-select mode: per-card checkbox + the floating action bar. */
.pet-card__batch {
    position: absolute;
    top: 6px;
    left: 6px;
    z-index: 3;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 2px 4px;
    line-height: 0;
}
.barracks-batch-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 10px 0;
    padding: 10px 14px;
    background: var(--bg-surface);
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-md);
}
.barracks-batch-bar__count { font-family: var(--font-mono); font-size: 0.85rem; color: var(--color-primary); }
.barracks-batch-bar__select {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    flex-basis: 100%;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border-color);
}
.barracks-batch-bar__close { margin-left: auto; }
/* Each batch form is a column (target list above its own action buttons) so the
   two buttons that act on the checked pets and the one that acts on every match
   can't be mistaken for each other across a single wrapped row. */
.barracks-batch-form {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    flex-direction: column;
    max-width: none;
    margin: 0;
}
.barracks-batch-form select { margin: 0; }
.barracks-batch-form__actions { display: flex; flex-wrap: wrap; gap: 6px; }
.barracks-batch-form__label { display: block; font-size: 0.85rem; margin-bottom: 4px; }
.barracks-batch-form__label .hint { font-weight: 400; }
.barracks-batch-select { min-width: 190px; }

/* ===== PET DETAIL PAGE ===== */
.pet-detail-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 14px;
}
.pet-detail-position { font-size: 0.9em; }

.pet-detail-hero { display: flex; gap: 24px; flex-wrap: wrap; }
.pet-detail-name-row h1 { font-family: var(--font-display); font-size: clamp(1.6rem, 1.2rem + 1.8vw, 2.2rem); letter-spacing: -0.02em; }
.pet-detail-image-col { flex: 0 1 500px; min-width: 0; max-width: 100%; }
.pet-detail-image-col img { display: block; width: 100%; max-width: 500px; height: auto; border-radius: var(--radius-md); }
.pet-detail-image-col .btn { margin-top: 12px; }
.pet-detail-info-col { flex: 1 1 260px; display: flex; flex-direction: column; gap: 10px; }

.pet-detail-name-row { display: flex; align-items: center; gap: 10px; }
.pet-detail-name-row h1 { margin: 0; }
.pet-detail-name-editor { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.pet-detail-name-editor input {
    padding: 6px 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    color: var(--text-primary);
}

.swatch-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.swatch-label { color: var(--text-muted); font-size: 11px; text-transform: uppercase; width: 40px; flex-shrink: 0; }

.appearance-grid {
    display: grid;
    grid-template-columns: minmax(140px, 180px) minmax(180px, 1fr) minmax(200px, 1fr);
    gap: 20px;
}
@media (max-width: 700px) { .appearance-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .appearance-grid { grid-template-columns: 1fr; } }
.appearance-col { display: flex; flex-direction: column; gap: 8px; }
.appearance-col h3 { margin: 0 0 4px 0; font-size: 15px; color: var(--text-primary); }

.marking-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--border-color);
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text-primary);
}

/* Marking Studio's current-layers list: a leading info cluster (swatch, name,
   opacity) grouped so .marking-row's justify-content:space-between splits it from
   the trailing Remove form instead of spreading every child evenly. */
.ms-marking-list { display: flex; flex-direction: column; gap: 8px; }
.ms-marking-row__info { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Marking Studio's Placement picker: a pet's existing layers are read-only rows
   (.ms-placement-existing, an indented .marking-row variant) with a selectable
   .inline-row radio slot above/below/between them for where the NEW marking goes.
   :has(input:checked) mirrors .mw-swatch's own selected-state treatment. */
.ms-placement-list { display: flex; flex-direction: column; gap: 2px; }
.ms-placement-slot { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: var(--radius-sm); }
.ms-placement-slot:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.ms-placement-slot:has(input:checked) { background: var(--bg-hover); font-weight: 600; }
.ms-placement-existing {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: 22px;
    padding: 6px 10px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--border-color);
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text-secondary);
}
.marking-meta { display: flex; align-items: center; gap: 8px; }

.mutation-row {
    display: flex;
    justify-content: space-between;
    align-items: start;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--color-primary);
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text-primary);
}
/* The Glimmer "strip this pet back to no mutation" action: its own form (it must
   not inherit the purchase form's fields), so it needs its own separation. Laid
   out as the same explanation/action pair as the .mw-commit bar directly above it
   (and overriding the global 420px form width for the same reason .mw-form does)
   so the page's two spends line up instead of one being full-width and the
   other a narrow left-hugging column. */
.mutation-remove-form {
    margin-top: 24px;
    padding-top: 20px;
    border-top: 1px solid var(--border-color);
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
    gap: 20px;
    align-items: end;
    max-width: none;
}
.mutation-remove-form > div { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.mutation-remove-form p { margin: 0; }
.mutation-remove-form .btn { width: 100%; justify-content: center; }

.mutation-row strong { display: block; font-size: 13px; }
.mutation-row small { color: var(--text-muted); font-size: 11px; display: block; margin-top: 2px; }

.stat-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
@media (max-width: 700px) { .stat-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 480px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
.stat-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px;
    border-radius: var(--radius-md);
    border-left: 3px solid transparent;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
}
.stat-card--might    { border-left-color: var(--color-error); }
.stat-card--grace    { border-left-color: var(--color-info); }
.stat-card--instinct { border-left-color: var(--color-warning); }
.stat-card--accent   { border-left-color: var(--color-primary); }
.stat-label { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.stat-val { font-family: var(--font-mono); font-size: 22px; font-weight: 700; color: var(--text-primary); font-variant-numeric: tabular-nums; }

.lineage-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 580px) { .lineage-grid { grid-template-columns: 1fr; } }
.lineage-parent-row { display: flex; flex-direction: column; gap: 8px; }
.lineage-offspring-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.lineage-card {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    text-decoration: none;
    border: 1px solid var(--border-color);
    background: var(--bg-subtle);
    transition: transform .15s, box-shadow .15s;
    color: var(--text-primary);
}
@media (hover: hover) { .lineage-card:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); } }
.lineage-card strong { font-size: 13px; display: block; }
.lineage-card--parent { border-left: 3px solid var(--color-warning); }
.lineage-card--child  { border-left: 3px solid var(--color-info); }
.lineage-card--released { opacity: 0.65; cursor: default; }
@media (hover: hover) { .lineage-card--released:hover { transform: none; box-shadow: none; } }
.lineage-empty {
    background: var(--bg-subtle);
    border: 2px dashed var(--border-color);
    border-radius: var(--radius-md);
    padding: 14px;
    font-size: 13px;
    color: var(--text-muted);
}
.lineage-empty a { color: var(--color-primary); }

/* ===== SETTINGS ===== */
.settings-page { max-width: 760px; margin: 0 auto; }
.settings-jumpbar {
    display: flex;
    gap: 6px;
    max-width: 760px;
    margin: 0 auto 14px;
    padding: 4px;
    overflow-x: auto;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    background: var(--bg-cool);
    scrollbar-width: thin;
}
.settings-jumpbar a {
    flex: 0 0 auto;
    padding: 7px 11px;
    border-radius: var(--radius-pill);
    color: var(--text-secondary);
    font-size: .82rem;
    text-decoration: none;
}
@media (hover: hover) { .settings-jumpbar a:hover { background: var(--bg-hover); color: var(--text-primary); } }
.settings-jumpbar a:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.settings-help { color: var(--text-secondary); font-size: 0.9rem; margin: 0 0 14px; }
.settings-toggles { display: flex; flex-direction: column; gap: 14px; }
.settings-toggles form { max-width: none; margin: 0; }
.setting-toggle {
    flex-direction: row;
    align-items: center;
    gap: 10px;
    color: var(--text-primary);
    font-weight: 500;
    cursor: pointer;
}

/* ===== SETTINGS: THEME CHOOSER ===== */
.theme-chooser {
    display: flex;
    flex-direction: column;
    gap: 24px;
}
.theme-group { display: flex; flex-direction: column; gap: 12px; }
.theme-group__header { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.theme-group__title { margin: 0; font-size: 1rem; }
.theme-group__note { color: var(--text-muted); font-family: var(--font-mono); font-size: .7rem; letter-spacing: .05em; text-transform: uppercase; }
.theme-group__cards {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}
.theme-choice-form { max-width: none; margin: 0; }
.theme-card {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    min-height: 224px;
    gap: 12px;
    padding: 10px;
    border: 2px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-subtle);
    color: var(--text-primary);
    text-align: left;
    cursor: pointer;
    transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.theme-card.is-selected {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
.theme-card:focus-visible {
    outline: 3px solid var(--color-primary);
    outline-offset: 2px;
}
@media (hover: hover) {
    .theme-card:hover {
        border-color: var(--color-primary);
        transform: translateY(-2px);
    }
}
.theme-card__preview {
    display: flex;
    flex-direction: column;
    min-height: 116px;
    gap: 8px;
    padding: 12px;
    overflow: hidden;
    border-radius: var(--radius-md);
    color: #fff;
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 16%);
}
.theme-preview--default { background: linear-gradient(135deg, #241638 0%, #39205b 58%, #5a35b5 100%); }
.theme-preview--daylight { background: linear-gradient(135deg, #f2eef9 0%, #d9f0ed 58%, #f0bac9 100%); color: #1c1230; }
.theme-preview--aurora { background: linear-gradient(135deg, #0e2532 0%, #14556a 58%, #4b3ea0 100%); }
.theme-preview--comet { background: linear-gradient(135deg, #fffaf4 0%, #f4d4bd 58%, #f0c0ae 100%); color: #2b1a12; }
.theme-preview--mosslight { background: linear-gradient(135deg, #112524 0%, #1d544c 58%, #16785e 100%); }
.theme-preview--emberwake { background: linear-gradient(135deg, #2a1720 0%, #66303a 58%, #9a5720 100%); }
.theme-preview--surveyor { background: linear-gradient(135deg, #ffffff 0%, #dbe7f2 58%, #b7dbe3 100%); color: #111827; }
.theme-preview--high-contrast { background: linear-gradient(135deg, #000000 0%, #242424 58%, #005a5a 100%); }
.theme-preview--colorblind-safe { background: linear-gradient(135deg, #081522 0%, #15364c 58%, #935f00 100%); }
.theme-card__preview-top,
.theme-card__preview-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.theme-card__preview-top { font-size: 1.2rem; }
.theme-card__preview-signal { width: 42px; height: 6px; border-radius: var(--radius-pill); background: currentColor; opacity: .45; }
.theme-card__preview-title { font-family: var(--font-display); font-size: 1.05rem; font-weight: 700; }
.theme-card__preview-row span { height: 7px; flex: 1; border-radius: var(--radius-pill); background: currentColor; opacity: .3; }
.theme-card__preview-row span:first-child { flex: 1.5; opacity: .65; }
.theme-card__preview-action { align-self: flex-start; padding: 4px 8px; border: 1px solid currentColor; border-radius: var(--radius-pill); font-family: var(--font-mono); font-size: .68rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.theme-card__copy { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.theme-card__name { font-size: 1rem; font-weight: 700; }
.theme-card__tone { color: var(--text-secondary); font-family: var(--font-mono); font-size: .7rem; letter-spacing: .05em; text-transform: uppercase; }
.theme-card__description { color: var(--text-secondary); font-size: .82rem; line-height: 1.45; }
.theme-card__status { margin-top: auto; align-self: flex-start; color: var(--color-primary); font-family: var(--font-mono); font-size: .7rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.theme-card__status[hidden] { display: none; }
.theme-card:disabled { transform: none; cursor: wait; }
.theme-chooser[aria-busy="true"] .theme-card { opacity: .72; }
.theme-save-status {
    margin: 12px 0 0;
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: .75rem;
    letter-spacing: .05em;
    text-transform: uppercase;
}
.theme-save-status[hidden] { display: none; }
/* ===== SETTINGS: FONT CHOOSER ===== */
.font-chooser {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}
.font-choice-form { max-width: none; margin: 0; }
.font-card {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    min-height: 188px;
    gap: 10px;
    padding: 10px;
    border: 2px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-subtle);
    color: var(--text-primary);
    text-align: left;
    cursor: pointer;
    transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.font-card.is-selected {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
.font-card:focus-visible {
    outline: 3px solid var(--color-primary);
    outline-offset: 2px;
}
@media (hover: hover) {
    .font-card:hover {
        border-color: var(--color-primary);
        transform: translateY(-2px);
    }
}
.font-card__preview {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: 7px;
    min-width: 0;
    padding: 12px;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--bg-cool);
    color: var(--text-primary);
}
.font-card__preview-title {
    font-family: var(--font-preview-family, var(--font-body));
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.1;
}
.font-card__preview-copy {
    font-family: var(--font-preview-family, var(--font-body));
    font-size: .92rem;
    line-height: 1.35;
}
.font-card__preview-data {
    margin-top: auto;
    color: var(--color-primary);
    font-family: var(--font-preview-family, var(--font-body));
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .02em;
}
.font-preview--system { --font-preview-family: system-ui, -apple-system, 'Segoe UI', sans-serif; }
.font-preview--nunito { --font-preview-family: 'Nunito', system-ui, sans-serif; }
.font-preview--lora { --font-preview-family: 'Lora', Georgia, serif; }
.font-preview--opendyslexic { --font-preview-family: 'Open-Dyslexic', system-ui, sans-serif; }
.font-preview--space-grotesk { --font-preview-family: 'Space Grotesk', system-ui, sans-serif; }
.font-preview--exo-2 { --font-preview-family: 'Exo 2', system-ui, sans-serif; }
.font-preview--quantico { --font-preview-family: 'Quantico', system-ui, sans-serif; }
.font-preview--cormorant-garamond { --font-preview-family: 'Cormorant Garamond', Georgia, serif; }
.font-card__copy { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.font-card__name { font-size: 1rem; font-weight: 700; }
.font-card__tone { color: var(--text-secondary); font-family: var(--font-mono); font-size: .7rem; letter-spacing: .05em; text-transform: uppercase; }
.font-card__description { color: var(--text-secondary); font-size: .82rem; line-height: 1.45; }
.font-card__status { margin-top: auto; align-self: flex-start; color: var(--color-primary); font-family: var(--font-mono); font-size: .7rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.font-card__status[hidden] { display: none; }
.font-card:disabled { transform: none; cursor: wait; }
.font-chooser[aria-busy="true"] .font-card { opacity: .72; }
.font-save-status {
    margin: 12px 0 0;
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: .75rem;
    letter-spacing: .05em;
    text-transform: uppercase;
}
.font-save-status[hidden] { display: none; }
@media (max-width: 768px) {
    .font-card { min-height: 0; gap: 8px; padding: 8px; }
    .font-card__preview { min-height: 112px; padding: 10px; }
    .font-card__description { font-size: .75rem; }
}
@media (max-width: 600px) {
    .font-chooser { grid-template-columns: 1fr; }
}

.theme-calibration {
    margin-top: 26px;
    padding-top: 24px;
    border-top: 1px solid var(--border-subtle);
}
.theme-calibration__header h3 { margin: 6px 0 4px; font-size: 1.15rem; }
.theme-calibration__header p { margin: 0; max-width: 68ch; color: var(--text-secondary); font-size: .88rem; }
.theme-calibration__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-top: 16px; }
.theme-calibration__sample {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
    padding: 16px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.theme-calibration__sample--conditions { background: var(--bg-cool); }
.theme-calibration__sample-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.theme-calibration__sample-head strong { display: block; margin-top: 4px; font-family: var(--font-heading); }
.theme-calibration__copy { margin: 0; color: var(--text-secondary); font-size: .86rem; line-height: 1.5; }
.theme-calibration__actions { display: flex; flex-wrap: wrap; gap: 8px; }
.theme-calibration__field { display: flex; flex-direction: column; gap: 5px; }
.theme-calibration__field .field-label { color: var(--text-secondary); font-size: .78rem; font-weight: 700; }
.theme-calibration__input {
    display: block;
    padding: 9px 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    color: var(--text-primary);
    font-size: .86rem;
}
.theme-calibration__field .form-hint { margin: 0; color: var(--text-muted); font-size: .74rem; }
.theme-calibration__validation {
    color: var(--color-error-ink);
    font-size: .76rem;
    line-height: 1.35;
}
.theme-calibration__focus-example {
    align-self: flex-start;
    padding: 7px 9px;
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: .76rem;
    outline: 3px solid var(--color-primary);
    outline-offset: 2px;
}
.theme-calibration__status-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.theme-calibration__conditions-strip { margin: 0; padding: 12px; }
.settings-calibration-disclosure > summary { display: none; }
@media (max-width: 600px) {
    .theme-group__cards,
    .theme-calibration__grid { grid-template-columns: 1fr; }
    .theme-group__header { align-items: flex-start; flex-direction: column; gap: 4px; }
}
@media (max-width: 768px) {
    .settings-jumpbar { border-radius: var(--radius-md); }
    .settings-calibration-disclosure > summary {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        min-height: 44px;
        padding: 10px 12px;
        border: 1px solid var(--border-color);
        border-radius: var(--radius-md);
        color: var(--text-primary);
        font-weight: 700;
        cursor: pointer;
        list-style: none;
    }
    .settings-calibration-disclosure > summary::-webkit-details-marker { display: none; }
    .settings-calibration-disclosure > summary span { color: var(--text-muted); font-size: .8rem; font-weight: 400; }
    .settings-calibration-disclosure[open] > summary { margin-bottom: 12px; }
    .theme-group__cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
    .theme-card { min-height: 0; gap: 8px; padding: 8px; }
    .theme-card__preview { min-height: 82px; gap: 5px; padding: 9px; }
    .theme-card__description { font-size: .75rem; }
}

/* ===== WARNING BANNER (UX10) ===== */
.warning-banner {
    margin: 0 16px 12px;
}
.warning-banner-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 16px;
    margin-top: 8px;
    background: var(--color-warning-bg);
    color: var(--color-warning-text);
    border: 1px solid var(--color-warning-border);
    border-radius: var(--radius-md);
}
.warning-banner-row p { margin: 0; }

/* ===== FORGE PARENT PICKER (UX6) ===== */
/* ===== CELESTIAL FORGE (breeding) ===== */
.forge-layout { display: flex; flex-direction: column; gap: 18px; }
.forge-cost-strip { margin-bottom: 18px; }
.forge-form { max-width: none; gap: 16px; }
.forge-hint-text { color: var(--text-secondary); font-size: 0.9rem; margin: 4px 0 0; }
.forge-catalyst { flex-direction: row; align-items: center; gap: 8px; font-weight: 500; color: var(--text-primary); }
.forge-catalyst input[type="checkbox"] { width: auto; margin: 0; flex-shrink: 0; }
.forge-catalyst__note { color: var(--text-muted); font-size: 0.85rem; font-weight: 400; }
.forge-swap-btn { align-self: flex-start; }

.parent-select-row {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    margin: 4px 0;
}
.parent-select-col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    flex: 1 1 220px;
    min-width: 200px;
}
.parent-select-col .input { width: 100%; }
.parent-select-media, .parent-select-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 96px;
    height: 96px;
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    overflow: hidden;
}
.parent-select-media img { width: 100%; height: 100%; object-fit: contain; }
.parent-select-empty { font-size: 1.6em; }
.parent-select-info { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; font-size: 0.82rem; margin: 0; }
.parent-pick-hint {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0;
    padding: 10px 14px;
    background: var(--bg-cool);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: 0.9rem;
}

/* Structured projection readout (replaces the old run-on parent-pick-hint) */
.forge-projected { margin: 2px 0; }
.forge-projected .breeding-preview-species-odds { font-size: 0.72em; }

/* Pre-submit confirmation + live cost total + affordability */
.forge-summary {
    padding: 12px 14px;
    background: var(--bg-cool);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.forge-summary__line { margin: 0; color: var(--text-primary); }
.forge-summary__total {
    margin: 0;
    display: flex;
    align-items: baseline;
    gap: 8px;
    color: var(--text-secondary);
}
.forge-summary__total .mono { color: var(--text-primary); font-weight: 700; }
.forge-summary__afford { margin: 2px 0 0; color: var(--color-error-text); font-weight: 600; }

.forge-submit-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* "Breed again" prefill-blocked notice: left-aligned card, not .empty-state
   (that's centered with 60px padding, made for a full empty picker, not a
   compact inline notice). */
.forge-prefill-notice {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 16px 18px;
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    margin-bottom: 16px;
}
.forge-prefill-notice__title { margin: 0; font-weight: 600; color: var(--text-primary); }
.forge-prefill-notice__reasons { margin: 0; padding-left: 20px; color: var(--text-secondary); font-size: 0.9rem; }
.forge-prefill-notice__reasons li { margin: 2px 0; }

/* Breeding queue + saved pairings panels */
.queue-list, .saved-pairing-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.queue-item, .saved-pairing {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 12px;
    background: var(--bg-cool);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.queue-item__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.queue-item__pets, .saved-pairing__pets { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-weight: 600; }
.queue-item__cross, .saved-pairing__cross { color: var(--text-muted); font-weight: 400; }
.queue-item__status { font-size: 0.82rem; color: var(--text-muted); }
.queue-item__status--ready { color: var(--color-reward); font-weight: 600; }
.queue-item__actions, .saved-pairing__actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* ===== BREEDING PREVIEW PAGE ===== */
.breeding-preview-result { display: flex; flex-direction: column; gap: 16px; margin-top: 16px; }
.breeding-preview-species-odds { display: flex; gap: 10px; flex-wrap: wrap; font-size: 0.7em; }
.breeding-preview-species-odds small { color: var(--text-muted); font-weight: 400; margin-left: 2px; }
.breeding-preview-markings {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 14px;
}
.preview-marking-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }

.breeding-preview-identity-row { display: flex; gap: 20px; flex-wrap: wrap; }
.breeding-preview-identity { display: flex; align-items: center; gap: 10px; flex: 1 1 200px; }
.breeding-preview-identity-img {
    width: 56px;
    height: 56px;
    border-radius: var(--radius-md);
    object-fit: contain;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
}
.breeding-preview-identity .parent-select-empty { width: 56px; height: 56px; font-size: 1.3em; }
.breeding-preview-identity-name { margin: 0; font-weight: 600; }
.breeding-preview-identity-meta { margin: 0; color: var(--text-secondary); font-size: 0.85em; }

.breeding-preview-actions-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.breeding-preview-id-lookup { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.breeding-preview-id-form {
    /* The base `form { flex-direction: column }` rule (see FORMS section)
       only loses to this class selector on properties we actually set here: 
       flex-direction isn't one of them unless stated explicitly, so without
       this line the fields silently stack instead of sitting in a row. */
    display: flex;
    flex-direction: row;
    align-items: flex-end;
    gap: 12px;
    flex-wrap: nowrap;
    overflow-x: auto;
    max-width: none;
    margin-top: 8px;
}
.breeding-preview-id-form .field { width: 100px; flex-shrink: 0; }
.breeding-preview-id-form button { flex-shrink: 0; }

.breeding-preview-baby-roll { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.breeding-preview-baby { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.breeding-preview-baby img {
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    object-fit: contain;
}

/* Active-nest cards */
.nest-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 14px;
}
.nest-card { display: flex; flex-direction: column; gap: 10px; }
.nest-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.nest-card__title { margin: 0; font-size: 1.02rem; }
.nest-card__cross { color: var(--text-muted); font-weight: 400; }
.nest-card__timer { margin: 0; color: var(--text-secondary); }
.nest-card__timer .mono { color: var(--text-primary); font-weight: 700; }

/* Incubation progress bar (U7). The fill animates from its render-time % to
   100% over exactly the remaining seconds, so it advances smoothly with no JS
   and needs no tick updates: the card re-renders on the next list refresh. */
.nest-progress {
    height: 6px;
    border-radius: 999px;
    background: var(--bg-subtle, var(--bg-cool));
    overflow: hidden;
}
.nest-progress__fill {
    height: 100%;
    width: var(--start-pct, 0%);
    border-radius: inherit;
    background: var(--color-reward);
    animation-name: nest-incubate;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
}
.nest-progress__fill--done { width: 100%; animation: none; }
@keyframes nest-incubate {
    from { width: var(--start-pct, 0%); }
    to   { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
    .nest-progress__fill { animation: none; width: var(--start-pct, 0%); }
}

.forge-picker-filter { max-width: 340px; }
.nest-card__actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: auto; }
.nest-card__actions form { max-width: none; margin: 0; }

/* Light inline empty message used inside panels */
.empty-inline { color: var(--text-secondary); text-align: center; padding: 24px 12px; margin: 0; }

/* ===== HATCH REVEAL (UX4) ===== */
.hatch-reveal {
    text-align: center;
    padding: 28px 24px;
    background: var(--bg-surface);
    border: 1px solid color-mix(in srgb, var(--color-reward) 45%, var(--border-color));
    border-radius: var(--radius-lg);
    box-shadow: var(--glow-reward), var(--shadow-lg);
    animation: hatch-pop 0.6s ease-out;
}
.hatch-reveal .eyebrow { color: var(--color-reward); }
.hatch-reveal-title { margin: 6px 0 4px; font-family: var(--font-display); }
.hatch-reveal-image {
    display: inline-block;
    margin: 8px auto;
}
.hatch-reveal-stats { color: var(--text-secondary); }
.hatch-reveal-stats .mono { color: var(--text-primary); }
/* The hatch's one genuinely variable roll. A standout says "exceptional" in words: the
   border and colour only tint what the text already states. */
.hatch-reveal-potential { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; margin: 6px 0 0; }
.potential-chip {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 1px 8px; border-radius: 999px;
    border: 1px solid var(--border-subtle);
    font-family: var(--font-mono); font-size: 0.76rem; color: var(--text-secondary);
}
.potential-chip--standout { border-color: var(--color-reward); color: var(--color-reward); }
.hatch-reveal-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 14px; }
.hatch-reveal-twins {
    display: flex;
    gap: 20px;
    justify-content: center;
    flex-wrap: wrap;
}
.hatch-reveal-twins .hatch-reveal-baby {
    flex: 1 1 220px;
    max-width: 300px;
    padding: 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.hatch-reveal-baby { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.collect-all-reveal-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 12px;
    text-align: left;
}
.collect-all-reveal-entry {
    padding: 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.collect-all-reveal-entry__node { margin: 0; font-weight: 700; }
.hatch-reveal-baby .btn-reward { margin-top: 8px; }
.hatch-mutation-callout {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--text-primary);
    font-weight: 600;
    animation: hatch-pop 0.6s ease-out 0.25s backwards;
}
@keyframes hatch-pop {
    0%   { opacity: 0; transform: scale(0.6); }
    70%  { transform: scale(1.04); }
    100% { opacity: 1; transform: scale(1); }
}
/* A limited-breed hatch (pets.Pet.LIMITED_BREED_SPECIES) gets its own tier above the
   callouts above: this is orders of magnitude rarer than a standout ceiling roll or a
   mutation, and sitting it in the same .hatch-mutation-callout list would read as "one
   more common roll." The whole card gets a border/glow, and the banner leads every
   other callout rather than being appended after them. Reduced-motion is handled by
   the existing global body.reduced-motion and prefers-reduced-motion rules (they clamp
   any animation-duration to ~0 via a wildcard selector), so no extra override is needed
   here. */
.hatch-reveal-baby--legendary {
    border: 2px solid var(--color-reward);
    border-radius: var(--radius-md);
    padding: 10px;
    box-shadow: 0 0 0 1px var(--color-reward), 0 0 18px -4px var(--color-reward);
}
.hatch-legendary-banner {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 14px;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-reward);
    background: color-mix(in srgb, var(--color-reward) 16%, transparent);
    animation: hatch-legendary-glow 2.4s ease-in-out infinite, hatch-pop 0.6s ease-out 0.15s backwards;
}
.hatch-legendary-banner-glyph { font-size: 1.8rem; line-height: 1; }
.hatch-legendary-banner-text { text-align: left; font-size: 0.9rem; color: var(--text-primary); }
.hatch-legendary-banner-text strong { display: block; font-size: 1.08rem; color: var(--color-reward); }
@keyframes hatch-legendary-glow {
    0%, 100% { box-shadow: 0 0 0 1px var(--color-reward), 0 0 10px -2px var(--color-reward); }
    50%      { box-shadow: 0 0 0 1px var(--color-reward), 0 0 22px 2px var(--color-reward); }
}

/* ===== HATCH REVEAL MODAL ===== */
/* Prominent celebratory overlay (below toasts at 9999 so a confirmation toast
   still floats over it). The card reuses the .hatch-reveal look. */
.hatch-modal {
    position: fixed;
    inset: 0;
    z-index: 9990;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.hatch-modal__backdrop {
    position: absolute;
    inset: 0;
    background: var(--modal-overlay);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
}
.hatch-modal__card {
    position: relative;
    z-index: 1;
    width: min(560px, 100%);
    /* dvh: at 100vh this card extended under the phone's browser chrome, putting its
       own bottom controls out of reach on the payoff moment of the breeding loop. */
    max-height: calc(100vh - 40px);
    max-height: calc(100dvh - 40px);
    overflow-y: auto;
    /* Stop scroll chaining: without this, scrolling past the end of a tall reveal hands
       the gesture to the page behind it. body.modal-open covers the desktop case; this
       is the half that matters on touch, where the fling keeps going. */
    overscroll-behavior: contain;
    text-align: center;
    padding: 28px 24px;
    background: var(--bg-surface);
    border: 1px solid color-mix(in srgb, var(--color-reward) 55%, var(--border-color));
    border-radius: var(--radius-lg);
    box-shadow: var(--glow-reward), var(--shadow-lg);
    animation: hatch-pop 0.55s ease-out;
}
.hatch-modal__card .eyebrow { color: var(--color-reward); }
.hatch-modal__sparkles {
    font-size: 2.4rem;
    line-height: 1;
    padding: 8px 0;
    margin: -8px 0 4px;
    animation: hatch-sparkle 1.6s ease-in-out infinite;
    /* contain: paint + overflow: hidden clip the scale/rotate keyframes to this box.
       Without it, Chrome folds the transformed glyph's overflow into
       .hatch-modal__card's scrollable overflow, so overflow-y: auto toggled a
       scrollbar on and off every animation cycle, reflowing the card width and
       bouncing Close along with the emoji. The padding (offset by the negative
       margin, so the layout footprint is unchanged) gives the scaled-up glyph room
       so the clip never eats it. */
    contain: paint;
    overflow: hidden;
}
@keyframes hatch-sparkle {
    0%, 100% { opacity: 0.55; transform: scale(1) rotate(-4deg); }
    50%      { opacity: 1;    transform: scale(1.18) rotate(4deg); }
}
@media (prefers-reduced-motion: reduce) {
    .hatch-modal__card { animation: none; }
    .hatch-modal__sparkles { animation: none; }
}

/* Generic centered content modal for a form: unlike .hatch-modal's ceremonial reveal
   styling (celebratory, text-centered, sparkles), this is a plain dialog shell. The
   squad-creation picker is the first user; nothing here is squad-specific. */
.form-modal {
    position: fixed;
    inset: 0;
    z-index: 9990;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}
.form-modal__backdrop {
    position: absolute;
    inset: 0;
    background: var(--modal-overlay);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
}
.form-modal__card {
    position: relative;
    z-index: 1;
    width: min(900px, 100%);
    max-height: calc(100vh - 40px);
    max-height: calc(100dvh - 40px);
    overflow-y: auto;
    /* Same reason as .hatch-modal__card: a fling past the end of a tall picker must not
       scroll the page behind the modal. */
    overscroll-behavior: contain;
    padding: 24px;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    display: flex;
    flex-direction: column;
    gap: 18px;
}
.form-modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.form-modal__head h2 { margin: 0; font-size: 1.15rem; }
.form-modal__head .eyebrow { display: block; margin-bottom: 3px; }
.form-modal__intro { margin: 5px 0 0; max-width: 52ch; color: var(--text-secondary); font-size: 0.84rem; line-height: 1.45; }
.form-modal__close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
    padding: 8px;
    border-radius: var(--radius-md);
    min-width: 44px;
    min-height: 44px;
}
@media (hover: hover) { .form-modal__close:hover { background: var(--bg-hover); color: var(--text-primary); } }
.form-modal__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }

/* Squad builder: the picker and preview share one surface on desktop, then become a
   single full-height flow on phones. The picker rows must explicitly restore a row
   direction because the shared `label` rule makes every label a vertical form field. */
/* No min-height: 0 here: that would let the flex/grid chain squeeze this box (and
   .squad-builder below it) shorter than its own content needs. With align-items: start,
   a shrunk grid track does not shrink its items to match: .squad-builder__picker's roster
   just kept its natural height and painted past the (too-short) box, over the Cancel/
   Create Squad buttons beneath it. Leaving these at their default min-height: auto keeps
   the box (and .form-modal__card's own overflow-y: auto above it) honest about how tall
   the content actually is, so a long roster scrolls the whole card instead of overlapping
   the action row. Mobile's own copies of these rules (below) are unaffected: they run in
   a different, intentionally-squeezed full-height layout and re-declare min-height: 0
   themselves. */
.squad-create-form { display: flex; flex-direction: column; gap: 14px; }
.squad-name-field { gap: 6px; }
.squad-builder { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(250px, 0.75fr); gap: 16px; align-items: start; }
.squad-builder__picker,
.squad-builder__preview { min-width: 0; }
.squad-picker-section { display: flex; flex-direction: column; gap: 10px; min-height: 0; }
.squad-picker-section__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.squad-picker-section__title { margin: 0; font-size: 1rem; }
.squad-picker-section__hint { margin: 3px 0 0; color: var(--text-secondary); font-size: 0.82rem; }
.squad-picker-count { flex: 0 0 auto; padding-top: 3px; color: var(--text-muted); font-size: 0.76rem; white-space: nowrap; }
.squad-search { position: relative; }
.squad-search .input { width: 100%; margin: 0; padding-right: 42px; }
.squad-search__clear {
    position: absolute;
    top: 50%;
    right: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    transform: translateY(-50%);
    color: var(--text-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
@media (hover: hover) { .squad-search__clear:hover { color: var(--text-primary); background: var(--bg-hover); } }
.squad-picker-empty { margin: 0; padding: 10px 12px; color: var(--text-secondary); background: var(--bg-subtle); border-radius: var(--radius-sm); }
.squad-builder__picker .squad-roster {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    max-height: min(440px, 52dvh);
    margin-top: 0;
    padding: 8px;
}
.squad-builder .squad-pet-pick { min-height: 62px; padding: 8px 38px 8px 10px; border-width: 1px; }
.squad-builder .squad-pet-pick:has(input:disabled) { opacity: 0.72; background: var(--bg-subtle); }
.squad-builder .squad-pet-pick__name { font-size: 0.9rem; }
.squad-builder .squad-pet-pick__meta { gap: 4px 6px; }
.squad-builder__preview {
    position: sticky;
    top: 0;
    padding: 16px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
.squad-builder__preview-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.squad-builder__preview-head h3 { margin: 0; font-size: 1.05rem; }
.squad-builder__preview-head .eyebrow { display: block; margin-bottom: 3px; }
.squad-builder__count { color: var(--text-secondary); font-family: var(--font-mono); font-size: 0.8rem; white-space: nowrap; }
.squad-selected-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.squad-selected-empty { width: 100%; margin: 0; padding: 10px 12px; color: var(--text-secondary); background: var(--bg-surface); border: 1px dashed var(--border-color); border-radius: var(--radius-sm); font-size: 0.82rem; }
.squad-selected-member { display: flex; align-items: center; gap: 8px; flex: 1 1 100%; min-width: 0; padding: 7px 8px 7px 10px; background: var(--bg-surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); }
.squad-selected-member__thumb { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.squad-selected-member__thumb--pending { display: flex; align-items: center; justify-content: center; font-size: 0.8rem; background: var(--bg-subtle); }
.squad-selected-member__identity { display: flex; flex: 1; flex-direction: column; min-width: 0; gap: 1px; }
.squad-selected-member__identity strong { overflow: hidden; color: var(--text-primary); font-size: 0.84rem; text-overflow: ellipsis; white-space: nowrap; }
.squad-selected-member__identity span { color: var(--text-muted); font-size: 0.72rem; }
.squad-selected-member__remove { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 30px; height: 30px; padding: 0; color: var(--text-muted); background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer; }
@media (hover: hover) { .squad-selected-member__remove:hover { color: var(--color-error-text); background: var(--color-error-bg); } }
.squad-preview-dice { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin-top: 14px; }
.squad-preview-stat { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; column-gap: 6px; padding: 7px 8px; background: var(--bg-surface); border-left: 3px solid var(--border-subtle); border-radius: var(--radius-sm); }
.squad-preview-stat span { color: var(--text-secondary); font-size: 0.75rem; }
.squad-preview-stat strong { font-family: var(--font-mono); font-size: 0.95rem; }
.squad-preview-stat small { grid-column: 1 / -1; color: var(--text-muted); font-size: 0.66rem; }
.squad-preview-stat--red { border-left-color: #e5573c; }
.squad-preview-stat--green { border-left-color: #4bc46a; }
.squad-preview-stat--blue { border-left-color: #4a9df0; }
.squad-preview-stat--gold { border-left-color: #f2c14e; }
.squad-preview-total { margin: 10px 0 0; color: var(--text-secondary); font-size: 0.82rem; text-align: right; }
.squad-preview-total strong { color: var(--text-primary); font-family: var(--font-mono); }
.squad-preview-traits { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-subtle); }
.squad-preview-traits > .eyebrow { display: block; margin-bottom: 6px; }
.squad-preview-traits__list { display: flex; flex-wrap: wrap; gap: 5px; }

@media (width < 768px) {
    .form-modal { align-items: stretch; padding: 0; }
    .form-modal__card { width: 100%; min-height: 100vh; min-height: 100dvh; max-height: 100vh; max-height: 100dvh; padding: max(16px, env(safe-area-inset-top, 0px)) 16px calc(12px + env(safe-area-inset-bottom, 0px)); overflow: hidden; border-radius: 0; gap: 12px; }
    .form-modal__head { gap: 8px; }
    .form-modal__head h2 { font-size: 1.08rem; }
    .form-modal__intro { max-width: 34ch; font-size: 0.8rem; }
    .squad-create-form { flex: 1; min-height: 0; gap: 10px; }
    .squad-builder { display: flex; flex: 1; flex-direction: column; min-height: 0; gap: 10px; overflow: hidden; }
    .squad-builder__preview { position: static; flex: 0 0 auto; padding: 10px 12px; }
    .squad-builder__picker { display: flex; flex: 1; flex-direction: column; min-height: 0; overflow: hidden; }
    .squad-picker-section { height: 100%; }
    .squad-picker-section__head { gap: 8px; }
    .squad-picker-section__hint { font-size: 0.78rem; }
    .squad-builder__picker .squad-roster { display: flex; flex: 1; flex-direction: column; min-height: 0; max-height: none; padding: 6px; gap: 6px; overflow-y: auto; }
    .squad-builder .squad-pet-pick { min-height: 56px; }
    .squad-selected-list { margin-top: 8px; gap: 5px; }
    .squad-selected-member { flex: 1 1 120px; padding-block: 5px; }
    .squad-preview-dice { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; margin-top: 10px; }
    .squad-preview-stat { display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 5px 3px; border-left: 0; border-top: 2px solid var(--border-subtle); text-align: center; }
    .squad-preview-stat strong { order: -1; font-size: 0.9rem; }
    .squad-preview-stat small { display: none; }
    .squad-preview-stat--red { border-top-color: #e5573c; }
    .squad-preview-stat--green { border-top-color: #4bc46a; }
    .squad-preview-stat--blue { border-top-color: #4a9df0; }
    .squad-preview-stat--gold { border-top-color: #f2c14e; }
    .squad-preview-total { margin-top: 5px; }
    .squad-preview-traits { display: none; }
    .form-modal__actions { flex: 0 0 auto; margin-top: 0; padding-top: 10px; border-top: 1px solid var(--border-subtle); }
    .form-modal__actions .btn { flex: 1; min-height: 46px; }
}

@media (max-width: 380px) {
    .form-modal__card { padding-inline: 12px; }
    .squad-builder__preview { padding-inline: 10px; }
    .squad-picker-count { font-size: 0.7rem; }
    .squad-builder .squad-pet-pick { padding-inline: 8px 34px; }
    .squad-pet-pick__meta { gap: 3px 4px; }
}

/* ===== STAR MAP (nodes + expeditions) ===== */
.map-layout { display: flex; flex-direction: column; gap: 18px; }
@media (min-width: 768px) {
    .map-layout { display: grid; grid-template-columns: 1.5fr 1fr; align-items: start; }
}
.map-node-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 14px;
}
.map-node-card { display: flex; flex-direction: column; gap: 8px; }
.map-node-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.map-node-card__title { margin: 0; font-size: 1.02rem; }
.map-node-card__desc { color: var(--text-secondary); font-size: 0.9rem; margin: 0; }
.map-node-card__meta { display: flex; gap: 12px; flex-wrap: wrap; }
.map-node-card__closed { color: var(--text-muted); font-size: 0.85rem; margin: 0; font-style: italic; }
.map-node-card form { max-width: none; gap: 10px; margin: 0; }
.node-pet-picker { display: flex; flex-direction: column; gap: 6px; }
.node-pet-check {
    flex-direction: row;
    align-items: center;
    gap: 8px;
    font-weight: 400;
    color: var(--text-primary);
    font-size: 0.9rem;
}
.node-pet-species { color: var(--text-muted); font-size: 0.82rem; }
/* Squad-first dispatch: the party going out, plus the customize/sub-in bench. */
.dispatch-party { list-style: none; margin: 6px 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.dispatch-party__member { display: flex; align-items: center; gap: 8px; font-size: 0.88rem; padding: 4px 8px; background: var(--bg-cool); border-radius: var(--radius-sm); }
.dispatch-party__member .btn { margin-left: auto; }
.dispatch-customize { margin-bottom: 6px; }
.node-pet-check--busy { opacity: 0.5; }
.node-pet-busy { color: var(--color-warning-text); }
.node-pet-member { color: var(--text-muted); }
.node-duration {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin: 0;
    padding: 6px 10px;
    background: var(--bg-cool);
    border-radius: var(--radius-sm);
}

/* Fracture list view-mode toggle (panel header, "Expanded" / "Compact"): a plain button
   pair, not a checkbox/radio card, since there are only two mutually exclusive states and
   each button already carries its own label. The active state is never colour alone: the
   selected button is filled (.btn-primary) and carries aria-pressed="true", the other is
   outlined (.btn-secondary) with aria-pressed="false". */
/* The base `form { flex-direction: column }` rule (FORMS section) beats this on
   flex-direction alone (a class selector doesn't override a property it never sets), so
   flex-direction has to be named here explicitly or the two buttons stack vertically. */
.view-mode-toggle { display: inline-flex; flex-direction: row; gap: 6px; margin: 0; }

/* Compact Fracture view: same dispatch form, same hidden pet_ids inputs, just far less of
   it on screen. .dispatch-party/.squad-traits-line/.node-duration are hidden with plain
   CSS rather than dropped from the template: display:none on an ancestor does not stop a
   type="hidden" input from submitting, so the party-picker's hidden pet_ids inputs (the
   dispatch-party <li>s, rendered by Alpine's x-for) still ride the POST even though the
   list itself is never shown. Dropping them from the DOM instead, the way _pet_card.html's
   rearrange mode does, would have silently broken "Customize party" in this view: the
   Alpine component's state would still track a custom party, but nothing would be left to
   emit its pet_ids. The "why is Dispatch disabled" hint stays visible either way: it has its
   own copy below the button, outside .dispatch-party. */
.map-node-grid--compact { gap: 10px; }
.map-node-card--compact { gap: 6px; padding: 10px 14px; }
.map-node-card--compact .map-node-card__meta { gap: 8px; font-size: 0.82rem; }
.map-node-card--compact .dispatch-party,
.map-node-card--compact .squad-traits-line,
.map-node-card--compact .node-duration { display: none; }
.map-node-card--compact .dispatch-customize { margin-bottom: 0; }

.expedition-count { margin: 0 0 14px; display: flex; align-items: center; gap: 8px; }
.expedition-count .mono { color: var(--text-primary); font-weight: 700; }
.expedition-grid { display: flex; flex-direction: column; gap: 12px; }
.expedition-card { display: flex; flex-direction: column; gap: 10px; }
.expedition-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.expedition-card__title { margin: 0; font-size: 1rem; }
.expedition-card__timer { margin: 0; color: var(--text-secondary); }
.expedition-card__timer .mono { color: var(--text-primary); font-weight: 700; }
.expedition-card__actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.expedition-card__actions form { max-width: none; margin: 0; }
.expedition-loot {
    list-style: none;
    margin: 0;
    padding: 10px 12px;
    background: var(--bg-subtle);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.expedition-loot li { display: flex; justify-content: space-between; gap: 10px; font-size: 0.9rem; }
.expedition-loot__name { color: var(--text-secondary); }
.expedition-loot .mono { color: var(--text-primary); font-weight: 700; }
.expedition-xp-summary { margin-top: 8px; }

/* The Rested-XP credit line, shared by the expedition collect card, the collect-all
   reveal and the incursion room result: one rule so a doubled grant can't be worded or
   weighted differently depending on which surface paid it. It is a real second line
   rather than a title= tooltip, which is dead to touch and keyboard alike. */
.rested-credit {
    display: block;
    font-size: 0.74rem;
    color: var(--text-secondary);
}

/* The daily-XP-taper note: shared by the same three surfaces as .rested-credit above,
   plus the incursion run debrief (.debrief__pet), which reads the run's own accrued
   pet_xp_earned/pet_xp_credited ledger rather than a live grant. An XP grant capped
   (partially, or entirely at 0) by pets.services.grant_pet_xp's midnight-reset daily
   taper must say so, or "+0 XP" reads as a bug rather than "this pet is done for
   today." Glyph + word, never colour alone, per the house rule. */
.xp-capped-note {
    display: block;
    font-size: 0.74rem;
    color: var(--color-warning-ink);
}

/* ===== INVENTORY ===== */
.inventory-items {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 14px;
}
.inventory-item {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 6px;
    overflow: hidden;
}
/* Top accent bar keyed to rarity (data-rarity set from item.rarity). */
.inventory-item::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--rarity-accent, transparent);
}
.inventory-item[data-rarity="uncommon"]  { --rarity-accent: var(--color-success); }
.inventory-item[data-rarity="rare"]      { --rarity-accent: var(--color-info); }
.inventory-item[data-rarity="epic"]      { --rarity-accent: var(--color-violet); }
.inventory-item[data-rarity="legendary"] { --rarity-accent: var(--color-reward); }
.inventory-item__icon { font-size: 2.2rem; line-height: 1; margin-top: 4px; }
.inventory-item__image { width: 100px; height: 100px; object-fit: cover; border-radius: var(--radius-md); margin-top: 4px; }
.inventory-item__name { font-family: var(--font-heading); font-weight: 600; font-size: 0.95rem; }
.inventory-item__qty { color: var(--text-secondary); font-weight: 700; }
.inventory-item__effect { color: var(--color-success-text); font-size: 0.74rem; font-weight: 700; }
/* The scoped label a system is shown under elsewhere in the UI, beside its canonical name
   on the page that exists to teach that the Void systems are distinct. */
.help-scope { display: inline-block; color: var(--text-muted); font-size: 0.78rem; font-weight: 600; }

/* Item Codex: a reference card rather than a buy/equip surface. */
.item-codex-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
    gap: 16px;
}
.item-codex-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 16px;
    border: 1px solid var(--border-color);
    border-top: 3px solid var(--rarity-accent, var(--border-color));
    border-radius: var(--radius-md);
    background: var(--bg-surface);
}
.item-codex-card[data-rarity="uncommon"] { --rarity-accent: var(--color-success); }
.item-codex-card[data-rarity="rare"] { --rarity-accent: var(--color-info); }
.item-codex-card[data-rarity="epic"] { --rarity-accent: var(--color-violet); }
.item-codex-card[data-rarity="legendary"] { --rarity-accent: var(--color-reward); }
.item-codex-card__art {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 88px;
    height: 88px;
    border-radius: var(--radius-md);
    background: var(--bg-hover);
    font-size: 2.3rem;
}
.item-codex-card__art img { width: 88px; height: 88px; object-fit: cover; border-radius: var(--radius-md); }
.item-codex-card__heading { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.item-codex-card__heading h2 { margin: 0; font-size: 1.05rem; }
.item-codex-card__category { color: var(--text-muted); font-size: 0.75rem; margin: -4px 0 0; text-transform: uppercase; letter-spacing: 0.06em; }
.item-codex-card__description { color: var(--text-secondary); font-size: 0.88rem; margin: 0; }
.item-codex-card__sources { list-style: none; display: flex; flex-direction: column; gap: 5px; margin: 2px 0 0; padding: 0; font-size: 0.82rem; }
.item-codex-card__sources li { display: flex; flex-direction: column; gap: 1px; }
.item-codex-card__sources strong { color: var(--color-primary); }
.item-codex-card__sources span { color: var(--text-secondary); }
.item-codex-card__recipe { font-size: 0.82rem; margin-top: 2px; }
.item-codex-card__recipe summary { cursor: pointer; color: var(--text-secondary); }
.item-codex-card__recipe ul { margin: 6px 0 0; padding-left: 18px; }
.item-codex-card__used-in { color: var(--text-muted); font-size: 0.78rem; margin: 0; }
.item-codex-card__shop-search { align-self: flex-start; margin-top: auto; }

.rarity-chip {
    font-family: var(--font-mono);
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-color);
    color: var(--text-muted);
    background: var(--bg-subtle);
}
.rarity-chip[data-rarity="uncommon"]  { color: var(--color-success); border-color: color-mix(in srgb, var(--color-success) 45%, var(--border-color)); }
.rarity-chip[data-rarity="rare"]      { color: var(--color-info);    border-color: color-mix(in srgb, var(--color-info) 45%, var(--border-color)); }
.rarity-chip[data-rarity="epic"]      { color: var(--color-violet);  border-color: color-mix(in srgb, var(--color-violet) 45%, var(--border-color)); }
.rarity-chip[data-rarity="legendary"] { color: var(--color-reward);  border-color: color-mix(in srgb, var(--color-reward) 50%, var(--border-color)); }

/* ===== FACTIONS ===== */
.faction-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
}
/* --faction-accent is set inline from each faction's emblem_hex (data-driven). */
.faction-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    border-left: 4px solid var(--faction-accent, var(--border-color));
}
.faction-card__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.faction-card__dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--faction-accent, var(--color-primary));
    flex-shrink: 0;
}
.faction-card__name { margin: 0; font-size: 1.1rem; flex: 1; min-width: 0; }
.faction-card__desc { color: var(--text-secondary); font-size: 0.9rem; margin: 0; }
/* auto-fit, not `1fr 1fr`: a fixed track list stays two-column at 320px. This card is
   313px wide on a 390px phone, so the two yield figures were held in 151.5px columns and
   their labels wrapped to three lines each. Needs no @media query of its own. */
.faction-card__yields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 10px;
    padding: 12px 0;
    border-top: 1px solid var(--border-subtle);
    border-bottom: 1px solid var(--border-subtle);
}
.faction-yield { display: flex; flex-direction: column; gap: 3px; }
.faction-yield .mono { font-size: 1.15rem; font-weight: 700; color: var(--text-primary); }
.faction-yield-hint { margin: 8px 0 0; font-size: 0.78rem; line-height: 1.4; color: var(--text-secondary); }
.faction-yield-hint .mono { font-weight: 700; color: var(--color-success-text); }
.faction-yield-hint__cap { display: block; margin-top: 2px; color: var(--text-muted); }
.faction-yield-hint__muted { color: var(--text-muted); }
.faction-card__signature {
    padding: 2px 0;
}
.faction-card__signature .faction-yield-hint {
    margin: 4px 0 0;
}
.faction-card__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    font-size: 0.82rem;
    color: var(--text-secondary);
}
.faction-stat { display: inline-flex; align-items: center; gap: 5px; }
.faction-stat .mono { color: var(--text-primary); font-weight: 700; }
.faction-card__foot { display: flex; flex-direction: column; gap: 8px; margin-top: auto; }
.faction-card__foot form { max-width: none; margin: 0; }
.faction-card__note { color: var(--text-muted); font-size: 0.82rem; margin: 0; }

/* Shared global construction goal (Incursion Star-Steel) */
.construction-goal {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-left: 4px solid var(--color-reward, var(--color-primary));
    border-radius: var(--radius-lg);
    padding: 16px 18px;
    margin-bottom: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.construction-goal__head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}
.construction-goal__name { margin: 2px 0 0; font-size: 1.15rem; }
.construction-goal__count { color: var(--text-primary); font-weight: 700; white-space: nowrap; }
.construction-goal__bar {
    height: 10px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    overflow: hidden;
}
.construction-goal__fill {
    display: block;
    height: 100%;
    background: var(--color-reward, var(--color-primary));
    border-radius: var(--radius-pill);
    transition: width 0.4s ease;
}
.construction-goal__note { color: var(--text-muted); font-size: 0.82rem; margin: 0; }

/* Supply Drive banner (limited-time Star-Steel multiplier) */
.supply-drive-banner {
    display: flex;
    align-items: center;
    gap: 14px;
    background: color-mix(in srgb, var(--color-reward) 12%, var(--bg-surface));
    border: 1px solid color-mix(in srgb, var(--color-reward) 40%, var(--border-color));
    border-radius: var(--radius-lg);
    padding: 12px 16px;
    margin-bottom: 20px;
}
.supply-drive-banner__icon { font-size: 1.6rem; }
.supply-drive-banner__title { margin: 0; font-weight: 700; }
.supply-drive-banner__note { margin: 2px 0 0; color: var(--text-secondary); font-size: 0.85rem; }

/* Faction Season leaderboard */
.season-panel { margin-bottom: 20px; }
.season-panel__header { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.season-panel__timer { margin: 0; color: var(--text-secondary); font-size: 0.85rem; }
.season-champion {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 4px 0 14px; color: var(--text-secondary); font-size: 0.9rem;
}
.season-standings { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.season-row {
    display: grid;
    grid-template-columns: minmax(120px, 1fr) minmax(80px, 3fr) auto;
    align-items: center;
    gap: 12px;
}
.season-row__name { font-weight: 600; min-width: 0; }
.season-row--leader .season-row__name { color: var(--color-reward); }
.season-row__bar {
    height: 12px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    overflow: hidden;
}
.season-row__fill {
    display: block; height: 100%;
    background: var(--faction-accent, var(--color-primary));
    border-radius: var(--radius-pill);
    transition: width 0.4s ease;
}
.season-row__value { color: var(--text-primary); font-weight: 700; white-space: nowrap; }
.season-panel__empty, .season-panel__you { color: var(--text-muted); font-size: 0.85rem; margin: 12px 0 0; }
.season-panel__you { color: var(--text-secondary); }

/* Top contributors + activity feed */
.faction-social {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 18px;
    margin-bottom: 20px;
}
.faction-social__col { margin: 0; }
.contributor-list, .activity-feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.contributor-row { display: flex; align-items: center; gap: 10px; font-size: 0.9rem; }
.contributor-rank { color: var(--text-muted); width: 1.4em; text-align: right; }
.contributor-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contributor-faction { color: var(--text-muted); font-size: 0.8rem; margin-left: auto; }
.contributor-steel { color: var(--text-primary); font-weight: 700; white-space: nowrap; }
.activity-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 0.85rem; }
.activity-text { color: var(--text-secondary); min-width: 0; }
.activity-time { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
.champion-archive { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.champion-archive__row { display: flex; align-items: center; gap: 10px; font-size: 0.88rem; }
.champion-archive__season { color: var(--text-muted); width: 2.4em; }
.champion-archive__winner { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.champion-archive__dates { color: var(--text-muted); font-size: 0.78rem; margin-left: auto; white-space: nowrap; }
.season-history { list-style: none; margin: 0; padding: 0; }
.season-history__row { display: grid; grid-template-columns: 42px 1fr; gap: 8px; align-items: baseline; padding: 7px 0; border-bottom: 1px solid var(--border-subtle); }
.season-history__row:last-child { border-bottom: 0; }
.season-history__result { display: flex; flex-wrap: wrap; gap: 4px 8px; }
.season-history__factions { color: var(--text-muted); font-size: 0.8rem; }

/* ===== FACTION BUILDINGS ===== */
.building-hub-empty { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.building-week-strip {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 16px; flex-wrap: wrap; margin-bottom: 20px;
}
.building-week-strip__note { color: var(--text-muted); font-size: 0.85rem; margin: 4px 0 0; max-width: 46em; }
.building-week-strip__meta { text-align: right; }
.building-week-strip__timer { margin: 0; color: var(--text-secondary); font-size: 0.85rem; }
.building-week-strip__balance { margin: 4px 0 0; font-size: 0.9rem; }
.building-active-perks { margin-bottom: 20px; }
.building-banked-perks { margin-bottom: 20px; }
.building-weekly-projects { margin-bottom: 20px; }
.faction-infrastructure { margin-bottom: 20px; }
.faction-infrastructure__intro { color: var(--text-secondary); font-size: 0.88rem; margin: -8px 0 16px; max-width: 60em; }
.building-perk-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.building-perk-list__row { color: var(--color-reward); font-size: 0.9rem; }
.building-perk-list__row--pending { color: var(--color-warning-ink); }
.building-active-perks__empty { color: var(--text-muted); font-size: 0.85rem; margin: 0; }

.building-slots {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
}
.building-slots__empty { color: var(--text-muted); grid-column: 1 / -1; }
/* --building-accent is set inline from the faction's emblem_hex, same as .faction-card. */
.building-card {
    display: flex; flex-direction: column; gap: 10px;
    min-width: 0;
    min-height: 100%;
    padding: 16px;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    border-left: 4px solid var(--building-accent, var(--border-color));
}
.building-card__head { display: flex; align-items: center; gap: 8px; }
.building-card__icon { font-size: 1.3rem; }
.building-card__name { margin: 0; }
.building-card__desc { color: var(--text-secondary); font-size: 0.88rem; margin: 0; }
.building-card__effect { color: var(--color-primary); font-size: 0.85rem; margin: 0; }
.building-card__bar {
    height: 10px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    overflow: hidden;
}
.building-card__fill {
    display: block; height: 100%;
    background: var(--color-reward, var(--color-primary));
    border-radius: var(--radius-pill);
    transition: width 0.4s ease;
}
.building-card__count { color: var(--text-muted); font-size: 0.8rem; margin: 0; }
.building-card__form { display: flex; gap: 8px; margin-top: auto; }
.building-card__form input[type="number"] {
    width: 6.5em; min-height: 44px; padding: 6px 10px;
    border: 1px solid var(--border-color); border-radius: var(--radius-md);
    background: var(--bg-surface); color: var(--text-primary);
}

/* Weekly rankings: same visual language as .season-standings/.season-row, plus a
   member-count/raw-total note column (per-member average is the ranking metric,
   see factions.services.building_week_standings). Kept as its own class family
   rather than widening .season-row's grid, so the season page's own layout can't
   regress from a change made here. */
.building-rank-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.building-rank-row {
    display: grid;
    grid-template-columns: minmax(120px, 1fr) minmax(80px, 3fr) auto;
    grid-template-areas: "name bar value" "name bar note";
    align-items: center;
    column-gap: 12px;
}
.building-rank-row__name { grid-area: name; font-weight: 600; min-width: 0; }
.building-rank-row--leader .building-rank-row__name { color: var(--color-reward); }
.building-rank-row__bar {
    grid-area: bar;
    height: 12px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    overflow: hidden;
}
.building-rank-row__fill {
    display: block; height: 100%;
    background: var(--color-reward, var(--color-primary));
    border-radius: var(--radius-pill);
    transition: width 0.4s ease;
}
.building-rank-row__value { grid-area: value; font-weight: 600; }
.building-rank-row__note { grid-area: note; font-size: 0.78rem; }

/* ===== REDUCED MOTION ===== */
body.reduced-motion * {
    animation-duration: 0.001s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
    scroll-behavior: auto !important;
}
/* OS-level floor: honour prefers-reduced-motion even before an account exists /
   the pref is toggled (first-time + anonymous visitors, e.g. the landing hero and
   hatch-pop). The account toggle above still forces it on for signed-in users who
   opt in regardless of OS setting. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001s !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001s !important;
        scroll-behavior: auto !important;
    }
}

/* ===== LOADING & SKELETONS =====
   (Reduced-motion is handled by the global rules above: the shimmer/spinner
   animations and the progress-bar transition all collapse to ~instant there.) */

/* Global HTMX top progress bar: element in base.html, driven by htmx-init.js.
   Delay-gated in JS so frequent fast polls never flash it. */
#htmx-progress {
    position: fixed;
    top: 0; left: 0;
    z-index: 9998;
    width: 100%;
    height: 3px;
    background: var(--gradient-primary);
    transform: scaleX(0);
    transform-origin: 0 50%;
    opacity: 0;
    pointer-events: none;
}
#htmx-progress.is-loading {
    opacity: 1;
    transform: scaleX(0.9);            /* crawl toward ~90% while in flight */
    transition: transform 6s cubic-bezier(0.1, 0.7, 0.1, 1), opacity 0.1s;
}
#htmx-progress.is-done {
    opacity: 0;
    transform: scaleX(1);              /* snap to 100% then fade */
    transition: transform 0.15s ease, opacity 0.25s ease 0.05s;
}

/* Button in-flight spinner: htmx adds .htmx-request to the request initiator.
   Covers a .btn that is itself the initiator and a submit .btn inside an
   initiating form. Pollers (notif bell, image status) aren't .btn, so no spin. */
.btn.htmx-request,
form.htmx-request button[type="submit"].btn {
    pointer-events: none;
    opacity: 0.85;
}
.btn.htmx-request::after,
form.htmx-request button[type="submit"].btn::after {
    content: '';
    width: 14px;
    height: 14px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Reusable skeleton shimmer for placeholder / loading content. The pet-image
   pending placeholder reuses the same sweep. */
.skeleton,
.pet-card-image-pending { position: relative; overflow: hidden; }
.skeleton::after,
.pet-card-image-pending::after {
    content: '';
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent,
        color-mix(in srgb, var(--text-primary) 8%, transparent), transparent);
    animation: skeleton-shimmer 1.3s ease-in-out infinite;
}
@keyframes skeleton-shimmer { to { transform: translateX(100%); } }

/* Barracks: dim the roster while a filter/sort/pagination swap is in flight, so
   the change registers even though the old grid stays until the response lands. */
#barracks-controls:has(.htmx-request) ~ #pet-grid,
#pet-grid:has(.page-link.htmx-request) {
    opacity: 0.5;
    pointer-events: none;
    transition: opacity var(--dur) var(--ease);
}

/* ===== MISC UTILITY ===== */
.btn-sm { padding: 6px 12px; min-height: 30px; font-size: 0.85em; }
.btn-tertiary {
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
}
@media (hover: hover) { .btn-tertiary:hover { background: var(--bg-subtle); color: var(--text-primary); } }
.text-muted { color: var(--text-muted); }

/* ===== ACCOUNT SETTINGS: INFO GRID ===== */
.info-grid { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
.info-row { display: flex; align-items: baseline; gap: 10px; }
.info-label {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--text-muted);
    min-width: 120px;
}
.info-value { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.info-note { color: var(--text-muted); font-size: 12px; font-style: italic; }

/* ===== USER PROFILE ===== */
.profile-container {
    max-width: 960px;
    margin: 30px auto;
    background: var(--bg-surface);
    border-radius: var(--radius-lg);
    padding: 30px;
    box-shadow: var(--shadow-sm);
}

.profile-page { max-width: 960px; margin: 0 auto; }
.profile-header {
    display: flex;
    gap: 30px;
    align-items: center;
    flex-wrap: wrap;
}
.panel.profile-header--has-faction {
    border-left: 4px solid var(--faction-accent, var(--border-color));
}

/* Chained with .panel (not just the modifier class alone) so this reliably wins over
   .panel's own `background: var(--bg-surface)` shorthand. Frontier needs the higher-
   specificity variant too: its later `body.interface-frontier .panel` surface rule
   otherwise resets background-size/repeat/position, which tiles the vista art. */
.panel.profile-header--has-background,
body.interface-frontier .panel.profile-header--has-background {
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

/* The vista art behind is user-chosen and arbitrary, so the name/meta/actions get their
   own near-opaque card rather than relying on a flat wash over the whole header: that
   guarantees readable text against any image, not just ones close to --bg-surface. */
.profile-header--has-background .profile-info {
    background: color-mix(in srgb, var(--bg-surface) 90%, transparent);
    border-radius: var(--radius-md);
    padding: 14px 18px;
    box-shadow: var(--shadow-sm);
}

.profile-avatar { flex-shrink: 0; }

.profile-avatar img {
    width: 150px;
    height: 150px;
    border-radius: var(--radius-lg);
    object-fit: cover;
    border: 3px solid var(--border-color);
}

.default-avatar {
    width: 150px;
    height: 150px;
    border-radius: var(--radius-lg);
    background: var(--gradient-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 60px;
    color: var(--text-inverse);
    font-weight: bold;
}

.profile-info { flex: 1 1 260px; min-width: 0; }
.profile-info h1 {
    margin: 6px 0 15px 0;
    font-family: var(--font-display);
    font-size: clamp(1.7rem, 1.2rem + 1.8vw, 2.1rem);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.profile-meta { display: flex; flex-direction: column; gap: 8px; }
.meta-item { display: flex; gap: 10px; align-items: baseline; min-width: 0; }
.meta-label {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--text-muted);
    min-width: 100px;
}
.meta-value { color: var(--text-primary); font-variant-numeric: tabular-nums; min-width: 0; overflow-wrap: anywhere; }
.profile-meta .state-chip { white-space: normal; overflow-wrap: anywhere; }

.profile-pronouns { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.pronoun-pill {
    display: inline-block;
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-size: 0.75rem;
    font-weight: 500;
}

.profile-header-actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.profile-showcase-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.profile-showcase-card { min-width: 0; }
.profile-level-readout { display: flex; align-items: center; gap: 12px; }
.profile-level-readout__value {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--bg-hover);
    border: 2px solid var(--color-primary);
    color: var(--color-primary);
    font-family: var(--font-display);
    font-size: 1.4rem;
    font-weight: 700;
}
.profile-level-readout strong, .profile-level-readout small { display: block; }
.profile-level-readout small { margin-top: 4px; color: var(--text-muted); }
.profile-stat-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 0; }
.profile-stat-grid div { padding: 10px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.profile-stat-grid dt { color: var(--text-muted); font-size: .72rem; }
.profile-stat-grid dd { margin: 3px 0 0; color: var(--text-primary); font-size: 1.2rem; font-weight: 700; }
.profile-crew-showcase .panel__header { align-items: center; }
.profile-crew-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.profile-crew-card { display: flex; min-width: 0; flex-direction: column; gap: 5px; padding: 10px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); color: inherit; text-decoration: none; }
.profile-crew-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
@media (hover: hover) { .profile-crew-card:hover { border-color: var(--color-primary); text-decoration: none; } }
.profile-crew-card__media { display: flex; align-items: center; justify-content: center; aspect-ratio: 1; overflow: hidden; border-radius: var(--radius-sm); background: var(--bg-hover); font-size: 2rem; }
.profile-crew-card__media img { width: 100%; height: 100%; object-fit: contain; }
.profile-crew-card__name { overflow: hidden; color: var(--text-primary); font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.profile-crew-card__meta { color: var(--text-muted); font-size: .76rem; line-height: 1.35; }
.profile-crew-showcase__actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* The featured-crew editor: a small reorderable strip (see reorderList in
   alpine-components.js) plus an add-select, both scoped under the swap target
   #profile-featured-crew but reading the "editing" toggle from an outer x-data the
   swap never replaces, same split petManage/_pet_detail_tabs.html already uses. */
.featured-crew-edit-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; margin-bottom: 10px; }
.featured-crew-edit-card { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.featured-crew-edit-card__head { display: flex; align-items: center; gap: 6px; }
.featured-crew-edit-card__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; color: var(--text-primary); font-size: .85rem; }
.featured-crew-edit-card__meta { color: var(--text-muted); font-size: .72rem; }
@media (pointer: coarse) {
    .featured-crew-edit-card .reorder-nudge__btn {
        width: 44px;
        height: 44px;
        min-width: 44px;
        min-height: 44px;
    }
}
.bio-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.bio-header h2 { margin: 0; font-family: var(--font-heading); font-size: 1.25rem; color: var(--text-primary); }
.bio-content { line-height: 1.6; color: var(--text-secondary); }
.bio-editor form { max-width: none; }
.bio-editor textarea {
    width: 100%;
    max-width: none;
    padding: 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 0.95rem;
    resize: vertical;
    background: var(--bg-surface);
    color: var(--text-primary);
}
.bio-editor-actions { margin-top: 10px; display: flex; gap: 10px; }
.form-hint {
    display: block;
    margin-top: 5px;
    color: var(--text-secondary);
    font-size: 0.78rem;
}
@media (max-width: 768px) {
    .profile-showcase-grid { grid-template-columns: 1fr; }
    .profile-crew-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
    .profile-crew-card { padding: 7px; }
    .profile-crew-card__meta { font-size: .68rem; }
}

/* ===== PRONOUN SELECTOR (account settings) ===== */
.pronoun-tag-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.pronoun-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    border-radius: 20px;
    border: 2px solid var(--border-color);
    background: var(--bg-surface);
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
}
.pronoun-tag input[type="checkbox"] { display: none; }
@media (hover: hover) { .pronoun-tag:hover { border-color: var(--color-primary); color: var(--text-primary); } }
.pronoun-tag:has(input:checked) {
    border-color: var(--color-primary);
    background: var(--color-primary);
    color: #fff;
}

/* ===== AVATAR SELECT ===== */
.avatar-select-container { max-width: 1200px; margin: 0 auto; }
.avatar-select-header { margin-bottom: 30px; }
.avatar-select-header h1 { font-family: var(--font-display); font-size: clamp(1.7rem, 1.2rem + 1.8vw, 2.1rem); letter-spacing: -0.02em; color: var(--text-primary); margin-bottom: 10px; }
.avatar-select-header .subtitle { color: var(--text-secondary); margin: 0; }

.avatar-filter-panel {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: 20px;
    margin-bottom: 20px;
    box-shadow: var(--shadow-sm);
}
.avatar-filter-form { display: flex; gap: 15px; align-items: flex-end; flex-wrap: wrap; max-width: none; }
.avatar-filter-form .filter-group { display: flex; flex-direction: column; gap: 6px; }
.avatar-filter-form label { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; }
.avatar-filter-form input,
.avatar-filter-form select {
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    color: var(--text-primary);
    font-size: 0.95rem;
    min-width: 150px;
}
.filter-actions { display: flex; gap: 8px; }
.results-info { color: var(--text-secondary); font-size: 0.85rem; margin-bottom: 15px; }

.avatar-pet-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; }
.avatar-pet-card {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}
.pet-image-wrapper {
    width: 100%;
    height: 200px;
    background: var(--bg-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.pet-thumbnail { width: 100%; height: 100%; object-fit: contain; }
.avatar-pet-card .pet-info { padding: 15px; }
.avatar-pet-card .pet-info h3 { margin: 0 0 5px 0; font-family: var(--font-heading); color: var(--text-primary); font-size: 1rem; font-weight: 600; }
.avatar-pet-card .species { margin: 0 0 3px 0; color: var(--text-secondary); font-size: 0.8rem; text-transform: capitalize; }
.avatar-pet-card .level-info { margin: 0 0 12px 0; color: var(--text-secondary); font-size: 0.8rem; }
.avatar-pet-card .btn { width: 100%; text-align: center; padding: 8px; }

.pagination-container { display: flex; justify-content: center; margin-top: 30px; }
.pagination { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; }
.page-link,
.page-number {
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    color: var(--text-primary);
    text-decoration: none;
    font-size: 13px;
}
@media (hover: hover) {
    .page-link:hover,
    .page-number:hover { background: var(--bg-subtle); border-color: var(--color-primary); color: var(--color-primary); }
}
.page-number.active { background: var(--color-primary); color: var(--text-inverse); border-color: var(--color-primary); }
/* Staff/mod list pagination reuses the same link chrome as the main .pagination
   system so every paginator across the site looks the same (login activity,
   celery history, mod lookup). */
.staff-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 16px;
    color: var(--text-secondary);
    font-size: 0.85rem;
}
.staff-pagination a {
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
}
@media (hover: hover) { .staff-pagination a:hover { background: var(--bg-subtle); border-color: var(--color-primary); color: var(--color-primary); text-decoration: none; } }

/* Touch tap targets: the WCAG/UX floor is 44px, but only on touch pointers; 
   mouse users keep the denser controls. Covers the small interactive elements
   that fall under 44px at their base size. */
@media (pointer: coarse) {
    .page-link, .page-number, .staff-pagination a,
    .pet-group-tab, .pet-group-tab-menu-btn { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
    .pet-group-tab-menu-btn { min-width: 44px; }

    /* Crew and squad controls use deliberately compact mouse styles below. Keep their
       touch path explicit here, after those component rules, so the 44px floor cannot be
       overridden by .btn-xs or the 22px reorder nudges. */
    #barracks-filters input[type="search"],
    #barracks-filters select,
    #barracks-filters .btn,
    .pet-card__foot .btn,
    .btn-sm,
    .btn-xs { min-height: 44px; }
}

/* Text-entry floor on touch: iOS Safari zooms the viewport whenever a focused field
   computes under 16px, and it does NOT zoom back out. Tapping the barracks search box
   therefore left the roster you were filtering off-screen. Measured offenders were the
   barracks toolbar (0.95rem search + 6 selects), marketplace and notification filters,
   the trade/gift/delivery note textareas (13px), .input-sm selects, and the incursion
   room-lock slot pickers.

   `!important` is deliberate and matches this file's existing use of it for rules that
   must not lose (x-cloak, reduced-motion, drawer hiding): the floor has to beat every
   class-scoped component rule, including ones not written yet, and a component author
   should not be able to reintroduce the zoom by being more specific. Checkboxes, radios
   and buttons are excluded: they take no text entry, so they never trigger the zoom. */
@media (pointer: coarse) {
    input[type="text"], input[type="password"], input[type="email"],
    input[type="number"], input[type="search"], input[type="tel"],
    input[type="url"], input[type="date"], input[type="datetime-local"],
    input:not([type]), select, textarea { font-size: 16px !important; }
}
.page-numbers { display: flex; gap: 4px; }

.empty-state {
    text-align: center;
    padding: 60px 20px;
    background: var(--bg-surface);
    border-radius: var(--radius-md);
    border: 1px solid var(--border-subtle);
}
.empty-state p { color: var(--text-secondary); margin-bottom: 20px; }

@media (max-width: 1024px) { .avatar-pet-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 768px) {
    .avatar-pet-grid { grid-template-columns: 1fr; }
    .avatar-filter-form { flex-direction: column; }
    .avatar-filter-form .filter-group { width: 100%; }
    .avatar-filter-form input,
    .avatar-filter-form select { width: 100%; }
    .filter-actions { width: 100%; }
    .filter-actions .btn { flex: 1; }
}

/* ===== PLAYERS LIST (Comms Array) ===== */
.players-page {
    width: 100%;
    max-width: 1040px;
    min-width: 0;
    margin: 0 auto;
}
.players-page .avatar-filter-panel { width: 100%; box-sizing: border-box; }
.players-page .avatar-filter-form { width: 100%; }
.players-page .avatar-filter-form .filter-group { width: min(100%, 420px); }
.players-page .avatar-filter-form input { width: 100%; min-width: 0; box-sizing: border-box; }
.players-page #player-results { min-width: 0; }
.player-table {
    display: table;
    width: 100%;
    max-width: none;
    margin-left: 0;
    margin-right: 0;
    table-layout: fixed;
    overflow: visible;
}
.player-table th:nth-child(1),
.player-table td:nth-child(1) { width: 44%; }
.player-table th:nth-child(2),
.player-table td:nth-child(2) { width: 18%; }
.player-table th:nth-child(3),
.player-table td:nth-child(3) { width: 20%; }
.player-table th:nth-child(4),
.player-table td:nth-child(4) { width: 18%; }
.player-table td:first-child,
.player-table th:first-child { padding-right: 16px; }
.player-table td:first-child { padding-right: 16px; white-space: normal; }
.player-row-user {
    display: inline-flex;
    align-items: center;
    gap: 14px;
    min-width: 0;
    max-width: 100%;
    color: var(--text-primary);
    font-weight: 600;
    text-decoration: none;
}
@media (hover: hover) { .player-row-user:hover { color: var(--color-primary); text-decoration: none; } }
.player-avatar-thumb {
    width: 50px;
    height: 50px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.player-avatar-thumb.default-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--gradient-primary);
    color: var(--text-inverse);
    font-size: 22px;
    font-weight: bold;
}
.online-dot {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    margin-right: 6px;
    vertical-align: middle;
}

@media (max-width: 1024px) {
    .player-table,
    .player-table thead,
    .player-table tbody,
    .player-table tr,
    .player-table td { display: block; }
    .player-table { max-width: none; border: 0; overflow: visible; }
    .player-table thead {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
    .player-table tbody { display: grid; gap: 10px; }
    .player-table tr {
        padding: 12px;
        border: 1px solid var(--border-color);
        border-radius: var(--radius-md);
        background: var(--bg-surface);
    }
    .player-table td {
        display: grid;
        grid-template-columns: minmax(6.2rem, 0.35fr) minmax(0, 1fr);
        align-items: center;
        gap: 10px;
        padding: 7px 0;
        border: 0;
        white-space: normal;
    }
    .player-table td::before {
        content: attr(data-label);
        color: var(--text-muted);
        font-family: var(--font-mono);
        font-size: 0.68rem;
        font-weight: 700;
        letter-spacing: 0.06em;
        text-transform: uppercase;
    }
    .player-table td:first-child { padding-right: 0; }
    .player-row-user { min-width: 0; gap: 9px; }
    .player-row-user .player-avatar-thumb { width: 42px; height: 42px; }
}
.online-dot.is-online { background: var(--color-success); }
.online-dot.is-offline { background: var(--text-secondary); opacity: 0.5; }
.status-text { vertical-align: middle; }
.status-text.is-online { color: var(--color-success); font-weight: 600; }
.status-text.is-offline { color: var(--text-secondary); }
.status-text.is-hidden { color: var(--text-muted); font-style: italic; }

/* ===== AVATAR CROP ===== */
.avatar-crop-container { padding: 20px; max-width: 1400px; margin: 0 auto; }
.avatar-crop-container h1 { font-family: var(--font-display); font-size: clamp(1.7rem, 1.2rem + 1.8vw, 2.1rem); letter-spacing: -0.02em; color: var(--text-primary); margin-bottom: 10px; }
.avatar-crop-container .subtitle { color: var(--text-secondary); margin-bottom: 30px; }

.crop-layout { display: grid; grid-template-columns: 1fr 350px; gap: 30px; margin-top: 20px; }
.crop-panel {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: 15px;
    box-shadow: var(--shadow-sm);
}
.image-container {
    width: 100%;
    max-height: 600px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-subtle);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.cropper-image { max-width: 100%; max-height: 600px; display: block; }

.preview-panel { display: flex; flex-direction: column; gap: 20px; }
.preview-section {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: 20px;
    box-shadow: var(--shadow-sm);
}
.preview-section h3 { color: var(--text-muted); margin: 0 0 15px 0; font-family: var(--font-mono); font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; }
.avatar-preview {
    width: 200px;
    height: 200px;
    border: 2px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 15px;
    overflow: hidden;
}
.avatar-preview canvas { width: 200px; height: 200px; display: block; }
.preview-hint { color: var(--text-secondary); font-size: 0.78rem; text-align: center; margin: 0; }

.controls-section {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: 20px;
    box-shadow: var(--shadow-sm);
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.controls-section .btn { width: 100%; text-align: center; padding: 10px 15px; font-size: 0.95rem; }

.status-message { padding: 12px 15px; border-radius: var(--radius-sm); font-size: 0.85rem; text-align: center; display: none; }
.status-message.info { background: var(--color-info-bg); color: var(--color-info-text); display: block; }
.status-message.success { background: var(--color-success-bg); color: var(--color-success-text); display: block; }
.status-message.error { background: var(--color-error-bg); color: var(--color-error-text); display: block; }

.cropper-canvas, .cropper-drag-box { background: var(--bg-subtle); }
.cropper-crop-box { border: 2px dashed var(--border-color); }
.cropper-face { background: rgba(0, 0, 0, 0.1); }
.cropper-line { background: var(--color-info-bg); }
.cropper-point { background: var(--color-info-bg); box-shadow: 0 0 4px rgba(0, 0, 0, 0.3); }

@media (max-width: 768px) {
    .crop-layout { grid-template-columns: 1fr; gap: 20px; }
    .image-container { max-height: 400px; }
    .cropper-image { max-height: 400px; }
    .preview-panel { order: -1; }
    .controls-section .btn { width: 100%; }
}

/* ===== CREATE PET WORKBENCH =====
   Celestial-Workbench-style layout: labeled trays for identity/species/colors/
   markings on the left, a sticky live-preview + cost "receipt" on the right: 
   see docs/ux-direction-set.md direction 3. The one signature element is the
   forge preview itself (radial containment field + a charging ring while a
   composite is in flight); everything else stays quiet and reuses existing
   tokens/patterns rather than introducing new color or decoration. */
.create-pet-intro { max-width: 640px; }
.create-pet-intro p { color: var(--text-secondary); }
.create-pet-eyebrow {
    margin: 0 0 6px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 0.78em;
    font-weight: 700;
}

.create-pet-form { max-width: none; }

.workbench-grid {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: 24px;
    align-items: start;
}

.workbench-main { display: flex; flex-direction: column; gap: 20px; min-width: 0; }

.workbench-section {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 18px 20px;
}
.workbench-section input[type="text"] { max-width: 420px; }

/* Every tray shares one header treatment: a hairline rule with a short
   gradient accent, so the page reads as one designed surface rather than a
   stack of generic bordered boxes. Repeated restrained motif, not a glow. */
.workbench-section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    position: relative;
    padding-bottom: 10px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--border-subtle);
}
.workbench-section-header::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -1px;
    width: 28px;
    height: 2px;
    background: var(--gradient-primary);
}
.workbench-section-title { margin: 0; font-size: 1.05em; font-weight: 700; color: var(--text-primary); }
.workbench-section-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.workbench-action-status { color: var(--color-success-text); font-size: 0.78em; }
.marking-count { color: var(--text-muted); font-weight: 500; font-size: 0.85em; }
.randomizer-options {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px 14px;
    margin: -2px 0 14px;
    color: var(--text-secondary);
    font-size: 0.82em;
}
.randomizer-count,
.randomizer-toggle { display: flex; align-items: center; gap: 6px; }
.randomizer-count input[type="number"] { width: 4.2rem; min-height: 34px; }
.randomizer-toggle { cursor: pointer; }
.randomizer-help { color: var(--text-muted); }

/* Species picker: radio cards styled like the parent-pick-card pattern
   elsewhere in the site. Radio input is visually hidden but stays
   keyboard-focusable, with the checked/focus states surfaced on the card via
   :has() rather than JS, so it degrades to a normal fieldset without Alpine. */
.species-picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 10px;
}
.species-pick-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 14px 8px;
    border: 2px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    cursor: pointer;
    text-align: center;
}
.species-pick-card input[type="radio"] {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}
@media (hover: hover) { .species-pick-card:hover { border-color: var(--color-primary); } }
.species-pick-card:has(input:checked) { border-color: var(--color-primary); background: var(--bg-hover); }
.species-pick-card:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* Dormant until picked, then "charges" into the same gradient-medallion
   language as .default-avatar (profile identity badges): a species becomes
   a chosen material, not just a highlighted option. */
.species-pick-sigil {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 1px solid var(--border-color);
    background: var(--bg-surface);
    color: var(--text-muted);
    font-weight: 700;
    font-size: 0.95em;
}
.species-pick-card:has(input:checked) .species-pick-sigil {
    background: var(--gradient-primary);
    border-color: transparent;
    color: var(--text-inverse);
}
.species-pick-name { font-weight: 600; color: var(--text-primary); font-size: 0.9em; }
.species-pick-cost { font-size: 0.78em; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.species-pick-cost--free { color: var(--color-success-text); font-weight: 600; }
/* Says why an off-list species costs Prisms while a free forge is available. The
   card stays fully pickable: the note prices it, it doesn't gate it. */
.species-pick-note { font-size: 0.7em; color: var(--text-muted); text-align: center; line-height: 1.2; }

/* Colors laid out as material samples: the native swatch itself is the
   sample, with a plain hex caption beneath it: no second colored chip
   competing for the same job (and unreadable for light hues like white). */
.color-material-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 14px;
}
/* .color-material is a <div> (not a <label>): the global label flex is no longer
   inherited, so set it explicitly. See create_pet.html for why it can't be a label. */
.color-material { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.color-material-label { font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }
.color-material input[type="color"] {
    width: 56px;
    height: 40px;
    padding: 0;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    cursor: pointer;
}
.color-material-hex { font-size: 0.78em; color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* Desktop-only custom colour popover, REPLACING the native OS colour popup on desktop
   (it exposes only RGB boxes with no pastable hex field on some browsers). On touch the
   native <input type="color"> stays the swatch, byte-identical to before; on desktop it is
   hidden as the value store and a custom swatch button opens this popover instead. Gated on
   a 1024px minimum plus `hover: hover`, so phones and tablets are untouched. The popover reuses
   the barracks .pet-group-manage-menu (toggler + click-outside + Escape, un-animated); its
   own guarded :hover is the only one involved, so the hover-sweep is unaffected.
   `.color-swatch-wrap` is the position:relative containing block for the absolute popover.
   Guards: pets/test_create_pet_ui.py::ColorPopover*, game/test_css_positioning.py. */
.color-swatch-wrap { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.color-swatch-btn { display: none; }   /* the desktop-only trigger (hidden on touch) */
.color-swatch-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.color-control .color-native-swatch {
    width: 56px;
    height: 40px;
    padding: 0;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    cursor: pointer;
}
.color-control .color-native-swatch.color-swatch-btn--marking { width: 32px; height: 28px; }

/* The popover panel: an elevated card on the page's own tokens (radius/shadow/elevated
   surface like the nav dropdowns), a live preview + hex on top, an even RGB grid below. */
.color-popover {
    /* Explicit width, not just min-width: the panel is position:absolute, so without it
       the number inputs' intrinsic (max-content) width sizes the whole popover far wider
       than intended. With a fixed width the minmax(0,1fr) RGB columns share it evenly. */
    width: 236px;
    padding: 13px;
    gap: 11px;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    background: var(--bg-elevated);
}
.color-popover__cap {
    font-family: var(--font-mono);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.color-popover__hex { display: flex; flex-direction: column; gap: 5px; }
.color-popover__top { display: flex; flex-direction: row; align-items: center; gap: 8px; margin: 0; }
.color-popover__preview {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-color);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
/* minmax(0, 1fr), not 1fr: a number input's default intrinsic width would otherwise
   inflate each column (and the whole popover) well past the min-width. */
.color-popover__rgb { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.color-popover__ch { flex-direction: column; align-items: stretch; gap: 4px; margin: 0; }
.color-popover__ch .color-popover__cap { text-align: center; }
.color-hex-input {
    flex: 1;
    min-width: 0;
    padding: 6px 9px;
    line-height: 1.3;
    font-family: var(--font-mono);
    font-size: 0.9rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.color-rgb-input {
    width: 100%;
    padding: 6px 4px;
    line-height: 1.3;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 0.9rem;
}
/* Clean centred numbers: drop the spinner buttons (keyboard ↑/↓ still steps). */
.color-rgb-input::-webkit-inner-spin-button,
.color-rgb-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.color-rgb-input { -moz-appearance: textfield; appearance: textfield; }

@media (min-width: 1024px) and (hover: hover) and (pointer: fine) {
    /* Desktop only: hide the native swatch, show the custom button that opens the
       iOS-inspired picker. Tablets and phones retain the OS picker. */
    .color-control .color-native-swatch { display: none; }
    .color-swatch-btn {
        display: inline-block;
        width: 56px;
        height: 40px;
        padding: 0;
        border: 1px solid var(--border-color);
        border-radius: var(--radius-sm);
        cursor: pointer;
        transition: border-color 0.12s ease, box-shadow 0.12s ease;
    }
    .color-swatch-btn:hover { border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--bg-hover); }
    .color-swatch-btn--marking { width: 32px; height: 28px; }
}

.color-control-label { color: var(--text-secondary); }
.color-picker-popover {
    position: fixed;
    z-index: 100;
    width: 270px;
    max-width: calc(100vw - 24px);
    max-height: calc(100dvh - 24px);
    overflow-y: auto;
    overscroll-behavior: contain;
    box-sizing: border-box;
    padding: 12px;
    gap: 10px;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    background: var(--bg-elevated);
}
.color-picker__topline { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.color-picker__topline strong { font-size: .84rem; color: var(--text-primary); }
.color-picker__tabs {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 3px;
    padding: 3px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
}
.color-picker__tab {
    min-height: 32px;
    padding: 5px 6px;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text-muted);
    font-size: .75rem;
    font-weight: 700;
    cursor: pointer;
}
@media (hover: hover) { .color-picker__tab:hover { color: var(--text-primary); } }
.color-picker__tab.is-active {
    background: var(--bg-elevated);
    color: var(--text-primary);
    box-shadow: var(--shadow-sm);
}
.color-picker__tab:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.color-picker__mode { display: flex; flex-direction: column; gap: 12px; }
.color-picker__subtabs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 3px;
    padding: 3px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
}
.color-picker__subtab {
    min-height: 30px;
    padding: 4px 6px;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: .7rem;
    font-weight: 700;
    cursor: pointer;
}
@media (hover: hover) { .color-picker__subtab:hover { color: var(--text-primary); } }
.color-picker__subtab.is-active {
    background: var(--bg-elevated);
    color: var(--text-primary);
    box-shadow: var(--shadow-sm);
}
.color-picker__subtab:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.color-picker__grid {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 8px;
}
.color-picker__grid-swatch {
    aspect-ratio: 1;
    min-width: 0;
    padding: 0;
    border: 2px solid transparent;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px rgb(0 0 0 / 25%);
    cursor: pointer;
    transition: transform .12s ease, box-shadow .12s ease;
}
@media (hover: hover) { .color-picker__grid-swatch:hover { transform: scale(1.08); } }
.color-picker__grid-swatch.is-selected {
    box-shadow: 0 0 0 2px var(--bg-elevated), 0 0 0 4px var(--color-primary);
}
.color-picker__grid-swatch:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.color-picker__sliders { display: flex; flex-direction: column; gap: 10px; }
.color-picker__slider-row {
    display: grid;
    grid-template-columns: 42px minmax(0, 1fr) 54px;
    align-items: center;
    gap: 8px;
    margin: 0;
}
.color-picker__slider-row .color-popover__cap { letter-spacing: .04em; }
.color-picker__slider-row input[type="range"] { width: 100%; min-width: 0; margin: 0; }
.color-picker__slider-row .color-rgb-input { width: 54px; }
.color-picker__slider--r { accent-color: #ff3b30; }
.color-picker__slider--g { accent-color: #34c759; }
.color-picker__slider--b { accent-color: #0a84ff; }
.color-picker__slider--h { accent-color: #af52de; }
.color-picker__slider--s { accent-color: #34c759; }
.color-picker__slider--l { accent-color: #ff9500; }
.color-picker__spectrum {
    position: relative;
    height: 140px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background:
        linear-gradient(to bottom, transparent 0%, #000 100%),
        linear-gradient(to right, #fff 0%, hsl(var(--picker-hue) 100% 50%) 100%);
    cursor: crosshair;
    touch-action: none;
}
.color-picker__spectrum:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.color-picker__spectrum-thumb {
    position: absolute;
    width: 18px;
    height: 18px;
    transform: translate(-50%, -50%);
    border: 2px solid #fff;
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgb(0 0 0 / 70%), 0 1px 4px rgb(0 0 0 / 45%);
    pointer-events: none;
}
.color-picker__hue { display: flex; flex-direction: column; gap: 5px; margin: 0; }
.color-picker__hue input[type="range"] {
    width: 100%;
    height: 18px;
    margin: 0;
    border-radius: var(--radius-pill);
    accent-color: var(--color-primary);
    background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
}
.color-picker__precise { display: flex; flex-direction: column; gap: 10px; }
.color-picker__hex { display: flex; flex-direction: column; gap: 5px; margin: 0; }
.color-picker__hex .color-hex-input { width: 100%; }

@media (max-width: 1024px), (hover: none), (pointer: coarse) {
    .color-picker-popover { max-width: calc(100vw - 24px); }
}

@media (max-height: 680px) and (min-width: 1024px) and (hover: hover) and (pointer: fine) {
    .color-picker-popover { padding: 10px; gap: 8px; }
    .color-picker__mode { gap: 8px; }
    .color-picker__spectrum { height: 120px; }
    .color-picker__grid { gap: 6px; }
    .color-picker__sliders { gap: 7px; }
}

/* Markings as cards, echoing pet_detail's .marking-row treatment. */
.marking-card {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
    margin-top: 10px;
}
.marking-card select { flex: 1 1 140px; }
.marking-swatch { display: flex; align-items: center; gap: 6px; }
.marking-swatch input[type="color"] {
    width: 32px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
    cursor: pointer;
}
.marking-hex { font-size: 0.78em; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.marking-opacity { flex-direction: row; align-items: center; gap: 6px; font-size: 0.85em; color: var(--text-secondary); }
.marking-opacity input[type="range"] { width: 100px; }
/* Per-row action cluster (reorder ↑/↓ + randomize 🎲 + remove), pushed to the row's end. */
.marking-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.marking-dice { line-height: 1; }
/* Marking reorder (creation page only). The ⠿ drag handle is desktop-only because
   HTML5 drag never fires on touch: the ↑/↓ nudge buttons are the touch/keyboard path
   and stay on every device. `.drag-handle` (shared) supplies the look; this only gates
   its visibility. Guard: pets/test_create_pet_ui.py::MarkingReorderTests. */
.marking-drag-handle { display: none; }
@media (hover: hover) { .marking-drag-handle { display: inline-flex; align-items: center; } }
.marking-card--drag-source { opacity: 0.55; }
.marking-reorder-hint { margin: 0 0 4px; font-size: 0.8em; color: var(--text-muted); }
/* Touch floor for the reorder nudges: .reorder-nudge__btn's own 22px beats the global
   :where(button) coarse floor, so restate it here, same as the dressing room does. */
@media (pointer: coarse) { .marking-actions .reorder-nudge__btn { width: 44px; height: 44px; } }

/* Sticky aside: the live composite is the primary object on the page. */
.workbench-aside {
    display: flex;
    flex-direction: column;
    gap: 16px;
    position: sticky;
    top: calc(var(--header-height) + 20px);
}
.preview-frame {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 16px;
    text-align: center;
}
/* The signature moment: a quiet containment field (reuses the landing hero's
   radial-vignette technique) that "charges" with a conic ring while a
   composite is in flight, and gives the finished pet a soft glow: the one
   place on this page glow is earned, per the workbench direction's own rule
   to reserve it for a genuinely actionable/rewarding state. */
.preview-canvas {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 260px;
    background: radial-gradient(ellipse at center, var(--bg-hover) 0%, var(--bg-subtle) 70%);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    margin-bottom: 10px;
    overflow: hidden;
    transition: opacity 0.15s ease;
}
.preview-canvas::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: 2px solid transparent;
    background: conic-gradient(var(--color-primary), var(--color-primary-dark), var(--color-primary)) border-box;
    -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: 0;
    animation: preview-charge 1.4s linear infinite;
}
.preview-canvas.htmx-request { opacity: 1; }
.preview-canvas.htmx-request::before { opacity: 1; }
@keyframes preview-charge { to { transform: rotate(1turn); } }
.preview-canvas img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-sm);
    filter: drop-shadow(0 0 16px var(--color-primary));
}
.preview-placeholder { color: var(--text-muted); font-size: 0.85em; padding: 0 12px; margin: 0; }
/* The Prism purchase page's "preview a reward on your pet" box sits in a
   full-width .panel, not a two-pane grid cell like .mw-compare gives the
   Mutation Workshop's own .preview-canvas: without a cap here the box (and
   the single 500x500 image inside it) stretched across the whole panel,
   leaving a bare strip of the box's own background on one side. Centering it
   at its own natural image size, the same as the box the image actually
   fills, is what "tightens" it back up. */
.bonus-item-preview-canvas { max-width: 500px; width: 100%; margin: 0 auto; }
/* Pairs the charging-ring animation with a text status: the ring alone is a
   motion-only signal, and reduced-motion users only get a static ring. */
.preview-compositing-label {
    margin: 8px 0 0;
    text-align: center;
    font-size: 0.78em;
    color: var(--text-muted);
    visibility: hidden;
}
/* Keyed to the shared .preview-canvas class, not one page's container id: this
   was #pet-preview-container (create_pet's id), so the Mutation Workshop's own
   #pet-mutation-preview-container never matched and its aria-live status stayed
   permanently hidden, leaving the ring as the only in-flight signal. */
.preview-canvas.htmx-request + .preview-compositing-label { visibility: visible; }
.preview-refresh-btn { width: 100%; justify-content: center; }
.preview-rate-note {
    margin: 8px 0 0;
    color: var(--text-muted);
    font-size: 0.72em;
    text-align: center;
}

/* ============================================================
   MUTATION WORKSHOP (.mw-*)
   Own namespace rather than more .workbench-* variants: create_pet's
   workbench is a tall form beside one sticky preview, this page is a
   before/after comparison with the controls underneath. The two want
   opposite layouts, and sharing classes is what produced the version
   where both canvases were squeezed into one 320px column.
   ============================================================ */

/* The whole panel is one <form>, and the global `form` rule is a 420px flex
   column, so every grid below silently collapsed to ~38% of the page and hit
   its own narrow breakpoint, with the flex gap stacking on top of the sections'
   own margins. display:block because .mw-compare/.mw-controls/.mw-commit each
   own their vertical rhythm already. */
.mw-form { display: block; max-width: none; }

/* Before/after: the comparison IS the page, so it gets the full width and
   both canvases get equal room. Nothing here is sticky: the player looks
   at this, changes a control below, and looks back. */
.mw-compare {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 20px;
    align-items: start;
    margin-bottom: 24px;
}
.mw-compare__pane { min-width: 0; }
.mw-compare .preview-canvas { min-height: 320px; margin-bottom: 8px; }
.mw-compare__label {
    margin: 0 0 8px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.72em;
    font-weight: 700;
}
.mw-compare__label--accent { color: var(--color-primary); }
.mw-compare__caption {
    margin: 0;
    text-align: center;
    font-size: 0.8em;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    flex-wrap: wrap;
    min-height: 1.4em;
}
.mw-compare__arrow {
    align-self: center;
    padding-top: 40px;
    font-size: 1.6em;
    color: var(--text-muted);
}

/* Gallery gets ~2/3, the two appearance trays stack in the remaining third. */
.mw-controls {
    display: grid;
    grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
    gap: 20px;
    align-items: start;
}
.mw-appearance { display: flex; flex-direction: column; gap: 20px; }
/* A <fieldset> brings its own border/padding and won't shrink in a grid track
   by default; these trays reuse .workbench-section's chrome instead. Their
   accessible name comes from aria-labelledby, not a <legend>: see the template. */
.mw-gallery-section, .mw-appearance fieldset { min-width: 0; margin: 0; }

.mw-gallery-toolbar {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 16px;
}
.mw-gallery-filter { flex: 1 1 180px; margin: 0; }
.mw-gallery-filter input { width: 100%; }
.mw-gallery-group + .mw-gallery-group { margin-top: 18px; }
.mw-gallery-group__label {
    margin: 0 0 8px;
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.mw-gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
    gap: 10px;
}
.mw-gallery-empty { margin: 14px 0 0; }

/* Swatch: same visually-hidden-radio + :has() pattern as .species-pick-card, so
   it stays a plain keyboard-navigable radio group with Alpine absent. */
.mw-swatch {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 6px 6px 8px;
    border: 2px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    cursor: pointer;
    text-align: center;
    overflow: hidden;
}
.mw-swatch input[type="radio"] { position: absolute; opacity: 0; width: 1px; height: 1px; }
.mw-swatch__art {
    width: 100%;
    height: auto;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-sm);
    display: block;
}
.mw-swatch__name {
    font-size: 0.72em;
    line-height: 1.25;
    color: var(--text-secondary);
    font-weight: 600;
}
@media (hover: hover) { .mw-swatch:hover { border-color: var(--color-primary); } }
.mw-swatch:has(input:checked) { border-color: var(--color-primary); background: var(--bg-hover); }
.mw-swatch:has(input:checked) .mw-swatch__name { color: var(--text-primary); }
.mw-swatch:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* Selection is not signalled by border colour alone: a checkmark medallion
   carries it for anyone who can't separate the two border states. */
.mw-swatch__check {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--gradient-primary);
    color: #fff;
    font-size: 0.7em;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transform: scale(0.6);
    transition: opacity 0.12s ease, transform 0.12s ease;
}
.mw-swatch:has(input:checked) .mw-swatch__check { opacity: 1; transform: scale(1); }

/* Blend modes as radio cards carrying their own explanation, rather than a
   3-item <select> of ImageMagick operator names. All three descriptions stay
   visible so the choice can be compared without clicking through it. */
.mw-blend-grid { display: flex; flex-direction: column; gap: 8px; }
.mw-blend-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 10px 12px;
    border: 2px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    cursor: pointer;
}
.mw-blend-card input[type="radio"] { position: absolute; opacity: 0; width: 1px; height: 1px; }
.mw-blend-card__name { font-weight: 700; font-size: 0.9em; color: var(--text-primary); }
.mw-blend-card__desc { font-size: 0.78em; line-height: 1.4; color: var(--text-muted); }
@media (hover: hover) { .mw-blend-card:hover { border-color: var(--color-primary); } }
.mw-blend-card:has(input:checked) { border-color: var(--color-primary); background: var(--bg-hover); }
.mw-blend-card:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.mw-opacity-value { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-primary); }
.mw-tint-toggle { margin-top: 4px; }
/* .marking-opacity's 100px range is sized for create_pet's narrow marking cards;
   here the slider is the whole control, so let it take the tray's width. Scoped
   so the marking editor keeps its compact version. */
.mw-appearance .marking-opacity { gap: 10px; }
.mw-appearance .marking-opacity input[type="range"] { flex: 1 1 auto; width: auto; min-width: 120px; }

/* Commit bar: the spend is its own surface at the end of the flow, not the
   last item in a column of controls. */
.mw-commit {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
    gap: 20px;
    align-items: end;
    margin-top: 24px;
    padding-top: 20px;
    border-top: 1px solid var(--border-color);
}
.mw-commit__summary { min-width: 0; }
.mw-commit__what { margin: 2px 0 8px; font-size: 1.05em; }
.mw-commit__what .text-muted { font-size: 0.82em; font-weight: 400; }
.mw-commit__action { display: flex; flex-direction: column; gap: 8px; }

.mutation-remove-form .hint-warn { margin: 6px 0 0; }

.mw-empty { text-align: center; padding: 40px 20px; color: var(--text-muted); }
.mw-empty__icon { font-size: 2.2em; margin: 0 0 8px; opacity: 0.6; }
.mw-empty p { margin: 0; }

@media (max-width: 1024px) {
    .mw-controls { grid-template-columns: 1fr; }
    .mw-commit,
    .mutation-remove-form { grid-template-columns: 1fr; align-items: stretch; }
}
@media (width < 768px) {
    /* Stack the comparison and drop the arrow: side-by-side at this width made
       both canvases too small to read the very difference they exist to show. */
    .mw-compare { grid-template-columns: 1fr; gap: 16px; }
    .mw-compare__arrow { display: none; }
    .mw-compare .preview-canvas { min-height: 260px; }
    .mw-gallery-grid { grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); }
}
body.reduced-motion .mw-swatch__check { transition-duration: 0.001s; }

.cost-summary {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.cost-summary-label {
    margin: 0 0 2px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.72em;
    font-weight: 700;
}
.cost-line { display: flex; justify-content: space-between; color: var(--text-secondary); font-size: 0.9em; font-variant-numeric: tabular-nums; }
.cost-line--total {
    border-top: 1px solid var(--border-color);
    padding-top: 8px;
    margin-top: 2px;
    font-weight: 700;
    color: var(--text-primary);
    font-size: 1em;
}
.cost-summary-free { color: var(--color-success-text); font-weight: 600; margin: 0; }
/* Why the price is showing instead of "Free forge". Warning-toned, not error-toned:
   nothing has gone wrong, the pet is simply outside what a free forge covers. */
.cost-summary-note { color: var(--color-warning-text); font-size: 0.85em; margin: 0; }
.marking-free-note { color: var(--color-warning-text); font-size: 0.85em; margin: 4px 0 0; }
.cost-summary-balance { color: var(--text-muted); font-size: 0.8em; margin: 0; font-variant-numeric: tabular-nums; }
.btn-block { width: 100%; justify-content: center; }

/* Saved appearance recipes sit below the refraction rather than competing with
   the live preview. The card/list treatment keeps a growing collection scannable
   without turning the workbench into another dense dashboard. */
.saved-designs-panel {
    margin-top: 24px;
    padding: 20px;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}
.saved-designs-heading {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--border-subtle);
}
.saved-designs-eyebrow {
    margin: 0 0 4px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: 0.72em;
    font-weight: 700;
}
.saved-designs-help {
    max-width: 640px;
    margin: 6px 0 0;
    color: var(--text-secondary);
    font-size: 0.9em;
}
.saved-design-count {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: 0.82em;
    font-variant-numeric: tabular-nums;
}
.saved-design-feedback {
    min-height: 1.35em;
    margin: 12px 0 4px;
    color: var(--color-success-text);
    font-size: 0.86em;
}
.saved-design-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 10px;
    margin-top: 8px;
}
.saved-design-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    min-width: 0;
    padding: 13px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.saved-design-card-main {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}
.saved-design-swatches {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    gap: 3px;
    width: 42px;
}
.saved-design-swatch {
    display: block;
    width: 18px;
    height: 18px;
    border: 1px solid var(--border-color);
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px rgb(0 0 0 / 10%);
}
.saved-design-swatch--marking { width: 12px; height: 12px; }
.saved-design-name {
    overflow: hidden;
    margin: 0;
    color: var(--text-primary);
    font-size: 0.96em;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.saved-design-meta {
    margin: 3px 0 0;
    color: var(--text-muted);
    font-size: 0.76em;
    line-height: 1.35;
}
.saved-design-actions {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 6px;
}
.saved-design-actions form { margin: 0; }
.saved-design-empty {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 14px;
    color: var(--text-secondary);
    background: var(--bg-subtle);
    border: 1px dashed var(--border-color);
    border-radius: var(--radius-md);
    font-size: 0.88em;
}
.saved-design-empty strong { color: var(--text-primary); }
.saved-design-editor {
    display: grid;
    grid-template-columns: minmax(180px, 0.7fr) minmax(0, 1.3fr);
    align-items: end;
    gap: 12px 18px;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid var(--border-subtle);
}
.saved-design-editor-copy label {
    display: block;
    color: var(--text-primary);
    font-size: 0.9em;
    font-weight: 700;
}
.saved-design-editor-copy p {
    margin: 4px 0 0;
    color: var(--text-muted);
    font-size: 0.78em;
}
.saved-design-editor-controls {
    display: flex;
    align-items: center;
    gap: 8px;
}
.saved-design-editor-controls input { min-width: 0; flex: 1 1 auto; }
.saved-design-current {
    grid-column: 2;
    margin: -4px 0 0;
    color: var(--text-muted);
    font-size: 0.78em;
}
.saved-design-current strong { color: var(--text-secondary); }

@media (width < 768px) {
    .saved-designs-panel { padding: 16px; }
    .saved-designs-heading { align-items: flex-start; }
    .saved-design-card { align-items: flex-start; flex-direction: column; }
    .saved-design-actions { width: 100%; }
    .saved-design-actions .btn { flex: 1 1 0; justify-content: center; }
    .saved-design-editor { grid-template-columns: 1fr; }
    .saved-design-current { grid-column: 1; }
}

@media (width < 768px) {
    .workbench-grid { grid-template-columns: 1fr; }
    .workbench-aside { position: static; order: -1; }
}

body.reduced-motion .preview-canvas { transition-duration: 0.001s; }
/* Drop the rotation but keep the ring a *state* signal. The `opacity` here has to
   stay scoped to .htmx-request: an unscoped `opacity: .6` has the same specificity
   as both the idle `opacity: 0` and the in-flight `opacity: 1` rules above and
   comes later in the cascade, so it won them both: the ring sat permanently at
   0.6 and a reduced-motion player got no in-flight indication at all. */
body.reduced-motion .preview-canvas::before { animation: none; }
body.reduced-motion .preview-canvas.htmx-request::before { opacity: 0.6; }

/* ============================================================
   DRESSING ROOM (.dr-*)
   Own namespace for the same reason .mw-* is: this is a two-column
   canvas-plus-inventory layout, not another workbench variant.
   ============================================================ */
.dr-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: start; }
@media (width < 768px) { .dr-layout { grid-template-columns: 1fr; } }

.dr-canvas-col, .dr-inventory-col { min-width: 0; }

/* The pet artwork is always a 500x500 canvas, regardless of viewport. The image
   partial also contains its status text and regenerate button below the art, so
   the frame itself can be taller than the portrait while it is polling. The
   overlay below is the actual square coordinate surface. */
.dr-canvas-frame {
    position: relative;
    width: 100%;
    max-width: 500px;
    aspect-ratio: 1 / 1;
    margin: 0 auto;
}

/* _pet_image.html keeps a 500x500 intrinsic size for the generated asset, but
   the dressing-room frame is fluid on phone widths. Let the image follow that
   frame so the fixed canvas contract does not become page-level overflow. */
.dr-canvas-frame > [id^="pet-image-block-"] > img {
    display: block;
    width: 100%;
    max-width: 100%;
    height: auto;
}

/* overflow: visible (not hidden) is deliberate: an item may extend slightly
   beyond the canvas when its resolved artwork bounds allow it. pointer-events:
   none keeps the overlay from intercepting clicks meant for the image or
   regenerate button beneath it; each .dr-chip opts back in. */
.dr-canvas-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    overflow: visible;
    pointer-events: none;
}

/* During a drag or keyboard nudge, show the real transparent apparel art at the
   marker position. Keep its intrinsic aspect ratio: some early apparel assets
   are tightly cropped rather than 500x500 canvases, and stretching those to the
   whole frame makes the drag preview look enormous. The Alpine component scales
   the intrinsic image with the responsive 500x500 canvas. */
.dr-live-layer {
    position: absolute;
    z-index: 1;
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    transform: translate(-50%, -50%) scale(var(--dr-canvas-scale, 1));
    transform-origin: center center;
    pointer-events: none;
    opacity: 0.62;
    filter: drop-shadow(0 0 2px var(--bg-surface));
}

.dr-chip {
    position: absolute;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: 50%;
    border: 2px solid var(--color-primary);
    background: var(--bg-elevated);
    box-shadow: var(--shadow-sm);
    cursor: grab;
    pointer-events: auto;
    /* Without this, a touch drag also scrolls/pans the page underneath it:
       the same reason every other pointer-drag surface in this codebase
       needs it (the browser's default touch gesture always wins otherwise). */
    touch-action: none;
    z-index: 2;
}
.dr-chip:active { cursor: grabbing; }
.dr-chip.is-selected {
    z-index: 5;
    border-color: var(--color-warning, var(--color-primary));
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 35%, transparent), var(--shadow-md);
}
.dr-chip img { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; pointer-events: none; }
.dr-chip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }

.dr-layers { margin-top: 16px; }
.dr-layer-card {
    display: flex;
    /* Wrapping, not nowrap. The v2 card added view toggles, a position-actions group and
       a scale/rotate form to the row, and a nowrap flex line ran to 635px inside a 375px
       phone. `body` is `overflow-x: clip`, so the excess was not scrollable-to: it was
       cut off. "Take off" and the whole transform row were simply gone on a phone,
       present in the DOM and reachable by keyboard, invisible and untappable to touch.
       Wrapping is a no-op at any width where the row already fits, so the live channel's
       shorter card is unaffected. */
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    margin-bottom: 6px;
    background: var(--bg-subtle);
}
.dr-layer-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.dr-layer-card.is-selected { border-color: var(--color-primary); background: var(--bg-hover); }
.dr-layer-card--body { background: var(--bg-hover); border-style: dashed; }
.dr-layer-card__thumb { border-radius: var(--radius-sm); object-fit: cover; }
.dr-layer-card__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dr-layer-card > .dr-inline-form { display: contents; }
.dr-layer-card__position-actions { display: flex; flex-wrap: wrap; gap: 4px; }
.dr-item-card form { width: 100%; max-width: none; margin: 0; }

.dr-inventory-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.dr-tab-btn {
    padding: 6px 12px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-color);
    background: var(--bg-subtle);
    color: var(--text-secondary);
    font-size: 0.85rem;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
@media (hover: hover) { .dr-tab-btn:hover { color: var(--text-primary); border-color: var(--color-primary); } }
.dr-tab-btn.is-active { color: var(--color-primary); border-color: var(--color-primary); background: var(--bg-hover); }
.dr-tab-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

@media (max-width: 480px) {
    .dr-inventory-tabs { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; }
    .dr-tab-btn { flex: 0 0 auto; }
}

@media (pointer: coarse) {
    .dr-chip { width: 44px; height: 44px; }
    .dr-tab-btn { min-height: 44px; }
    .dr-layers .reorder-nudge__btn { width: 44px; height: 44px; }
}

.dr-inventory-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.dr-category-empty { grid-column: 1 / -1; margin: 8px 0; }
.dr-item-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    text-align: center;
    padding: 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.dr-item-card.is-equipped { border-color: var(--color-primary); background: var(--bg-hover); }
.dr-item-card__art {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: var(--radius-md);
    background: var(--bg-hover);
    font-size: 1.8em;
    margin-bottom: 4px;
}
.dr-item-card__art img { border-radius: var(--radius-md); object-fit: cover; }
.dr-item-card__name { font-weight: 600; margin: 0; }

.dr-empty { text-align: center; padding: 40px 20px; color: var(--text-muted); }
.dr-empty__icon { font-size: 2.2em; margin: 0 0 8px; opacity: 0.6; }
.dr-empty p { margin: 0; }
.dr-empty--hint { padding: 20px 12px; margin-bottom: 16px; border: 1px dashed var(--border-color); border-radius: var(--radius-md); }
.dr-empty__actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; margin-top: 12px !important; }

/* Backdrop tab: a settings form, not a card grid, so it deliberately does not
   reuse .dr-inventory-grid's auto-fill grid-template-columns. */
.dr-backdrop-panel { display: flex; flex-direction: column; gap: 14px; max-width: 360px; }
.dr-backdrop-preview {
    width: 100%;
    height: 90px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-hover);
}

/* Visible, not .sr-only: the canvas marker position and the layer-stack save
   are each their surface's only feedback, and an .sr-only status leaves a
   sighted player with nothing (docs/ux-rules.md). Both nodes are also the
   aria-live region, so the two audiences read the same sentence. */
.dr-status {
    margin: 6px 0 0;
    min-height: 1.25em;
    font-size: 0.85rem;
    color: var(--text-muted);
}

body.reduced-motion .dr-chip { transition-duration: 0.001s; }

/* Wardrobe (/apparel/wardrobe/): item-centric, so each row leads with the
   item, not a pet. Reuses .rarity-chip/.empty-state/.page-header rather than
   introducing parallel tokens for things already styled elsewhere. */
.wardrobe-list { display: flex; flex-direction: column; gap: 10px; }
.wardrobe-filters {
    margin-bottom: 14px;
    padding: 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.wardrobe-filters__controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
.wardrobe-filters__controls label { flex: 1 1 180px; margin: 0; }
.wardrobe-filters__controls input, .wardrobe-filters__controls select { width: 100%; }
.wardrobe-filters .form-hint { margin: 8px 0 0; }
.wardrobe-item {
    display: flex;
    gap: 14px;
    align-items: flex-start;
    padding: 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.wardrobe-item__art {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    flex: 0 0 auto;
    border-radius: var(--radius-md);
    background: var(--bg-hover);
    font-size: 1.6em;
}
.wardrobe-item__art img { border-radius: var(--radius-md); object-fit: cover; }
.wardrobe-item__body { min-width: 0; }
.wardrobe-item__name { font-weight: 600; margin: 0 0 4px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wardrobe-item__worn-by { margin: 4px 0 0; font-size: 0.88em; color: var(--text-secondary); }
.wardrobe-dress-menu { margin-top: 10px; }
.wardrobe-dress-menu summary { cursor: pointer; list-style: none; display: inline-flex; }
.wardrobe-dress-menu summary::-webkit-details-marker { display: none; }
.wardrobe-dress-menu__options { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.wardrobe-dress-menu__options form { margin: 0; }

.apparel-inspection { display: grid; grid-template-columns: minmax(180px, 260px) minmax(0, 1fr); gap: 24px; align-items: start; }
.apparel-inspection__art { display: flex; align-items: center; justify-content: center; min-height: 220px; border: 1px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-subtle); font-size: 5em; }
.apparel-inspection__art img { width: 100%; height: auto; max-width: 220px; object-fit: contain; }
.apparel-inspection__body > p:first-child { display: inline-block; }
.apparel-inspection__facts { display: flex; flex-wrap: wrap; gap: 18px; margin: 18px 0; }
.apparel-inspection__facts div { min-width: 92px; }
.apparel-inspection__facts dt { color: var(--text-muted); font-size: .85em; }
.apparel-inspection__facts dd { margin: 2px 0 0; font-size: 1.2em; font-weight: 700; }
.apparel-source-list { padding-left: 20px; }
.apparel-source-list li { margin-bottom: 8px; }

.dr-looks { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border-color); }
.dr-looks form { max-width: none; }
.dr-look-save-form, .dr-look-list, .dr-looks > form:last-child { margin-top: 12px; }
.dr-look-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border-color); }
.dr-look-row span { min-width: 0; display: flex; flex-direction: column; }
.dr-look-row small { color: var(--text-muted); }
.dr-look-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.dr-look-feedback { margin-top: 12px; padding: 10px; border: 1px solid var(--color-warning); border-radius: var(--radius-md); color: var(--text-primary); }
.dr-look-feedback p { margin: 6px 0 2px; }
.dr-look-feedback ul { margin: 4px 0 0; padding-left: 20px; }

@media (max-width: 700px) {
    .apparel-inspection { grid-template-columns: 1fr; }
    .apparel-inspection__art { min-height: 160px; }
    .dr-layer-card__position-actions { width: 100%; }
}

/* NPC Shops (/shops/): reuses .dr-item-card/.dr-inventory-grid/
   .dr-inventory-tabs wholesale (same catalog-card shape as the dressing
   room's inventory), only adding the description line and the per-currency
   buy-button stack a purchasable card needs on top. Shared by all three
   shops (Prism, Stardust, Apparel). */
.shop-item-grid {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
    align-items: stretch;
}
.shop-item-card {
    align-items: stretch;
    min-width: 0;
    min-height: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 16px;
    gap: 8px;
    text-align: left;
}
.shop-item-card .dr-item-card__art {
    align-self: center;
    width: 84px;
    height: 84px;
    margin-bottom: 4px;
    border: 1px solid var(--border-subtle);
}
.shop-item-card .dr-item-card__name {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    min-height: 2.3em;
    text-align: left;
    line-height: 1.25;
}
.shop-item-card > .rarity-chip { align-self: flex-start; }
.shop-item-card__meta-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}
.shop-item-card__meta-row > * { margin: 0; }
.shop-item-card__desc {
    min-height: 3.3em;
    font-size: 0.82em;
    line-height: 1.4;
    margin: 0;
}
.shop-item-card__buy {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    margin-top: auto;
    padding-top: 12px;
    border-top: 1px solid var(--border-subtle);
}
.shop-item-card__buy form { display: flex; flex-direction: column; gap: 4px; }
.shop-item-card__buy .btn {
    width: 100%;
    justify-content: center;
    min-height: 44px;
    padding-inline: 10px;
    line-height: 1.25;
    white-space: normal;
    overflow-wrap: anywhere;
}
.shop-item-card__buy .hint-warn {
    margin: 0;
    padding: 6px 8px;
    font-size: 0.76rem;
    line-height: 1.35;
    text-align: left;
}
/* Apparel shop stock rotation: an out-of-stock card is dimmed AND states the
   reason in real markup (never opacity alone, per the house accessibility
   rule); the variance chip carries a glyph + word so a discount/markup never
   rests on colour alone either. */
.shop-item-card--out-of-stock {
    opacity: 0.6;
}
.shop-item-card--out-of-stock .dr-item-card__art {
    filter: grayscale(1);
}
.price-variance-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    align-self: flex-start;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.74rem;
    font-weight: 600;
    border: 1px solid var(--border-subtle);
}
.price-variance-chip--discount {
    color: var(--color-success-text, var(--color-success));
    background: var(--color-success-bg, transparent);
    border-color: var(--color-success-border, var(--color-success));
}
.price-variance-chip--markup {
    color: var(--color-error-text, var(--color-error));
    background: var(--color-error-bg, transparent);
    border-color: var(--color-error-border, var(--color-error));
}

/* How many copies are left this rotation (ApparelShopStock.quantity_remaining).
   Same chip shape as .price-variance-chip, neutral by default; the --low
   variant (2 or fewer left) borrows the markup chip's warning color so a
   near-sellout reads as urgency without a third color needing a name. */
.stock-remaining-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    align-self: flex-start;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--text-muted);
    border: 1px solid var(--border-subtle);
}
.stock-remaining-chip--low {
    color: var(--color-error-text, var(--color-error));
    background: var(--color-error-bg, transparent);
    border-color: var(--color-error-border, var(--color-error));
}

/* How many copies of this item the viewer already holds in their own
   inventory. Same neutral chip shape as .stock-remaining-chip (that one
   states the shop's shelf count, this one states the player's own), shown
   on every NPC/User Shop item card regardless of whether it's 0. */
.owned-count-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    align-self: flex-start;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--text-muted);
    border: 1px solid var(--border-subtle);
}

/* Bulk-buy quantity row: a number input plus a few quick-pick buttons
   (5/10/99, clamped to the server-computed max-affordable for that item).
   Mirrors .crafting-quantity-row's shape. */
.shop-quantity-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) repeat(3, minmax(32px, auto));
    align-items: center;
    gap: 6px;
}
.shop-quantity-row label {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 4px;
    min-width: 0;
    font-size: 0.82em;
    color: var(--text-muted);
}
.shop-quantity-row input[type="number"] { width: 100%; min-width: 0; padding-inline: 8px; }
.qty-quick-btn {
    min-width: 32px;
    padding: 2px 8px;
    min-height: 26px;
    font-size: 0.76rem;
    border-radius: var(--radius-sm, 4px);
    border: 1px solid var(--border-subtle);
    background: var(--bg-surface);
    color: var(--text-secondary);
    cursor: pointer;
}
@media (hover: hover) { .qty-quick-btn:hover { border-color: var(--color-primary); color: var(--text-primary); } }

/* NPC Shop Hub (/shops/): three storefront cards, each a plain link carrying
   its own shopkeeper preview (avatar + name + a random line, resolved from the
   shared personas registry, see personas/registry.py) so the hub reads as three
   characters, not three currency readouts. The 290px floor keeps the card's
   identity, quote, balance, and action readable before the grid drops a column. */
.shop-hub-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr));
    gap: 16px;
    align-items: stretch;
}
.shop-hub-card {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
    padding: 18px;
    text-decoration: none;
    color: inherit;
    position: relative;
    overflow: hidden;
    transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.shop-hub-card:focus-visible { text-decoration: none; }
@media (hover: hover) { .shop-hub-card:hover { text-decoration: none; } }
.shop-hub-card::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: linear-gradient(90deg, var(--color-primary), transparent);
    opacity: 0.7;
}
.shop-hub-card__avatar {
    flex: none;
    font-size: 2.2rem;
    line-height: 1;
    width: 60px;
    height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--bg-hover);
    border: 1px solid var(--border-subtle);
}
.shop-hub-card__top { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 12px; min-width: 0; }
.shop-hub-card__heading { min-width: 0; }
.shop-hub-card__kicker {
    display: block;
    margin-bottom: 3px;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.64rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}
.shop-hub-card__heading .panel-title { margin: 0; }
.shop-hub-card__npc { margin: 0; font-size: 0.85em; color: var(--text-secondary); }
.shop-hub-card__body { display: flex; flex: 1 1 auto; flex-direction: column; gap: 10px; min-width: 0; }
.shop-hub-card__desc { margin: 0; line-height: 1.45; }
.shop-hub-card__quote {
    margin: auto 0 0;
    font-style: italic;
    color: var(--text-muted);
    font-size: 0.88em;
    line-height: 1.4;
    border-left: 2px solid var(--border-subtle);
    padding-left: 8px;
}
.shop-hub-card__footer {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding-top: 12px;
    border-top: 1px solid var(--border-subtle);
}
.shop-hub-card .cost-summary-balance { margin: 0; padding: 0; }
.shop-hub-card__cta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    font-size: 0.82em;
    font-weight: 600;
    color: var(--color-primary);
}
@media (hover: hover) {
    .shop-hub-card:hover {
        border-color: var(--color-primary);
        box-shadow: var(--shadow-md, var(--shadow-sm));
        transform: translateY(-2px);
    }
}
.shop-hub-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }

@media (pointer: coarse) {
    .qty-quick-btn { min-height: 44px; }
}

/* Shared persona rendering. Domain surfaces may add a dialect-specific class
   (shop banner, workbench panel, or signal plate), but the registry always
   supplies the same portrait/placeholder semantics. */
.persona-surface { min-width: 0; }
.persona-surface__portrait { position: relative; overflow: hidden; }
.persona-surface__portrait img { display: block; width: 100%; height: 100%; object-fit: contain; }
.persona-surface__placeholder {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-size: 2.4rem;
    opacity: 0.72;
}
.persona-surface__body { min-width: 0; }
.persona-surface__name,
.persona-surface__role,
.persona-surface__line { margin-top: 0; }

/* The shopkeeper banner at the top of each individual shop page (Prism/
   Stardust/Apparel). Same portrait-box shape as .crafting-npc__portrait, laid
   out horizontally instead of as a sidebar card since a shop page is a wide
   item grid with no natural sidebar column. */
.shop-npc-banner {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px 18px;
    margin-bottom: 18px;
    border-radius: var(--radius-lg);
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    box-shadow: var(--shadow-sm);
}
.shop-npc-banner__portrait {
    flex: none;
    width: 72px;
    height: 72px;
    border-radius: 50%;
    background: var(--bg-hover);
    border: 1px solid var(--border-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.shop-npc-banner__portrait img { width: 100%; height: 100%; object-fit: contain; }
.shop-npc-banner__placeholder { font-size: 2.2em; opacity: 0.75; }
.shop-npc-banner__body { min-width: 0; }
.shop-npc-banner__name { font-family: var(--font-heading); font-weight: 700; margin: 0 0 2px; }
.shop-npc-banner__line { color: var(--text-muted); font-style: italic; margin: 0; }

.shop-switcher {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: -4px 0 16px;
}
.shop-switcher__label {
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-right: 4px;
}
.shop-switcher__link {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    padding: 7px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    color: var(--text-secondary);
    font-weight: 600;
}
.shop-switcher__link.is-active {
    border-color: var(--color-primary);
    background: var(--bg-hover);
    color: var(--color-primary);
}
.shop-filter-form {
    margin: 0 0 18px;
    padding: 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-surface);
}
.shop-filter-form__head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}
.shop-filter-form__summary { margin: 3px 0 0; color: var(--text-secondary); font-size: 0.84rem; }
.shop-filter-form__grid {
    display: grid;
    grid-template-columns: minmax(180px, 2fr) repeat(3, minmax(120px, 1fr)) minmax(150px, 1.2fr);
    gap: 10px;
    align-items: end;
}
.shop-filter-form__grid .filter-group { min-width: 0; }
.shop-filter-form__grid label {
    display: block;
    margin-bottom: 5px;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.67rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}
.shop-filter-form__grid input,
.shop-filter-form__grid select { width: 100%; min-width: 0; }
.shop-filter-form__price-fields { display: flex; align-items: center; gap: 5px; }
.shop-filter-form__price-fields input { min-width: 0; }
.shop-filter-empty { margin-top: 10px; }

@media (max-width: 1024px) {
    .shop-filter-form__grid { grid-template-columns: repeat(3, minmax(140px, 1fr)); }
    .shop-filter-form__search { grid-column: span 2; }
}
@media (max-width: 600px) {
    .shop-npc-banner { align-items: flex-start; padding: 12px; }
    .shop-filter-form { padding: 12px; }
    .shop-filter-form__grid { grid-template-columns: 1fr; }
    .shop-filter-form__search { grid-column: auto; }
    .shop-filter-form__price-fields { max-width: 18rem; }
}

/* ============================================================
   OBSERVATORY OVERHAUL: nav components, panels, Command Post
   Added for the Deep-Field Observatory identity. Reuses tokens
   above; introduces no new color outside the theme system.
   ============================================================ */

/* ---- Header: currency cluster, weather, notif bell, account menu ---- */
.site-nav-right { gap: 14px; }

/* Site clock: inline in the header from --bp-sm (768px) up; below that it is hidden here
   and shown in the account menu instead (.account-menu-clock), since the phone header row
   has no room beside the currency chips. Tabular figures so the ticking seconds do not
   jitter the width. */
.site-clock {
    display: none;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    font-size: 0.82rem;
    color: var(--text-muted);
    white-space: nowrap;
}
.site-clock__ico { font-size: 0.9rem; line-height: 1; }
.site-clock__time { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.site-clock__tz { font-size: 0.7rem; letter-spacing: 0.02em; }
@media (min-width: 768px) { .site-clock--header { display: inline-flex; } }

.account-menu-clock {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 12px 10px;
}
.account-menu-clock .mono { color: var(--text-primary); font-weight: 700; font-variant-numeric: tabular-nums; }
@media (min-width: 768px) { .account-menu-clock { display: none; } }

.currency-cluster { display: inline-flex; align-items: center; gap: 8px; }
.currency-cluster .nav-currency {
    padding: 5px 11px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    font-family: var(--font-mono);
    font-weight: 700;
    font-size: 0.8rem;
    color: var(--text-primary);
}

.weather-chip {
    font-family: var(--font-mono);
    font-size: 0.74rem;
    letter-spacing: 0.02em;
    background: var(--bg-cool);
    border-color: var(--border-subtle);
}

.notif-bell { position: relative; display: flex; }
.notif-bell-toggle {
    position: relative;
    background: none;
    border: 1px solid transparent;
    cursor: pointer;
    font-size: 1.15rem;
    line-height: 1;
    color: var(--text-secondary);
    padding: 7px;
    border-radius: var(--radius-md);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
@media (hover: hover) { .notif-bell-toggle:hover { background: var(--bg-hover); color: var(--text-primary); } }
.notif-bell-toggle[aria-expanded="true"] { background: var(--bg-hover); color: var(--text-primary); border-color: var(--border-color); }
/* Unread treatment keys off the badge's presence, not a server-rendered class: the
   badge is OOB-swapped on every list refresh, so a class on the button would keep
   glowing after the last notification was read. */
.notif-bell-toggle:has(.notif-badge) { color: var(--text-primary); }
.notif-bell-toggle:has(.notif-badge) .notif-bell-glyph { animation: notif-bell-nudge 4.5s var(--ease) infinite; }
@keyframes notif-bell-nudge {
    0%, 88%, 100% { transform: rotate(0deg); }
    91% { transform: rotate(11deg); }
    94% { transform: rotate(-9deg); }
    97% { transform: rotate(4deg); }
}
.notif-bell-glyph { display: inline-block; transform-origin: 50% 15%; }
/* Explicit `none`, not left to the global reduced-motion block: that one clamps
   animation-duration to 0.001s without clamping iteration-count, which turns an
   infinite animation into a rapid flicker rather than stopping it. Same reason
   .preview-canvas::before is disabled by hand. */
body.reduced-motion .notif-bell-toggle .notif-bell-glyph { animation: none; }
@media (prefers-reduced-motion: reduce) {
    .notif-bell-toggle .notif-bell-glyph { animation: none; }
}
.notif-badge {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    background: var(--color-reward);
    color: #fff;
    font-family: var(--font-mono);
    font-size: 0.62rem;
    font-weight: 700;
    box-shadow: var(--glow-reward);
}

/* Sidebar "you have an incursion in progress" dot on the Incursions link. */
.nav-active-dot { margin-left: 6px; color: var(--color-reward); font-size: 0.7rem; vertical-align: middle; animation: nav-dot-pulse 1.8s ease-in-out infinite; }
@keyframes nav-dot-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }

.notif-dropdown,
.account-menu-dropdown {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    background: var(--bg-elevated);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    z-index: 200;
    overflow: hidden;
}
.notif-dropdown { width: 380px; max-width: calc(100vw - 24px); }
#notif-dropdown-body { display: flex; flex-direction: column; max-height: min(62vh, 560px); max-height: min(62dvh, 560px); }
.notif-panel__loading { margin: 0; padding: 22px; text-align: center; color: var(--text-muted); font-size: 0.86rem; }

/* ---- Notifications: dropdown panel + inbox page ----
   One row markup serves both surfaces (partials/_notification_row.html); everything
   below that differs between them is width and chrome, handled here rather than by
   forking the template. Unread never rests on colour alone: an accent rail, a heavier
   title and a "New" chip all carry it. */

/* Panel chrome (dropdown only: the inbox page uses .page-header instead). */
.notif-panel__head {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    padding: 12px 14px 10px;
    background: var(--bg-elevated);
    border-bottom: 1px solid var(--border-subtle);
}
.notif-panel__head::after {
    content: '';
    position: absolute;
    left: 14px;
    bottom: -1px;
    width: 28px;
    height: 2px;
    background: var(--gradient-primary);
}
.notif-panel__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--text-primary);
}

.notif-tabs { display: inline-flex; gap: 2px; padding: 2px; background: var(--bg-subtle); border: 1px solid var(--border-subtle); border-radius: var(--radius-pill); }
.notif-tab {
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px 11px;
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: 0.66rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}
@media (hover: hover) { .notif-tab:hover { color: var(--text-primary); } }
.notif-tab.is-active { background: var(--bg-elevated); color: var(--color-primary); box-shadow: var(--shadow-sm); }

.notif-panel__foot {
    position: sticky;
    bottom: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 14px;
    background: var(--bg-elevated);
    border-top: 1px solid var(--border-subtle);
}
.notif-panel__foot form { margin: 0; max-width: none; }
.notif-panel__link {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.82rem;
    color: var(--color-primary);
}
@media (hover: hover) { .notif-panel__link:hover { color: var(--color-primary-dark); text-decoration: underline; } }

/* The scrolling middle of the dropdown. On the inbox page it is just a block. */
#notif-dropdown-body .notif-groups { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; }

.notif-group + .notif-group { margin-top: 4px; }
.notif-group__label {
    margin: 0;
    padding: 9px 14px 7px;
    font-family: var(--font-mono);
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-muted);
    background: var(--bg-subtle);
    border-block: 1px solid var(--border-subtle);
}

/* ---- The row ---- */
.notif-row {
    position: relative;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: start;
    gap: 11px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--border-subtle);
}
.notif-row:last-child { border-bottom: none; }
@media (hover: hover) { .notif-row:hover { background: var(--bg-hover); } }
.notif-row--unread { background: color-mix(in srgb, var(--color-primary) 6%, transparent); }
.notif-row--unread::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--gradient-primary);
}
.notif-row__icon {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    font-size: 0.95rem;
    flex-shrink: 0;
}
.notif-row__body { min-width: 0; }
.notif-row__title {
    display: block;
    font-family: var(--font-heading);
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--text-secondary);
    line-height: 1.35;
}
@media (hover: hover) { a.notif-row__title:hover { color: var(--color-primary); text-decoration: none; } }
.notif-row--unread .notif-row__title { color: var(--text-primary); font-weight: 700; }
.notif-row__message {
    margin: 3px 0 0;
    font-size: 0.8rem;
    line-height: 1.45;
    color: var(--text-muted);
    /* Two lines in the panel; the inbox page below lifts the clamp entirely. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.notif-row__meta {
    margin: 6px 0 0;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: 0.66rem;
    color: var(--text-muted);
}
.notif-row__new {
    padding: 1px 7px;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--color-reward) 18%, transparent);
    border: 1px solid color-mix(in srgb, var(--color-reward) 45%, var(--border-color));
    color: var(--color-reward);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* Always visible, never hover-revealed: a hover-only control is unreachable on touch
   and invisible to a keyboard user until it already has focus. */
.notif-row__actions { display: flex; gap: 2px; }
.notif-row__act {
    background: none;
    border: 1px solid transparent;
    cursor: pointer;
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1;
}
@media (hover: hover) { .notif-row__act:hover { background: var(--bg-subtle); border-color: var(--border-color); color: var(--color-primary); } }
@media (hover: hover) { .notif-row__act--dismiss:hover { color: var(--color-error); border-color: color-mix(in srgb, var(--color-error) 45%, var(--border-color)); } }

/* ---- Empty state ---- */
.notif-empty { padding: 34px 20px; text-align: center; }
.notif-empty__glyph { display: block; font-size: 1.9rem; opacity: 0.65; margin-bottom: 8px; }
.notif-empty__title { margin: 0; font-family: var(--font-heading); font-weight: 700; font-size: 0.92rem; color: var(--text-secondary); }
.notif-empty__hint { margin: 5px auto 0; max-width: 34ch; font-size: 0.8rem; color: var(--text-muted); line-height: 1.5; }

/* ---- Inbox page: same rows, page-scale chrome ---- */
.notif-feed {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}
.notif-feed .notif-row { padding: 15px 20px; }
.notif-feed .notif-row__title { font-size: 0.95rem; }
.notif-feed .notif-row__message { -webkit-line-clamp: none; display: block; overflow: visible; }
.notif-feed .notif-group__label { padding: 11px 20px 7px; }
.notif-feed .notif-row__icon { width: 36px; height: 36px; font-size: 1.05rem; }
.notif-feed .staff-pagination { padding: 14px; margin-top: 0; border-top: 1px solid var(--border-subtle); }

.notif-filters {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    max-width: none;
    margin: 0;
    padding: 16px 20px;
    border-bottom: 1px solid var(--border-subtle);
}
.notif-filters label { display: flex; flex-direction: column; gap: 4px; margin: 0; }
.notif-filters select { margin: 0; font-family: var(--font-body); font-size: 0.9rem; }
.notif-filters__count { margin: 0 0 6px; margin-left: auto; font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); }
.notif-filters__count + .btn { margin-bottom: 2px; }
.notif-filters__mark-form { margin: 0 0 0 auto; }
.notif-row__action-form { display: contents; }
.staff-quick-action { display: contents; }

/* ---- Preference toggles ---- */
/* A bare <form> is display:flex/column here, so the submit button would stretch to the
   full panel width without this. */
.notif-prefs { max-width: none; margin: 0; align-items: flex-start; }
.notif-prefs__list { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 8px; }
.notif-pref {
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.notif-pref__label {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0;
    padding: 11px 14px;
    cursor: pointer;
}
.notif-pref--locked .notif-pref__label { cursor: default; }
.notif-pref__label input[type="checkbox"] { margin: 0; flex-shrink: 0; width: 17px; height: 17px; accent-color: var(--color-primary); }
.notif-pref__icon { font-size: 1.05rem; flex-shrink: 0; }
.notif-pref__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.notif-pref__name { font-family: var(--font-heading); font-weight: 600; font-size: 0.9rem; color: var(--text-primary); }
.notif-pref__help { font-size: 0.79rem; color: var(--text-muted); line-height: 1.45; }
@media (hover: hover) { .notif-pref:hover { border-color: var(--border-color); } }
.notif-pref--locked { opacity: 0.78; }
@media (hover: hover) { .notif-pref--locked:hover { border-color: var(--border-subtle); } }

@media (max-width: 600px) {
    .notif-dropdown { width: calc(100vw - 20px); position: fixed; right: 10px; top: calc(var(--header-height) + 6px); }
    .notif-filters { padding: 14px; }
    .notif-filters label { flex: 1 1 140px; }
    .notif-filters__count { margin-left: 0; width: 100%; }
    .notif-feed .notif-row { padding: 13px 14px; }
    .notif-feed .notif-group__label { padding: 10px 14px 6px; }
}

@media (pointer: coarse) {
    .notif-row__act { width: 40px; height: 40px; }
    .notif-tab { min-height: 36px; display: inline-flex; align-items: center; }
}

.account-menu { position: relative; display: flex; }
.account-menu-toggle {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    background: none;
    border: 1px solid transparent;
    cursor: pointer;
    padding: 3px 8px 3px 3px;
    border-radius: var(--radius-pill);
    color: var(--text-secondary);
    font-family: var(--font-heading);
    font-weight: 600;
}
@media (hover: hover) { .account-menu-toggle:hover { background: var(--bg-hover); color: var(--text-primary); } }
.account-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--gradient-primary);
    color: #fff;
    display: grid;
    place-items: center;
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.85rem;
    flex-shrink: 0;
}
.account-name { font-size: 0.92rem; }
.account-menu-dropdown { width: 224px; padding: 6px; }
.account-menu-dropdown a,
.account-menu-dropdown button {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 12px;
    border-radius: var(--radius-md);
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font-heading);
    font-weight: 500;
    font-size: 0.92rem;
}
@media (hover: hover) {
    .account-menu-dropdown a:hover,
    .account-menu-dropdown button:hover { background: var(--bg-hover); color: var(--text-primary); text-decoration: none; }
}
.account-menu-sep { height: 1px; background: var(--border-subtle); margin: 5px 6px; }
.account-menu-form { display: block; margin: 0; max-width: none; }

/* ---- Mobile bottom tab bar (replaces the desktop rail below 1024px) ---- */
.mobile-tabbar { display: none; }
@media (width < 1024px) {
    .mobile-tabbar {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 150;
        display: flex;
        justify-content: space-around;
        gap: 2px;
        padding: 6px 6px calc(6px + env(safe-area-inset-bottom, 0px));
        /* Solid surface, no backdrop-filter: a fixed element with backdrop-filter
           failed to paint as fixed until first scroll on some mobile browsers.
           translateZ(0) also forces its own compositing layer up front so it
           sticks from the initial paint. */
        background: var(--bg-surface);
        border-top: 1px solid var(--border-color);
        transform: translateZ(0);
    }
    .mobile-tab {
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        padding: 6px 2px;
        min-height: 48px;
        border-radius: var(--radius-md);
        color: var(--text-muted);
        font-family: var(--font-heading);
        font-size: 0.64rem;
        font-weight: 600;
        white-space: nowrap;
    }
    @media (hover: hover) { .mobile-tab:hover { text-decoration: none; } }
    .mobile-tab .nav-ico { font-size: 1.25rem; line-height: 1; }
    .mobile-tab.is-active { color: var(--text-primary); }
    .mobile-tab.is-active .nav-ico { filter: drop-shadow(0 0 6px var(--color-primary)); }
}

/* ---- Mobile "More" drawer (full destination list on phone/tablet) ----
   The bottom tab bar carries 4 top destinations + a "More" button that opens
   this drawer, which mirrors the desktop sidebar so every page (Create Pet,
   Factions, Inventory, …) stays reachable ≤899px. Hidden ≥900 where the real
   sidebar takes over. */
button.mobile-tab {
    background: none;
    border: none;
    cursor: pointer;
    font-family: var(--font-heading);
}
.nav-drawer-backdrop {
    position: fixed;
    inset: 0;
    z-index: 160;
    background: var(--modal-overlay);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
}
.nav-drawer {
    position: fixed;
    z-index: 170;
    right: 0;
    top: 0;
    bottom: 0;
    width: min(320px, 84vw);
    display: flex;
    flex-direction: column;
    background: var(--bg-elevated);
    border-left: 1px solid var(--border-color);
    box-shadow: var(--shadow-lg);
    padding: 14px 12px calc(14px + env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
    /* Same reason as .hatch-modal__card: a fling past the end of the destination list
       must not scroll the page underneath the open drawer. */
    overscroll-behavior: contain;
}
.nav-drawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 4px 6px 12px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--border-subtle);
}
.nav-drawer__title {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    font-size: 0.7rem;
    color: var(--text-muted);
}
.nav-drawer__close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
    padding: 8px;
    border-radius: var(--radius-md);
    min-width: 44px;
    min-height: 44px;
}
@media (hover: hover) { .nav-drawer__close:hover { background: var(--bg-hover); color: var(--text-primary); } }
.nav-drawer .sidebar-nav a { font-size: 1rem; padding: 12px 12px; min-height: 44px; }
/* Open/close fade+scale is handled by Alpine's default x-transition (inline
   styles); no CSS transition here so the two don't fight. */
/* Safety: the drawer/backdrop are only reachable via the ≤1023 tab bar, but hard
   hide them once the sidebar takes over so a stale open-state can never leak. */
@media (min-width: 1024px) {
    .nav-drawer, .nav-drawer-backdrop { display: none !important; }
}

/* ---- Site footer ---- */
.site-footer {
    margin-top: 40px;
    padding: 18px 20px;
    border-top: 1px solid var(--border-subtle);
    text-align: center;
}
.site-footer-inner {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    font-size: 0.85rem;
    color: var(--text-muted);
}
.site-footer-inner a { color: var(--text-secondary); }
@media (hover: hover) { .site-footer-inner a:hover { color: var(--text-primary); } }

/* ---- Generic panel + the repeated accent-hairline header motif ---- */
.panel {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 20px;
}
.panel--cool { background: var(--bg-cool); }
.panel__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    position: relative;
    padding-bottom: 12px;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--border-subtle);
}
.panel__header::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -1px;
    width: 30px;
    height: 2px;
    background: var(--gradient-primary);
}
.panel-title { margin: 0; font-family: var(--font-heading); font-size: 1.05rem; font-weight: 700; }
.panel-link { font-family: var(--font-heading); font-weight: 600; font-size: 0.86rem; }
/* Explanatory line under a panel header, above the panel's content. */
.panel-note { margin: -6px 0 14px; font-size: 0.84rem; color: var(--text-secondary); max-width: 78ch; }

/* ---- Readiness marker: icon + text + color, never color alone ---- */
.state-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    background: var(--bg-subtle);
    white-space: nowrap;
}
.state-chip--ready { color: var(--color-reward-ink); border-color: color-mix(in srgb, var(--color-reward-ink) 50%, var(--border-color)); }
.state-chip--active { color: var(--color-primary); border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border-color)); }
.state-chip--warn { color: var(--color-warning-ink); border-color: color-mix(in srgb, var(--color-warning-ink) 45%, var(--border-color)); }
.state-chip--busy { color: var(--text-secondary); border-style: dashed; }
/* A finished-and-successful state (a sold listing), distinct from --ready, which means
   "waiting for you". --muted is the closed-without-happening end (a cancelled listing). */
.state-chip--done { color: var(--color-primary); border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border-color)); }
.state-chip--muted { color: var(--text-muted); border-style: dashed; }
/* A closed-and-lost state (an incursion room whose lock held), distinct from --warn, which
   is a caution about something still ahead. */
.state-chip--bad { color: var(--color-error-ink); border-color: color-mix(in srgb, var(--color-error-ink) 45%, var(--border-color)); }

/* ---- Command Post (dashboard) ---- */
.command-grid { display: grid; grid-template-columns: 1fr; gap: 18px; }
@media (min-width: 768px) {
    .command-grid { grid-template-columns: 1.6fr 1fr; align-items: start; }
}

/* Deliberately a sibling of .command-grid, not a child: see the template comment
   above its markup for why a fourth section can't join the Living Orbit grid. */
.dashboard-news { margin-top: 18px; }
.dashboard-news .news-card-list { margin-top: 4px; gap: 0; }
.dashboard-news .news-card { padding: 14px 0; }
.dashboard-news .news-card + .news-card { border-top: 1px solid var(--border-subtle); }

/* The "New dispatch" strip above .command-grid. Built from the same tokens and the
   same shape as .conditions-strip below, so it inherits all ten Legacy palettes and
   both Frontier modes without a rule of its own per theme. It is deliberately outside
   .command-grid, which is why this one rule serves the Legacy orbit and the Frontier
   command/glance layout alike. The left edge is emphasis only: "new" is carried by the
   eyebrow word, never by the colour. */
.news-strip {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    padding: 14px 20px;
    margin-bottom: 20px;
    background: var(--bg-cool);
    border: 1px solid var(--border-subtle);
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-lg);
}
.news-strip__icon { font-size: 1.6rem; line-height: 1; }
/* 12rem, not 18: an 18rem basis exceeds a phone's content width once the icon and
   actions are counted, which wrapped the strip into three stacked rows (measured 231px
   at 390px wide). At 12rem the icon and body share a row at every width. */
.news-strip__body { flex: 1 1 12rem; min-width: 0; }
.news-strip__title { font-family: var(--font-heading); font-weight: 700; font-size: 1.02rem; margin: 3px 0 0; }
.news-strip__title a { color: var(--text-primary); text-decoration: none; }
.news-strip__title a:focus-visible { text-decoration: underline; }
@media (hover: hover) {
    .news-strip__title a:hover { text-decoration: underline; }
}
/* Two lines is the cap: the excerpt is a pointer to the post, not the post. */
.news-strip__excerpt {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 3px 0 0;
    color: var(--text-secondary);
    font-size: 0.88rem;
}
.news-strip__actions { display: flex; align-items: center; gap: 8px; }
.news-strip__dismiss {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    color: var(--text-muted);
    background: transparent;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    font-size: 0.9rem;
    cursor: pointer;
}
.news-strip__dismiss:focus-visible { color: var(--text-primary); border-color: var(--border-color); }
@media (hover: hover) {
    .news-strip__dismiss:hover { color: var(--text-primary); border-color: var(--border-color); }
}
@media (pointer: coarse) {
    .news-strip__dismiss { width: 44px; height: 44px; }
}
@media (width < 640px) {
    .news-strip__excerpt { -webkit-line-clamp: 1; }
}
@media (width < 480px) {
    /* The title above is already a link to the same post, so "Read ->" is pure
       duplication at the width where the strip can least afford a second row. The
       dismiss control has no such twin and stays. */
    .news-strip__actions .btn { display: none; }
}

.conditions-strip {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    padding: 15px 20px;
    margin-bottom: 20px;
    background: var(--bg-cool);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
}
.conditions-strip__icon { font-size: 1.9rem; line-height: 1; }
.conditions-strip__body { flex: 1; min-width: 0; }
.conditions-strip__name { font-family: var(--font-heading); font-weight: 700; font-size: 1.05rem; margin: 3px 0 0; }
.conditions-strip__note { color: var(--text-secondary); font-size: 0.88rem; margin: 3px 0 0; }

/* The Now / Soon / Recently stack. On wide screens the same three sections become a
   quiet orbit around the player's current work; below the wide line they retain the
   straightforward reading order that is faster on a phone and tablet. */
.command-column { display: grid; gap: 18px; align-content: start; }

@media (min-width: 1280px) {
    .command-grid {
        position: relative;
        grid-template-columns: minmax(15rem, .8fr) minmax(24rem, 1.35fr) minmax(15rem, .8fr);
        grid-template-areas: "soon now glance" "recently now glance";
        align-items: start;
        gap: 22px;
    }
    .command-grid::before {
        content: '';
        position: absolute;
        z-index: 0;
        inset: 7% 23% 7% 23%;
        border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
        border-radius: 50%;
        pointer-events: none;
    }
    .command-column { display: contents; }
    .command-column > section { position: relative; z-index: 1; }
    .command-recently { grid-area: now; align-self: center; }
    .command-column > section:nth-child(2) { grid-area: soon; }
    .command-column > section:nth-child(3) { grid-area: recently; }
    .command-glance { grid-area: glance; position: relative; z-index: 1; align-self: center; }
}

.attention-deck { display: grid; gap: 12px; }
/* Deck/panel empty copy. Not .empty-state: that's the full-page treatment with
   60px of padding, far too tall for a card slot. */
.deck-empty { color: var(--text-secondary); font-size: 0.88rem; margin: 2px 0; }
/* An empty state that offers a way out rather than only reporting emptiness. */
.deck-empty--actionable { display: grid; gap: 10px; }
.deck-empty--actionable p { margin: 0; }

.glance-block { display: grid; gap: 12px; margin-top: 16px; }

/* Shared event banner: the counter is a fact, while the colour is only a supporting
   cue. The text and progressbar carry the meaning when colour is unavailable. */
.void-surge-card { display: grid; gap: 10px; margin-bottom: 20px; }
.void-surge-card__head { display: flex; align-items: start; justify-content: space-between; gap: 14px; }
.void-surge-card__description { color: var(--text-secondary); margin: 0; max-width: 72ch; }
.void-surge-card__meter { height: 10px; overflow: hidden; background: var(--bg-cool); border: 1px solid var(--border-subtle); border-radius: 999px; }
.void-surge-card__meter-fill { display: block; height: 100%; background: var(--color-primary); border-radius: inherit; }
.void-surge-card__progress { display: flex; justify-content: space-between; gap: 12px; color: var(--text-secondary); font-size: .82rem; }
.void-surge-card__progress strong { color: var(--text-primary); font-family: var(--font-mono); }
.void-surge-card__reward { margin: 0; color: var(--color-reward); font-weight: 700; }
.void-surge-card__reward .hint { font-weight: 400; }
.void-surge-card--complete { border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border-color)); }

/* Void Flux consequence markers. Each pairs a glyph with its label, so the
   up/down/sealed distinction never rests on colour alone. */
.flux-effect { font-weight: 700; white-space: nowrap; }
.flux-effect--up { color: var(--color-reward); }
.flux-effect--down { color: var(--color-warning); }
.flux-effect--sealed { color: var(--text-muted); }

/* "Recently": domain-derived activity entries. */
.activity-log { list-style: none; padding: 0; margin: 0; display: grid; gap: 2px; }
.activity-entry {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    padding: 9px 4px;
    border-bottom: 1px solid var(--border-subtle);
    font-size: 0.89rem;
}
.activity-entry:last-child { border-bottom: 0; }
.activity-entry__icon { flex-shrink: 0; }
.activity-entry__verb {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-secondary);
    flex-shrink: 0;
}
.activity-entry__text { flex: 1 1 14rem; min-width: 0; }
.activity-entry__when { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
/* Outcome tone. The verb is always a word ("Extracted" / "Failed"); this only tints the
   word it already says, so the distinction survives colour being unavailable. */
.activity-entry__verb--good { color: var(--color-reward); }
.activity-entry__verb--bad { color: var(--color-warning); }

/* Command Post only. The Recently panel sits in a 276px orbit column (and full-width
   but narrow on a phone), where the default flex row wraps icon, verb, text and time
   onto four lines each: measured at 143px per entry, 913px for six. A fixed two-row
   grid puts the metadata on one line and the text on the next at every width, so an
   entry costs what it should. `_activity_log.html` is shared with the Command Log
   page, which has the full page width and keeps the default. minmax(0, 1fr), never a
   bare 1fr: a long entry would otherwise push its min-content out of the column. */
[data-surface="command-post"] .activity-entry {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr);
    grid-template-areas:
        "icon verb when"
        "text text text";
    align-items: baseline;
    gap: 2px 8px;
    padding: 8px 4px;
}
[data-surface="command-post"] .activity-entry__icon { grid-area: icon; }
[data-surface="command-post"] .activity-entry__verb { grid-area: verb; }
[data-surface="command-post"] .activity-entry__when { grid-area: when; justify-self: end; }
[data-surface="command-post"] .activity-entry__text { grid-area: text; flex: none; }
.command-glance__summary { cursor: pointer; list-style: none; }
.command-glance__summary::-webkit-details-marker { display: none; }
.command-glance__summary::after {
    content: '⌄';
    margin-left: auto;
    color: var(--text-muted);
    font-size: 1.1rem;
}
.command-glance[open] .command-glance__summary::after { content: '⌃'; }
.command-glance__toggle { display: none; color: var(--text-muted); font-size: 0.78rem; }
@media (min-width: 768px) {
    .command-glance__summary { cursor: default; }
    .command-glance__summary::after { display: none; }
}
@media (width < 768px) {
    .command-glance__toggle { display: inline; margin-left: auto; }
    /* The Recently panel, not `.command-recently`: that class is on the *Now* panel
       and contains no .activity-entry, so this rule was a no-op for two releases. */
    .command-activity .activity-entry:nth-child(n + 4) { display: none; }
}
.attention-card {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
    padding: 15px 16px;
    border-radius: var(--radius-lg);
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
}
a.attention-card { color: inherit; text-decoration: none; transition: border-color var(--dur) var(--ease); }
@media (hover: hover) { a.attention-card:hover { border-color: var(--color-primary); text-decoration: none; } }
.attention-card--ready {
    border-color: color-mix(in srgb, var(--color-reward) 55%, var(--border-color));
    box-shadow: var(--glow-reward);
}
.attention-card--faction {
    border-left: 4px solid var(--faction-accent, var(--border-color));
}
/* flex-direction is explicit because this class is also used on a <label>, and the
   global `label` rule sets flex-direction:column; a class beats a type selector on
   display but the column direction still won, stacking the Mutation Workshop's
   "Recolour the overlay" checkbox above its own text. A no-op on div/span users. */
.inline-row { display: flex; flex-direction: row; align-items: center; gap: 8px; flex-wrap: wrap; }
.attention-card__icon {
    font-size: 1.5rem;
    width: 42px;
    height: 42px;
    display: grid;
    place-items: center;
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    flex-shrink: 0;
}
.attention-card__body { flex: 1 1 12rem; min-width: 0; }
.attention-card__title { font-family: var(--font-heading); font-weight: 700; font-size: 1rem; margin: 0; }
.attention-card__meta { color: var(--text-secondary); font-size: 0.85rem; margin: 3px 0 0; }
.attention-card__meta .mono { color: var(--text-primary); }
.attention-card__action { flex-shrink: 0; }

.stat-readout-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 10px;
}
.stat-readout {
    padding: 12px 14px;
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
}
.stat-readout__value {
    font-family: var(--font-mono);
    font-weight: 700;
    font-size: 1.35rem;
    color: var(--text-primary);
    margin-top: 5px;
    font-variant-numeric: tabular-nums;
}
.stat-readout__value small { color: var(--text-muted); font-size: 0.72rem; font-weight: 400; }
.stat-readout__value--warn { color: var(--color-warning); }

.quick-actions { display: flex; flex-wrap: wrap; gap: 10px; }

.starter-cta {
    position: relative;
    overflow: hidden;
    background: var(--bg-surface);
    border: 1px solid color-mix(in srgb, var(--color-reward) 45%, var(--border-color));
    border-radius: var(--radius-lg);
    padding: 22px;
    box-shadow: var(--glow-reward);
    margin-bottom: 20px;
}
.starter-cta h2 { margin: 0 0 6px; }
.starter-cta p { color: var(--text-secondary); margin: 0 0 14px; }

/* ===== BBCODE QUOTE BLOCK ===== */
.bbcode-quote {
    margin: 10px 0;
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--color-primary);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    overflow: hidden;
}

.bbcode-quote-author {
    padding: 5px 12px;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--bg-subtle);
    border-bottom: 1px solid var(--border-color);
}

.bbcode-quote-body {
    margin: 0;
    padding: 8px 12px;
    font-style: italic;
    color: var(--text-secondary);
    background: var(--bg-surface);
    border: none;
    quotes: none;
    word-break: break-word;
}

/* ===== BBCODE SPOILER BLOCK ===== */
.bbcode-spoiler {
    margin: 10px 0;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.bbcode-spoiler-title {
    padding: 6px 12px;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--bg-subtle);
    cursor: pointer;
    user-select: none;
    list-style: none;
}

.bbcode-spoiler-title::-webkit-details-marker {
    display: none;
}

.bbcode-spoiler-title::before {
    content: '▶ ';
    font-size: 0.7em;
}

details.bbcode-spoiler[open] .bbcode-spoiler-title::before {
    content: '▼ ';
}

.bbcode-spoiler-content {
    padding: 8px 12px;
    background: var(--bg-surface);
    word-break: break-word;
}

.bbcode-columns {
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
}
.bbcode-col {
    flex: 1;
    min-width: 150px;
}

.bbcode-h1 {
    font-size: 1.6em;
    font-weight: 700;
    color: var(--text-primary);
    margin: 0.75em 0 0.25em;
    border-bottom: 2px solid var(--border-color);
    padding-bottom: 0.25em;
}
.bbcode-h2 {
    font-size: 1.3em;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0.6em 0 0.2em;
}
.bbcode-h3 {
    font-size: 1.1em;
    font-weight: 600;
    color: var(--text-secondary);
    margin: 0.5em 0 0.15em;
}

.bbcode-divider {
    border: none;
    border-top: 1px solid var(--border-color);
    margin: 1em 0;
}

.bbcode-highlight {
    background: var(--color-warning-bg);
    color: var(--color-warning-text);
    padding: 0 3px;
    border-radius: var(--radius-sm);
}

.bbcode-code {
    background: #1e1e2e;
    border: 1px solid #313244;
    border-radius: var(--radius-md);
    margin: 0.75em 0;
    overflow: hidden;
}
.bbcode-code-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 5px 12px;
    background: #181825;
    border-bottom: 1px solid #313244;
    min-height: 30px;
}
.bbcode-code-lang {
    font-size: 0.75rem;
    color: #a6adc8;
    font-family: monospace;
    text-transform: lowercase;
}
.bbcode-code-copy {
    background: none;
    border: 1px solid #45475a;
    border-radius: var(--radius-sm);
    color: #a6adc8;
    font-size: 0.72rem;
    padding: 1px 8px;
    cursor: pointer;
    margin-left: auto;
    transition: background 0.15s, color 0.15s;
}
@media (hover: hover) {
    .bbcode-code-copy:hover {
        background: #313244;
        color: #cdd6f4;
    }
}
.bbcode-code pre {
    margin: 0;
    padding: 14px 16px;
    overflow-x: auto;
    color: #cdd6f4;
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.875rem;
    line-height: 1.6;
    white-space: pre;
}
.bbcode-code pre code {
    background: none;
    border: none;
    padding: 0;
    color: inherit;
    font-size: inherit;
}

/* ============================================================
   UX PASS: error pages, breadcrumbs, listing surfaces, header
   overflow containment. Tokens only; no new colours.
   ============================================================ */

/* ---- Branded error / recovery pages (404, 403, 500, listing-gone) ---- */
.error-page {
    max-width: 60ch;
    margin: clamp(24px, 8vh, 80px) auto;
    text-align: center;
}
.error-page__glyph { font-size: 3.4rem; line-height: 1; margin: 0 0 10px; }
.error-page__body { color: var(--text-secondary); margin: 12px 0 24px; }
.error-page__actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }

/* ---- Breadcrumbs: contextual back-navigation on detail/help pages ---- */
.breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px;
    font-family: var(--font-heading);
    font-size: 0.82rem;
    color: var(--text-muted);
}
.breadcrumbs a { color: var(--text-secondary); }
@media (hover: hover) { .breadcrumbs a:hover { color: var(--text-primary); } }
.breadcrumbs [aria-current="page"] { color: var(--text-primary); font-weight: 600; }

/* ---- Shared shell for the "explainer" pages: breeding_help, world, trait_codex,
   mutation_codex, incursion_help. Each of these five templates used to carry its own
   copy-pasted <style> block (never migrated here, against the house "no inline styles"
   rule), so a fix to one never reached the other four: world.html had already dropped
   the `max-width: 70ch` below at some point, while the other four still had prose
   sitting far short of its own bordered .breeding-help-section box. One copy now. ---- */
.breeding-help-page { max-width: 900px; margin: 0 auto; padding: 24px 16px; }
.breeding-help-header { margin-bottom: 32px; }
.breeding-help-header h1 { font-size: 1.8rem; margin-bottom: 8px; }
.breeding-help-intro { color: var(--text-secondary); line-height: 1.6; }
.breeding-help-sections { display: flex; flex-direction: column; gap: 32px; }
.breeding-help-section {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: 20px 24px;
    scroll-margin-top: 16px;
}
.breeding-help-section h2 {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 16px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border-subtle);
}
.breeding-help-subhead {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
    margin: 20px 0 8px;
}
.breeding-help-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.breeding-help-table th {
    text-align: left;
    padding: 6px 10px;
    color: var(--text-secondary);
    font-weight: 600;
    border-bottom: 1px solid var(--border-subtle);
}
.breeding-help-table td {
    padding: 8px 10px;
    border-bottom: 1px solid var(--border-subtle);
    vertical-align: top;
}
.breeding-help-table tr:last-child td { border-bottom: none; }
.breeding-help-desc {
    font-size: 0.92rem;
    color: var(--text-secondary);
    line-height: 1.6;
    margin-bottom: 12px;
}
.breeding-help-note { margin-top: 12px; font-size: 0.85rem; color: var(--text-secondary); line-height: 1.6; }
/* Page-closing note that sits outside .breeding-help-sections, so it does not inherit that
   container's 32px gap and needs its own separation. Replaces an inline style on world.html. */
.breeding-help-footer { margin-top: 24px; }
.breeding-help-steps,
.breeding-help-list {
    margin: 0;
    padding-left: 20px;
    color: var(--text-secondary);
    font-size: 0.92rem;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.breeding-help-steps strong,
.breeding-help-list strong,
.breeding-help-note strong,
.breeding-help-desc strong,
.breeding-help-table strong { color: var(--text-primary); }

.trait-codex-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.trait-codex-grid .trait-card { margin-top: 0; }

/* Public persona directory: a compact reference card around the shared portrait
   partial, with the real game surface kept as the primary action. */
.known-characters-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
    gap: 16px;
}
.known-character-card {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
    padding: 16px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.known-character-card__persona { display: flex; flex-direction: column; gap: 12px; }
.known-character-card__portrait {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 4 / 3;
    min-height: 150px;
    overflow: hidden;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-primary) 7%, var(--bg-surface));
}
.known-character-card__art { object-fit: contain !important; }
.known-character-card__placeholder { font-size: 3rem; }
.known-character-card__body { display: flex; flex-direction: column; gap: 3px; }
.known-character-card__name { margin: 0; color: var(--text-primary); font-family: var(--font-heading); font-weight: 700; }
.known-character-card__role { margin: 0; color: var(--text-secondary); font-size: 0.82rem; }
.known-character-card__line { margin: 8px 0 0; color: var(--text-muted); font-size: 0.82rem; line-height: 1.5; }
.known-character-card__species { margin: 0; color: var(--text-muted); font-size: 0.78rem; }
.known-character-card__actions { display: flex; flex-direction: column; gap: 8px; margin-top: auto; }
.known-character-card__actions .btn { width: 100%; min-height: 44px; }
.known-character-card__story { text-align: center; }

/* Roomier than the Workshop's 88px picker grid: this page is for looking at the art,
   not for hitting a target in a form beside a live preview. */
.mutation-codex-section .mw-gallery-grid { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
.mutation-codex-section .mw-gallery-group + .mw-gallery-group { margin-top: 20px; }
.mutation-codex-card {
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 6px 6px 8px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    text-align: center;
}

/* ---- Listing terms: flat price vs accepting-offers, told apart by shape and
   label as well as colour (a bare number and a bare word looked interchangeable). ---- */
.listing-terms {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-color);
    background: var(--bg-subtle);
    font-size: 0.82rem;
}
.listing-terms__label {
    font-family: var(--font-mono);
    font-size: 0.64rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}
.listing-terms--flat { border-color: color-mix(in srgb, var(--color-reward) 45%, var(--border-color)); }
.listing-terms--flat .listing-price { color: var(--color-reward); font-weight: 700; }
/* Dashed edge for offer mode: "no fixed price" reads as an unsettled boundary. */
.listing-terms--offer { border-style: dashed; }
.listing-terms--offer .listing-price { color: var(--text-secondary); }
.listing-terms__or { color: var(--text-muted); font-size: 0.75rem; }

/* A sub-heading inside a panel, below the h2 .panel-title. */
.section-heading {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 0.95rem;
    margin: 4px 0 10px;
}

.listing-card { display: flex; flex-direction: column; gap: 8px; }
.listing-card__seller { color: var(--text-muted); font-size: 0.8rem; margin: 0; }
.listing-card__cta { margin-top: auto; text-align: center; }
@media (hover: hover) { .listing-card__cta:hover { text-decoration: none; } }

.listing-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

/* Result count above a filtered grid. role="status" in the markup, so this is only the
   visual treatment. */
.results-count {
    margin: 0 0 12px;
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: 0.78rem;
    letter-spacing: 0.02em;
}

.listing-detail { display: grid; grid-template-columns: 1fr; gap: 18px; align-items: start; }
@media (min-width: 768px) {
    .listing-detail { grid-template-columns: 1fr 1fr; }
}
.listing-detail__media { text-align: center; }
.listing-detail__media img { max-width: 100%; height: auto; }
.listing-detail__facts { list-style: none; padding: 0; margin: 14px 0 0; display: grid; gap: 10px; }
.listing-detail__facts li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding-bottom: 9px;
    border-bottom: 1px solid var(--border-subtle);
}
.listing-detail__facts li:last-child { border-bottom: 0; padding-bottom: 0; }
.listing-detail__facts .eyebrow { flex: 0 0 7.5rem; }
.listing-detail__note {
    margin: 14px 0 0;
    padding: 10px 14px;
    border-left: 3px solid var(--border-color);
    color: var(--text-secondary);
    font-style: italic;
}

/* ---- Account-menu wallet: the exact balances, in full. The header chips are
   compact and their exact value lives in a title, which a touch device cannot
   show; this block is where those users read the real numbers. ---- */
.account-menu-wallet { display: grid; gap: 5px; padding: 8px 12px 10px; }
.account-menu-wallet__row {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    font-size: 0.84rem;
    color: var(--text-secondary);
}
.account-menu-wallet__row .mono { color: var(--text-primary); font-weight: 700; }

/* ---- Header overflow containment ----
   The header is a flex row whose items all carry white-space: nowrap. Flex items
   default to min-width: auto, so none of them would shrink below their content
   width and the row pushed the whole document wider than the viewport (measured
   375 client / 662 scroll at a 390px viewport). Three parts to the fix:
     1. min-width: 0 on the shrinkable ancestors, so shrinking is permitted at all;
     2. the currency cluster scrolls *within itself* rather than pushing the page,
        and is the only item allowed to shrink (brand, bell and avatar stay put);
     3. compact number formatting server-side, so it rarely needs to scroll.
   overflow-x on the cluster is a containment backstop, not the mechanism: nothing
   is hidden, and every balance is also listed in full in the account menu. */
.site-header { min-width: 0; }
.site-nav-right { min-width: 0; }
.currency-cluster {
    min-width: 0;
    flex: 0 1 auto;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-padding: 2px;
}
.currency-cluster::-webkit-scrollbar { display: none; }
/* Each chip must establish a containing block. `.sr-only` is `position: absolute` with
   no left/top, so it sits at its static position, and its containing block was
   `.site-header` (`position: sticky`, therefore positioned) rather than this cluster.
   A positioned descendant whose containing block sits outside a scroll container is NOT
   clipped by it, so the four 1px exact-value spans escaped to their static positions in
   the cluster's unclipped content: the last landed near x=441 on a 390px phone and
   extended the document's scrollable width to 442. Every authenticated page then laid
   out 52px wider than the screen and rendered shrunk. Nothing VISIBLE overflowed (the
   chips themselves are clipped and scrollable here), and `body { overflow-x: clip }`
   suppressed the sideways scroll, which is why a 1px hidden span survived several
   releases as a global mobile layout break. Do not remove: `position: relative` here is
   what keeps those spans inside the clip. */
.currency-cluster .nav-currency { position: relative; }
.site-brand,
.notif-bell,
.account-menu { flex: 0 0 auto; }
/* Backstop so no future regression elsewhere can widen the document itself. `clip`,
   not `hidden`: `overflow-x: hidden` makes the element a scroll container, which breaks
   `position: sticky` on .site-header. `clip` constrains without that side effect. This is
   a safety net behind the three real fixes above, not the fix. */
body { overflow-x: clip; }

/* Background scroll lock while a modal is open (alpine-components.js::modalShell adds
   and removes this class). Without it a swipe aimed at the hatch reveal scrolls the
   forge behind it, which on a phone is how the payoff moment of the breeding loop got
   dismissed by accident.

   `overflow: hidden` (both axes), NOT the `clip` the rule above uses, and the difference
   is worth stating because the two rules look contradictory. The rule above sets
   overflow-x ALONE, and a single hidden axis forces the other to compute to `auto`,
   which makes <body> a real scroll container and breaks position: sticky on .site-header;
   `clip` carries no such side effect, which is why it is used there. Here both axes are
   set at once, so nothing computes to auto: the value propagates to the viewport and
   <body>'s own used overflow stays `visible`, leaving sticky intact and the scroll
   offset preserved. `clip` would be the riskier pick here, since a non-scrollable
   viewport has nowhere to keep the offset the player scrolls back to on close.

   The padding replaces the scrollbar the lock removes, or the whole page shifts sideways
   under the modal on a mouse device; --scrollbar-gap is measured in JS and is 0 on
   mobile overlay scrollbars. */
body.modal-open {
    overflow: hidden;
    padding-right: var(--scrollbar-gap, 0px);
}

/* ---- Tighter header on small screens ---- */
/* Header condense: below the tablet line the nav-right set (weather + 3 currency
   pills + bell + name + avatar) crowds the bar, so drop the two least-critical
   items (weather, display name) and tighten the currency pills. Tablets ≥768 have
   room for the full set. */
@media (width < 768px) {
    .site-brand { font-size: 1rem; }
    .weather-chip { display: none; }
    .account-name { display: none; }
    .currency-cluster .nav-currency { padding: 5px 8px; font-size: 0.74rem; }
}
/* Smallest phones (--bp-xxs): the brand drops its " Pets" suffix and the header tightens.
   Measured budget at 320px: brand 145 + nav-right 139 + gaps needed 364px against a 320px
   screen. Dropping the suffix reclaims ~45px, which is what actually closes it; shrinking
   the currency pills alone could not (there are only two chips left down here). */
@media (max-width: 380px) {
    .currency-cluster { gap: 5px; }
    .currency-cluster .nav-currency { padding: 4px 6px; font-size: 0.7rem; }
    .site-header { gap: 8px; padding: 0 12px; }
    .site-brand__suffix { display: none; }
}

/* ---- Phone wallet: two chips + a ＋3 opener (--bp-xs) ----
   Separate concern from the phantom-width fix above. Even correctly clipped, five chips
   in a ~92px scrollable strip means a player swipes a 44px-tall box to read their own
   Glimmer, and most of the five balances are off-screen at rest. So below this line the
   cluster carries the two currencies a player actually spends day-to-day and the ＋3
   button opens the account menu's Resources block, which lists all five in full.

   The hidden chips stay in the DOM (display: none, not removed): htmx-init.js's
   currency-updated listener resolves every chip by `#nav-<CURRENCY_FIELDS name>` and must
   still find them, or a spend would leave a stale figure behind the ＋3. */
.nav-currency-more { display: none; }
.nav-currency-more__label { display: none; }
@media (min-width: 480px) and (max-width: 900px) {
    .currency-cluster .nav-currency--glimmer,
    .currency-cluster .nav-currency--bloodline_essence,
    .currency-cluster .nav-currency--star_steel { display: none; }
    .nav-currency-more__4 { display: none; }
    .nav-currency-more {
        flex: 0 0 auto;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 38px;
        min-height: 32px;
        padding: 4px 8px;
        border: 1px dashed var(--border-color);
        border-radius: var(--radius-pill);
        background: var(--bg-subtle);
        color: var(--text-secondary);
        font-family: var(--font-mono);
        font-size: .72rem;
        font-weight: 700;
        cursor: pointer;
    }
    @media (hover: hover) { .nav-currency-more:hover { color: var(--text-primary); border-style: solid; } }
}
@media (max-width: 480px) {
    .currency-cluster .nav-currency--stardust,
    .currency-cluster .nav-currency--bloodline_essence,
    .currency-cluster .nav-currency--star_steel { display: none; }
    /* The brand loses its " Pets" suffix at the phone line, not just below 380px: the
       header budget at 390px is brand 145 + bell 41 + avatar 43, leaving ~130px for a
       wallet that needs ~165px for two chips plus the 44px opener. Dropping the suffix
       frees ~48px, which is what actually makes two visible balances fit. */
    .site-brand__suffix { display: none; }
    /* The cluster must stop shrinking down here, or the flex row compresses it to ~35px and
       clips the second chip: the whole point of this tier is two VISIBLE balances, and with
       `flex: 0 1 auto` the player saw one. Never solve that with `overflow-x: visible`
       instead: the cluster would then spill its chips across the bell and the account
       avatar, which swallows every tap on them. */
    .currency-cluster { flex: 0 0 auto; }
    .nav-currency-more__4 { display: none; }
    .nav-currency-more {
        /* Fixed size, never shrinks: it is a sibling of the cluster, not inside it. */
        flex: 0 0 auto;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 34px;
        min-height: 30px;
        padding: 4px 8px;
        border-radius: var(--radius-pill);
        background: var(--bg-subtle);
        border: 1px dashed var(--border-color);
        color: var(--text-secondary);
        font-family: var(--font-mono);
        font-size: 0.72rem;
        font-weight: 700;
        cursor: pointer;
    }
    @media (hover: hover) { .nav-currency-more:hover { color: var(--text-primary); border-style: solid; } }
}
@media (max-width: 480px) and (pointer: coarse) {
    /* Touch floor, same rule as every other coarse-pointer control. */
    .nav-currency-more { min-width: 44px; min-height: 44px; }
}
/* Narrowest phones: ONE balance. Brand + two chips + opener + bell + avatar does not fit
   360px, and a clipped chip is worse than an honestly hidden one. Glimmer stays because it
   is the everyday spending currency, and the label follows the real hidden count.

   This block MUST come after the 480px one. Both are max-width, so at 320px both match and
   the later wins on equal specificity: while it sat above, the 480 block's
   `.nav-currency-more__4 { display: none }` beat the rule turning it back on and the button
   rendered with no label at all. */
@media (max-width: 380px) {
    .currency-cluster .nav-currency--prisms { display: none; }
    .nav-currency-more__3 { display: none; }
    .nav-currency-more__4 { display: inline; }
}

/* ===== INCURSIONS (Tactical Dice engine) ===== */
/* Small generic helpers used by the incursions templates. */
.incursions-scope { /* placeholder to keep this block greppable */ }
.hint { color: var(--text-muted); font-size: 0.85rem; margin: 4px 0 0; }
.inline-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: 0.8rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; }
.input, .input-sm {
    background: var(--bg-subtle); color: var(--text-primary);
    border: 1px solid var(--border-color); border-radius: var(--radius-sm);
    padding: 8px 10px; font-family: var(--font-body); font-size: 0.95rem;
}
.input-sm { padding: 4px 8px; font-size: 0.85rem; }
.btn-xs { padding: 3px 9px; min-height: 26px; font-size: 0.78rem; }
.badge { display: inline-block; padding: 1px 8px; border-radius: var(--radius-pill); font-size: 0.72rem; font-weight: 700; }
.badge-active { background: var(--color-success-bg); color: var(--color-success-text); border: 1px solid var(--color-success-border); }
.badge-danger { background: var(--color-error-bg); color: var(--color-error-text); border: 1px solid var(--color-error-border); }

/* Staff catalog: every currently-active way an item can be obtained, as a wrapping chip list. */
.catalog-acquisition-tags { display: flex; flex-wrap: wrap; gap: 4px; margin: 0; padding: 0; list-style: none; }
.catalog-acquisition-tags li { white-space: nowrap; }

/* Global construction goal bar */
.goal-panel { margin-bottom: 18px; }
.goal-bar { height: 14px; background: var(--bg-subtle); border-radius: var(--radius-pill); overflow: hidden; border: 1px solid var(--border-color); margin: 10px 0 6px; }
.goal-bar__fill { height: 100%; background: linear-gradient(90deg, var(--color-primary), var(--color-violet)); border-radius: var(--radius-pill); transition: width 0.4s ease; }
.goal-meta { color: var(--text-secondary); font-size: 0.88rem; margin: 0; }

/* Dice chips + dice faces */
.dice-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: var(--radius-pill); font-size: 0.82rem; font-weight: 600; background: var(--bg-subtle); border: 1px solid var(--border-subtle); }
.dice-total { color: var(--text-muted); font-size: 0.82rem; }
.die {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: var(--radius-sm);
    font-weight: 800; font-size: 1.05rem; color: #10121a;
    box-shadow: inset 0 -2px 0 rgba(0,0,0,0.25);
}
/* Color-initial corner tag so dice read without relying on color alone (WCAG 1.4.1). */
.die-tag { position: absolute; top: 1px; left: 3px; font-size: 0.5rem; font-weight: 800; line-height: 1; opacity: 0.75; pointer-events: none; }
.stacked-die .die-tag { top: 0; left: 2px; }
.die-red   { background: #e5573c; }
.die-green { background: #4bc46a; }
.die-blue  { background: #4a9df0; }
.die-gold  { background: #f2c14e; }
.die-star  { color: #10121a; box-shadow: inset 0 0 0 2px rgba(255,255,255,0.55); }

/* Squad manager */
/* 280px was plenty for the old single-line roster row, but the .squad-pet-pick cards
   (thumbnail + name + Lvl/trait/status pills) need more room than that: at 280px the
   card's own padding + the roster box's padding + the row's own padding + a 40px
   thumbnail leaves well under half the card for the pills, so a trait or status chip
   routinely poked out past the row's rounded border instead of wrapping cleanly. */
.squad-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr)); gap: 16px; }
.squad-card { background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px; }
.squad-card__head { display: flex; align-items: center; gap: 8px; }
.drag-handle { cursor: grab; color: var(--text-muted); font-size: 1.1rem; user-select: none; }

/* Keyboard/touch reorder controls: the accessible equivalent of .drag-handle.
   Shared by the squad manager and the barracks roster (see reorderList). */
.reorder-nudge { display: inline-flex; gap: 2px; align-items: center; }
.reorder-nudge__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    font-size: 0.8rem;
    line-height: 1;
    color: var(--text-muted);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    cursor: pointer;
}
@media (hover: hover) { .reorder-nudge__btn:hover { color: var(--text-primary); border-color: var(--color-primary); } }
/* On a pet card the controls float over the artwork; the card itself is a link
   target, so they sit above it rather than inside the flow. */
.reorder-nudge--card {
    position: absolute;
    top: 6px;
    right: 6px;
    z-index: 2;
    padding: 2px 4px;
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
}
.squad-card__name { margin: 0; font-size: 1.05rem; display: flex; align-items: center; gap: 8px; flex: 1; }
.squad-dice { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.squad-stats-line { color: var(--text-secondary); font-size: 0.84rem; margin: 0; }
/* Progress toward the next die of each colour. Dice capacity is floor division, so most
   single stat allocations move nothing; this is the only thing on either screen that
   shows an allocation accumulating. Shared by the squad card and the incursion preflight,
   so the two cannot style the same fact differently. */
.squad-next-die,
.preflight-next-die { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 6px 0 0; }
.next-die-chip {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 1px 7px; border-radius: 999px;
    border: 1px solid var(--border-subtle);
    font-family: var(--font-mono); font-size: 0.74rem; color: var(--text-secondary);
}
.squad-pet-count { color: var(--text-muted); font-size: 0.78rem; font-family: var(--font-mono); white-space: nowrap; }
.squad-members { display: flex; flex-wrap: wrap; gap: 6px; }
.member-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--bg-hover); border-radius: var(--radius-pill); padding: 2px 10px 2px 2px; font-size: 0.82rem; }
.member-chip__thumb { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; background: var(--bg-subtle); }
.member-chip__thumb--pending { display: flex; align-items: center; justify-content: center; font-size: 0.72rem; }
/* Collapsed by default (see squads.html's comment on this toggle); the button itself
   needs no more than the shared .btn-xs styling, already touch-floored inside
   .squad-card by the @media (pointer: coarse) block below. */
.squad-details { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.squad-card__actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rename-wrap { display: inline-flex; gap: 6px; align-items: center; }
.rename-form { display: inline-flex; gap: 6px; }
.squad-roster-wrap { border-top: 1px solid var(--border-subtle); padding-top: 10px; }
.squad-roster { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; max-height: min(420px, 60dvh); overflow-y: auto; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 6px; }
.roster-row-form { display: contents; }
.pet-group-checkbox-form { display: contents; }
.roster-celestial { color: var(--color-warning); }
.roster-full-hint { color: var(--color-warning-text); margin-bottom: 4px; }
.roster-busy { color: var(--color-warning-text); }
.roster-member-tag { color: var(--text-muted); }
/* Wraps the toggle-membership pick card and (when the Commander holds a usable item,
   e.g. Void Fog Essence) a small "feed it to this pet" form beneath it. Was a bare
   .roster-row-form { display: contents } before the feed form existed, so .squad-pet-pick
   sat directly as .squad-roster's own flex item; this div is now that flex item instead,
   deliberately not display:contents, since it needs to actually stack the two forms. */
.squad-pet-row { display: flex; flex-direction: column; gap: 4px; }
.squad-pet-feed {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    padding: 4px 10px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
}
.squad-pet-feed select { flex: 1; min-width: 0; }

/* A sick-pet utility belongs beside its row on wider screens: it stays visually attached
   to the pet while preserving vertical room for the rest of a larger roster. Phones keep
   the stacked form because the row would otherwise squeeze the pet name and status pills. */
@media (min-width: 640px) {
    .squad-pet-row {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(220px, 28%);
        align-items: center;
        gap: 8px;
    }
    .squad-pet-feed {
        padding: 4px 0 4px 10px;
        background: transparent;
        border: 0;
        border-left: 1px solid var(--border-subtle);
        border-radius: 0;
    }
}

/* Squad add/remove + squad-creation pet picker: a big, clearly-selectable row instead of
   a bare native checkbox, via the same visually-hidden-input + :has(:checked) technique as
   .mw-swatch/.species-pick-card. Also the incursion launch screen's own "Adjust party" bench
   (incursions/home.html, partyPicker): the two rosters are deliberately styled as one shared
   design rather than diverging surfaces, so a pick card looks and behaves the same wherever
   a Commander is choosing which pets go somewhere. */
.squad-pet-pick {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    padding: 8px 40px 8px 10px;
    min-height: 44px;
    border: 2px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.squad-pet-pick .pet-picker-thumb,
.squad-pet-pick .pet-picker-thumb-pending { width: 40px; height: 40px; }
.squad-pet-pick input[type="checkbox"] { position: absolute; opacity: 0; width: 1px; height: 1px; }
@media (hover: hover) { .squad-pet-pick:hover { border-color: var(--color-primary); } }
.squad-pet-pick:has(input:checked) { border-color: var(--color-primary); background: var(--bg-hover); }
.squad-pet-pick:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.squad-pet-pick:has(input:disabled) { opacity: 0.45; cursor: not-allowed; }
/* A slightly bigger thumbnail reads as an icon rather than a decorative sliver: scoped
   here rather than resizing the shared .pet-picker-thumb every other picker uses. */
.squad-pet-pick .pet-picker-thumb,
.squad-pet-pick .pet-picker-thumb-pending { width: 40px; height: 40px; font-size: 1rem; }
/* One line: name, then Lvl/trait/status pills, wrapping onto a second line only if a
   narrow card genuinely has no room. Previously name sat on its own line above the
   pills, which read as taller and choppier than it needed to be now that the card has
   room (see .squad-grid's 380px minimum) to fit all of it side by side. Species and level
   stay grouped in the first pill so the name remains the strongest scan target; species
   also feeds the search box via each row's data-search. */
.squad-pet-pick__body { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; flex: 1; min-width: 0; }
.squad-pet-pick__name {
    flex: 1 1 auto;
    min-width: 40px;
    font-weight: 600;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* A non-shrinking group so its pills wrap together onto their own line rather than
   interleaving with a truncating name mid-sentence. */
.squad-pet-pick__meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 0.78rem; flex: 0 1 auto; }
.squad-pet-pick__level {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    color: var(--text-muted);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: 999px;
    padding: 1px 7px;
    white-space: nowrap;
}
/* Selection is not signalled by border colour alone: a checkmark medallion carries it,
   same convention as .mw-swatch__check. */
.squad-pet-pick__check {
    position: absolute;
    top: 50%;
    right: 10px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--gradient-primary);
    color: #fff;
    font-size: 0.75em;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transform: translateY(-50%) scale(0.6);
    transition: opacity 0.12s ease, transform 0.12s ease;
}
.squad-pet-pick:has(input:checked) .squad-pet-pick__check { opacity: 1; transform: translateY(-50%) scale(1); }

/* The squad component rules intentionally keep the desktop controls compact. Keep the
   touch-sized overrides after them so the 44px floor survives the cascade. */
@media (pointer: coarse) {
    .squad-card .btn-xs,
    .squad-card .roster-toggle,
    .squad-pet-feed .input,
    .squad-create-form .input,
    .squad-create-form .btn,
    .squad-search__clear,
    .squad-selected-member__remove { min-height: 44px; }
    .squad-card .reorder-nudge__btn {
        width: 44px;
        height: 44px;
        min-width: 44px;
        min-height: 44px;
    }
    .squad-search__clear,
    .squad-selected-member__remove { width: 44px; min-width: 44px; }
}

/* ── Incursion preflight (launch screen) ─────────────────────────────────────
   The DOM order is the phone order (party -> final dice -> mode -> launch -> world
   context) and the grid only rearranges it from 768px up, so the primary action stays
   near the top of a phone without a second markup path. The whole form is deliberately
   NOT width-capped: it was `max-width: 480px`, a compact single-action rule applied to a
   screen that carries party composition plus mission intelligence, which left half of a
   1440px page empty. */
/* The compact switch in the run board's own header (partials/_board_channel_switch.html).
   The note sits below the button in the header's own note row, alongside the Abandon
   consequence, rather than in a title= that touch and keyboard cannot reach. */
.board-channel-switch { display: contents; }
.board-channel-switch__note { flex-basis: 100%; margin: 0; }

/* The board channel switch on the launch screen (partials/_board_channel_toggle.html).

   Two peer options rather than a checkbox: "on/off" cannot say which board is on, and the
   current channel has to be legible without reading the control's state. The in-use option
   is `disabled` and carries `aria-current`, so the state is in words, in the pressed
   semantics and in the border, never in colour alone. */
.board-channel {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: 8px 12px; margin-bottom: 12px; padding: 8px 12px;
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
.board-channel__lead { flex: 1 1 240px; min-width: 0; }
.board-channel__title {
    margin: 0; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
    font-weight: 700; color: var(--text-muted);
}
.board-channel__note { margin: 0; font-size: 0.78rem; color: var(--text-muted); line-height: 1.4; max-width: 60ch; }
.board-channel__options { display: flex; flex-wrap: wrap; gap: 8px; }
.board-channel__options form { margin: 0; }
.board-channel__option {
    display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
    padding: 8px 12px; min-height: 44px; cursor: pointer;
    background: var(--bg-panel); color: var(--text-primary);
    border: 1px solid var(--border-color); border-radius: var(--radius-sm);
    font: inherit; text-align: left;
}
.board-channel__option-name { font-size: 0.84rem; font-weight: 700; }
.board-channel__option-state { font-size: 0.72rem; color: var(--text-muted); }
.board-channel__option.is-current {
    border-color: var(--color-primary); background: var(--bg-elevated); cursor: default;
    opacity: 1;   /* a disabled button here means "already in use", not "unavailable" */
}
.board-channel__option.is-current .board-channel__option-state { color: var(--color-primary); }
@media (hover: hover) {
    .board-channel__option:not(.is-current):hover { border-color: var(--color-primary); }
}

.preflight { display: flex; flex-direction: column; gap: 14px; }
.preflight__grid { display: grid; grid-template-columns: 1fr; gap: 14px; align-items: start; }
.preflight-stats { color: var(--text-secondary); font-size: 0.86rem; margin: 10px 0 6px; }
.preflight-stats strong { color: var(--text-primary); }

/* The authoritative loadout fragment. It is swapped on every party edit, so it carries a
   pending treatment of its own: without one, a slow request leaves the previous total on
   screen looking current. */
.preflight-loadout { display: flex; flex-direction: column; gap: 10px; }
.preflight-loadout.htmx-request { opacity: 0.55; transition: opacity 0.12s ease; }
.preflight-loadout__total { margin: 0; font-size: 1.05rem; color: var(--text-secondary); }
.preflight-loadout__total strong { font-size: 1.5rem; color: var(--text-primary); }
.preflight-loadout__party-size { font-size: 0.85rem; color: var(--text-muted); }
.preflight-dice { display: flex; flex-wrap: wrap; gap: 6px; }
.preflight-sources { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.preflight-sources__row { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.4; }
.preflight-traits { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding-top: 8px; border-top: 1px solid var(--border-subtle); }
.preflight-traits .eyebrow { flex-basis: 100%; }
.preflight-traits__effect { flex-basis: 100%; margin: 0; font-size: 0.76rem; color: var(--text-secondary); line-height: 1.4; }
.preflight-warn { margin-top: 4px; font-size: 0.78rem; }
.preflight-blockers { padding: 8px 10px; border: 1px solid var(--color-error-border); background: var(--color-error-bg); border-radius: var(--radius-sm); }
.preflight-blockers__list { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; font-size: 0.82rem; color: var(--color-error-text); line-height: 1.4; }

.preflight__commit { display: flex; flex-direction: column; gap: 12px; }
.mode-choice__note { margin: 2px 0 0; }
.btn-launch { align-self: flex-start; min-height: 44px; padding-inline: 28px; font-size: 1rem; }

/* World context: a one-line summary that expands. Native <details> rather than an Alpine
   toggle, so it works with JS absent and is keyboard/screen-reader navigable by default. */
.preflight__world { display: flex; flex-direction: column; gap: 8px; }
.preflight-world-card { background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: var(--radius-md); }
.preflight-world-card > summary { display: flex; align-items: center; gap: 10px; padding: 10px 12px; cursor: pointer; min-height: 44px; }
.preflight-world-card__icon { font-size: 1.3rem; line-height: 1; }
.preflight-world-card__lead { display: flex; flex-direction: column; gap: 1px; }
.preflight-world-card__name { font-weight: 600; font-size: 0.9rem; }
.preflight-world-card__body { margin: 0; padding: 0 12px 12px; font-size: 0.82rem; color: var(--text-secondary); line-height: 1.45; }

@media (min-width: 768px) {
    /* Party gets the wider column: it holds a roster, the conditions column holds figures. */
    .preflight__grid { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
    .preflight__commit,
    .preflight__world { grid-column: 1 / -1; }

    /* A WRAPPING row in which each fieldset owns a full line, rather than one nowrap row
       shared by every child. This block was written when .preflight__commit had exactly two
       children (the dice-rules fieldset and the launch button), so a single row was the whole
       layout. The Resonance Trials "Run track" fieldset was later added as a THIRD child;
       being a flex item its automatic minimum size is min-content, and its two-column
       .track-choice__options grid makes that ~737px of a 1009px row. That left the
       dice-rules chooser (flex: 1 1 0) with 101px, rendering Pathfinder and Blind as two
       101px-wide, 500px-tall slivers of wrapped text for every account on the allowlist.
       Ordinary accounts never saw it: without the track fieldset the original two-child row
       still resolved correctly, which is why it survived.

       Giving each fieldset its own line keeps its own option cards side by side (~494px
       each) in both states, and no future sibling added here can starve one of the others. */
    .preflight__commit { flex-flow: row wrap; align-items: flex-end; gap: 16px 20px; }
    .preflight__commit > .track-choice { flex: 1 1 100%; }
    .preflight__commit .mode-choice { flex: 1 1 100%; flex-direction: row; flex-wrap: wrap; }
    .preflight__commit .mode-option { flex: 1 1 280px; }
    /* Keeps the launch button on the right of its own final line, as it sat on the right of
       the shared row before. */
    .preflight__commit .btn-launch { margin-left: auto; }
    .mode-choice__note { flex-basis: 100%; }
    .preflight__world { flex-direction: row; }
    .preflight-world-card { flex: 1 1 0; }
}

.preview-members { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.preview-member { display: flex; justify-content: space-between; gap: 12px; font-size: 0.82rem; }
.preview-member__name { color: var(--text-primary); }
.preview-member__stats { color: var(--text-muted); white-space: nowrap; }
.preview-member__gold { color: var(--color-warning); }

/* Inline help affordance (e.g. Void Stress explainer) */
.help-glyph { cursor: help; opacity: 0.65; font-size: 0.85em; }

/* Marketplace bulk lister: a fixed-height scrollable pet picker (thumbnail + name +
   species) so a Commander with a large roster isn't scrolling a full-page grid, plus the
   per-pet pricing table it feeds. Forms are unconstrained width by default (see
   ".form-compact" under "FORMS") — this one genuinely needs the room for a multi-column
   picker/pricing table, so it's left at the default rather than opting into the compact
   cap the way the small single-action forms elsewhere on this page do. */
.pet-picker-box { display: flex; flex-direction: column; gap: 3px; max-height: 340px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 6px; margin-top: 8px; }
.pet-picker-row { display: flex; align-items: center; gap: 10px; width: 100%; padding: 5px 8px; border: 1px solid transparent; border-radius: var(--radius-sm); background: none; cursor: pointer; text-align: left; font: inherit; color: inherit; }
@media (hover: hover) { .pet-picker-row:hover { background: var(--bg-hover); } }
.pet-picker-row.is-selected { border-color: var(--color-primary); background: var(--bg-subtle); }
/* .pet-picker-thumb / .pet-picker-thumb-pending are also reused by the squad roster
   (_squad_list.html) and the Star Map party bench (_map_nodes_inner.html): the same
   small-thumbnail row treatment, on top of each page's own existing checkbox markup
   rather than a duplicate set of thumbnail rules per page. */
.pet-picker-thumb, .pet-picker-thumb-pending { width: 32px; height: 32px; border-radius: var(--radius-sm); flex-shrink: 0; object-fit: cover; }
.pet-picker-thumb-pending { display: flex; align-items: center; justify-content: center; background: var(--bg-subtle); font-size: 0.85rem; }
.pet-picker-name { font-weight: 500; }
.pet-picker-species { color: var(--text-muted); font-size: 0.82rem; }
.pet-picker-check { margin-left: auto; color: var(--color-primary); }

.bulk-fill-row { display: flex; flex-wrap: wrap; gap: 16px; margin: 12px 0; }
.bulk-fill-row .form-group { margin: 0; }
.bulk-fill-row .form-group > div { display: flex; gap: 6px; }
.bulk-fill-row input[type="number"] { width: 100px; }

/* The pricing table itself is a plain <table> — the global `table` rule (see "TABLES"
   above) already gives it the display:block self-scroll technique, borders, and cell
   padding; this only adds the one thing that's specific to it, the input width. Setting
   width:100% here would fight that global rule's width:max-content and force the table
   narrower than its content needs, which is what caused the pricing table to clip with a
   spurious horizontal scrollbar even on a wide page. */
.bulk-price-table input[type="number"] { width: 90px; }

/* ── Run board: the command table ────────────────────────────────────────────
   One markup path, three compositions. Phone: a single column with the dice in the inline
   encounter hand. Tablet (768-1023): still one column, because a 260px reference rail beside
   a multi-slot room card left roughly 560px for the board itself. Desktop (1024+): a sticky
   command rail beside the encounter, placed by grid-template-areas so the DOM can stay in
   phone order (HUD -> encounter -> route -> notes) regardless of which side the rail is on. */
.board-shell { display: flex; flex-direction: column; gap: 14px; }
.board-layout { display: grid; grid-template-columns: 1fr; gap: 16px; align-items: start; }
.board-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.board-rail { min-width: 0; }

/* Persistent run HUD. Six facts, no explanations: the explanations live in the field notes
   and the help guide, and repeating them here is what made the old tray 953px tall. */
.run-hud {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 2px;
    background: var(--border-subtle);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.run-hud__cell { display: flex; flex-direction: column; gap: 1px; padding: 8px 12px; background: var(--bg-surface); min-width: 0; }
.run-hud__label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.run-hud__value { font-size: 1.15rem; font-weight: 700; color: var(--text-primary); overflow-wrap: anywhere; }
.run-hud__value--haul { font-size: 0.95rem; color: var(--color-success-text); }
.run-hud__value--loss { color: var(--color-error-text); }
.run-hud__sub { font-size: 0.7rem; color: var(--text-muted); line-height: 1.35; }
.run-hud__hand-summary { display: flex; flex-wrap: wrap; gap: 0 4px; }
.run-hud__hand-label { color: var(--text-secondary); }
.run-hud__next { display: flex; flex-direction: column; gap: 1px; margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--border-subtle); }
.run-hud__next-label { font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.run-hud__next-value { font-size: 0.82rem; font-weight: 700; color: var(--color-success-text); white-space: nowrap; }
.run-hud__next-note { font-size: 0.66rem; color: var(--text-muted); line-height: 1.3; }
.run-hud__cell--stress:not([data-stress="0"]) { background: var(--color-error-bg); }
.run-hud__cell--stress:not([data-stress="0"]) .run-hud__value { color: var(--color-error-text); }

/* Reachable-room switcher, beside the encounter it changes. */
.room-switcher { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.room-switcher__label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.room-switcher__btn {
    min-height: 36px; padding: 6px 14px;
    background: var(--bg-subtle); color: var(--text-secondary);
    border: 1px solid var(--border-color); border-radius: var(--radius-pill);
    font: inherit; font-size: 0.85rem; cursor: pointer;
}
@media (hover: hover) { .room-switcher__btn:hover { border-color: var(--color-primary); } }
.room-switcher__btn.is-selected { border-color: var(--color-primary); background: var(--bg-surface); color: var(--text-primary); font-weight: 600; }

/* Route choice: the next-room decision is deliberately above the full encounter. The old
   pill switcher was easy to miss on phones and repeated the route map without exposing the
   stakes that make one branch preferable to another. */
.room-chooser { display: flex; flex-direction: column; gap: 10px; }
.room-chooser__head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 8px 18px; }
.room-chooser__title { margin: 2px 0 0; font-size: 1rem; }
.room-chooser__note { margin: 0; max-width: 58ch; font-size: 0.78rem; color: var(--text-muted); line-height: 1.4; }
.room-chooser__options { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; }
.room-chooser__option {
    display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
    min-height: 104px; padding: 10px 12px; text-align: left; font: inherit; color: inherit;
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md); cursor: pointer;
}
@media (hover: hover) { .room-chooser__option:hover { border-color: var(--color-primary); background: var(--bg-hover); } }
.room-chooser__option.is-selected { border-color: var(--color-primary); box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 35%, transparent); background: var(--bg-subtle); }
.room-chooser__option-head { display: flex; align-items: center; gap: 6px; width: 100%; }
.room-chooser__icon { font-size: 1.05rem; }
.room-chooser__name { font-weight: 700; }
.room-chooser__selected { margin-left: auto; color: var(--color-primary); font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.room-chooser__type { color: var(--text-muted); font-size: 0.72rem; }
.room-chooser__reward { color: var(--color-success-text); font-size: 0.78rem; line-height: 1.3; }
.room-chooser__risk { color: var(--color-warning-text); font-size: 0.74rem; line-height: 1.3; }
.room-chooser__staged { color: var(--color-primary); font-size: 0.72rem; }
.room-chooser__playability { font-size: 0.72rem; line-height: 1.3; }
.room-chooser__playability--clean { color: var(--color-success-text); }
.room-chooser__playability--forced { color: var(--color-warning-text); }
.room-chooser__playability--blind { color: var(--color-info-text); }
.room-chooser__playability--blocked { color: var(--color-warning-text); }

.route-node__playability { display: block; color: var(--color-warning-text); font-size: 0.72rem; line-height: 1.3; }

/* The active encounter: the largest, clearest object on the board. */
.encounter { background: var(--bg-surface); border: 1px solid var(--color-primary); border-radius: var(--radius-lg); overflow: hidden; box-shadow: 0 2px 14px rgba(0,0,0,0.16); scroll-margin-top: calc(var(--header-height) + 14px); }
.encounter__body { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px; }
.encounter__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
.encounter__title { margin: 0; font-size: 1.15rem; }
.encounter__step { margin: 0 0 2px; color: var(--text-muted); font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em; }
.encounter__step span { color: var(--color-primary); font-weight: 700; }
.encounter__instruction { margin: -2px 0 0; color: var(--text-secondary); font-size: 0.86rem; line-height: 1.35; }
.encounter__stakes { display: flex; flex-direction: column; gap: 6px; margin: 0; }
.encounter__stake { display: flex; flex-direction: column; gap: 4px; min-width: 0; font-size: 0.86rem; }
.encounter__stake dt { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.encounter__stake dd { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px; margin: 0; min-width: 0; }
.encounter__stake--reward dd { color: var(--color-success-text); }
.encounter__stake--penalty dd { color: var(--color-warning-text); }
.encounter__stake-value { font-weight: 700; }
.encounter__stake-condition { color: var(--text-secondary); }
.encounter__stake-free { color: var(--text-muted); }
.encounter__unlocks { margin: 0; font-size: 0.8rem; color: var(--text-muted); }
.encounter-empty { text-align: center; }

/* The card's action hierarchy: a quick deterministic placement, an inline hand on touch
   surfaces, then the lock itself. The desktop rail remains the spacious reference surface. */
.encounter-quick-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.encounter-quick-actions__hint { color: var(--text-muted); font-size: 0.74rem; line-height: 1.35; }
.encounter-hand { display: none; flex-direction: column; gap: 6px; padding: 10px 0 2px; border-top: 1px solid var(--border-subtle); }
.encounter-hand__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.encounter-hand__head h3 { margin: 0; font-size: 0.84rem; }
.encounter-hand__head > span { color: var(--text-muted); font-size: 0.76rem; }
.encounter-hand__head strong { color: var(--color-primary); }
.encounter-hand__hint { margin: 0; color: var(--text-muted); font-size: 0.74rem; line-height: 1.35; }
.encounter-hand__dice { min-height: 40px; }
.room-lock__blocked { margin: 10px 0 0; padding: 9px 11px; border-left: 3px solid var(--color-warning); background: var(--bg-subtle); color: var(--color-warning-text); font-size: 0.82rem; line-height: 1.4; }
.room-fail-form { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin-top: 12px; padding: 10px 12px; border: 1px solid var(--color-warning); border-radius: var(--radius-md); background: var(--bg-subtle); }
.room-fail-form__note { flex: 1 1 100%; margin: 0; color: var(--color-warning-text); font-size: 0.8rem; line-height: 1.4; }
.encounter-retreat { display: none; align-items: center; flex-wrap: wrap; gap: 8px 12px; padding: 10px 0 0; }
.encounter-retreat .hint { margin: 0; font-size: 0.74rem; }

/* Art slot: a designed placeholder that reserves the final aspect ratio, so dropping real
   art in later shifts no layout. Never an empty frame labelled "art coming soon". */
.incursion-room-art,
.incursion-boss-art {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    height: 72px;
    aspect-ratio: auto;
    max-height: none;
    background:
        radial-gradient(farthest-side at 50% 120%, color-mix(in srgb, var(--color-primary) 24%, transparent), transparent),
        linear-gradient(160deg, var(--bg-subtle), var(--bg-hover));
    border-bottom: 1px solid var(--border-subtle);
}
.incursion-boss-art { height: 92px; }
.incursion-room-art__glyph,
.incursion-boss-art__glyph { font-size: 2rem; opacity: 0.5; }
.incursion-boss-art__glyph { font-size: 2.5rem; }

/* A named floor modifier, stated where the floor is named. */
.floor-modifier { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 8px 0 0; }
.floor-modifier__blurb { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.4; }

/* ── Room resolution: the primary feedback moment ──────────────────────────── */
.room-result {
    border: 1px solid var(--border-color);
    border-left: 4px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    padding: 14px 16px;
    display: flex; flex-direction: column; gap: 10px;
}
/* Programmatic focus lands here after every resolve, so the ring hangs off plain :focus:
   a browser will not reliably treat programmatic focus as :focus-visible. */
.room-result:focus { outline: 2px solid var(--color-primary); outline-offset: 3px; }
/* Outcome is carried by the heading's own words and glyph; these only tint what already
   says the thing. */
.room-result--clean { border-left-color: var(--color-success); }
.room-result--forced { border-left-color: var(--color-warning); }
.room-result--fail,
.room-result--collapse { border-left-color: var(--color-error); }
.room-result__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.room-result__verdict { margin: 0; font-size: 1.1rem; }
.room-result--clean .room-result__verdict { color: var(--color-success-text); }
.room-result--forced .room-result__verdict { color: var(--color-warning-text); }
.room-result--fail .room-result__verdict,
.room-result--collapse .room-result__verdict { color: var(--color-error-text); }

.result-slots { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.result-slot { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; background: var(--bg-subtle); border-radius: var(--radius-sm); }
.result-slot__dice { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.result-slot__arrow { color: var(--text-muted); }
.result-slot__outcome { margin: 0; font-size: 0.84rem; color: var(--text-secondary); }
.result-slot--open .result-slot__outcome { color: var(--color-success-text); }
.result-slot--forced .result-slot__outcome { color: var(--color-warning-text); }
.result-slot--shut .result-slot__outcome { color: var(--color-error-text); }
/* The die that opened the slot, marked with a ring rather than by colour alone: its
   accessible name says "opened the lock" too. */
.die-winner { box-shadow: inset 0 0 0 2px var(--text-primary), 0 0 0 2px var(--color-success); }

.room-result__spent { margin: 0; font-size: 0.82rem; color: var(--text-secondary); }
.room-result__spent strong { color: var(--text-primary); }
.result-changes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.result-change { font-size: 0.86rem; }
.result-change--good { color: var(--color-success-text); }
.result-change--bad { color: var(--color-error-text); }
.result-change--route { color: var(--color-primary); }
.result-change--neutral { color: var(--text-muted); }
.result-change__running { color: var(--text-muted); font-size: 0.8rem; }
.room-result__boss { margin: 0; font-size: 0.86rem; color: var(--text-secondary); }
.room-result__xp { list-style: none; margin: 4px 0 6px; padding: 0; }
.room-result__xp li {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    align-items: baseline;
    font-size: 0.84rem;
}
.room-result__xp-name { flex: 1 1 auto; }
.room-result__xp .rested-credit,
.room-result__xp .xp-capped-note { flex: 1 0 100%; }

/* Entrance only. Every value is already in the DOM and readable; this staggers the slots so
   the causal story reads in order. body.reduced-motion clamps all of it globally. */
@keyframes result-slot-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}
.room-result.is-revealing .result-slot {
    animation: result-slot-in 0.32s ease both;
    animation-delay: calc(var(--reveal-index, 0) * 0.18s);
}
.room-result.is-revealing .result-changes { animation: result-slot-in 0.3s ease 0.5s both; }

/* Route: compact nodes. A locked node used to repeat its full reward sentence, its penalty
   sentence, "a clean clear costs nothing" and the word Locked twice; seven of those filled a
   phone screen above the one room that could be played. */
.route { display: flex; flex-direction: column; gap: 10px; }
.route__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.route__title { margin: 0; font-size: 1rem; }
.route__note { margin: 0; font-size: 0.78rem; color: var(--text-muted); }
.route-node {
    position: relative; z-index: 1;
    flex: 1 1 170px; max-width: 240px; min-width: 0;
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md); padding: 8px 10px;
}
/* Focused programmatically after a resolve swap (restoreFocus), so the ring has to come
   from :focus; a browser will not always treat programmatic focus as :focus-visible, and
   without it a keyboard player has no idea where they landed. */
.route-node:focus { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.route-node--cleared { opacity: 0.72; border-color: var(--color-success-border); }
.route-node--failed { opacity: 0.6; border-color: var(--color-error-border); }
.route-node--skipped { opacity: 0.52; border-color: var(--border-subtle); border-style: dashed; }
.route-node--locked { opacity: 0.75; }
.route-node--available { border-color: var(--color-primary); }
.route-node.is-selected { box-shadow: 0 0 0 2px var(--color-primary); }
.route-node__select {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    width: 100%; min-height: 44px; padding: 0;
    background: none; border: 0; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.route-node__head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.route-node__icon { font-size: 1rem; }
.route-node__name { font-size: 0.86rem; font-weight: 600; }
.route-node__staged { flex-basis: 100%; font-size: 0.72rem; color: var(--color-primary); }
.route-node__detail { margin-top: 6px; font-size: 0.76rem; }
.route-node__detail > summary { cursor: pointer; color: var(--text-muted); min-height: 24px; }
.route-node__detail .room-reward,
.route-node__detail .room-penalty { margin: 4px 0 0; font-size: 0.76rem; }

/* Field notes: everything that explains the run rather than being part of the next decision.
   Native <details>, so it works with JS absent and is keyboard-navigable by default. */
.field-notes { display: flex; flex-direction: column; gap: 6px; }
.field-notes__heading { margin: 0 0 2px; font-size: 0.9rem; }
.field-note { background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: var(--radius-md); }
.field-note > summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 12px; min-height: 44px; cursor: pointer; }
.field-note__label { font-size: 0.84rem; font-weight: 600; }
.field-note__value { font-size: 0.84rem; color: var(--text-secondary); font-family: var(--font-mono); }
.field-note__body { padding: 0 12px 12px; font-size: 0.82rem; color: var(--text-secondary); line-height: 1.5; }
.field-note__body p { margin: 0 0 6px; }
.field-note[data-stress]:not([data-stress="0"]) { border-color: var(--color-error-border); }
.field-note--warn { border-color: var(--color-warning-border); padding: 8px 12px; }
.field-note--warn .hint { margin: 0; }
.board-squad__trait-effect { font-size: 0.74rem; color: var(--text-muted); padding-left: 2px; }

/* ── Dice tray: sticky rail at 1024+; touch layouts use the inline hand ──────── */
.dice-tray { display: flex; flex-direction: column; gap: 10px; background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 12px; }
.dice-tray__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.dice-tray__title { margin: 0; font-size: 0.85rem; font-weight: 600; }
.dice-tray__count { font-size: 1.3rem; font-weight: 800; color: var(--color-primary); }
.dice-tray__body { display: flex; flex-direction: column; gap: 10px; }
.dice-tray__hint { margin: 0; font-size: 0.76rem; color: var(--text-muted); line-height: 1.35; }
.dice-tray__mode-note { margin: 0; font-size: 0.74rem; }
.dice-pool { display: flex; flex-wrap: wrap; gap: 8px; }
/* A die is a real button: HTML5 drag events never fire on touch, so tap-to-place is the
   only interaction that works on the device this board is mostly played on. */
.die-btn { border: 0; cursor: pointer; font: inherit; }
@media (hover: hover) { .die-btn:hover { transform: translateY(-1px); } }
.lock-slot-v2.is-armed { border-style: solid; border-color: var(--color-primary); background: var(--bg-subtle); }
.slot-controls { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.slot-arm { flex: 0 0 auto; min-height: 32px; }
.slot-controls .slot-pick { flex: 1 1 140px; width: auto; }
.meter-note { font-size: 0.72rem; color: var(--text-muted); line-height: 1.35; }
.btn-attempt { min-height: 44px; }
/* Pathfinder dice-match assist: ring the pool dice that open the hovered/focused slot. */
.die.die-hint-match { outline: 2px solid var(--color-success); outline-offset: 2px; }
.die.die-hint-sacrifice { outline: 2px dashed var(--color-warning); outline-offset: 2px; }
/* Void Sickness forewarning: shown before the Fail that would trigger a cooldown. */
.hint-warn {
    color: var(--color-warning-text);
    background: var(--color-warning-bg);
    border: 1px solid var(--color-warning-border);
    border-radius: var(--radius-sm);
    padding: 6px 8px;
    line-height: 1.4;
}
.void-sickness-warn { margin-top: 10px; font-size: 0.76rem; }

/* The Void Flux this run was launched under (frozen at start_run). */
/* "Frozen at launch" was a title=, i.e. unreadable on the phones this board is played on. */

/* Void Resonance: what this floor's boss pays, and what the next one would. */

.tray-actions { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-subtle); display: flex; flex-direction: column; gap: 6px; }
.tray-actions__hint { margin: 0; font-size: 0.74rem; color: var(--text-muted); line-height: 1.35; }

/* The minimap is a visual route thumbnail, not a second room-card surface. The graph is
   laid out server-side from the same RoomNode rows/edges as the full map; only reachable
   nodes are buttons, so the click target remains an existing room-selection action. */
.incursion-minimap { display: flex; flex-direction: column; gap: 8px; padding: 10px; background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: var(--radius-md); }
.incursion-minimap__head { display: flex; align-items: end; justify-content: space-between; gap: 8px; }
.incursion-minimap__title { margin: 2px 0 0; font-size: 0.9rem; }
.incursion-minimap__progress { color: var(--text-muted); font-size: 0.72rem; font-family: var(--font-mono); }
.incursion-minimap__plot { position: relative; min-height: 224px; overflow: hidden; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); background: var(--bg-subtle); }
.incursion-minimap__plot::before { content: ""; position: absolute; inset: 0; opacity: 0.28; background-image: linear-gradient(to bottom, transparent 49.5%, var(--border-subtle) 50%, transparent 50.5%), linear-gradient(to right, transparent 49.5%, var(--border-subtle) 50%, transparent 50.5%); pointer-events: none; }
.incursion-minimap__edges { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.incursion-minimap__edge { fill: none; stroke: var(--border-color); stroke-width: 1.3; vector-effect: non-scaling-stroke; opacity: 0.78; }
.incursion-minimap__edge.edge-cleared { stroke: var(--color-success); opacity: 0.95; }
.incursion-minimap__edge.edge-dead { stroke: var(--color-error); stroke-dasharray: 3 3; opacity: 0.7; }
.incursion-minimap__nodes { position: absolute; inset: 0; }
.incursion-minimap__node { position: absolute; z-index: 1; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; transform: translate(-50%, -50%); border: 1px solid var(--border-color); border-radius: 50%; background: var(--bg-surface); color: var(--text-secondary); font: inherit; font-size: 0.88rem; line-height: 1; text-align: center; }
.incursion-minimap__node[title] { cursor: help; }
button.incursion-minimap__node { cursor: pointer; }
.incursion-minimap__node--cleared { border-color: var(--color-success-border); background: var(--color-success-bg); color: var(--color-success-text); }
.incursion-minimap__node--failed { border-color: var(--color-error-border); background: var(--color-error-bg); color: var(--color-error-text); opacity: 0.72; }
.incursion-minimap__node--skipped { border-style: dashed; color: var(--text-muted); opacity: 0.58; }
.incursion-minimap__node--locked { border-style: dashed; opacity: 0.58; }
.incursion-minimap__node--available { border-color: var(--color-primary); background: var(--bg-surface); color: var(--text-primary); box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 22%, transparent); }
.incursion-minimap__node.is-selected { border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-primary), 0 0 14px color-mix(in srgb, var(--color-primary) 45%, transparent); }
.incursion-minimap__node.node-boss { border-color: var(--color-reward); }
.incursion-minimap__node.node-boss.is-selected { box-shadow: 0 0 0 3px var(--color-reward), 0 0 14px color-mix(in srgb, var(--color-reward) 45%, transparent); }
button.incursion-minimap__node:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.incursion-minimap__legend { display: flex; flex-wrap: wrap; gap: 5px 8px; color: var(--text-muted); font-size: 0.66rem; line-height: 1.2; }
.incursion-minimap__legend > span { display: inline-flex; align-items: center; gap: 3px; }
.incursion-minimap__legend-swatch { display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; border: 1px solid var(--border-color); border-radius: 50%; font-style: normal; font-size: 0.58rem; }
.incursion-minimap__legend-swatch--cleared { border-color: var(--color-success-border); color: var(--color-success-text); }
.incursion-minimap__legend-swatch--reachable { border-color: var(--color-primary); color: var(--color-primary); }
.incursion-minimap__legend-swatch--skipped { border-style: dashed; color: var(--text-muted); }
.incursion-minimap__legend-swatch--locked { border-style: dashed; }
.incursion-minimap__help { margin: 0; color: var(--text-muted); font-size: 0.7rem; line-height: 1.3; }

/* Pending-haul HUD (the Extract-vs-Descend stake) */
/* The Extract-keeps-all / Fail-forfeits-N% terms, previously title= only. */
.run-summary__haul { color: var(--text-secondary); font-size: 0.95rem; margin: 6px 0 14px; }
.floor-map-wrap { position: relative; }
.floor-edges-layer { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.floor-edges { width: 100%; height: 100%; overflow: visible; }
.floor-edges .edge { stroke-width: 2; fill: none; }
.floor-edges .edge-open { stroke: var(--text-muted); opacity: 0.45; }
.floor-edges .edge-cleared { stroke: var(--color-success); opacity: 0.85; }
.floor-edges .edge-dead { stroke: var(--color-error); opacity: 0.5; stroke-dasharray: 4 4; }
.floor-map { display: flex; flex-direction: column; gap: 14px; }
.floor-row { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; }
/* The card is focused programmatically after a resolve swap (restoreFocus), so the ring
   has to come from :focus; a browser won't always treat programmatic focus as
   :focus-visible, and without it a keyboard player has no idea where they landed. */
.node-boss { border-color: var(--color-reward); }
.node-faction_supply { border-color: var(--color-info-border); }
.room-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; }
.room-type { font-weight: 700; font-size: 0.9rem; }
.room-status { font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; }
.room-lock { display: flex; flex-direction: column; gap: 8px; }
/* Slot requirement chip. The die-face palette (#e5573c/#4bc46a/#4a9df0) is tuned for dark
   backgrounds and lands at 1.8–3.0:1 on the light themes' --bg-subtle, well under the 4.5:1
   WCAG 1.4.3 minimum for this 0.8rem bold text, and this chip is the most-read element on
   the board. Tokenised so each theme can supply a shade that actually passes; the R/G/B
   letter still carries the meaning independently of colour (WCAG 1.4.1). */
.slot-req { display: inline-flex; align-items: center; justify-content: center; min-width: 46px; padding: 2px 6px; border-radius: var(--radius-sm); font-weight: 700; font-size: 0.8rem; background: var(--bg-subtle); }
.slot-req.slot-red { color: var(--dice-red-text, #e5573c); }
.slot-req.slot-green { color: var(--dice-green-text, #4bc46a); }
.slot-req.slot-blue { color: var(--dice-blue-text, #4a9df0); }
.slot-req.slot-any { color: var(--text-secondary); }
/* The floor decision, presented immediately after the boss result. Two columns of facts,
   neither styled as the recommended one: the panel explains consequences, it does not
   advise. */
.boss-choice-modal { position: relative; margin-top: 14px; }
.boss-choice-modal__backdrop { display: none; }
.boss-choice-modal__dialog { width: 100%; }
.boss-choice-modal--open {
    position: fixed;
    inset: 0;
    z-index: 9990;
    display: grid;
    place-items: center;
    padding: 20px;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.boss-choice-modal--open .boss-choice-modal__backdrop {
    display: block;
    position: absolute;
    inset: 0;
    background: var(--modal-overlay);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
}
.boss-choice-modal--open .boss-choice-modal__dialog {
    position: relative;
    z-index: 1;
    width: min(780px, 100%);
    max-height: calc(100vh - 40px);
    max-height: calc(100dvh - 40px);
    overflow-y: auto;
    overscroll-behavior: contain;
}
.boss-choice { border-color: var(--color-reward); }
.boss-choice__title { text-align: center; }
.boss-choice__lede { margin: 6px auto 12px; max-width: 58ch; color: var(--text-muted); text-align: center; }
.boss-choice__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; margin-top: 10px; }
.boss-choice__option { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px; background: var(--bg-subtle); border: 1px solid var(--border-color); border-radius: var(--radius-md); }
.boss-choice__option-title { margin: 0; font-size: 1rem; }
.boss-choice__facts { display: flex; flex-direction: column; gap: 8px; margin: 0; flex: 1; }
.boss-choice__facts > div { display: flex; flex-direction: column; gap: 1px; }
.boss-choice__facts dt { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.boss-choice__facts dd { margin: 0; font-size: 0.86rem; color: var(--text-primary); }
.boss-choice__sub { display: block; font-size: 0.74rem; color: var(--text-muted); }
.boss-choice__option .btn { min-height: 44px; }
.run-summary { text-align: center; }
.run-summary__actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 12px; }

/* Launch screen: Pathfinder / Blind mode chooser */
.mode-choice { display: flex; flex-direction: column; gap: 8px; border: 0; padding: 0; margin: 0; }
.mode-choice legend { padding: 0; margin-bottom: 2px; }
.mode-option { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border: 1px solid var(--border-color); border-radius: var(--radius-md); cursor: pointer; transition: border-color 0.15s ease, background 0.15s ease; }
@media (hover: hover) { .mode-option:hover { background: var(--bg-hover); } }
.mode-option:has(input:checked) { border-color: var(--color-primary); background: var(--bg-subtle); }
.mode-option input { margin-top: 3px; }
.mode-option__body { display: flex; flex-direction: column; gap: 2px; }
.mode-option__body strong { font-size: 0.95rem; }
.mode-option__body small { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.35; }
.mode-option:has(input:disabled) { opacity: 0.55; cursor: not-allowed; }

/* Blind mode: face-down dice */
.die-blind { color: #10121a; opacity: 0.9; box-shadow: inset 0 0 0 2px rgba(0,0,0,0.28), inset 0 -2px 0 rgba(0,0,0,0.2); }

/* Room lock: chip-based dice slots (both modes, _room_lock.html) */
.lock-slot-v2 { display: flex; flex-direction: column; gap: 6px; padding: 8px; border: 1px dashed var(--border-color); border-radius: var(--radius-sm); }
.lock-slot-v2__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.slot-odds { font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); }
.slot-stack { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-height: 34px; }
.slot-empty { color: var(--text-muted); font-size: 0.8rem; font-style: italic; }
.stacked-die { position: relative; display: inline-flex; align-items: center; gap: 1px; padding: 0; width: 34px; height: 34px; justify-content: center; border: none; border-radius: var(--radius-sm); font-weight: 800; font-size: 0.95rem; color: #10121a; cursor: pointer; box-shadow: inset 0 -2px 0 rgba(0,0,0,0.25); }
.stacked-die__x { font-size: 0.7rem; opacity: 0; transition: opacity 0.12s ease; }
@media (hover: hover) { .stacked-die:hover .stacked-die__x { opacity: 0.85; } }
.stacked-die.die-sacrifice { box-shadow: inset 0 0 0 2px var(--color-warning), inset 0 -2px 0 rgba(0,0,0,0.25); }
.slot-pick { width: 100%; }
.sacrifice-warn { margin: 2px 0 0; padding: 5px 8px; font-size: 0.78rem; line-height: 1.3; color: var(--color-warning-text); background: var(--color-warning-bg); border: 1px solid var(--color-warning-border); border-radius: var(--radius-sm); }
.sacrifice-tax { margin: 4px 0 0; font-size: 0.76rem; line-height: 1.3; font-weight: 600; color: var(--color-error-text); }

/* Room reward/penalty reveal (#5) + reachable emphasis + unlocks hint (#7) */
.room-reward { margin: 0 0 4px; font-size: 0.82rem; color: var(--color-success-text); }
.room-penalty { margin: 0 0 8px; font-size: 0.8rem; color: var(--color-warning-text); }
/* "A clean clear costs nothing" was in the penalty's title=. It is the reassuring half of
   the sentence, so it renders in the room's own muted tone rather than the warning colour. */
.room-penalty__free { color: var(--text-muted); }
.room-unlocks { margin: 8px 0 0; font-size: 0.78rem; color: var(--text-muted); letter-spacing: 0.02em; }
.room-unlocks__child + .room-unlocks__child { margin-left: 3px; }

/* Board node-type legend (#7) */
.node-legend { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-subtle); display: flex; flex-wrap: wrap; gap: 6px 10px; }
.node-legend__title { flex-basis: 100%; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.node-legend__item { font-size: 0.78rem; color: var(--text-secondary); white-space: nowrap; }

/* Roster search (#10) */
.roster-search { width: 100%; margin-bottom: 4px; }

.boss-choice__hint { color: var(--text-muted); font-size: 0.82rem; margin: 10px auto 0; max-width: 46ch; }

@media (width < 600px) {
    .boss-choice-modal--open { align-items: end; padding: 10px; }
    .boss-choice-modal--open .boss-choice-modal__dialog { max-height: calc(100dvh - 20px); }
    .boss-choice-modal--open .boss-choice { padding: 14px; }
}

/* Authored room identity + the Merchant's actual choice. */
.encounter__flavour { margin: 0; font-size: 0.86rem; color: var(--text-secondary); line-height: 1.45; }
.encounter__type { color: var(--text-muted); }
.encounter__moment { margin: 0; font-size: 0.82rem; color: var(--color-primary); font-style: italic; }
.reward-choice { display: flex; flex-wrap: wrap; gap: 8px; border: 0; padding: 0; margin: 0; }
.reward-choice__legend { padding: 0; margin-bottom: 4px; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.reward-choice__context { flex-basis: 100%; margin: 0 0 2px; font-size: 0.76rem; color: var(--text-secondary); line-height: 1.35; }
.reward-choice__option { flex: 1 1 160px; display: flex; align-items: flex-start; gap: 8px; padding: 8px 10px; min-height: 44px; border: 1px solid var(--border-color); border-radius: var(--radius-md); cursor: pointer; }
@media (hover: hover) { .reward-choice__option:hover { background: var(--bg-hover); } }
.reward-choice__option:has(input:checked) { border-color: var(--color-primary); background: var(--bg-subtle); }
.reward-choice__body { display: flex; flex-direction: column; gap: 1px; }
.reward-choice__body small { color: var(--text-muted); font-size: 0.74rem; line-height: 1.3; }

/* Bounded room decisions: readable before the dice tray on a phone, and two-up on wider
   screens so the choice feels like part of the encounter rather than a hidden form field. */
.room-interaction { display: flex; flex-wrap: wrap; gap: 8px; border: 0; padding: 0; margin: 0; }
.room-interaction__legend { flex-basis: 100%; padding: 0; margin-bottom: 2px; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.room-interaction__option { flex: 1 1 190px; display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; min-height: 52px; border: 1px solid var(--border-color); border-radius: var(--radius-md); cursor: pointer; background: var(--bg-subtle); }
@media (hover: hover) { .room-interaction__option:hover { background: var(--bg-hover); } }
.room-interaction__option:has(input:checked) { border-color: var(--color-primary); box-shadow: 0 0 0 1px var(--color-primary); }
.room-interaction__body { display: flex; flex-direction: column; gap: 3px; }
.room-interaction__body small { color: var(--text-secondary); font-size: 0.78rem; line-height: 1.35; }
/* Each option's own concrete payout, so two choices whose blurbs both promise "more" can't
   render identically: see incursions/views.py::_room_interaction_options. A COLUMN, not a
   wrapping chip row: these lines are the room contract in a fixed order (lock, cost now,
   clean clear, forced or failed, changes later), and a player learns that order by reading
   it down the card. Wrapped side by side, the same six lines re-flow differently on every
   room and the order stops being legible at all. */
.room-interaction__figures { display: flex; flex-direction: column; gap: 2px; font-size: 0.78rem; font-weight: 700; margin-top: 2px; }
.room-interaction__figure--lock { color: var(--text-secondary); }
.room-interaction__figure--reward { color: var(--color-success-text); }
.room-interaction__figure--penalty { color: var(--color-warning-text); }
.room-interaction__figure--cost { color: var(--color-warning-text); }
.room-interaction__figure--effect { color: var(--text-primary); }
/* An option that cannot do anything right now is dimmed and says why in real markup, never
   a title=. It stays on the card rather than disappearing: an option that vanishes reads as
   a missing feature, while a stated reason teaches the rule. */
.room-interaction__option--unavailable { opacity: 0.72; cursor: not-allowed; border-style: dashed; }
.room-interaction__option--unavailable strong { color: var(--text-muted); }
.room-interaction__blocked { display: flex; align-items: flex-start; gap: 6px; margin-top: 2px; font-size: 0.76rem; font-weight: 700; color: var(--color-warning-text); line-height: 1.35; }
.camp-colour__note { flex-basis: 100%; color: var(--text-muted); font-size: 0.74rem; line-height: 1.3; }
.room-interaction__cost { flex-basis: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 10px; border: 1px solid var(--color-warning); border-radius: var(--radius-md); background: var(--bg-subtle); font-size: 0.8rem; }
.room-interaction__cost > span { display: flex; flex-direction: column; gap: 2px; }
.room-interaction__cost small { color: var(--text-muted); font-size: 0.74rem; line-height: 1.3; }
.room-interaction__cost select { min-height: 44px; min-width: 180px; }
/* Nested inside a chosen option's own .room-interaction__body (a column flex container),
   not a sibling of .room-interaction__option: the colour picker only ever makes sense
   attached to the option that requires it, so it lives in that option's own card. */
.camp-colour { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 2px; font-size: 0.8rem; color: var(--text-muted); }
.camp-colour select { min-height: 44px; min-width: 110px; }
.route-node__promise { flex-basis: 100%; color: var(--color-primary); font-size: 0.72rem; }
.node-camp { border-color: var(--color-primary); }
.node-camp .route-node__name { color: var(--color-primary); }
@media (max-width: 600px) {
    .room-interaction__option, .room-interaction__cost { flex-basis: 100%; }
    .room-interaction__cost { align-items: stretch; flex-direction: column; }
    .room-interaction__cost select { width: 100%; }
}

/* First-descent onboarding: taught on the real board, inferred from run history, and plain
   markup that the page reads fine without. */
.first-run-tips { border: 1px solid var(--color-primary); border-radius: var(--radius-md); background: var(--bg-subtle); }
.first-run-tips__summary { padding: 10px 14px; min-height: 44px; display: flex; align-items: center; gap: 8px; cursor: pointer; font-weight: 600; }
.first-run-tips__body { padding: 0 14px 12px; }
.first-run-tips__list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; font-size: 0.86rem; color: var(--text-secondary); line-height: 1.45; }
.first-run-tips__list strong { color: var(--text-primary); }
.first-run-tips__more { margin: 10px 0 0; font-size: 0.8rem; color: var(--text-muted); }

/* ── Debrief: the canonical field report ───────────────────────────────────── */
.debrief { display: flex; flex-direction: column; gap: 16px; }
.debrief__head { padding: 14px 16px; border: 1px solid var(--border-color); border-left: 4px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-surface); }
.debrief__head--extracted { border-left-color: var(--color-success); }
.debrief__head--failed { border-left-color: var(--color-error); }
.debrief__head--abandoned { border-left-color: var(--text-muted); }
.debrief__verdict { margin: 0; font-size: 1.3rem; font-weight: 700; }
.debrief__reason { margin: 2px 0 10px; font-size: 0.9rem; color: var(--text-secondary); }
.debrief__meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin: 0; }
.debrief__meta dt { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.debrief__meta dd { margin: 0; font-size: 0.9rem; }
.debrief__gone { color: var(--text-muted); font-style: italic; }
.debrief__section { padding: 14px 16px; border: 1px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-surface); }
.debrief__boon-card + .debrief__boon-card { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-subtle); }
.debrief__section-title { margin: 0 0 10px; font-size: 1rem; }
.debrief__ledger { display: flex; flex-direction: column; gap: 4px; }
.debrief__ledger-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--border-subtle); font-size: 0.9rem; }
.debrief__ledger-row:last-child { border-bottom: 0; }
.debrief__ledger-label { color: var(--text-muted); }
.debrief__ledger-value { font-weight: 600; overflow-wrap: anywhere; }
.debrief__ledger-row--secured .debrief__ledger-value { color: var(--color-success-text); }
.debrief__ledger-row--lost .debrief__ledger-value { color: var(--color-error-text); }
.debrief__steel { margin: 10px 0 0; font-size: 0.86rem; color: var(--text-secondary); }
.debrief__sickness-change { margin-top: 10px; }
.debrief__sickness-change .hint { margin-bottom: 4px; }
.debrief__sickness-list { margin: 0; padding-left: 20px; color: var(--text-secondary); font-size: 0.82rem; line-height: 1.4; }
.debrief__stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin: 0; }
.debrief__stats dt { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.debrief__stats dd { margin: 0; font-size: 1.1rem; font-weight: 700; }
.debrief__route { margin-top: 12px; font-size: 0.86rem; }
.debrief__route > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; color: var(--text-secondary); }
.debrief__route-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; counter-reset: none; }
.debrief__route-entry { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; padding: 6px 8px; background: var(--bg-subtle); border-radius: var(--radius-sm); font-size: 0.82rem; }
.debrief__route-floor { font-family: var(--font-mono); color: var(--text-muted); }
.debrief__route-room { font-weight: 600; }
.debrief__route-entry--clean .debrief__route-outcome { color: var(--color-success-text); }
.debrief__route-entry--forced .debrief__route-outcome { color: var(--color-warning-text); }
.debrief__route-entry--failed .debrief__route-outcome { color: var(--color-error-text); }
.debrief__route-dice { margin-left: auto; color: var(--text-muted); }
.debrief__pets { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.debrief__pet { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 0.88rem; }
.debrief__pet-name { font-weight: 600; }
.debrief__pet-xp { color: var(--color-reward); font-family: var(--font-mono); font-size: 0.82rem; }
.debrief__pet-bonus { color: var(--text-muted); }
.debrief__pet .xp-capped-note { flex-basis: 100%; }
.debrief__actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.debrief__actions .btn { min-height: 44px; }
.debrief__rerun-note { flex-basis: 100%; margin: 0; }

/* Run history page */
.history-reason { margin: 6px 0 0; font-size: 0.82rem; color: var(--text-secondary); }
.history-lost { color: var(--color-error-text); }
.history-row__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 8px; }
.history-open { margin-left: auto; font-size: 0.84rem; }
.history-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 16px; }
@media (max-width: 480px) { .history-stats { grid-template-columns: 1fr; } }
.history-stats .stat-sub { font-size: 0.74rem; color: var(--text-muted); }
.history-list { display: flex; flex-direction: column; gap: 12px; }
.history-row { padding: 14px 16px; }
.history-row.history-failed { border-color: var(--color-error-border); }
.history-row.history-extracted { border-color: var(--color-success-border); }
.history-row.history-retreat { border-color: var(--color-warning-border); }
.history-row__head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.history-badge { padding: 2px 10px; border-radius: var(--radius-pill); font-size: 0.78rem; font-weight: 700; }
.history-badge.badge-extracted { background: var(--color-success-bg); color: var(--color-success-text); border: 1px solid var(--color-success-border); }
.history-badge.badge-failed { background: var(--color-error-bg); color: var(--color-error-text); border: 1px solid var(--color-error-border); }
.history-badge.badge-abandoned { background: var(--bg-subtle); color: var(--text-muted); border: 1px solid var(--border-subtle); }
.history-badge.badge-retreat { background: var(--color-warning-bg); color: var(--color-warning-text); border: 1px solid var(--color-warning-border); }
.history-meta { font-weight: 600; }
.history-date { margin-left: auto; color: var(--text-muted); font-size: 0.82rem; }
.history-rewards { margin: 8px 0 0; color: var(--text-secondary); font-size: 0.9rem; }
.history-stress { margin: 4px 0 0; color: var(--color-error-text); font-size: 0.82rem; }
.history-squad { color: var(--text-secondary); font-size: 0.82rem; }
.history-count { margin: 0 0 12px; color: var(--text-muted); font-size: 0.82rem; }
/* Focus lands here after a page turn (historyPager). Programmatic focus is not reliably
   :focus-visible, so the ring has to hang off plain :focus or the player cannot see where
   they were put: same reason the board's #board-heading carries one. */
.history-count:focus { outline: 2px solid var(--color-primary); outline-offset: 3px; border-radius: var(--radius-sm); }

/* Per-squad retention strip. auto-fit rather than a fixed column count: the number of
   squads is player-driven and uncapped, so a breakpoint per width would be guesswork. */
.squad-stats { margin-bottom: 20px; }
.squad-stats__heading { font-size: 0.9rem; margin: 0 0 8px; }
.squad-stats__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; }
.squad-stats__note { margin: 8px 0 0; color: var(--text-muted); font-size: 0.78rem; }
.squad-stat-card { padding: 12px 14px; }
.squad-stat-card__name { display: block; font-weight: 700; margin-bottom: 8px; }
.squad-stat-card__figures { display: flex; flex-wrap: wrap; gap: 4px 20px; margin: 0; }
.squad-stat-card__figures dt { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.squad-stat-card__figures dd { margin: 0; font-size: 1.1rem; font-weight: 700; }
.squad-stat-card__sub { display: block; font-size: 0.72rem; font-weight: 400; color: var(--text-muted); }

/* Board: frozen squad roster (who is actually descending) */
.board-squad { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-subtle); }
.board-squad__title { display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 6px; }
.board-squad__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.board-squad__pet { display: flex; justify-content: space-between; gap: 10px; font-size: 0.8rem; }
.board-squad__stats { color: var(--text-muted); white-space: nowrap; }
.board-squad__gold { color: var(--color-warning); }
.board-squad__hint { margin: 6px 0 0; font-size: 0.72rem; color: var(--text-muted); }

/* Board: the shared construction goal this run's Star-Steel feeds */
.board-goal { margin-top: 18px; }
.board-goal__pending { margin: 8px 0 0; font-size: 0.85rem; color: var(--text-secondary); }

/* Pathfinder: determined outcome of the staged attempt (see incursionBoard.roomVerdict) */
.attempt-verdict { margin: 4px 0 0; font-size: 0.78rem; line-height: 1.35; font-weight: 600; }
.attempt-verdict.verdict-clear { color: var(--color-success-text); }
.attempt-verdict.verdict-sacrifice { color: var(--color-warning-text); }
.attempt-verdict.verdict-fail { color: var(--color-error-text); }

/* Room lock: live success meter */
.blind-meter { display: flex; flex-direction: column; gap: 4px; }
.meter-bar { height: 8px; background: var(--bg-subtle); border: 1px solid var(--border-subtle); border-radius: var(--radius-pill); overflow: hidden; }
.meter-fill { height: 100%; border-radius: var(--radius-pill); transition: width 0.2s ease, background 0.2s ease; }
.meter-fill.meter-high { background: var(--color-success); }
.meter-fill.meter-mid { background: var(--color-warning); }
.meter-fill.meter-low { background: var(--color-error); }
.meter-label { font-size: 0.78rem; color: var(--text-secondary); }
.meter-clean { color: var(--text-muted); }

/* Desktop: a compact command rail beside the encounter. grid-template-areas rather than
   column order, so the DOM can stay in phone order (HUD -> encounter -> route -> notes)
   while the rail renders on the left. */
@media (min-width: 1024px) {
    .board-layout { grid-template-columns: 260px minmax(0, 1fr); grid-template-areas: "tray main"; }
    .board-main { grid-area: main; }
    .board-rail {
        grid-area: tray;
        position: sticky;
        /* Follows the real header height. It was hardcoded to 76px, the DESKTOP header, so
           on a phone the tray parked a gap below the bar it was supposed to sit under. */
        top: calc(var(--header-height) + 14px);
        max-height: calc(100vh - var(--header-height) - 28px);   /* fallback */
        max-height: calc(100dvh - var(--header-height) - 28px);
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        gap: 12px;
        scrollbar-gutter: stable;
    }
    .encounter__body { padding: 18px 20px; }
}

/* Coarse-pointer floor for the Incursion pillar's own controls.

   This block sits at the END of the incursion styles, not up with the other
   `@media (pointer: coarse)` rules, and that placement is the whole point: most of these
   controls also carry `.btn-xs` / `.btn-sm`, whose `min-height` is declared further down
   the file at the SAME specificity, so a coarse rule written earlier loses the cascade and
   silently does nothing. Browser-measured at 390px: every one of these came back 24-36px
   with the rule in the wrong place. */
@media (pointer: coarse) {
    .slot-arm, .slot-pick, .room-switcher__btn,
    .roster-toggle, .board-header .btn, .preview-member .btn,
    .route-node__detail > summary, .room-interaction__option,
    .camp-colour select { min-height: 44px; }
    .slot-arm, .room-switcher__btn,
    .roster-toggle, .board-header .btn, .preview-member .btn {
        display: inline-flex; align-items: center; justify-content: center;
    }
    .route-node__detail > summary { display: flex; align-items: center; }
}

/* Touch surfaces get the hand beside the lock (.encounter-hand) instead of a fixed drawer.
   The drawer looked useful in a screenshot but covered the room, the route controls and the
   Attempt button the moment a player scrolled, so it was retired here. Desktop keeps the
   sticky rail above.

   This is the ONLY board rule set below 1024px. For two releases a second, earlier
   `@media (width < 1024px)` block built the whole drawer (fixed positioning, a toggle, an
   `is-open` body capped at 30dvh, reserved `.board-shell` padding and matching
   `scroll-margin-bottom` compensation) and this block then set `display: none` over all of
   it, so roughly sixty lines of browser-measured reasoning were dead at every width while
   CLAUDE.md, `_dice_tray.html` and `docs/ux-rules.md` all still described the drawer as the
   phone interaction. Keep phone board rules here; do not open a second block.

   The scroll offset is the one thing that outlived the drawer, re-aimed at what is actually
   fixed on a phone: the bottom tab bar. scroll-into-view knows nothing about a fixed
   overlay, so without this a browser will happily centre a slot behind the bar. */
@media (width < 1024px) {
    .dice-tray { display: none; }
    .incursion-minimap { display: none; }
    .encounter-hand { display: flex; }
    .encounter-retreat { display: flex; }
    .incursion-room-art { aspect-ratio: 16 / 5; max-height: 130px; }
    .encounter,
    .lock-slot-v2,
    .btn-attempt { scroll-margin-bottom: calc(var(--tabbar-height) + 18px); }
}

@media (max-width: 600px) {
    .room-chooser__options { grid-template-columns: 1fr; }
    .room-chooser__option { min-height: 88px; }
    .encounter__body { padding: 12px; }
    .incursion-room-art { max-height: 104px; }
}

/* Desktop command view: keep the decision surface in the first viewport. The original
   desktop composition let the long haul sentence stretch every HUD cell to 170px, then
   wrapped the header actions below the title before a 190px art banner and the lock even
   began. This is a command board, not a gallery: preserve the information, but give the
   room and its next action the visual priority. */
@media (min-width: 1024px) {
    .board-header {
        align-items: flex-start;
        flex-wrap: nowrap;
    }
    .board-header .page-header__titles { flex: 1 1 auto; }
    .board-header .page-header__actions {
        flex: 0 0 min(300px, 31%);
        align-content: flex-start;
    }

    .run-hud { grid-template-columns: repeat(5, minmax(0, 1fr)); }
    .run-hud__cell { min-height: 78px; padding: 7px 10px; }
    .run-hud__value { font-size: 1.05rem; }
    .run-hud__value--haul {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
        font-size: 0.82rem;
        line-height: 1.3;
    }
    .run-hud__sub {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
    }

    .room-chooser { gap: 7px; }
    .room-chooser__head { gap: 4px 14px; }
    .room-chooser__title { font-size: 0.92rem; }
    .room-chooser__note { font-size: 0.74rem; }
    .room-chooser__options { gap: 6px; }
    .room-chooser__option { min-height: 78px; padding: 7px 10px; gap: 2px; }
    .room-chooser__type,
    .room-chooser__reward,
    .room-chooser__risk,
    .room-chooser__staged { font-size: 0.7rem; }

    .encounter { box-shadow: none; }
    .incursion-room-art { aspect-ratio: 14 / 3; max-height: 112px; }
    .incursion-room-art__glyph { font-size: 2rem; }
    .encounter__body { gap: 8px; padding: 12px 14px; }
    .encounter__title { font-size: 1.02rem; }
    .encounter__instruction { font-size: 0.78rem; }
    .encounter__flavour { font-size: 0.8rem; line-height: 1.35; }
    .encounter__moment { font-size: 0.76rem; }
    .encounter__stakes { flex-direction: row; flex-wrap: wrap; gap: 6px; }
    .encounter__stake {
        flex: 1 1 220px;
        padding: 6px 8px;
        background: var(--bg-subtle);
        border-radius: var(--radius-sm);
    }
    .encounter__stake dt { font-size: 0.64rem; }
    .encounter__stake dd { font-size: 0.78rem; }
    .room-interaction { gap: 6px; }
    .room-interaction__option { padding: 7px 9px; min-height: 44px; }
    .room-interaction__body small { font-size: 0.72rem; }
    .room-lock { gap: 6px; }
    .lock-slot-v2 { gap: 4px; padding: 6px; }
    .slot-stack { min-height: 28px; }
    .btn-attempt { min-height: 40px; }
}

/* ── Incursion cockpit composition ──────────────────────────────────────────
   The board is a sequence, not a stack of equal-weight cards: orient the player, present
   the current room, then let them solve it. The compact route compass preserves the past and
   the destination without making the full graph compete with the active lock. */
.route-compass { display: flex; flex-direction: column; gap: 8px; }
.route-compass__head { display: flex; align-items: end; justify-content: space-between; gap: 12px; }
.route-compass__title { margin: 2px 0 0; font-size: 0.98rem; }
.route-compass__progress { color: var(--text-muted); font-size: 0.76rem; }
.route-compass__track { display: grid; grid-template-columns: minmax(0, 1fr) minmax(190px, 1.2fr) minmax(0, 1fr); gap: 8px; }
.route-compass__zone { display: flex; flex-direction: column; justify-content: center; gap: 4px; min-height: 78px; padding: 8px 10px; background: var(--bg-surface); border: 1px solid var(--border-color); border-radius: var(--radius-md); }
.route-compass__zone--behind { border-left: 3px solid var(--color-success); }
.route-compass__zone--current { border-color: var(--color-primary); background: var(--bg-subtle); text-align: center; }
.route-compass__zone--ahead { border-right: 3px solid var(--color-reward); }
.route-compass__label { color: var(--text-muted); font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; }
.route-compass__zone--current .route-compass__label { color: var(--color-primary); }
.route-compass__zone strong { font-size: 0.82rem; }
.route-compass__hint,
.route-compass__empty,
.route-compass__more { color: var(--text-muted); font-size: 0.72rem; }
.route-compass__nodes { display: flex; gap: 4px; min-width: 0; overflow: hidden; }
.route-compass__node { display: inline-flex; align-items: center; gap: 3px; min-width: 0; color: var(--text-secondary); font-size: 0.7rem; white-space: nowrap; }
.route-compass__node span:last-child { overflow: hidden; text-overflow: ellipsis; }
.route-compass__node--failed { color: var(--color-error-ink); text-decoration: line-through; }

/* Route promises: what the next row of rooms is FOR, so a branch choice can be made before
   dice are committed. Lives on the compass rather than the minimap, which deliberately
   carries topology and state rather than room copy. A wrapping row on wide screens, one
   column on a phone, and each promise is plain text: nothing here is carried by colour. */
.route-promises { display: flex; flex-direction: column; gap: 6px; }
.route-promises__label { color: var(--text-muted); font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; }
.route-promises__list { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
.route-promise { flex: 1 1 220px; display: flex; flex-direction: column; gap: 3px; min-width: 0; padding: 8px 10px; background: var(--bg-surface); border: 1px solid var(--border-color); border-left: 3px solid var(--color-primary); border-radius: var(--radius-md); }
.route-promise__head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.route-promise__name { font-size: 0.82rem; font-weight: 700; }
.route-promise__kind { padding: 1px 7px; border: 1px solid var(--border-color); border-radius: 999px; background: var(--bg-subtle); color: var(--text-secondary); font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.route-promise__text { color: var(--text-secondary); font-size: 0.76rem; line-height: 1.35; }
@media (max-width: 600px) {
    .route-promise { flex-basis: 100%; }
}

.route-details { border: 1px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-surface); }
.route-details__summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 52px; padding: 8px 12px; cursor: pointer; list-style: none; }
.route-details__summary::-webkit-details-marker { display: none; }
.route-details__summary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.route-details__summary > span:first-child { display: flex; flex-direction: column; gap: 2px; }
.route-details__summary strong { font-size: 0.86rem; }
.route-details__summary small { color: var(--text-muted); font-size: 0.72rem; }
.route-details__chevron { color: var(--text-muted); font-size: 1.1rem; line-height: 1; transition: transform 0.15s ease; }
.route-details[open] .route-details__chevron { transform: rotate(180deg); }
.route-details > .route { padding: 0 12px 12px; }

.encounter__body { gap: 12px; }
.encounter__brief,
.encounter__solve { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.encounter__solve-head { display: flex; flex-direction: column; gap: 2px; padding-bottom: 8px; border-bottom: 1px solid var(--border-subtle); }
.encounter__solve-head strong { font-size: 0.88rem; }

@media (min-width: 1024px) {
    .route-compass { gap: 4px; }
    .route-compass__head { gap: 8px; }
    .route-compass__title { font-size: 0.9rem; }
    .route-compass__progress { font-size: 0.7rem; }
    .route-compass__zone { min-height: 60px; padding: 6px 8px; }
    .room-chooser { gap: 5px; }
    .room-chooser__head { gap: 2px 12px; }
    .room-chooser__title { font-size: 0.9rem; }
    .room-chooser__option { min-height: 68px; padding: 6px 8px; }

    .board-layout { grid-template-columns: minmax(0, 1fr) 260px; grid-template-areas: "main tray"; }
    .encounter__body { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(300px, 1.1fr); align-items: start; }
    .encounter__solve { padding-left: 14px; border-left: 1px solid var(--border-subtle); }
    .incursion-room-art { width: 100%; height: 112px; aspect-ratio: auto; }
}

@media (width < 1024px) {
    .encounter__body { display: flex; }
    .encounter__solve { padding-top: 4px; }
    .run-hud { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .run-hud__cell--haul { grid-column: span 3; }
    .run-hud__value--haul { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
    .run-hud__sub { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
    .incursion-room-art { width: 100%; height: 130px; aspect-ratio: auto; }
}

@media (max-width: 600px) {
    /* Two compact rows keep the route intelligible without forcing the player through a
       three-panel tour before the room. The current position is always first; behind/ahead
       remain visible as labeled context rather than disappearing into a hidden map. */
    .run-hud { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .run-hud__cell--haul { grid-column: span 2; }
    .run-hud__cell { padding: 7px 8px; }
    .run-hud__value--haul { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; font-size: 0.78rem; line-height: 1.3; }
    .run-hud__sub { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; }

    .route-compass { gap: 6px; }
    .route-compass__head { align-items: baseline; }
    .route-compass__title { font-size: 0.9rem; }
    .route-compass__progress { font-size: 0.7rem; white-space: nowrap; }
    .route-compass__track { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
    .route-compass__zone { min-height: 62px; padding: 7px 8px; }
    .route-compass__zone--current { grid-column: 1 / -1; order: -1; min-height: 58px; }
    .route-compass__zone strong { font-size: 0.78rem; }

    .room-chooser__options { grid-auto-flow: column; grid-auto-columns: minmax(220px, 78%); grid-template-columns: none; overflow-x: auto; padding-bottom: 4px; scroll-snap-type: x proximity; }
    .room-chooser__option { min-height: 118px; scroll-snap-align: start; }
    .route-details__summary { min-height: 48px; }
    .incursion-room-art { height: 104px; }
}

/* Until authored room art exists, the large icon banner is visual noise rather than useful
   information. The room type remains beside the title, so removing this reserved block lets
   the player reach the actual lock/solve controls without changing room identity. */
.incursion-room-art { display: none; }

/* XP / PROGRESSION (pet-detail progression block + Commander panel) */
.progression-xp-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; }
.progression-xp-label { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-muted); }
.progression-xp-value { font-family: var(--font-mono); font-size: 0.82rem; color: var(--text-secondary); }
.ceiling-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-top: 12px; }
.ceiling-row { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border-radius: var(--radius-md); border: 1px solid var(--border-subtle); }
.ceiling-row__head { display: flex; justify-content: space-between; align-items: baseline; }
.ceiling-row__label { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.ceiling-row__value { font-family: var(--font-mono); font-size: 15px; font-weight: 700; }
.ceiling-row__value--capped { color: var(--color-reward); }
/* What a point buys, at the one screen where it is spent. Small and quiet: this is
   reference text the player reads once per decision, not a headline. */
.ceiling-row__effects { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ceiling-row__effects li { font-size: 0.76rem; line-height: 1.35; color: var(--text-muted); }
/* The lifetime point budget. Bordered like .trait-card because it is the same kind of
   thing: one fact about this pet with its consequence stated beside it. */
.point-budget { display: flex; flex-direction: column; gap: 4px; padding: 10px 12px; border-radius: var(--radius-md); border: 1px solid var(--border-subtle); margin-top: 12px; }
.point-budget__line { margin: 0; font-family: var(--font-mono); font-size: 0.84rem; color: var(--text-secondary); }
.point-budget__line strong { color: var(--color-reward); }
.trait-card { display: flex; flex-direction: column; gap: 4px; padding: 12px; border-radius: var(--radius-md); border: 1px solid var(--border-subtle); margin-top: 12px; }
.trait-card__name { font-weight: 700; }
.trait-card__summary { margin: 0; font-family: var(--font-mono); font-size: 0.78rem; color: var(--color-reward); }
.trait-card__link { font-size: 0.78rem; margin-top: 2px; }
.trait-card--inert { border-style: dashed; }
/* Reuses the trait-card chrome: same "one fact about this pet, with its consequence
   spelled out" shape, just tinted since it is a live restriction rather than a passive. */
.void-sickness-card { border-color: var(--color-warning); }
.void-sickness-card .trait-card__name { color: var(--color-warning-text); }
/* Compact trait marker for rosters, pet cards, party pickers and the incursion board.
   The label is always spelled out and the tooltip carries the concrete effect: an
   inert trait is distinguished by the dashed border and the muted color, never by hue
   alone, so it does not depend on color perception to read as "no effect yet". */
.trait-chip {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 1px 7px; border-radius: 999px; white-space: nowrap;
    font-family: var(--font-mono); font-size: 0.72rem; line-height: 1.6;
    color: var(--color-reward);
    border: 1px solid color-mix(in srgb, var(--color-reward) 40%, var(--border-color));
}
.trait-chip--inert {
    color: var(--text-muted);
    border-color: var(--border-subtle);
    border-style: dashed;
}
.squad-traits-line { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 6px 0 0; }
/* .pet-card is ~200px and .trait-chip is nowrap: let a long trait label wrap inside
   the chip there rather than spill past the card border (same reason .state-chip is
   handled specially for that card, see the rule near the top of this file). */
.pet-card > .trait-chip { max-width: 100%; white-space: normal; }
.infusion-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.infusion-actions form { max-width: none; margin: 0; }
.infusion-actions select { margin-right: 6px; }
/* Recalibrate quote: a .trait-card variant so the paid stat-reset reads as one more
   explained fact about the pet, beside the allocation rows it undoes, rather than as a
   second danger zone. Its form must not inherit the global `form` width cap, same as
   .infusion-actions form above. */
.stat-reset-card form { max-width: none; margin: 0; }
.stat-reset-card .hint-warn { margin: 0; }
/* progressionPanel puts focus back on this heading after an allocate/infuse/recalibrate
   swap. A browser will not reliably treat programmatic focus as :focus-visible, so
   without an explicit ring a keyboard player has no idea where they landed. */
.panel-title:focus { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.progression-danger-zone { margin-top: 16px; padding-top: 16px; border-top: 1px dashed var(--border-subtle); }
/* Last-pet release block: mirrors pets.services.release_pet's guard. */
.progression-warn { margin: 0 0 8px; padding: 8px 10px; font-size: 0.85rem; line-height: 1.4; color: var(--color-warning-text); background: var(--color-warning-bg); border: 1px solid var(--color-warning-border); border-radius: var(--radius-sm); }

.commander-hero { display: flex; flex-wrap: wrap; gap: 20px; align-items: center; justify-content: space-between; }
.commander-unlock-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-top: 14px; }
.commander-unlock-card { display: flex; flex-direction: column; gap: 8px; padding: 14px; border-radius: var(--radius-md); border: 1px solid var(--border-subtle); }
.commander-unlock-card__head { display: flex; align-items: center; gap: 8px; font-weight: 700; }
.commander-unlock-card form { max-width: none; margin: 0; }

/* ===== VISTAS (Log Book) ===== */
.vista-equipped__preview { display: flex; gap: 14px; align-items: center; }
.vista-equipped__preview img { border-radius: var(--radius-md); object-fit: cover; }
.vista-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-top: 14px; }
.vista-card {
    display: flex; flex-direction: column; gap: 8px; padding: 14px;
    border-radius: var(--radius-md); border: 1px solid var(--border-subtle); text-align: center;
}
.vista-card form { max-width: none; margin: 0; }
.vista-card__art { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius-sm); }
.vista-card__name { margin: 0; font-family: var(--font-display); }
.vista-card__flavor { color: var(--text-secondary); font-size: 0.85rem; margin: 0; }
.vista-card--mystery { opacity: 0.6; border-style: dashed; }
.vista-card__silhouette {
    width: 100%; aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center;
    font-size: 2.4rem; border-radius: var(--radius-sm); background: color-mix(in srgb, var(--text-secondary) 10%, transparent);
}

/* ===== STAFF: ERROR MONITOR (/staff/errors/) =====
   Dense triage surface. Deliberately quiet: status is carried by the badge
   pills and a single left border per row, so a wall of unresolved errors reads
   as a list to work through rather than a wall of red. */
.error-summary { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 16px; }
.error-summary__stat {
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    color: var(--text-secondary);
    font-size: 0.82rem;
}
.error-summary__stat strong { color: var(--text-primary); font-family: var(--font-mono); }
.error-summary__stat--unresolved { border-color: var(--color-error-border); color: var(--color-error-text); }
.error-summary__stat--unresolved strong { color: var(--color-error-text); }

/* flex-direction is explicit because this is a <form>: the global `form` rule
   is column-direction, and only setting `display: flex` leaves the filters
   stacked in a narrow right-aligned column. Same reason as .staff-filters. */
.error-filters { display: flex; flex-direction: row; flex-wrap: wrap; align-items: flex-end; gap: 12px; max-width: none; margin-bottom: 12px; }
.error-filters label { display: flex; flex-direction: column; gap: 4px; font-size: 0.78rem; color: var(--text-muted); }
.error-filters__search { flex: 1 1 260px; }
.error-filters__search input { width: 100%; }

.error-bulkbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    margin-bottom: 10px;
    border-radius: var(--radius-md);
    background: var(--bg-elevated);
    border: 1px solid var(--color-primary);
}
.error-bulkbar__count { font-family: var(--font-mono); font-size: 0.8rem; color: var(--color-primary); margin-right: 4px; }

.error-row--resolved { opacity: 0.65; }
.error-row--ignored { opacity: 0.5; }
.error-row td:first-child { border-left: 3px solid transparent; }
.error-row--unresolved td:first-child { border-left-color: var(--color-error); }
.error-row__type { font-weight: 700; }
.error-row__summary { color: var(--text-secondary); font-size: 0.84rem; margin-top: 2px; }
.error-row__culprit { color: var(--text-muted); font-size: 0.72rem; margin-top: 2px; }
.error-row__count { font-family: var(--font-mono); font-weight: 700; text-align: right; }
.error-row__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }

/* Status + origin pills. Both use the shared .badge base. */
.badge-unresolved { background: var(--color-error-bg); color: var(--color-error-text); border: 1px solid var(--color-error-border); }
.badge-resolved   { background: var(--color-success-bg); color: var(--color-success-text); border: 1px solid var(--color-success-border); }
.badge-ignored    { background: var(--bg-subtle); color: var(--text-muted); border: 1px solid var(--border-subtle); }
.badge.origin-web       { background: var(--color-info-bg); color: var(--color-info-text); border: 1px solid var(--color-info-border); }
.badge.origin-celery    { background: var(--color-warning-bg); color: var(--color-warning-text); border: 1px solid var(--color-warning-border); }
.badge.origin-nginx,
.badge.origin-gunicorn,
.badge.origin-other     { background: var(--bg-subtle); color: var(--text-secondary); border: 1px solid var(--border-subtle); }

.error-detail-head { margin-bottom: 18px; }
.error-detail-head__top { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.error-detail-head__type { font-family: var(--font-mono); font-size: 1.15rem; margin: 0 0 4px; word-break: break-word; }
.error-detail-head__summary { color: var(--text-secondary); margin: 0 0 4px; word-break: break-word; }
.error-detail-head__culprit { color: var(--text-muted); font-size: 0.78rem; margin: 0 0 12px; word-break: break-word; }
.error-detail-head__actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 0; max-width: none; }

.occurrence { padding: 0; margin-bottom: 10px; }
.occurrence__summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    cursor: pointer;
    font-size: 0.84rem;
}
@media (hover: hover) { .occurrence__summary:hover { background: var(--bg-hover); border-radius: var(--radius-lg); } }
.occurrence__time { font-weight: 700; }
.occurrence__user { color: var(--color-primary); }
.occurrence__ip, .occurrence__logger { color: var(--text-muted); font-size: 0.74rem; }
.occurrence__logger { margin-left: auto; }
.occurrence__body { padding: 0 16px 16px; border-top: 1px solid var(--border-subtle); }
.occurrence__block { margin-top: 14px; }
.occurrence__block h4 {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin: 0 0 6px;
}
.occurrence__redacted {
    text-transform: none;
    letter-spacing: 0;
    color: var(--color-success-text);
    font-size: 0.66rem;
    margin-left: 6px;
}
.occurrence__pre {
    margin: 0;
    padding: 12px 14px;
    max-height: 420px;
    overflow: auto;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    font-family: var(--font-mono);
    font-size: 0.76rem;
    line-height: 1.55;
    white-space: pre-wrap;
    word-break: break-word;
}
.occurrence__pre--traceback { max-height: 560px; }

/* ============================================================
   STAFF TOOLS (/staff/*)
   The back-of-house surfaces read as an instrument panel: same tokens as the
   game, but denser, mono-flavoured, and deliberately unglamorous. The only
   colour that carries meaning here is status: everything else stays quiet so
   a real problem is the loudest thing on the page.
   ============================================================ */

/* ---- Shared tab strip across every staff page (replaces the per-page
   "back to Staff Tools" links, which gave no sense of where you were). ---- */
.staff-subnav {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 2px;
    margin: 0 0 22px;
    padding-bottom: 2px;
    border-bottom: 1px solid var(--border-subtle);
    overflow: visible;
}
.staff-subnav a,
.staff-subnav-group > summary {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 9px 14px;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    color: var(--text-secondary);
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.86rem;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    transition: color var(--dur) ease, background var(--dur) ease;
}
@media (hover: hover) { .staff-subnav a:hover, .staff-subnav-group > summary:hover { background: var(--bg-hover); color: var(--text-primary); text-decoration: none; } }
.staff-subnav a.is-active {
    color: var(--color-primary);
    border-bottom-color: var(--color-primary);
    background: var(--bg-subtle);
}
.staff-subnav__ext { color: var(--text-muted); font-weight: 500; }
.staff-subnav-group { position: relative; }
.staff-subnav-group > summary {
    list-style: none;
    cursor: pointer;
}
.staff-subnav-group > summary::-webkit-details-marker { display: none; }
.staff-subnav-group > summary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.staff-subnav-group > summary::after { content: '⌄'; color: var(--text-muted); font-size: 0.9em; }
.staff-subnav-group[open] > summary::after { content: '⌃'; }
.staff-subnav-group > summary.is-active { color: var(--color-primary); background: var(--bg-subtle); border-bottom-color: var(--color-primary); }
.staff-subnav-group__panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + 2px);
    left: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 240px;
    padding: 8px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    box-shadow: var(--shadow-lg);
}
.staff-subnav-group__panel a { border: 0; border-radius: var(--radius-sm); min-height: 40px; }
.staff-action-feedback {
    margin: -10px 0 22px;
    padding: 10px 14px;
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    background: var(--bg-subtle);
    font-size: .86rem;
}
/* All four take real tokens so the bar follows the palette. --color-danger was
   never defined anywhere in this file, so the error tone silently fell through to
   its hardcoded fallback and stayed the same red in all ten Legacy palettes and
   both Frontier modes while its three siblings moved. The token is --color-error. */
.staff-action-feedback--confirm { border-left-color: var(--color-warning); }
.staff-action-feedback--pending { border-left-color: var(--color-primary); }
.staff-action-feedback--success { border-left-color: var(--color-success); }
.staff-action-feedback--error { border-left-color: var(--color-error); }
.staff-experimental-banner {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin: 0 0 24px;
    padding: 14px 16px;
    border: 1px solid var(--border-color);
    border-left: 4px solid var(--color-primary);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    color: var(--text-secondary);
}
.staff-experimental-banner__mark {
    flex: 0 0 auto;
    color: var(--color-primary);
    font-family: var(--font-mono);
    font-size: 1.1rem;
    line-height: 1.35;
}
.staff-experimental-banner p { margin: 0; font-size: .84rem; line-height: 1.55; }
.staff-experimental-banner strong { color: var(--text-primary); }
.confirming { border-color: var(--color-warning, #c58a27); }
.is-pending { cursor: wait; }
button.is-pending { opacity: .72; }
.staff-subnav-more .staff-subnav-group__panel { min-width: min(680px, calc(100vw - 32px)); display: grid; grid-template-columns: repeat(4, minmax(130px, 1fr)); gap: 12px; }
.staff-subnav-more .staff-subnav-group__panel { left: auto; right: 0; }
.staff-subnav-more .staff-subnav-group__panel[style*="position: fixed"] { z-index: 1000; right: auto; }
.staff-subnav-more__section { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.staff-subnav-more__label { padding: 4px 10px; color: var(--text-muted); font-family: var(--font-mono); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
@media (max-width: 600px) {
    .staff-subnav {
        flex-wrap: wrap;
        gap: 4px;
    }
    .staff-subnav > a,
    .staff-subnav > .staff-subnav-group {
        flex: 1 1 calc(50% - 4px);
    }
    .staff-subnav > a,
    .staff-subnav-group > summary {
        justify-content: center;
        min-height: 44px;
        padding: 10px 8px;
    }
    .staff-subnav-group__panel,
    .staff-subnav-more .staff-subnav-group__panel { position: static; min-width: 0; width: 100%; grid-template-columns: 1fr; box-shadow: none; margin-top: 2px; }
    .staff-subnav-more__section { grid-column: 1; }
}

/* ---- Status readout strip: at-a-glance signal, one tile per fact. A tile
   only takes a colour when its value is actually actionable. ---- */
.staff-readouts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
    gap: 12px;
    margin-bottom: 24px;
}
.staff-readout {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 13px 16px;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--border-color);
    border-radius: var(--radius-md);
}
.staff-readout__label {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--text-muted);
}
.staff-readout__value {
    font-family: var(--font-mono);
    font-size: 1.32rem;
    font-weight: 700;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
    word-break: break-word;
}
.staff-readout__note { font-size: 0.74rem; color: var(--text-muted); }
.staff-readout__note code { white-space: normal; overflow-wrap: anywhere; }
.staff-readout--ok    { border-left-color: var(--color-success); }
.staff-readout--ok .staff-readout__value { color: var(--color-success-text); }
.staff-readout--warn  { border-left-color: var(--color-warning); }
.staff-readout--warn .staff-readout__value { color: var(--color-warning-text); }
.staff-readout--alert { border-left-color: var(--color-error); }
.staff-readout--alert .staff-readout__value { color: var(--color-error-text); }

/* ---- Hub tool cards ---- */
.staff-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
    gap: 14px;
}
.staff-card {
    display: flex;
    gap: 14px;
    padding: 18px;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    color: inherit;
    transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
@media (hover: hover) {
    .staff-card:hover {
        text-decoration: none;
        border-color: var(--color-primary);
        transform: translateY(-2px);
    }
}
.staff-card__icon {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    font-size: 1.2rem;
}
.staff-card__body { min-width: 0; }
.staff-card__title {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0 0 4px;
    font-family: var(--font-heading);
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-primary);
}
@media (hover: hover) { .staff-card:hover .staff-card__title { color: var(--color-primary); } }
.staff-card__desc { margin: 0; font-size: 0.84rem; color: var(--text-secondary); line-height: 1.5; }
.staff-card__scope { display: block; margin-top: 7px; color: var(--text-muted); font-size: 0.74rem; line-height: 1.4; }

/* ---- Shared workspace landing and staff-team surfaces ---- */
.staff-workspace-scope {
    margin: 10px 0 0;
    color: var(--text-secondary);
    font-size: 0.84rem;
}
.staff-workspace-scope strong { color: var(--text-primary); }
.staff-search-form { align-items: end; margin-bottom: 8px; }
.staff-filter-loading {
    display: block;
    min-height: 1.2em;
    margin: 0 0 14px;
    color: var(--text-muted);
    font-size: 0.76rem;
}
.staff-filter-loading { visibility: hidden; }
form.htmx-request ~ .staff-filter-loading { visibility: visible; }
.staff-table-scroll {
    /* Load-bearing: this box must be the containing block for the .sr-only
       <caption>/<label> nodes its table holds. .sr-only is position: absolute
       with no offsets, so against a static scroller it resolves to the initial
       containing block, escapes the overflow clip and contributes its static
       position to the document's scrollable width. See docs/ux-rules.md
       "Layout and overflow". */
    position: relative;
    width: 100%;
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.staff-table-scroll:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 3px;
}
.staff-table-scroll > table {
    width: max-content;
    min-width: 100%;
    max-width: none;
    overflow: visible;
}
.staff-table-scroll__hint {
    display: none;
    margin: -8px 0 14px;
    color: var(--text-muted);
    font-size: 0.72rem;
}
@media (max-width: 700px) {
    .staff-table-scroll__hint { display: block; }
}

/* ---- Safety desk: a case file has a quieter information hierarchy than the
   operations hub. The rail keeps the high-impact actions and evidence anchors
   in view while the main column carries the full record. ---- */
.mod-room-intro { display: flex; align-items: baseline; gap: 12px; margin: -8px 0 18px; color: var(--text-secondary); font-size: .86rem; }
.mod-room-intro p { margin: 0; }
.mod-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 22px; }
.mod-nav a { display: inline-flex; min-height: 40px; align-items: center; padding: 8px 13px; border: 1px solid var(--border-color); border-radius: var(--radius-pill); background: var(--bg-surface); color: var(--text-secondary); font-family: var(--font-heading); font-size: .84rem; font-weight: 600; text-decoration: none; }
@media (hover: hover) { .mod-nav a:hover { border-color: var(--color-primary); color: var(--text-primary); text-decoration: none; } }
.mod-case-summary { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin: 0 0 18px; padding: 18px 20px; border: 1px solid var(--border-color); border-left: 4px solid var(--color-primary); border-radius: var(--radius-lg); background: var(--bg-surface); }
.mod-case-summary__identity, .mod-case-summary__status { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.mod-case-summary__identity { min-width: 0; flex: 1 1 auto; }
.mod-case-summary__identity strong { color: var(--text-primary); font-family: var(--font-heading); font-size: 1.12rem; }
.mod-case-summary__status { justify-content: flex-end; }
.mod-case-summary__balances { color: var(--text-muted); font-family: var(--font-mono); font-size: .74rem; }
.mod-case-layout { display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 20px; align-items: start; }
.mod-case-rail { position: sticky; top: 18px; display: flex; flex-direction: column; gap: 3px; padding: 12px 0; border-right: 1px solid var(--border-subtle); }
.mod-case-rail a { padding: 7px 12px; border-left: 2px solid transparent; color: var(--text-secondary); font-size: .8rem; text-decoration: none; }
@media (hover: hover) { .mod-case-rail a:hover { border-left-color: var(--color-primary); color: var(--text-primary); background: var(--bg-hover); text-decoration: none; } }
.mod-case-main { display: grid; gap: 14px; min-width: 0; }
.mod-section { min-width: 0; padding: 18px; border: 1px solid var(--border-color); border-radius: var(--radius-lg); background: var(--bg-surface); }
.mod-section--risk { border-left: 3px solid var(--color-warning, #c58a27); }
.mod-section h2 { margin: 0; font-size: 1.05rem; }
.mod-section__header { display: flex; align-items: start; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.mod-section__hint { color: var(--text-muted); font-family: var(--font-mono); font-size: .68rem; letter-spacing: .04em; text-transform: uppercase; text-align: right; }
.mod-status { display: inline-flex; align-items: center; min-height: 26px; padding: 3px 9px; border: 1px solid var(--border-color); border-radius: var(--radius-pill); font-family: var(--font-mono); font-size: .7rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.mod-status--ok { border-color: color-mix(in srgb, var(--color-success) 45%, var(--border-color)); color: var(--color-success-text); background: color-mix(in srgb, var(--color-success) 10%, var(--bg-surface)); }
.mod-status--warning { border-color: color-mix(in srgb, var(--color-warning) 55%, var(--border-color)); color: var(--color-warning-text); background: color-mix(in srgb, var(--color-warning) 10%, var(--bg-surface)); }
.mod-status--danger { border-color: color-mix(in srgb, var(--color-error) 55%, var(--border-color)); color: var(--color-error-text); background: color-mix(in srgb, var(--color-error) 10%, var(--bg-surface)); }
.mod-warning { margin: 9px 0 0; padding: 9px 11px; border-left: 2px solid var(--color-warning, #c58a27); background: var(--bg-subtle); color: var(--text-secondary); font-size: .82rem; }
.mod-note { padding: 11px 0; border-top: 1px solid var(--border-subtle); }
.mod-note-body { margin: 0 0 6px; white-space: pre-wrap; overflow-wrap: anywhere; }
.mod-note-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; color: var(--text-muted); font-size: .75rem; }
.mod-note-empty { color: var(--text-muted); }
.mod-section .staff-table-scroll { margin-top: 14px; }

/* ---- Forum comms triage ---- */
.forum-report-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 0 0 18px; }
.forum-report-metrics > div { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.forum-report-metrics span { color: var(--text-muted); font-family: var(--font-mono); font-size: .68rem; letter-spacing: .07em; text-transform: uppercase; }
.forum-report-metrics strong { color: var(--text-primary); font-family: var(--font-mono); font-size: 1.25rem; }
.forum-report-table { min-width: 850px; }
.forum-report-table td { vertical-align: top; }
.forum-report-table td form { margin: 0; }
.forum-report-target-type, .forum-report-reviewer { display: block; margin-top: 4px; color: var(--text-muted); font-size: .7rem; }
.forum-report-reason { max-width: 340px; min-width: 230px; }
.forum-report-empty { padding: 26px 14px; color: var(--text-muted); text-align: center; }
@media (max-width: 768px) {
    .mod-room-intro { display: block; }
    .mod-room-intro p { margin-top: 5px; }
    .mod-case-summary { align-items: flex-start; flex-direction: column; }
    .mod-case-summary__status { justify-content: flex-start; }
    .mod-case-layout { display: block; }
    .mod-case-rail { position: static; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; margin-bottom: 12px; padding: 0 0 10px; border-right: 0; border-bottom: 1px solid var(--border-subtle); }
    .mod-case-rail .eyebrow { grid-column: 1 / -1; padding: 0 4px 4px; }
}
@media (max-width: 600px) {
    .forum-report-metrics { grid-template-columns: 1fr; }
    .mod-section { padding: 14px; }
    .mod-section__header { display: block; }
    .mod-section__hint { display: block; margin-top: 5px; text-align: left; }
}
.staff-team-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 0 0 18px; }
.staff-team-metrics > div { display: flex; flex-direction: column; gap: 3px; padding: 11px 13px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.staff-team-metrics span { color: var(--text-muted); font-size: 0.72rem; }
.staff-team-metrics strong { color: var(--text-primary); font-family: var(--font-mono); font-size: 1.2rem; }
.staff-team-results { margin-top: 14px; }
.staff-team-results__summary { margin: 0 0 8px; color: var(--text-secondary); font-size: 0.84rem; }
.staff-team-role__header { display: flex; align-items: start; justify-content: space-between; gap: 10px; }
.staff-team-role__header h3 { min-width: 0; }
.staff-team-role__count { display: inline-grid; min-width: 30px; min-height: 30px; place-items: center; border: 1px solid var(--border-subtle); border-radius: var(--radius-pill); color: var(--text-primary); font-family: var(--font-mono); font-size: 0.78rem; }
.staff-team-role__members { margin: 0; padding-left: 18px; color: var(--text-secondary); }
.staff-team-role__members li { display: list-item; }
.staff-team-role__members li small { display: block; color: var(--text-muted); font-size: 0.74rem; }
.staff-identity { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.82em; }
.staff-team-role-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
}
.staff-team-role {
    padding: 16px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-surface);
}
.staff-team-role h3 { margin: 0 0 5px; font-family: var(--font-heading); color: var(--text-primary); }
.staff-team-role p { margin: 0 0 12px; color: var(--text-secondary); font-size: 0.82rem; line-height: 1.45; }
.staff-team-role ul { margin: 0; padding-left: 18px; color: var(--text-secondary); }
.staff-team-role li + li { margin-top: 4px; }
.staff-team-admin-list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.staff-team-admin-list li {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.staff-team-admin-list span { color: var(--text-muted); font-size: 0.82rem; }
.staff-team-role-options { display: grid; gap: 10px; }
.staff-team-role-option {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 13px 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
.staff-team-role-option span { display: grid; gap: 3px; }
.staff-team-role-option small { color: var(--text-secondary); line-height: 1.4; }
.staff-team-role-options { min-width: 0; margin: 0; padding: 0; border: 0; }
.staff-team-role-options legend { margin: 0 0 10px; padding: 0; color: var(--text-secondary); font-size: 0.84rem; }
.staff-team-role:last-child { grid-column: span 2; }
@media (max-width: 900px) {
    .staff-team-role-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .staff-team-role:last-child { grid-column: auto; }
}
@media (max-width: 600px) {
    .staff-team-metrics { grid-template-columns: 1fr; }
    .staff-team-role-grid { grid-template-columns: 1fr; }
    .staff-team-admin-list li { align-items: flex-start; flex-direction: column; gap: 3px; }
}
@media (max-width: 1024px) {
    .staff-table-scroll__hint { display: block; }
}
@media (pointer: coarse) {
    .staff-search-form input,
    .staff-search-form button,
    .error-filters input,
    .error-filters select,
    .error-filters button,
    .staff-filters input,
    .staff-filters select,
    .staff-filters button,
    .staff-team-role-option { min-height: 44px; }
}

/* ---- Apparel bulk import (/staff/apparel-import/) ---- */
.apparel-import-row {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px;
    margin: 0 0 14px;
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
}
.apparel-import-row legend {
    padding: 0 6px;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-secondary);
}
.apparel-import-row__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 12px;
}
.apparel-import-row__wide { grid-column: 1 / -1; }
.apparel-import-row__checkbox {
    flex-direction: row !important;
    align-items: center;
    gap: 8px !important;
}
.apparel-import-row__actions { display: flex; justify-content: flex-end; gap: 8px; }
.apparel-import-addbar { display: flex; flex-wrap: wrap; gap: 10px; margin: 4px 0 20px; }
.apparel-import-submitbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.apparel-import-grant {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    max-width: 720px;
    padding: 12px 14px;
    margin: 0 0 14px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
.apparel-import-grant input { margin-top: 3px; }
.apparel-import-grant span { display: flex; flex-direction: column; gap: 3px; }
.apparel-import-grant small { color: var(--text-muted); line-height: 1.45; }
.apparel-import-form-hint { max-width: 720px; margin: 0 0 14px; }
.apparel-import-result {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    max-width: 720px;
    padding: 10px 12px;
    margin-top: 12px;
    color: var(--color-success-text);
    background: var(--color-success-bg);
    border: 1px solid var(--color-success-border);
    border-radius: var(--radius-md);
    font-size: 0.86rem;
}
.apparel-import-result a { font-weight: 700; }

/* ---- Inline alert for broker/worker/Redis failures. These previously
   rendered as bare paragraphs on a class that was never defined. ---- */
.staff-alert {
    display: flex;
    gap: 10px;
    padding: 12px 16px;
    margin: 0 0 14px;
    border-radius: var(--radius-md);
    background: var(--color-error-bg);
    border: 1px solid var(--color-error-border);
    color: var(--color-error-text);
    font-size: 0.88rem;
}
.staff-alert__icon { flex-shrink: 0; }

/* ---- Filter bar shared by Task History (mirrors .error-filters) ---- */
.staff-filters {
    display: flex;
    /* These are <form> elements, and the global `form` rule is column-direction
       with a 420px cap: both must be overridden or the filters stack up in a
       narrow right-aligned column. */
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    max-width: none;
    margin-bottom: 14px;
}
.staff-filters label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 0.78rem;
    color: var(--text-muted);
}

/* ---- Compact count chips (task-history status breakdown) ---- */
.staff-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.staff-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 12px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    color: var(--text-secondary);
    font-size: 0.8rem;
}
.staff-chip strong { color: var(--text-primary); font-family: var(--font-mono); }

/* ---- Celery task-result status pills (reuse the shared .badge base) ---- */
.badge.task-success { background: var(--color-success-bg); color: var(--color-success-text); border: 1px solid var(--color-success-border); }
.badge.task-failure { background: var(--color-error-bg); color: var(--color-error-text); border: 1px solid var(--color-error-border); }
.badge.task-retry,
.badge.task-started { background: var(--color-warning-bg); color: var(--color-warning-text); border: 1px solid var(--color-warning-border); }
.badge.task-revoked,
.badge.task-pending { background: var(--bg-subtle); color: var(--text-muted); border: 1px solid var(--border-subtle); }

/* Worker/broker liveness dot: always paired with the text beside it. */
.staff-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
.staff-dot--up   { background: var(--color-success); }
.staff-dot--down { background: var(--color-error); }

/* Mono cell for ids/paths inside staff tables. */
.staff-mono { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-secondary); }

/* ---- Site Settings: sticky section rail + live field filter ---- */
.staff-settings-group-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
    margin-bottom: 22px;
}
.staff-settings-group-card {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    min-width: 0;
    padding: 15px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    color: var(--text-primary);
    text-decoration: none;
    transition: border-color 140ms ease, transform 140ms ease;
}
@media (hover: hover) {
    .staff-settings-group-card:hover {
        border-color: var(--color-primary);
        text-decoration: none;
        transform: translateY(-1px);
    }
}
.staff-settings-group-card__icon { flex: 0 0 auto; font-size: 1.3rem; line-height: 1.2; }
.staff-settings-group-card__body { display: grid; gap: 3px; min-width: 0; }
.staff-settings-group-card__body strong { color: var(--text-primary); }
.staff-settings-group-card__body span,
.staff-settings-group-card__body small { color: var(--text-muted); font-size: 0.78rem; line-height: 1.4; }
.staff-settings-group-card__body small { color: var(--color-primary); font-family: var(--font-mono); font-size: 0.68rem; }
.staff-settings-group-card__arrow { margin-left: auto; color: var(--text-muted); }

.staff-settings-layout {
    display: grid;
    grid-template-columns: 212px minmax(0, 1fr);
    gap: 24px;
    align-items: start;
}
.staff-settings-rail {
    position: sticky;
    top: calc(var(--header-height) + 14px);
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: calc(100vh - var(--header-height) - 40px);
    max-height: calc(100dvh - var(--header-height) - 40px);
    overflow-y: auto;
}
.staff-settings-rail a {
    padding: 7px 11px;
    border-radius: var(--radius-sm);
    border-left: 2px solid transparent;
    color: var(--text-secondary);
    font-size: 0.82rem;
    line-height: 1.35;
}
@media (hover: hover) { .staff-settings-rail a:hover { background: var(--bg-hover); color: var(--text-primary); text-decoration: none; } }
.staff-settings-rail__group { display: grid; gap: 2px; margin-bottom: 13px; }
.staff-settings-rail__group-title { align-items: baseline; display: flex; justify-content: space-between; }
.staff-settings-rail__group-title strong { font-size: 0.72rem; }
.staff-settings-rail__group-title small { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.65rem; }
.staff-settings-rail__group > a:not(.staff-settings-rail__group-title) { font-size: 0.76rem; padding-left: 17px; }
.staff-settings-rail a.is-active {
    background: var(--bg-subtle);
    border-left-color: var(--color-primary);
    color: var(--color-primary);
    font-weight: 600;
}
/* Section filtered out of view: its jump link would go nowhere. */
.staff-settings-rail a.is-muted { opacity: 0.35; }
/* The live filter hides fields/sections with the `hidden` attribute. The global
   `label { display: flex }` rule outranks the UA sheet's [hidden] { display:none },
   so re-assert it here at a specificity that wins. */
.staff-settings-layout [hidden] { display: none; }
.staff-settings-search { margin-bottom: 16px; }
.staff-settings-search input { width: 100%; }
.staff-settings-search__hint { display: block; margin-top: 5px; font-size: 0.76rem; color: var(--text-muted); }

.staff-settings-group { display: grid; gap: 14px; scroll-margin-top: calc(var(--header-height) + 16px); }
.staff-settings-group + .staff-settings-group { margin-top: 28px; }
.staff-settings-group__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
    padding: 4px 2px;
}
.staff-settings-group__header h2 { margin: 3px 0 2px; }
.staff-settings-group__header p { margin: 0; color: var(--text-secondary); font-size: 0.86rem; }
.staff-settings-section__purpose { margin: 3px 0 0; max-width: 66ch; color: var(--text-secondary); font-size: 0.8rem; line-height: 1.4; }
.staff-settings-risk-badge {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    padding: 4px 8px;
    border: 1px solid var(--border-subtle);
    border-radius: 999px;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.66rem;
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.staff-settings-risk-badge--high { border-color: color-mix(in srgb, var(--color-warning) 55%, var(--border-subtle)); color: var(--color-warning-text); }
.staff-settings-risk-note {
    display: flex;
    gap: 6px;
    margin-top: 14px;
    padding: 10px 12px;
    border-left: 3px solid var(--color-warning);
    background: color-mix(in srgb, var(--color-warning) 7%, var(--bg-surface));
    color: var(--text-secondary);
    font-size: 0.8rem;
    line-height: 1.45;
}
.staff-settings-risk-note strong { color: var(--color-warning-text); }
.staff-settings-confirm-check {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 11px;
    color: var(--text-secondary);
    font-size: 0.82rem;
}
.staff-settings-confirm-check input { margin-top: 3px; }

.staff-settings-confirmation {
    position: sticky;
    z-index: 3;
    top: calc(var(--header-height) + 12px);
    margin: 0 0 20px;
    padding: 16px;
    border: 1px solid color-mix(in srgb, var(--color-warning) 55%, var(--border-subtle));
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    box-shadow: var(--shadow-md);
}
/* An explicit :focus ring, not :focus-visible: a browser will not reliably treat
   programmatic focus as focus-visible, and this panel is focused by script when it
   opens. Same reason the incursion board and the pet progression heading carry theirs. */
.staff-settings-confirmation:focus { outline: 2px solid var(--color-warning); outline-offset: 3px; }
.staff-settings-confirmation__header { display: flex; gap: 10px; align-items: flex-start; }
.staff-settings-confirmation__icon { font-size: 1.3rem; line-height: 1.2; }
.staff-settings-confirmation h2 { margin: 0; font-size: 1rem; }
.staff-settings-confirmation p { margin: 4px 0 0; color: var(--text-secondary); font-size: 0.82rem; }
.staff-settings-confirmation__stale { color: var(--color-warning-text) !important; }
.staff-settings-confirmation__changes { display: grid; gap: 6px; margin: 14px 0; padding-left: 0; list-style: none; }
.staff-settings-confirmation__changes li { display: grid; grid-template-columns: minmax(120px, 0.8fr) minmax(150px, 1.2fr) minmax(90px, 1fr) auto minmax(90px, 1fr); gap: 8px; align-items: baseline; padding: 7px 9px; border-radius: var(--radius-sm); background: var(--bg-subtle); font-size: 0.78rem; }
.staff-settings-confirmation__section { color: var(--text-muted); font-size: 0.68rem; }
.staff-settings-confirmation__value { overflow-wrap: anywhere; color: var(--text-muted); font-family: var(--font-mono); }
.staff-settings-confirmation__value--after { color: var(--color-warning-text); }
.staff-settings-confirmation__actions { display: flex; justify-content: flex-end; gap: 8px; }

.staff-settings-history__list { display: grid; gap: 1px; overflow: hidden; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); }
.staff-settings-history__row { padding: 13px 15px; background: var(--bg-subtle); }
.staff-settings-history__row + .staff-settings-history__row { border-top: 1px solid var(--border-subtle); }
.staff-settings-history__meta { display: flex; justify-content: space-between; gap: 12px; }
.staff-settings-history__meta time { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.72rem; }
.staff-settings-history__detail { margin: 6px 0 0; color: var(--text-secondary); font-family: var(--font-mono); font-size: 0.76rem; overflow-wrap: anywhere; }

.staff-settings-intro,
.staff-persona-studio-intro {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 18px;
    padding: 14px 16px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 28%, var(--border-color));
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-primary) 5%, var(--bg-surface));
}
.staff-settings-intro__icon,
.staff-persona-studio-intro__icon { flex: 0 0 auto; font-size: 1.35rem; line-height: 1.2; }
.staff-settings-intro strong,
.staff-persona-studio-intro strong { color: var(--text-primary); }
.staff-settings-intro p,
.staff-persona-studio-intro p { margin: 3px 0 0; color: var(--text-secondary); font-size: 0.86rem; line-height: 1.5; }

.staff-settings-sections { display: grid; gap: 20px; min-width: 0; }
.staff-settings-form { max-width: none; gap: 0; }
/* Anchor targets must clear the fixed header when jumped to from the rail. */
.staff-settings-form .panel { scroll-margin-top: calc(var(--header-height) + 16px); }
.staff-settings-section__header { align-items: flex-start; gap: 12px; }
.staff-settings-section__header > div { min-width: 0; }
.staff-settings-section__count,
.staff-settings-section__dirty { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.74rem; }
.staff-settings-section__header .btn { flex: 0 0 auto; }
.staff-settings-section__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--border-subtle);
}
.staff-settings-section__dirty.is-dirty { color: var(--color-warning-text); }
.settings-field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 16px 20px;
}
.settings-field-grid label { gap: 4px; }
/* Several knobs' help_text names sibling knobs by their real field name, and a run like
   "ceiling_mut_jackpot_min_bonus..ceiling_mut_jackpot_max_bonus" offers no break
   opportunity: 59 unbreakable characters. A flex/grid item's automatic minimum size is its
   min-content width, so that one span sized its whole label past its track and laid the
   document out 32px wider than a 390px phone (and 7px wider at 768). `anywhere` rather than
   `break-word`: only `anywhere` lets the introduced break opportunities count toward
   min-content, which is the figure the track sizing actually reads. */
.settings-field-grid .field-help {
    font-weight: 400;
    font-size: 0.82rem;
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}
/* Boolean knobs span the full grid row: checkbox + label on one line, the help
   text on its own line beneath. Left in a single 220px column the label and its
   help text get squeezed into a two-word-per-line ribbon. */
.settings-field-grid .field-toggle {
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 9px;
}
.settings-field-grid .field-toggle input { flex-shrink: 0; }
.settings-field-grid .field-toggle .field-help { flex-basis: 100%; }
/* An edited-but-unsaved field marks itself, so a 100+ knob page can show you
   exactly what you touched before you commit it. */
.settings-field.is-dirty {
    padding-left: 10px;
    margin-left: -12px;
    border-left: 2px solid var(--color-warning);
}
.staff-settings-empty { color: var(--text-muted); font-size: 0.9rem; padding: 8px 0; }

.staff-settings-save {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    padding: 12px 16px;
    margin-top: 4px;
    background: var(--bg-elevated);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
}
.staff-settings-save__status {
    margin-right: auto;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-muted);
}
.staff-settings-save__status.is-dirty { color: var(--color-warning-text); }

/* ---- Persona Studio: roster, contract, and authored dialogue ---- */
.staff-persona-roster { max-width: none; }
.staff-persona-roster .panel__header { align-items: flex-start; gap: 12px; }
.staff-persona-roster .panel__header > div { min-width: 0; }
.staff-persona-roster__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
}
.staff-persona-roster-card {
    display: grid;
    grid-template-columns: 92px minmax(0, 1fr);
    gap: 14px;
    min-width: 0;
    min-height: 190px;
    padding: 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    color: var(--text-primary);
}
@media (hover: hover) { .staff-persona-roster-card:hover { text-decoration: none; border-color: var(--color-primary); } }
.staff-persona-roster-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.staff-persona-roster-card__portrait,
.persona-studio-identity__mark {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    min-height: 92px;
    overflow: hidden;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-surface));
    color: var(--color-primary);
}
.staff-persona-roster-card__portrait { flex-direction: column; gap: 5px; font-size: 2.25rem; }
.staff-persona-roster-card__portrait small { color: var(--text-muted); font-size: 0.66rem; text-align: center; }
.staff-persona-roster-card__portrait.is-signal,
.persona-studio-identity__mark.is-signal { background: color-mix(in srgb, #6f66ff 13%, var(--bg-surface)); }
.staff-persona-roster-card__portrait img,
.persona-studio-identity__mark img { width: 100%; height: 100%; object-fit: cover; }
.staff-persona-roster-card__body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.staff-persona-roster-card__heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.staff-persona-roster-card__heading h3 { min-width: 0; margin: 0; font-size: 1rem; overflow-wrap: anywhere; }
.staff-persona-roster-card__role { margin: 0; color: var(--text-secondary); font-size: 0.78rem; }
.staff-persona-roster-card__summary { display: -webkit-box; margin: 0; color: var(--text-muted); font-size: 0.78rem; line-height: 1.4; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 3; }
.staff-persona-roster-card__stats,
.staff-persona-roster-card__surfaces,
.persona-studio-chip-row,
.persona-studio-surface-list { display: flex; flex-wrap: wrap; gap: 5px; }
.staff-persona-roster-card__stats { margin-top: auto; color: var(--text-muted); font-family: var(--font-mono); font-size: 0.68rem; }
.staff-persona-roster-card__stats span + span::before { content: '·'; margin-right: 5px; color: var(--border-color); }
.staff-persona-roster-card__stats .is-emphasis { color: var(--color-primary); }
.staff-persona-roster-card__surfaces span,
.persona-studio-surface-list span,
.persona-studio-chip {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    padding: 3px 7px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    color: var(--text-secondary);
    font-size: 0.68rem;
    overflow-wrap: anywhere;
}
.staff-persona-studio-badge {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    padding: 3px 7px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.66rem;
    white-space: nowrap;
}
.staff-persona-studio-badge.is-ready { border-color: var(--color-success); color: var(--color-success-text); }
.staff-persona-studio-badge.is-warning,
.persona-studio-chip.is-warning { border-color: var(--color-warning); color: var(--color-warning-text); }
.persona-studio-detail { display: grid; gap: 20px; }
.persona-studio-identity {
    display: grid;
    grid-template-columns: 150px minmax(0, 1fr);
    gap: 20px;
    padding: 18px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-elevated);
}
.persona-studio-identity__mark { min-height: 150px; font-size: 3.3rem; }
.persona-studio-identity__mark small { padding: 12px; color: var(--text-muted); font-size: 0.73rem; line-height: 1.4; text-align: center; }
.persona-studio-identity__body { min-width: 0; }
.persona-studio-identity__body h2 { margin: 3px 0 6px; overflow-wrap: anywhere; }
.persona-studio-identity__body > p { max-width: 84ch; margin: 0 0 12px; color: var(--text-secondary); line-height: 1.55; }
.persona-studio-chip.is-ready { border-color: var(--color-success); color: var(--color-success-text); }
.persona-studio-surface-list { align-items: center; margin-top: 14px; }
.persona-studio-surface-list strong { margin-right: 3px; color: var(--text-muted); font-size: 0.74rem; }
.persona-studio-overview-grid { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(280px, 0.85fr); gap: 20px; }
.persona-studio-lore,
.persona-studio-contract { max-width: none; }
.persona-studio-lore .panel__header,
.persona-studio-contract .panel__header { align-items: flex-start; gap: 12px; }
.persona-studio-lore p { color: var(--text-secondary); line-height: 1.6; }
.persona-studio-pending { color: var(--color-warning-text) !important; }
.persona-studio-contract__list { display: grid; gap: 9px; margin: 0; }
.persona-studio-contract__list > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--border-subtle); }
.persona-studio-contract__list dt { color: var(--text-muted); font-size: 0.76rem; }
.persona-studio-contract__list dd { max-width: 62%; margin: 0; color: var(--text-secondary); font-size: 0.78rem; text-align: right; overflow-wrap: anywhere; }
.persona-studio-genart__body { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 18px; align-items: start; }
.persona-studio-genart__preview { aspect-ratio: 1 / 1; border-radius: var(--radius-md); overflow: hidden; border: 1px solid var(--border-color); background: var(--bg-subtle); display: grid; place-items: center; }
.persona-studio-genart__preview img { width: 100%; height: 100%; object-fit: contain; display: block; }
.persona-studio-genart__empty { font-size: 3rem; opacity: 0.6; }
.persona-studio-genart__meta { display: grid; gap: 12px; min-width: 0; }
.persona-studio-genart__meta form { margin: 0; }
@media (max-width: 640px) { .persona-studio-genart__body { grid-template-columns: 1fr; } }

.persona-dialogue-group + .persona-dialogue-group { margin-top: 24px; }
.persona-dialogue-group__heading { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.persona-dialogue-group__heading h3 { margin: 0; font-size: 1rem; }
.persona-dialogue-group__heading span { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.72rem; }
.persona-studio-dialogue { display: grid; gap: 16px; }
.persona-studio-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.persona-studio-section-heading h2 { margin: 3px 0 5px; }
.persona-studio-section-heading p { max-width: 76ch; margin: 0; color: var(--text-secondary); font-size: 0.88rem; line-height: 1.5; }
.staff-back-link { display: inline-flex; min-height: 44px; align-items: center; color: var(--color-primary); font-size: 0.82rem; }
.staff-persona-dialogue-card__meta { display: flex; flex-wrap: wrap; gap: 6px 12px; color: var(--text-muted); font-size: 0.76rem; line-height: 1.4; }
.staff-persona-dialogue-card__meta span { overflow-wrap: anywhere; }
.staff-persona-dialogue-grid { display: grid; gap: 14px; }
.staff-persona-dialogue-card {
    display: grid;
    gap: 12px;
    padding: 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.staff-persona-dialogue-card.is-overridden {
    border-color: color-mix(in srgb, var(--color-primary) 38%, var(--border-color));
}
.staff-persona-dialogue-card__heading {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}
.staff-persona-dialogue-card__heading .panel-title { margin: 0 0 3px; font-size: 1rem; }
.staff-persona-dialogue-card__status {
    flex: 0 0 auto;
    padding: 4px 8px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 40%, var(--border-color));
    border-radius: var(--radius-pill);
    color: var(--color-primary);
    font-family: var(--font-mono);
    font-size: 0.68rem;
    white-space: nowrap;
}
.staff-persona-dialogue-card__status.is-default { color: var(--text-muted); border-color: var(--border-subtle); }
.staff-persona-dialogue-form { display: grid; gap: 10px; }
.staff-persona-dialogue-form label { display: flex; flex-direction: column; gap: 5px; }
.staff-persona-dialogue-form textarea { width: 100%; min-height: 8.5rem; resize: vertical; }
.staff-persona-dialogue-card__voice { margin: 0; color: var(--text-secondary); font-size: 0.86rem; line-height: 1.5; }
.staff-persona-dialogue-preview {
    padding: 10px 12px;
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-sm);
    background: var(--bg-surface);
}
.staff-persona-dialogue-preview p { margin: 4px 0 0; color: var(--text-secondary); font-style: italic; }
.staff-persona-dialogue-form__actions,
.staff-persona-dialogue-reset { display: flex; flex-wrap: wrap; gap: 8px; }
.staff-persona-dialogue-defaults { color: var(--text-secondary); font-size: 0.84rem; }
.staff-persona-dialogue-defaults summary { min-height: 44px; display: inline-flex; align-items: center; cursor: pointer; color: var(--color-primary); }
.staff-persona-dialogue-defaults ul { margin: 6px 0 0; padding-left: 20px; }
.staff-persona-dialogue-defaults li + li { margin-top: 4px; }
.staff-persona-dialogue-card [hidden] { display: none; }
@media (max-width: 600px) {
    .staff-persona-dialogue-card__heading { flex-direction: column; }
    .staff-persona-dialogue-card__status { align-self: flex-start; }
}

/* ---- Incursion staff console: one-page control room ---- */
.sic-console,
.sic-console * { min-width: 0; }
.sic-console { display: flex; flex-direction: column; gap: 18px; }
.sic-layout {
    display: grid;
    grid-template-columns: 216px minmax(0, 1fr);
    gap: 24px;
    align-items: start;
}
.sic-rail {
    position: sticky;
    top: calc(var(--header-height) + 14px);
    max-height: calc(100vh - var(--header-height) - 40px);
    max-height: calc(100dvh - var(--header-height) - 40px);
    overflow-y: auto;
}
.sic-tabs { display: flex; flex-direction: column; gap: 4px; }
.sic-tab-group { display: grid; gap: 4px; padding: 8px 0; border-bottom: 1px solid var(--border-subtle); }
.sic-tab-group:last-child { border-bottom: 0; }
.sic-tab-group__label { padding: 0 12px 4px; color: var(--text-muted); font-family: var(--font-mono); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.sic-tab-group__links { display: grid; gap: 3px; }
.sic-tab-group--danger .sic-tab-group__label { color: var(--color-error-text); }
.sic-tab {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-height: 44px;
    padding: 9px 12px;
    border-left: 3px solid transparent;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.25;
}
.sic-tab__label { min-width: 0; overflow-wrap: anywhere; }
@media (hover: hover) { .sic-tab:hover { background: var(--bg-hover); color: var(--text-primary); text-decoration: none; } }
.sic-tab.is-active { background: var(--bg-subtle); border-left-color: var(--color-primary); color: var(--text-primary); }
.sic-tab.is-active .sic-tab__label { font-weight: 700; }
.sic-tab--danger.is-active { border-left-color: var(--color-error); }
.sic-tab:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.sic-tab__badge {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border: 1px solid var(--color-error);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--color-error) 16%, var(--bg-surface));
    color: var(--color-error-text);
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-weight: 700;
}
.sic-main { display: flex; flex-direction: column; gap: 18px; }
.sic-editor-form { display: flex; flex-direction: column; gap: 18px; }
.sic-content { display: grid; gap: 18px; }
.sic-anchor { scroll-margin-top: calc(var(--header-height) + 18px); }
.sic-panel { max-width: none; margin: 0; }
.sic-panel__header { align-items: flex-start; gap: 12px; }
.sic-panel__header > div { min-width: 0; }
.sic-section-count { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.74rem; white-space: nowrap; }
.sic-lede,
.sic-section-help { color: var(--text-secondary); max-width: 78ch; line-height: 1.5; }
.sic-section-help { margin: -4px 0 16px; font-size: 0.88rem; }
.sic-section-note-card { display: grid; gap: 3px; margin: 0 0 16px; padding: 11px 13px; border-left: 3px solid var(--color-primary); border-radius: var(--radius-sm); background: color-mix(in srgb, var(--color-primary) 6%, var(--bg-subtle)); color: var(--text-secondary); font-size: 0.8rem; line-height: 1.45; }
.sic-section-note-card strong,
.sic-section-note-card b { color: var(--text-primary); }
.sic-content-intro { display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; padding: 4px 0 0; }
.sic-content-intro p { max-width: 54ch; margin: 0; color: var(--text-secondary); font-size: 0.86rem; line-height: 1.5; }
.sic-content-nav { display: flex; flex-wrap: wrap; gap: 7px; padding: 2px 0; }
.sic-content-nav a { display: inline-flex; align-items: center; gap: 7px; min-height: 36px; padding: 7px 10px; border: 1px solid var(--border-subtle); border-radius: var(--radius-pill); color: var(--text-secondary); font-size: 0.78rem; font-weight: 700; }
@media (hover: hover) { .sic-content-nav a:hover { border-color: var(--color-primary); color: var(--text-primary); text-decoration: none; } }
.sic-content-nav span { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.7rem; font-weight: 400; }
.sic-preview-settings {
    margin: 18px 0;
    padding: 16px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 28%, var(--border-color));
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-primary) 5%, var(--bg-surface));
}
.sic-preview-controls { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.sic-run-sim-inputs {
    display: grid;
    gap: 12px;
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--border-subtle);
}
.sic-run-sim-inputs__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.sic-run-sim-inputs__head h3 { margin: 0 0 3px; font-size: 1rem; }
.sic-run-sim-inputs__head p { margin: 0; color: var(--text-muted); font-size: 0.82rem; line-height: 1.45; }
.sic-run-sim-controls { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.sic-alert {
    padding: 11px 14px;
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
}
.sic-alert--error { border-left-color: var(--color-error); color: var(--color-error-text); }
.sic-alert--success { border-left-color: var(--color-success); color: var(--color-success-text); }
.sic-workflow { display: grid; gap: 14px; }
.sic-workflow__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.sic-workflow__id { min-width: 0; }
.sic-workflow__title-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.sic-workflow__meta,
.sic-workflow__detail { color: var(--text-secondary); font-size: 0.86rem; line-height: 1.5; max-width: 82ch; margin: 0; }
.sic-workflow__meta { margin-top: 4px; }
.sic-status {
    align-self: flex-start;
    padding: 5px 11px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-size: 0.76rem;
    font-weight: 600;
    white-space: nowrap;
}
.sic-status--neutral { color: var(--text-muted); }
.sic-status--warning { color: var(--color-warning-text); border-color: var(--color-warning); background: color-mix(in srgb, var(--color-warning) 12%, var(--bg-surface)); }
.sic-status--success { color: var(--color-success-text); border-color: var(--color-success); background: color-mix(in srgb, var(--color-success) 12%, var(--bg-surface)); }
.sic-steps { display: flex; align-items: stretch; gap: 8px; flex-wrap: wrap; margin: 0; padding: 0; list-style: none; }
.sic-step { display: flex; align-items: center; gap: 10px; flex: 1 1 190px; min-width: 160px; padding: 0; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.sic-step > a { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 10px 12px; color: inherit; text-decoration: none; }
.sic-step > a:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; border-radius: var(--radius-sm); }
.sic-step > a.is-focused .sic-step__label { color: var(--color-primary); }
.sic-step__num { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: 1px solid var(--border-color); border-radius: 50%; font-family: var(--font-mono); font-size: 0.78rem; }
.sic-step__label { display: flex; flex-direction: column; gap: 1px; font-weight: 700; font-size: 0.9rem; }
.sic-step__label small { color: var(--text-muted); font-weight: 400; font-size: 0.73rem; }
.sic-step.is-current { border-color: var(--color-primary); }
.sic-step.is-current .sic-step__num { border-color: var(--color-primary); background: var(--bg-elevated); color: var(--color-primary); }
.sic-step.is-done { border-color: var(--color-success); }
.sic-step.is-done .sic-step__num { border-color: var(--color-success); color: var(--color-success-text); }
.sic-step-arrow { display: flex; align-items: center; color: var(--text-muted); }
.sic-section-note { color: var(--text-muted); font-size: 0.74rem; white-space: nowrap; }
.sic-knob-groups { display: grid; gap: 12px; }
.sic-knob-group,
.sic-result-details,
.sic-advanced details { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.sic-knob-group summary,
.sic-result-details summary,
.sic-advanced summary { cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 11px 13px; color: var(--text-primary); font-weight: 700; }
.sic-summary-count,
.sic-result-details summary span { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.72rem; font-weight: 400; }
.sic-knob-group[open] summary { border-bottom: 1px solid var(--border-subtle); }
.sic-field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px 18px; padding: 14px; }
.sic-control { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.sic-control > span { color: var(--text-secondary); font-size: 0.78rem; font-weight: 600; line-height: 1.25; }
.sic-control small { color: var(--text-muted); font-size: 0.73rem; line-height: 1.35; overflow-wrap: anywhere; }
.sic-control input,
.sic-control select,
.sic-control textarea { width: 100%; max-width: 100%; }
.sic-control textarea { min-height: 76px; resize: vertical; }
.sic-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(285px, 1fr)); gap: 14px; }
.sic-card-grid--materials { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.sic-edit-card,
.sic-choice-card,
.sic-preview-room {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
    padding: 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.sic-edit-card__head,
.sic-choice-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.sic-edit-card__head strong { display: block; margin-top: 3px; }
.sic-toggle { display: inline-flex; align-items: center; gap: 6px; color: var(--text-secondary); font-size: 0.76rem; white-space: nowrap; }
.sic-toggle input { flex: 0 0 auto; }
.sic-inline-fields { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
.sic-inline-fields--wide { grid-template-columns: minmax(0, 1.4fr) minmax(150px, 0.6fr); }
.sic-range-fields { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 6px; }
.sic-range-fields span { color: var(--text-muted); font-size: 0.78rem; text-align: center; }
.sic-key { display: block; color: var(--text-muted); font-family: var(--font-mono); font-size: 0.7rem; overflow-wrap: anywhere; }
.sic-room-list { display: grid; gap: 10px; }
.sic-room-card { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); overflow: hidden; }
.sic-room-card summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 15px; cursor: pointer; }
@media (hover: hover) { .sic-room-card summary:hover { background: var(--bg-hover); } }
.sic-room-card__title { display: flex; align-items: baseline; gap: 9px; min-width: 0; flex-wrap: wrap; }
.sic-room-card__title strong { overflow-wrap: anywhere; }
.sic-room-card__title small,
.sic-room-card__state { color: var(--text-muted); font-size: 0.76rem; }
.sic-room-card__state { white-space: nowrap; }
.sic-room-card[open] summary { border-bottom: 1px solid var(--border-subtle); }
.sic-room-card__body { display: grid; gap: 16px; padding: 15px; }
.sic-room-copy { display: grid; grid-template-columns: minmax(0, 0.7fr) minmax(0, 1.3fr); gap: 12px; }
.sic-room-tuning { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.sic-room-interaction,
.sic-choice-list { display: grid; gap: 12px; padding-top: 14px; border-top: 1px solid var(--border-subtle); }
.sic-subhead { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.sic-subhead span { font-weight: 700; }
.sic-subhead small { color: var(--text-muted); font-size: 0.76rem; }
.sic-control--wide { grid-column: span 2; }
.sic-room-interaction .sic-room-tuning:first-of-type { grid-template-columns: minmax(180px, 0.6fr) minmax(0, 1.4fr); }
.sic-room-interaction .sic-room-tuning:last-of-type { grid-template-columns: minmax(180px, 0.6fr) minmax(0, 1.4fr); }
.sic-room-interaction ul { display: flex; flex-wrap: wrap; gap: 5px 10px; margin: 0; padding: 0; list-style: none; }
.sic-room-interaction li { display: inline-flex; align-items: center; gap: 5px; }
.sic-choice-list { gap: 10px; }
.sic-choice-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 12px; }
.sic-choice-fields .sic-control:nth-child(1),
.sic-choice-fields .sic-control:nth-child(2) { grid-column: span 3; }
.sic-toggle--box { align-self: end; min-height: 42px; padding: 8px 10px; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); background: var(--bg-surface); }
.sic-actionbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 13px 15px; border: 1px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-elevated); box-shadow: var(--shadow-md); position: sticky; top: calc(var(--header-height) + 10px); z-index: 3; }
.sic-actionbar strong,
.sic-actionbar small { display: block; }
.sic-actionbar strong { font-size: 0.86rem; }
.sic-actionbar small { color: var(--text-muted); margin-top: 3px; font-size: 0.75rem; }
.sic-actionbar__buttons { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
.sic-actionbar button:disabled { opacity: 0.45; cursor: not-allowed; }
.sic-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(125px, 1fr)); gap: 10px; margin: 14px 0; }
.sic-stat-grid > div,
.sic-simulation-grid > div { display: flex; flex-direction: column; gap: 4px; padding: 11px 12px; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); background: var(--bg-subtle); }
.sic-stat-grid span,
.sic-simulation-grid span { color: var(--text-muted); font-size: 0.72rem; }
.sic-stat-grid strong,
.sic-simulation-grid strong { font-family: var(--font-mono); font-size: 1rem; overflow-wrap: anywhere; }
.sic-preview-rooms { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px; }
.sic-preview-room { gap: 8px; padding: 11px 12px; }
.sic-result-details { margin-top: 12px; padding: 0 13px 13px; }
.sic-result-details summary { margin: 0 -13px; }
.sic-result-details p { color: var(--text-secondary); font-size: 0.84rem; line-height: 1.45; }
.sic-simulation-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; }
.sic-inline-summary { margin-bottom: 0; }
.sic-run-sim-result { display: grid; gap: 14px; }
.sic-run-sim-assumptions { margin: 0; }
.sic-run-sim-assumptions ul { display: grid; gap: 4px; margin: 0; padding-left: 18px; color: var(--text-secondary); font-size: 0.8rem; }
.sic-run-sim-party { display: flex; flex-wrap: wrap; gap: 5px 12px; padding: 11px 12px; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); background: var(--bg-subtle); color: var(--text-secondary); font-size: 0.8rem; }
.sic-run-sim-party strong { color: var(--text-primary); }
.sic-run-sim-party span { overflow-wrap: anywhere; }
.sic-run-sim-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.sic-run-sim-card { display: flex; flex-direction: column; gap: 12px; min-width: 0; padding: 13px; border: 1px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-subtle); }
.sic-run-sim-card__head { display: flex; flex-direction: column; gap: 3px; }
.sic-run-sim-card__head h4 { margin: 0; font-size: 1rem; }
.sic-run-sim-card__head span { color: var(--text-muted); font-size: 0.76rem; line-height: 1.4; }
.sic-run-sim-card .sic-stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 0; }
.sic-run-sim-card .sic-stat-grid > div { padding: 9px 10px; }
.sic-run-sim-rewards,
.sic-run-sim-causes { padding-top: 11px; border-top: 1px solid var(--border-subtle); }
.sic-run-sim-rewards h5,
.sic-run-sim-causes h5 { margin: 0 0 8px; font-size: 0.8rem; }
.sic-run-sim-rewards p { margin: 5px 0 0; color: var(--text-secondary); font-size: 0.76rem; line-height: 1.4; overflow-wrap: anywhere; }
.sic-run-sim-causes ul { display: grid; gap: 5px; margin: 0; padding: 0; list-style: none; }
.sic-run-sim-causes li { display: flex; justify-content: space-between; gap: 8px; color: var(--text-secondary); font-size: 0.76rem; }
.sic-run-sim-causes li strong { color: var(--text-primary); font-family: var(--font-mono); white-space: nowrap; }
.sic-empty-state { display: flex; flex-direction: column; gap: 5px; padding: 16px; border: 1px dashed var(--border-color); border-radius: var(--radius-md); color: var(--text-secondary); }
.sic-empty-state strong { color: var(--text-primary); }
.sic-filter-form { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)) auto; align-items: end; gap: 10px; padding: 12px; margin-bottom: 14px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.sic-filter-form .btn { min-height: 40px; }
.sic-stat-grid--metrics { margin-top: 0; }
.sic-metric-columns { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px; margin-top: 18px; }
.sic-metric-columns > div { min-width: 0; }
.sic-metric-columns h3 { margin: 0 0 8px; font-size: 0.95rem; }
.sic-metric-group { padding: 12px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.sic-metric-list { display: grid; gap: 2px; }
.sic-metric-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--border-subtle); color: var(--text-secondary); font-size: 0.78rem; }
.sic-metric-row:last-child { border-bottom: 0; }
.sic-metric-row span { min-width: 0; overflow-wrap: anywhere; }
.sic-metric-row strong { flex: 0 0 auto; color: var(--text-primary); font-family: var(--font-mono); }
.sic-metric-row small { color: var(--text-muted); line-height: 1.4; text-align: right; }
.sic-metric-row--wide { align-items: flex-start; }
.sic-metric-empty { margin: 0; color: var(--text-muted); font-size: 0.78rem; line-height: 1.4; }
.sic-metric-columns p { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; }
.sic-metric-columns p span { display: inline-flex; align-items: baseline; gap: 6px; max-width: 100%; padding: 4px 8px; border: 1px solid var(--border-subtle); border-radius: var(--radius-pill); color: var(--text-secondary); font-size: 0.76rem; overflow-wrap: anywhere; }
.sic-metric-columns b { color: var(--text-primary); font-family: var(--font-mono); }
.sic-history-list { display: grid; gap: 8px; }
.sic-history-row { display: grid; grid-template-columns: minmax(180px, 0.7fr) minmax(0, 1.3fr) auto; align-items: center; gap: 14px; padding: 12px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.sic-history-row > div { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.sic-history-row small { color: var(--text-muted); font-size: 0.74rem; overflow-wrap: anywhere; }
.sic-history-row--current { border-color: var(--color-primary); }
.sic-badge { align-self: flex-start; padding: 2px 7px; border: 1px solid var(--color-success); border-radius: var(--radius-pill); color: var(--color-success-text); font-size: 0.68rem; }
.sic-diff { margin-bottom: 14px; padding: 12px; border: 1px solid var(--border-color); border-left: 3px solid var(--color-primary); border-radius: var(--radius-md); background: var(--bg-subtle); }
.sic-diff__head { display: flex; justify-content: space-between; gap: 10px; margin-bottom: 9px; }
.sic-diff ul { display: grid; gap: 6px; margin: 0; padding-left: 18px; }
.sic-diff li { display: flex; flex-wrap: wrap; gap: 6px 10px; color: var(--text-secondary); font-size: 0.8rem; }
.sic-diff li strong { color: var(--text-primary); }
.sic-advanced details { padding: 0 13px 13px; }
.sic-advanced summary { margin: 0 -13px; }
.sic-advanced summary > span { display: flex; flex-direction: column; gap: 3px; }
.sic-advanced summary small { color: var(--text-muted); font-weight: 400; }
.sic-advanced p { color: var(--text-secondary); font-size: 0.84rem; line-height: 1.45; }

/* New console chrome: action-bar status, room toolbar, filter actions, danger zone */
.sic-actionbar__status { min-width: 0; }
.sic-actionbar__status strong { font-size: 0.86rem; overflow-wrap: anywhere; }
.sic-room-toolbar { display: flex; align-items: flex-end; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.sic-room-search,
.sic-room-filter { display: flex; flex: 1 1 180px; flex-direction: column; gap: 5px; min-width: 150px; }
.sic-room-search span,
.sic-room-filter span { color: var(--text-secondary); font-size: 0.76rem; font-weight: 700; }
.sic-room-search input,
.sic-room-filter select { width: 100%; min-height: 40px; }
.sic-room-count { align-self: center; margin-right: auto; color: var(--text-muted); font-size: 0.76rem; white-space: nowrap; }
.sic-room-count strong { color: var(--text-primary); font-family: var(--font-mono); }
.sic-room-card__state.is-on { color: var(--color-success-text); }
.sic-room-card__state.is-off { color: var(--text-muted); }
.sic-control--error input,
.sic-control--error select,
.sic-control--error textarea { border-color: var(--color-error); }
.sic-control--error > span { color: var(--color-error-text); }
.sic-filter-form__actions { display: flex; align-items: center; gap: 8px; }
.sic-filter-form__actions .btn { min-height: 40px; }
.sic-history-row--selected { border-color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-subtle)); }
.sic-danger-zone {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding: 16px;
    border: 1px solid var(--color-error);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-error) 6%, var(--bg-subtle));
}
.sic-danger-zone__copy { flex: 1 1 320px; min-width: 0; }
.sic-danger-zone__copy strong { display: block; }
.sic-danger-zone__copy p { margin: 4px 0 0; color: var(--text-secondary); font-size: 0.84rem; line-height: 1.45; }

/* Preview result: a self-explanatory content-generation readout */
.sic-preview-intro {
    display: grid;
    gap: 8px;
    margin-bottom: 16px;
    padding: 13px 15px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 28%, var(--border-color));
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-primary) 5%, var(--bg-surface));
}
.sic-preview-intro p { margin: 0; color: var(--text-secondary); font-size: 0.86rem; line-height: 1.5; }
.sic-preview-intro b { color: var(--text-primary); font-weight: 700; }
.sic-preview-group { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border-subtle); }
.sic-preview-group:first-of-type { border-top: 0; padding-top: 0; }
.sic-preview-group__head { margin-bottom: 12px; }
.sic-preview-group__head h3 { margin: 0 0 3px; font-size: 1rem; }
.sic-preview-group__head p { margin: 0; color: var(--text-muted); font-size: 0.82rem; line-height: 1.45; max-width: 82ch; }
.sic-preview-room__head { display: flex; flex-direction: column; gap: 2px; }
.sic-preview-room__head strong { overflow-wrap: anywhere; }
.sic-preview-room__locks { display: flex; flex-wrap: wrap; gap: 5px; }
.sic-lock {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    background: var(--bg-surface);
    font-size: 0.74rem;
    font-family: var(--font-mono);
    white-space: nowrap;
}
.sic-lock::before { content: ""; flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: var(--sic-lock-dot, var(--text-muted)); }
.sic-lock--red { --sic-lock-dot: #e5573c; }
.sic-lock--green { --sic-lock-dot: #4bc46a; }
.sic-lock--blue { --sic-lock-dot: #4a9df0; }
.sic-lock--gold { --sic-lock-dot: #f2c14e; }
.sic-lock--none { color: var(--text-muted); }
.sic-lock--none::before { display: none; }
.sic-preview-room__facts { display: grid; gap: 4px; margin: 0; }
.sic-preview-room__facts > div { display: flex; flex-wrap: wrap; gap: 3px 8px; }
.sic-preview-room__facts dt { color: var(--text-muted); font-size: 0.73rem; }
.sic-preview-room__facts dt::after { content: ":"; }
.sic-preview-room__facts dd { margin: 0; color: var(--text-secondary); font-size: 0.78rem; overflow-wrap: anywhere; }
.sic-preview-legend { margin-top: 18px; }
.sic-preview-legend__cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px 24px; padding-top: 4px; }
.sic-preview-legend__cols h4 { margin: 0 0 6px; font-size: 0.85rem; }
.sic-preview-legend__cols ul { display: grid; gap: 5px; margin: 0; padding-left: 18px; }
.sic-preview-legend__cols li { color: var(--text-secondary); font-size: 0.8rem; line-height: 1.4; overflow-wrap: anywhere; }
.sic-preview-legend__cols b { color: var(--text-primary); }

@media (max-width: 1024px) {
    .sic-layout { grid-template-columns: minmax(0, 1fr); }
    .sic-rail {
        position: sticky;
        top: calc(var(--header-height) + 6px);
        max-height: none;
        overflow: visible;
        z-index: 4;
        padding: 6px 0;
        background: var(--bg-body);
        border-bottom: 1px solid var(--border-subtle);
    }
    .sic-tabs { flex-direction: row; overflow-x: auto; gap: 6px; padding-bottom: 4px; }
    .sic-tab {
        flex: 0 0 auto;
        border: 1px solid var(--border-subtle);
        border-radius: var(--radius-pill);
        background: var(--bg-surface);
        white-space: nowrap;
    }
    .sic-tab.is-active { border-color: var(--color-primary); }
    .sic-tab--danger.is-active { border-color: var(--color-error); }
    .sic-room-tuning { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .sic-filter-form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sic-preview-controls,
    .sic-run-sim-controls { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
    .sic-panel { padding: 14px; }
    .sic-panel__header { flex-direction: column; }
    .sic-field-grid,
    .sic-card-grid,
    .sic-card-grid--materials,
    .sic-room-copy,
    .sic-room-tuning,
    .sic-room-interaction .sic-room-tuning:first-of-type,
    .sic-room-interaction .sic-room-tuning:last-of-type,
    .sic-choice-fields,
    .sic-filter-form,
    .sic-metric-columns,
    .sic-preview-legend__cols,
    .sic-preview-controls,
    .sic-run-sim-controls { grid-template-columns: minmax(0, 1fr); }
    .sic-run-sim-grid { grid-template-columns: minmax(0, 1fr); }
    .sic-control--wide,
    .sic-choice-fields .sic-control:nth-child(1),
    .sic-choice-fields .sic-control:nth-child(2) { grid-column: auto; }
    .sic-actionbar { position: static; flex-direction: column; align-items: stretch; }
    .sic-actionbar__buttons { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sic-actionbar__buttons .btn { width: 100%; padding-inline: 6px; }
    .sic-history-row { grid-template-columns: minmax(0, 1fr); }
    .sic-history-row .btn,
    .sic-history-row [aria-current] { justify-self: start; }
    .sic-steps { flex-direction: column; }
    .sic-step { flex-basis: auto; }
    .sic-step-arrow { display: none; }
    .sic-danger-zone { flex-direction: column; align-items: stretch; }
    .sic-danger-zone .btn { width: 100%; }
    .sic-filter-form__actions { display: grid; grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 380px) {
    .sic-actionbar__buttons { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1024px) {
    .sic-tabs { gap: 8px; }
    .sic-tab-group { padding: 6px 0; }
    .sic-tab-group__links { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sic-tab { border: 1px solid var(--border-subtle); border-left-width: 1px; border-radius: var(--radius-pill); background: var(--bg-surface); }
    .sic-tab.is-active { border-color: var(--color-primary); }
    .sic-tab--danger.is-active { border-color: var(--color-error); }
}

@media (max-width: 700px) {
    .sic-content-intro { flex-direction: column; align-items: stretch; gap: 6px; }
    .sic-content-nav { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sic-content-nav a { justify-content: space-between; }
    .sic-room-toolbar { align-items: stretch; }
    .sic-room-search,
    .sic-room-filter { flex-basis: 100%; }
    .sic-room-count { align-self: flex-start; margin-right: 0; }
    .sic-workflow { gap: 10px; }
    .sic-workflow__title-row { align-items: flex-start; flex-direction: column; gap: 6px; }
    .sic-workflow__meta,
    .sic-workflow__detail { font-size: 0.78rem; }
    .sic-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
    .sic-step { min-width: 0; padding: 0; gap: 0; }
    .sic-step > a { min-height: 44px; padding: 8px; gap: 6px; }
    .sic-step__label { font-size: 0.78rem; }
    .sic-step__label small { display: none; }
    .sic-step-arrow { display: none; }
    .sic-actionbar { position: static; }
    .sic-metric-row--wide { display: grid; gap: 3px; }
    .sic-metric-row small { text-align: left; }
}

@media (max-width: 1024px) {
    .staff-settings-group-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .staff-settings-layout { grid-template-columns: minmax(0, 1fr); }
    .staff-settings-rail {
        position: static;
        flex-direction: row;
        flex-wrap: nowrap;
        overflow-x: auto;
        max-height: none;
        padding-bottom: 6px;
        gap: 6px;
    }
    .staff-settings-rail a {
        white-space: nowrap;
        border: 1px solid var(--border-subtle);
        border-radius: var(--radius-pill);
        background: var(--bg-surface);
    }
    .staff-settings-rail a.is-active { border-color: var(--color-primary); }
}

@media (max-width: 700px) {
    .staff-settings-group-grid { grid-template-columns: minmax(0, 1fr); }
    .staff-settings-group__header { flex-direction: column; }
    .staff-settings-confirmation__changes li { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
    .staff-settings-confirmation__changes li strong,
    .staff-settings-confirmation__section { grid-column: 1 / -1; }
    .staff-settings-intro,
    .staff-persona-studio-intro { padding: 12px; }
    .staff-settings-section__header { flex-direction: column; }
    .staff-settings-section__header .btn { width: 100%; }
    .staff-settings-section__footer { align-items: stretch; flex-direction: column; }
    .staff-settings-section__footer .btn { width: 100%; }
    .staff-persona-roster__grid,
    .persona-studio-overview-grid { grid-template-columns: minmax(0, 1fr); }
    .staff-persona-roster-card { grid-template-columns: 78px minmax(0, 1fr); padding: 11px; }
    .persona-studio-identity { grid-template-columns: minmax(0, 1fr); padding: 14px; }
    .persona-studio-identity__mark { min-height: 120px; }
    .persona-studio-section-heading { flex-direction: column; }
}

@media (max-width: 380px) {
    .staff-persona-roster-card { grid-template-columns: minmax(0, 1fr); }
    .staff-persona-roster-card__portrait { min-height: 82px; }
    .staff-settings-save { align-items: stretch; flex-direction: column; }
    .staff-settings-save__status { margin-right: 0; }
    .staff-settings-save .btn { width: 100%; }
}

/* Persona Studio authoring workspace polish. This is appended so the staff
   workspace can evolve independently of the shared persona surface dialects. */
.persona-studio-command {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(260px, 0.72fr);
    gap: 16px;
    margin-bottom: 20px;
}
.persona-studio-command__metrics {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 10px;
}
.persona-studio-command__metric,
.persona-studio-command__focus {
    display: grid;
    align-content: center;
    gap: 4px;
    min-width: 0;
    padding: 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-cool);
}
.persona-studio-command__metric strong {
    color: var(--text-primary);
    font-family: var(--font-display);
    font-size: 1.45rem;
    line-height: 1;
}
.persona-studio-command__metric span {
    color: var(--text-secondary);
    font-size: 0.74rem;
    line-height: 1.3;
}
.persona-studio-command__focus {
    background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-surface));
}
.persona-studio-command__focus strong { color: var(--text-primary); }
.persona-studio-command__focus p {
    margin: 0;
    color: var(--text-secondary);
    font-size: 0.8rem;
    line-height: 1.5;
}
.persona-studio-roster-toolbar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 190px auto;
    align-items: end;
    gap: 10px;
    margin-bottom: 16px;
    padding: 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-cool);
}
.persona-studio-roster-toolbar label {
    display: grid;
    gap: 5px;
    min-width: 0;
}
.persona-studio-roster-toolbar label > span {
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.persona-studio-roster-toolbar input,
.persona-studio-roster-toolbar select { width: 100%; min-height: 42px; }
.staff-persona-roster-card.is-focus { border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border-color)); }
.staff-persona-roster-card__priority {
    display: block;
    margin-bottom: 2px;
    color: var(--color-primary);
    font-family: var(--font-mono);
    font-size: 0.63rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.persona-studio-roster-empty {
    margin: 18px 0 0;
    padding: 18px;
    border: 1px dashed var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    text-align: center;
}
.persona-studio-authoring-grid {
    display: grid;
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    gap: 20px;
}
.persona-studio-authoring-card { max-width: none; }
.persona-studio-authoring-card .btn { margin-top: 16px; }
.persona-studio-guardrails {
    padding: 18px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 34%, var(--border-color));
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-primary) 7%, var(--bg-surface));
}
.persona-studio-guardrails h2 { margin: 4px 0 10px; font-size: 1.15rem; }
.persona-studio-guardrails ul { display: grid; gap: 8px; margin: 0; padding-left: 20px; color: var(--text-secondary); line-height: 1.5; }
.persona-studio-state-index {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    padding: 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-cool);
}
.persona-studio-state-index strong {
    margin-right: 3px;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.persona-studio-state-index__link {
    display: inline-flex;
    min-height: 36px;
    align-items: center;
    gap: 4px;
    padding: 5px 9px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    color: var(--text-secondary);
    font-size: 0.72rem;
}
@media (hover: hover) { .persona-studio-state-index__link:hover { border-color: var(--color-primary); color: var(--text-primary); text-decoration: none; } }
.persona-studio-state-index__link:focus-visible { border-color: var(--color-primary); color: var(--text-primary); text-decoration: none; }
.persona-studio-state-index__link.is-overridden { border-color: color-mix(in srgb, var(--color-primary) 45%, var(--border-color)); color: var(--color-primary); }
.staff-persona-dialogue-card > summary { list-style: none; }
.staff-persona-dialogue-card > summary::-webkit-details-marker { display: none; }
.staff-persona-dialogue-card__heading { min-height: 44px; align-items: center; cursor: pointer; }
.staff-persona-dialogue-card__heading:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; border-radius: var(--radius-sm); }
.staff-persona-dialogue-card__summary-side { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.staff-persona-dialogue-card__toggle {
    display: inline-flex;
    width: 28px;
    height: 28px;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-subtle);
    border-radius: 50%;
    color: var(--color-primary);
    font-size: 1rem;
    line-height: 1;
}
.staff-persona-dialogue-card[open] .staff-persona-dialogue-card__toggle { font-size: 0; }
.staff-persona-dialogue-card[open] .staff-persona-dialogue-card__toggle::before { content: '−'; font-size: 1rem; }
.staff-persona-dialogue-card__body { display: grid; gap: 12px; padding-top: 12px; }
.staff-persona-dialogue-preview ol { display: grid; gap: 5px; margin: 6px 0 0; padding-left: 22px; color: var(--text-secondary); }
.staff-persona-dialogue-preview__note { margin: 8px 0 0 !important; color: var(--text-muted) !important; font-size: 0.76rem; }

@media (max-width: 900px) {
    .persona-studio-command { grid-template-columns: 1fr; }
    .persona-studio-authoring-grid { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
    .persona-studio-command__metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .persona-studio-roster-toolbar { grid-template-columns: 1fr; align-items: stretch; }
    .persona-studio-roster-toolbar .btn { width: 100%; }
    .persona-studio-state-index { align-items: stretch; }
    .persona-studio-state-index strong { flex-basis: 100%; }
    .persona-studio-state-index__link { flex: 1 1 auto; justify-content: center; }
}
@media (max-width: 380px) {
    .persona-studio-command__metric { padding: 11px 10px; }
    .persona-studio-command__metric span { font-size: 0.68rem; }
    .staff-persona-dialogue-card__summary-side { align-items: flex-end; flex-direction: column; }
}
@media (prefers-reduced-motion: reduce) {
    .staff-persona-dialogue-card__toggle { transition: none; }
}

/* ── Trading Square (gifts/deliveries/trades) ─────────────────────────── */
.offer-builder__section {
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    padding: 12px 16px 16px;
    margin-bottom: 16px;
}
/* The gift/deliver/trade compose forms and the Trade card's own "Add My Side" form
   all wrap `tradeOfferBuilder` (currency inputs + item/pet pickers) — real width-hungry
   content, not the app's typical narrow single-field form the global `form` rule (under
   "FORMS") is sized for. Opts out the same way `.marketplace-bulk-form` does. */
.offer-builder-form { max-width: none; }
.offer-builder__section legend { padding: 0 4px; }
.offer-builder__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 0;
    border-bottom: 1px solid var(--border-subtle);
}
.offer-builder__row:last-child { border-bottom: none; }
.offer-builder__row input[type="number"] { max-width: 90px; margin-left: auto; }
/* A row/button becomes part of the offer the moment a qty > 0 is entered or a pet is
   clicked: this highlight (plus the .offer-builder__check mark and the live "You're
   offering" summary below) is what actually signals that, since neither previously had
   any visible feedback: the item row had no state change at all, and the pet button's
   old .is-active class had no matching CSS rule anywhere in this file. */
.offer-builder__row.is-selected {
    background: var(--bg-hover);
    border-radius: var(--radius-sm);
}
.offer-builder__check {
    color: var(--color-primary);
    font-weight: 700;
}
.offer-builder__row-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}
.offer-pet-pick {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    color: var(--text-primary);
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}
@media (hover: hover) { .offer-pet-pick:hover { border-color: var(--color-primary); } }
.offer-pet-pick:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.offer-pet-pick.is-selected {
    border-color: var(--color-primary);
    background: var(--bg-hover);
}
.offer-builder__summary {
    margin-top: 4px;
    padding-top: 12px;
    border-top: 1px solid var(--border-subtle);
}
.offer-builder__summary .offer-builder__row-list { margin-top: 6px; }
.offer-builder__row-list-vertical {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.trade-sides {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin: 12px 0;
}
.trade-side {
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    padding: 12px;
}
.history-row {
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    padding: 10px 14px;
}
.history-row__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}
.history-row__head .mono { margin-left: auto; color: var(--text-muted); }
.history-row__contents {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--border-subtle);
}
.history-row__side .eyebrow { display: block; margin-bottom: 4px; }
@media (max-width: 640px) {
    .history-row__contents { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
    .trade-sides { grid-template-columns: 1fr; }
}

/* ---- Staff: Waitlist (/staff/waitlist/) ---- */
/* Reuses .error-filters / .error-bulkbar / .staff-pagination wholesale: the
   two staff list tables are the same interaction, so only the state badges and
   the export strip are specific to this page. Deliberately NOT reusing
   .badge-unresolved for "Unsubscribed": that badge is red because an unresolved
   error needs action, whereas an unsubscribe is a legitimate finished state. */
.badge-waiting  { background: var(--color-warning-bg); color: var(--color-warning-text); border: 1px solid var(--color-warning-border); }
.badge-invited  { background: var(--color-success-bg); color: var(--color-success-text); border: 1px solid var(--color-success-border); }
.badge-gone     { background: var(--bg-subtle); color: var(--text-muted); border: 1px solid var(--border-subtle); }
.waitlist-row--gone td { opacity: 0.55; }
.waitlist-staff__export {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 0 0 14px;
}
.waitlist-staff__export-note { color: var(--text-muted); font-size: 0.78rem; }

/* ---- Invite codes (/staff/invites/ and the player-facing /invites/) ---- */
/* Reuses the waitlist page's state badges, .staff-pagination and .staff-readouts:
   same table, same states. Only the two compose forms and the player's own
   code list need rules of their own. */
.invite-forms { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-bottom: 18px; }
.invite-form { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; max-width: none; }
.invite-form label { display: flex; flex-direction: column; gap: 4px; font-size: 0.78rem; color: var(--text-muted); width: 100%; }
.invite-form input, .invite-form textarea { width: 100%; }
.invite-form--email textarea { resize: vertical; }
.field-hint { color: var(--text-muted); font-size: 0.74rem; font-weight: 400; }
.invite-row--spent td { opacity: 0.55; }

.invite-allowance { margin: 0 0 14px; color: var(--text-secondary); font-size: 0.9rem; }
.invite-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.invite-list__item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
}
.invite-list__item--spent { opacity: 0.6; }
.invite-list__code { font-size: 1.05rem; font-weight: 700; letter-spacing: 2px; }
.invite-list__link { flex: 1 1 260px; min-width: 0; font-size: 0.78rem; }
.invite-list__meta { color: var(--text-muted); font-size: 0.76rem; margin-left: auto; }
.invite-referral-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.invite-referral-list__item { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: baseline; justify-content: space-between; padding: 10px 12px; border: 1px solid var(--border-subtle); border-radius: 8px; }

/* ============================================================
   SPARKY'S DAILY WHEEL  (templates/game/daily_wheel.html)

   "Void-forged brass": Sparky's rig is genuinely a salvage machine (brass,
   oxidized copper, stamped iron, warm bulbs) but it is not a second design
   system. Everything around the machine (panels, type, chips, tables) uses the
   site tokens, and the jackpot tier is Star-Steel, a pale luminous teal keyed
   to --color-primary, rather than the gold-chrome a real fairground wheel
   would use. That is what keeps the wheel legibly part of Starforge instead of
   a steampunk page someone bolted onto it, and it gives the jackpot a colour
   that cannot be confused with the amber Golden Gears.

   The metals live as scoped custom properties on .wheel-shop so the two light
   themes can retune them in one block at the bottom of this section rather
   than overriding thirty rules.

   Geometry note: the wedges, labels and pegs are all positioned from
   --wheel-count, which the template writes from the live plate count. Staff
   deactivating a plate in admin gives an 11-wedge wheel, not a broken one.
   ============================================================ */

.wheel-shop {
    /* ---- Salvage metals ---- */
    --brass: #b0813c;
    --brass-lit: #e0b061;
    --brass-dark: #5f4319;
    --copper: #bd6a35;
    --copper-lit: #e08a51;
    --iron: #48413d;
    --iron-lit: #6b615b;
    --starsteel: #7fd8ca;
    --starsteel-lit: #d9f7f1;
    --gold-gear: #f5b426;
    --gold-gear-lit: #ffdc7a;

    --rig-shell: #241d1a;
    --rig-shell-lit: #3c312b;
    --rig-edge: #14100e;
    --bulb: #ffd79a;
    --seam: rgba(0, 0, 0, 0.55);
    --plate-ink: #17110d;

    display: grid;
    grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
    gap: 28px;
    align-items: start;
    margin-bottom: 24px;
}

/* ---------- Sparky ---------- */
.wheel-shop__crew { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.sparky { margin: 0; }
.sparky__frame {
    position: relative;
    /* The stencil placeholder sizes itself in cqw so it stays legible in both
       the full portrait slot and the 92px bust the mobile layout collapses it
       to. Fixed rem sizes cropped it to "PARK / GEARS" on a phone, which reads
       as a broken image rather than a placeholder. Real art needs none of this:
       object-fit crops the same photo to a bust on its own. */
    container-type: inline-size;
    aspect-ratio: 3 / 4;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background:
        linear-gradient(160deg, var(--rig-shell-lit) 0%, var(--rig-shell) 55%, var(--rig-edge) 100%);
    border: 1px solid var(--rig-edge);
    box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.sparky__art { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Placeholder until the character plates land (personas/registry.py::art_ready).
   Deliberately a stencilled workshop panel rather than an empty frame or a
   "missing image" box: an unfinished slot should still look like part of the
   machine. */
.sparky__stencil {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    background-image:
        repeating-linear-gradient(135deg,
            rgba(255, 255, 255, 0.028) 0 12px,
            transparent 12px 24px);
    color: color-mix(in srgb, var(--brass-lit) 72%, transparent);
}
.sparky__stencil-mark {
    font-size: 17cqw;
    line-height: 1;
    opacity: 0.55;
    animation: sparky-cog 14s linear infinite;
}
.sparky__stencil-name {
    font-family: var(--font-display);
    font-size: 9.5cqw;
    font-weight: 800;
    letter-spacing: 0.16em;
    text-indent: 0.16em;
    color: var(--brass-lit);
}
.sparky__stencil-sub {
    font-family: var(--font-mono);
    font-size: 3.9cqw;
    letter-spacing: 0.38em;
    text-indent: 0.38em;
    color: color-mix(in srgb, var(--brass-lit) 60%, transparent);
}
/* The name/sub are now the persona's own name and role (data-driven, any length),
   so keep a long one wrapping and centered inside the plate rather than clipping
   or overrunning the rivets. */
.sparky__stencil { padding-inline: 7%; }
.sparky__stencil-name,
.sparky__stencil-sub { max-width: 100%; text-align: center; overflow-wrap: anywhere; }
/* Four corner rivets, drawn as one element so the placeholder needs no extra
   markup it will lose the moment real art arrives. */
.sparky__stencil-rivets {
    position: absolute;
    inset: 12px;
    border-radius: var(--radius-md);
    border: 1px dashed color-mix(in srgb, var(--brass) 40%, transparent);
    background-image:
        radial-gradient(circle, var(--brass) 38%, transparent 40%),
        radial-gradient(circle, var(--brass) 38%, transparent 40%),
        radial-gradient(circle, var(--brass) 38%, transparent 40%),
        radial-gradient(circle, var(--brass) 38%, transparent 40%);
    background-size: 7px 7px;
    background-position: 4px 4px, calc(100% - 4px) 4px, 4px calc(100% - 4px), calc(100% - 4px) calc(100% - 4px);
    background-repeat: no-repeat;
}
@keyframes sparky-cog { to { transform: rotate(360deg); } }
@container (max-width: 150px) {
    .sparky__stencil-name,
    .sparky__stencil-sub { display: none; }
    .sparky__stencil-mark { font-size: 34cqw; }
    .sparky__stencil-rivets { inset: 6px; }
}

.sparky__plate {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin-top: 10px;
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--brass);
}
.sparky__name { font-family: var(--font-heading); font-weight: 700; }
.sparky__role {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* Speech: a shop-floor note, not a chat bubble. */
.sparky-speech {
    position: relative;
    padding: 13px 15px;
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-top: 2px solid color-mix(in srgb, var(--brass) 60%, var(--border-color));
    min-height: 76px;
}
.sparky-speech__line { margin: 0; color: var(--text-secondary); font-size: 0.93rem; }

/* ---------- The Overdrive gauge ----------
   --pressure (0-100) and --redline (0-100) are server-rendered from the same
   numbers the readout below states in words, so the needle can never disagree
   with the sentence beside it. */
.boiler {
    position: relative;
    padding: 14px 16px 16px;
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
}
.wheel-rig__assembly {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    max-width: 520px;
    border: 1px solid var(--rig-edge);
    border-radius: var(--radius-lg);
    background: linear-gradient(160deg, var(--rig-shell-lit), var(--rig-shell) 58%, var(--rig-edge));
}
.wheel-rig__assembly > .wheel-rig {
    width: 100%;
    max-width: 520px;
    min-width: 0;
}
/* The lever is part of the machine's primary action bay. Keeping it between
   the wheel and the secondary console makes the action reachable before a
   player has to study the streak system. */
.wheel-rig__assembly > .wheel-lever {
    align-self: stretch;
    padding: 14px 12px 18px;
    border-top: 1px solid var(--rig-edge);
}
/* At the default width the pressure console remains attached below the action
   bay, so the wheel keeps its full size and the lever stays in the first
   viewport. */
.wheel-rig__assembly > .boiler {
    width: auto;
    max-width: none;
    margin: 0;
    border-color: var(--rig-edge);
    border-width: 1px 0 0;
    border-style: solid;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    background:
        linear-gradient(160deg,
            color-mix(in srgb, var(--rig-shell-lit) 22%, var(--bg-subtle)),
            var(--bg-subtle) 42%);
}
.boiler__connector {
    position: absolute;
    left: 50%;
    top: -14px;
    width: 28px;
    height: 14px;
    transform: translateX(-50%);
    border: 1px solid var(--rig-edge);
    border-bottom: 0;
    border-radius: 6px 6px 0 0;
    background: linear-gradient(to right, var(--copper), var(--copper-lit) 45%, var(--copper));
    z-index: 1;
}
/* A wide desktop has enough room to make the gauge a side-mounted console
   without shrinking the wheel. The same outer shell and shared border keep it
   reading as one machine, while the connector rotates into a short side pipe. */
@media (min-width: 1280px) {
    .wheel-rig__assembly {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(210px, 240px);
        grid-template-areas:
            "wheel gauge"
            "lever gauge";
        align-items: stretch;
        column-gap: 0;
        row-gap: 0;
        max-width: 780px;
    }
    .wheel-rig__assembly > .wheel-rig { grid-area: wheel; }
    .wheel-rig__assembly > .wheel-lever { grid-area: lever; }
    .wheel-rig__assembly > .boiler {
        grid-area: gauge;
        border-width: 0 0 0 1px;
        border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
    }
    .wheel-rig__assembly > .boiler .boiler__connector {
        left: -14px;
        top: 50%;
        width: 14px;
        height: 28px;
        transform: translateY(-50%);
        border-width: 1px 0 1px 1px;
        border-radius: 6px 0 0 6px;
    }
}
.boiler__heading {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    font-weight: 400;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin: 0 0 10px;
}
.boiler__dial {
    position: relative;
    width: 100%;
    max-width: 190px;
    margin: 0 auto 16px;
    aspect-ratio: 2 / 1;
    overflow: visible;
}
/* The dial face: a half-annulus. The red zone is a conic slice of the same
   ring, starting exactly at --redline. */
.boiler__dial::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 999px 999px 0 0;
    background: conic-gradient(from 270deg,
        var(--iron) 0deg 180deg, transparent 180deg);
    -webkit-mask: radial-gradient(circle farthest-side at 50% 100%, transparent 56%, #000 58%);
    mask: radial-gradient(circle farthest-side at 50% 100%, transparent 56%, #000 58%);
}
.boiler__zone {
    position: absolute;
    inset: 0;
    border-radius: 999px 999px 0 0;
    background: conic-gradient(from 270deg,
        transparent 0deg calc(var(--redline) * 1.8deg),
        var(--copper) calc(var(--redline) * 1.8deg) 180deg,
        transparent 180deg);
    -webkit-mask: radial-gradient(circle farthest-side at 50% 100%, transparent 56%, #000 58%);
    mask: radial-gradient(circle farthest-side at 50% 100%, transparent 56%, #000 58%);
}
.boiler__ticks {
    position: absolute;
    inset: 0;
    border-radius: 999px 999px 0 0;
    /* Etched into the dial face, not painted on it: white ticks read as glare
       across the arc, and the needle is the only thing on this dial that
       should be bright. */
    background: repeating-conic-gradient(from 270deg,
        rgba(0, 0, 0, 0.55) 0deg 0.8deg,
        transparent 0.8deg 18deg);
    -webkit-mask: radial-gradient(circle farthest-side at 50% 100%, transparent 68%, #000 70%, #000 92%, transparent 94%);
    mask: radial-gradient(circle farthest-side at 50% 100%, transparent 68%, #000 70%, #000 92%, transparent 94%);
}
.boiler__mount {
    position: absolute;
    left: 50%;
    bottom: -4px;
    width: 36px;
    height: 20px;
    transform: translateX(-50%);
    border: 1px solid var(--rig-edge);
    border-radius: 7px 7px 12px 12px;
    background: linear-gradient(to bottom, var(--rig-shell-lit), var(--rig-edge));
    box-shadow: 0 2px 3px rgba(0, 0, 0, 0.35);
    z-index: 1;
}
.boiler__mount::before {
    content: '';
    position: absolute;
    left: 50%;
    top: -8px;
    width: 10px;
    height: 12px;
    transform: translateX(-50%);
    border: 1px solid var(--rig-edge);
    border-bottom: 0;
    border-radius: 5px 5px 0 0;
    background: linear-gradient(to bottom, var(--brass-lit), var(--brass-dark));
}
.boiler__needle {
    position: absolute;
    left: 50%;
    bottom: 0;
    width: 3px;
    height: 82%;
    border-radius: 2px 2px 0 0;
    background: linear-gradient(to top, var(--brass-dark), var(--brass-lit));
    transform-origin: 50% 100%;
    /* -90deg is empty, +90deg is full: 1.8deg of sweep per percent. */
    transform: translateX(-50%) rotate(calc(-90deg + var(--pressure) * 1.8deg));
    transition: transform 0.9s var(--ease);
    z-index: 2;
}
.boiler--hot .boiler__needle { animation: needle-judder 0.42s ease-in-out infinite alternate; }
@keyframes needle-judder {
    from { transform: translateX(-50%) rotate(calc(-90deg + var(--pressure) * 1.8deg)); }
    to   { transform: translateX(-50%) rotate(calc(-87deg + var(--pressure) * 1.8deg)); }
}
/* A spin does not advance the streak, so the needle keeps its authoritative
   pressure reading. The small running rattle and lit connector acknowledge
   the same machine action the wheel and steam vent are showing. */
.boiler.is-running {
    border-color: var(--brass);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--brass-lit) 28%, transparent), var(--shadow-sm);
}
.boiler.is-running .boiler__connector { filter: brightness(1.18); }
.boiler.is-running .boiler__needle {
    animation: needle-running 0.22s ease-in-out infinite alternate;
}
@keyframes needle-running {
    from { transform: translateX(-50%) rotate(calc(-92deg + var(--pressure) * 1.8deg)); }
    to   { transform: translateX(-50%) rotate(calc(-88deg + var(--pressure) * 1.8deg)); }
}
.boiler__cap {
    position: absolute;
    left: 50%;
    bottom: -3px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    transform: translateX(-50%);
    background: radial-gradient(circle at 35% 30%, var(--brass-lit), var(--brass-dark));
    border: 1px solid var(--rig-edge);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
    z-index: 3;
}
.boiler__meter {
    position: relative;
    height: 8px;
    margin: 0 0 8px;
    overflow: hidden;
    border-radius: var(--radius-pill);
    background: var(--iron);
    border: 1px solid var(--rig-edge);
}
.boiler__meter-fill {
    display: block;
    width: calc(var(--pressure) * 1%);
    height: 100%;
    border-radius: inherit;
    background: linear-gradient(to right, var(--brass), var(--brass-lit));
}
.boiler__meter-redline {
    position: absolute;
    top: -1px;
    bottom: -1px;
    left: calc(var(--redline) * 1%);
    width: 2px;
    background: var(--copper-lit);
    box-shadow: 0 0 5px var(--copper-lit);
}
.boiler__steps {
    display: flex;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.boiler__step {
    flex: 1;
    min-width: 0;
    text-align: center;
}
.boiler__step-dot {
    display: block;
    width: 9px;
    height: 9px;
    margin: 0 auto 4px;
    border-radius: 50%;
    border: 1px solid var(--iron-lit);
    background: var(--bg-surface);
}
.boiler__step-label {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.61rem;
    color: var(--text-muted);
    white-space: nowrap;
}
.boiler__step.is-complete .boiler__step-dot,
.boiler__step.is-current .boiler__step-dot {
    border-color: var(--brass-lit);
    background: var(--brass);
}
.boiler__step.is-complete .boiler__step-dot::after {
    content: '✓';
    display: block;
    margin-top: -3px;
    color: var(--plate-ink);
    font-size: 0.62rem;
    font-weight: 800;
}
.boiler__step.is-current .boiler__step-dot {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--brass-lit) 22%, transparent);
}
.boiler__step.is-current .boiler__step-label { color: var(--text-primary); font-weight: 700; }
.boiler__step.is-overdrive .boiler__step-label { color: var(--gold-gear-lit); }
.boiler__meter-note {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin: 7px 0 0;
    font-family: var(--font-mono);
    font-size: 0.66rem;
    color: var(--text-muted);
}
.boiler__readout { margin: 0; font-size: 0.86rem; color: var(--text-secondary); }
.boiler__state { margin: 8px 0 4px; }
.boiler__note { margin: 0; font-size: 0.82rem; color: var(--text-muted); }
.boiler__streak {
    margin: 10px 0 0;
    padding-top: 9px;
    border-top: 1px solid var(--border-subtle);
    font-size: 0.82rem;
    color: var(--text-muted);
}

/* ---------- The rig ---------- */
.wheel-shop__rig { display: flex; flex-direction: column; align-items: center; gap: 18px; min-width: 0; }
.wheel-legend {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 5px 12px;
    width: 100%;
    max-width: 520px;
    padding: 7px 10px;
    border-top: 1px solid var(--border-subtle);
    border-bottom: 1px solid var(--border-subtle);
}
.wheel-legend__title { margin-right: 2px; }
.wheel-legend__item { display: inline-flex; align-items: center; gap: 4px; font-size: 0.76rem; color: var(--text-secondary); }

.wheel-rig {
    --slice-angle: calc(360deg / var(--wheel-count, 12));
    width: 100%;
    max-width: 520px;
}

/* Marquee bulbs across the top arch. They breathe at rest, chase while the
   wheel turns, and burn hot in Overdrive: three states, all of which also have
   a worded equivalent elsewhere on the page. */
/* The marquee is the top of the same welded box as the frame below it, so it
   shares its side borders and only the outer corners round. */
.wheel-rig__marquee {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: clamp(8px, 2.6vw, 16px);
    padding: 10px 18px;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    background:
        linear-gradient(to bottom, var(--rig-shell-lit), var(--rig-shell) 70%, var(--rig-edge));
    border: 1px solid var(--rig-edge);
    border-bottom: none;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
}
.marquee-bulb {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 32%, var(--bulb), #8a6031 70%);
    box-shadow: 0 0 6px rgba(255, 190, 110, 0.45);
    animation: bulb-breathe 3.2s ease-in-out infinite;
    animation-delay: calc(var(--b) * 0.14s);
}
.wheel-rig.is-spinning .marquee-bulb {
    animation: bulb-chase 0.72s linear infinite;
    animation-delay: calc(var(--b) * 0.08s);
}
.wheel-shop.is-overdrive .marquee-bulb {
    background: radial-gradient(circle at 35% 32%, #fff3d0, var(--gold-gear) 70%);
    box-shadow: 0 0 12px rgba(245, 180, 38, 0.75);
}
@keyframes bulb-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
@keyframes bulb-chase { 0%, 60% { opacity: 0.35; } 30% { opacity: 1; } }

.wheel-rig__frame {
    position: relative;
    padding: clamp(14px, 4vw, 26px);
    background:
        linear-gradient(150deg, var(--rig-shell-lit) 0%, var(--rig-shell) 60%, var(--rig-edge) 100%);
    border: 1px solid var(--rig-edge);
    box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
/* Welded plate: rivets down both sides of the frame, which is the detail that
   stops the box behind the wheel reading as an empty dark rectangle. */
.wheel-rig__frame::before {
    content: '';
    position: absolute;
    inset: 6px;
    pointer-events: none;
    border: 1px solid rgba(255, 255, 255, 0.05);
    /* Explicit `circle 2.5px at ...` rather than a bare `circle`: a bare radial
       gradient is sized to its tile, so a 10x62 tile stretched each rivet into
       a capsule and the two columns read as film-strip perforations. Sparse
       pitch for the same reason. */
    background-image:
        radial-gradient(circle 2.5px at 5px 12px,
            rgba(255, 255, 255, 0.20) 0 55%, rgba(0, 0, 0, 0.5) 62%, transparent 72%),
        radial-gradient(circle 2.5px at 5px 12px,
            rgba(255, 255, 255, 0.20) 0 55%, rgba(0, 0, 0, 0.5) 62%, transparent 72%);
    background-size: 10px 58px, 10px 58px;
    background-position: 4px 10px, calc(100% - 4px) 10px;
    background-repeat: repeat-y, repeat-y;
}

/* The ticker: a spring-loaded ratchet finger that clatters against the pegs. */
.wheel-ticker {
    /* Overlaps the rim rather than hovering above it: the whole point of the
       ratchet finger is that it is in contact with the pegs. */
    position: absolute;
    top: calc(clamp(14px, 4vw, 26px) - 14px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    width: 34px;
    height: 62px;
}
.wheel-ticker__arm {
    position: absolute;
    inset: 0;
    background: linear-gradient(100deg, var(--brass-lit) 0%, var(--brass) 42%, var(--brass-dark) 100%);
    clip-path: polygon(14% 0, 86% 0, 74% 62%, 54% 100%, 46% 100%, 26% 62%);
    transform-origin: 50% 6%;
    filter: drop-shadow(0 3px 4px rgba(0, 0, 0, 0.65));
}
.wheel-ticker__pivot { z-index: 1; }
/* --tick-ms is written per spin stage by the dailyWheel component: fast while
   the disc is flat out, slow while it settles. The arm is meant to be a finger
   being knocked aside by each passing peg, so a clatter running at one fixed
   rate through a spin that visibly decelerates is the detail that gave the
   whole rig away as a CSS loop rather than a machine. Nothing is simulated
   here; two rates is enough for the motion to agree with the disc. */
.wheel-ticker.is-clattering .wheel-ticker__arm {
    animation: ticker-clatter var(--tick-ms, 0.085s) ease-in-out infinite alternate;
    transition: none;
}
@keyframes ticker-clatter { from { transform: rotate(-7deg); } to { transform: rotate(7deg); } }
.wheel-ticker__pivot {
    position: absolute;
    top: -3px;
    left: 50%;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    transform: translateX(-50%);
    background: radial-gradient(circle at 35% 30%, var(--brass-lit), var(--brass-dark));
    border: 1px solid var(--rig-edge);
}

/* ---------- The disc ---------- */
/* Registered so it INTERPOLATES. An unregistered custom property jumps
   straight to its new value, which would snap the labels to their final
   orientation on frame one while the disc was still turning. Registering it is
   what lets one animated value drive both the disc and the counter-rotation,
   so the two cannot drift apart at any frame. Where @property is unsupported
   the transition simply does not run and the wheel snaps to its result, which
   is the same outcome reduced motion already produces. */
@property --disc-rot {
    syntax: '<angle>';
    inherits: true;
    initial-value: 0deg;
}

.wheel-disc {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    border-radius: 50%;
    transform: rotate(var(--disc-rot, 0deg));
    /* --spin-ms and --spin-ease are both written by the dailyWheel component,
       once per STAGE rather than once per spin: a launch that accelerates and
       holds, then a long deceleration onto the winning plate. One curve across
       the whole spin had the disc already slowing before its first full turn,
       which reads as a dial being set rather than a wheel being thrown.

       The site's reduced-motion rules clamp the duration with !important, so a
       calm-motion player gets an instant move without the component having to
       know: it only declines to add the extra turns and the jitter, so the
       disc lands square on the winning plate instead of teleporting to a
       deliberately off-centre one. */
    transition: --disc-rot var(--spin-ms, 4600ms) var(--spin-ease, cubic-bezier(0.18, 0.02, 0.05, 1));
    box-shadow:
        0 0 0 6px var(--iron),
        0 0 0 8px var(--brass-dark),
        0 10px 30px rgba(0, 0, 0, 0.5);
}
.wheel-disc__plates {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    overflow: hidden;
}
/* Sinks the plates into the iron band around them. Without this the wedges sit
   flush with the rim and the disc reads as printed rather than assembled. */
.wheel-disc__plates::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    box-shadow:
        inset 0 0 14px rgba(0, 0, 0, 0.55),
        inset 0 0 0 3px rgba(0, 0, 0, 0.35);
}

/* Each wedge is a triangle from the centre out past the rim; the round parent
   clips it back to a true sector. tan() gives the half-width at the rim, so
   the geometry follows --wheel-count with no per-count magic numbers. */
.wheel-slice {
    position: absolute;
    inset: 0;
    transform: rotate(calc(var(--i) * var(--slice-angle)));
    clip-path: polygon(
        50% 50%,
        calc(50% - 50% * tan(var(--slice-angle) / 2)) 0%,
        calc(50% + 50% * tan(var(--slice-angle) / 2)) 0%);
}
/* Each plate is dark at the hub and lifts to a single sheen band near the rim,
   then falls back. A straight ramp to the light tone washed every wedge out to
   pastel and the whole disc read as a pie chart; a band reads as light catching
   one edge of a metal plate, which is the thing being drawn. */
.wheel-slice--iron {
    background: linear-gradient(to top, #221e1c 0%, #38322f 52%, var(--iron-lit) 86%, #3f3936 100%);
}
.wheel-slice--bronze {
    background: linear-gradient(to top, #3d2a0e 0%, #7a5620 52%, var(--brass) 86%, #6a4a1b 100%);
}
.wheel-slice--copper {
    background: linear-gradient(to top, #4a230d 0%, #8e4a1f 52%, var(--copper) 86%, #7b3d18 100%);
}
.wheel-slice--starsteel {
    background: linear-gradient(to top, #10312e 0%, #2c6b62 52%, var(--starsteel) 88%, #3f8a7e 100%);
}
.wheel-slice.is-golden {
    background: linear-gradient(to top, #6b4703 0%, #b8830c 50%, var(--gold-gear-lit) 86%, var(--gold-gear) 100%);
}

/* ---------- Marking a wedge ----------
   The winning plate is marked by DIMMING EVERY OTHER PLATE, never by painting
   the winner. That inversion is the whole design, and it took three attempts
   to get here:

   1. `filter: brightness(1.5)` on the winner. The wedges are authored as dark
      at the hub rising to a single sheen band near the rim, which is what
      makes them read as metal plates rather than a pie chart. Multiplying that
      flattens the ramp into one neon smear.
   2. A warm gradient laid over the winner. Softer, same fault: at any alpha
      strong enough to read across the room it repaints an Iron plate the
      colour of a Bronze one, so the marking fights the tier colour that is
      already carrying meaning. At any alpha weak enough not to, it is
      invisible.
   3. Dim the others. The winner is not touched at all: it keeps its exact
      authored gradient and tier colour, and reads as lit because the wheel
      around it has fallen away. A spotlight, not a repaint.

   Also worth knowing before drawing anything on a wedge: a .wheel-slice is a
   CLIPPED TRIANGLE inside a full-size square box, so an `inset: 0`
   pseudo-element covers the whole disc rather than the wedge, and an `inset`
   box-shadow on it glows inward from the SQUARE's four edges before being
   clipped to the triangle: a halo with entirely the wrong geometry.

   The dim is keyed off container classes (`has-landed` / `has-highlight`) so
   nothing is dimmed until there is actually something to point at: on a fresh
   page load, and on the "already pulled today" page, the wheel is simply a
   wheel. */
.wheel-slice,
.wheel-label {
    transition: filter 0.4s ease, opacity 0.4s ease;
}
.wheel-disc.has-landed .wheel-slice:not(.is-landed),
.wheel-disc.has-highlight .wheel-slice:not(.is-highlighted):not(.is-landed) {
    filter: brightness(0.45) saturate(0.55);
}
.wheel-disc.has-landed .wheel-label:not(.is-landed),
.wheel-disc.has-highlight .wheel-label:not(.is-highlighted):not(.is-landed) {
    opacity: 0.42;
}
/* A hover/focus on an odds row is transient and gets a lighter touch than a
   result: enough to find the wedge while scanning the table, not enough to
   make a 12-row list feel like it is strobing the wheel. `has-landed` wins,
   so brushing the table can never dull the plate that actually won. */
.wheel-disc.has-highlight:not(.has-landed) .wheel-slice:not(.is-highlighted) {
    filter: brightness(0.62) saturate(0.7);
}
.wheel-disc.has-highlight:not(.has-landed) .wheel-label:not(.is-highlighted) {
    opacity: 0.55;
}
/* The winner's own label gets a small warm halo and NO scale: the labels sit
   close enough at 12 plates that enlarging one pushes it into its neighbours,
   and a label that pops on a wheel which has just stopped reads as the page
   still loading. */
.wheel-label.is-landed {
    text-shadow:
        0 1px 2px rgba(0, 0, 0, 0.85),
        0 0 10px rgba(255, 226, 160, 0.7);
}
.wheel-label.is-landed.is-golden {
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}

.wheel-disc__seams {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    /* A dark seam with a thin lit edge on one side: a riveted joint between two
       plates, not a divider line. At 0.5deg and flat black it was invisible
       against the plates at every size the wheel actually renders at. */
    background: repeating-conic-gradient(from calc(var(--slice-angle) / -2),
        var(--seam) 0deg 0.85deg,
        rgba(255, 255, 255, 0.16) 0.85deg 1.25deg,
        transparent 1.25deg var(--slice-angle));
}
.wheel-disc__marks { position: absolute; inset: 0; pointer-events: none; }

/* Labels and pegs are placed by polar coordinates rather than
   rotate-then-counter-rotate: the label stays upright with no wrapper element,
   which is what makes a glyph plus a number readable at the bottom of the
   wheel as well as the top. */
.wheel-label {
    --a: calc(var(--i) * var(--slice-angle));
    position: absolute;
    left: calc(50% + 34% * sin(var(--a)));
    top: calc(50% - 34% * cos(var(--a)));
    /* Rides around with its plate but stays the right way up. Without the
       counter-rotation the wheel comes to rest with half its payouts printed
       upside down and stays that way until tomorrow, which reads as a bug
       rather than as a wheel. Driven by the same interpolated --disc-rot as
       the disc, so it cannot lag by a frame. */
    transform: translate(-50%, -50%) rotate(calc(-1 * var(--disc-rot, 0deg)));
    display: flex;
    flex-direction: column;
    align-items: center;
    line-height: 1;
    /* Light ink on dark plates. The wedges are darkest toward the hub and the
       label sits at 34% out, so it is always over the mid-to-dark part of the
       ramp: a dark ink read as a smudge on iron and vanished on copper. */
    color: #fdf6ea;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85), 0 0 6px rgba(0, 0, 0, 0.5);
}
.wheel-label__icon { font-size: clamp(0.95rem, 3.6vw, 1.35rem); }
.wheel-label__amt {
    font-size: clamp(0.66rem, 2.2vw, 0.86rem);
    font-weight: 700;
    margin-top: 3px;
    letter-spacing: 0.02em;
}
/* The golden gears are the one genuinely bright plate, so they take the dark
   ink instead. */
.wheel-label.is-golden {
    color: #2b1c00;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
}

.wheel-peg {
    --a: calc((var(--i) - 0.5) * var(--slice-angle));
    position: absolute;
    left: calc(50% + 45.5% * sin(var(--a)));
    top: calc(50% - 45.5% * cos(var(--a)));
    width: 7px;
    height: 7px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle at 34% 30%, #cfc6bd, #4b4340 72%);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

/* The hub: a stack of brass gears bolted through the middle. */
.wheel-hub {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 27%;
    aspect-ratio: 1;
    transform: translate(-50%, -50%);
}
/* Teeth hug the hub body. At inset -15% with a 61% mask they read as a dashed
   ring floating clear of the gear rather than as its teeth. */
.wheel-hub__teeth {
    position: absolute;
    inset: -9%;
    border-radius: 50%;
    /* Twenty-four 15-degree teeth close evenly around the circle. The old
       13-degree cadence left a fractional final repeat, which made the hub
       look subtly lopsided even though its bounding box was centered. */
    background: repeating-conic-gradient(
        var(--brass) 0deg 6deg, var(--brass-dark) 6deg 6.6deg, transparent 6.6deg 15deg);
    /* farthest-side, not the default farthest-corner: on a square box the
       corner radius is 1.41x the half-width, so a percentage stop lands
       outside the element and the whole ring masks away to nothing. That is
       what made the teeth invisible the first time round. */
    -webkit-mask: radial-gradient(circle farthest-side, transparent 76%, #000 78%);
    mask: radial-gradient(circle farthest-side, transparent 76%, #000 78%);
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.6));
}
.wheel-hub__face {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background:
        radial-gradient(circle at 34% 28%, var(--brass-lit), var(--brass) 52%, var(--brass-dark) 100%);
    border: 2px solid var(--rig-edge);
    box-shadow: inset 0 0 0 4px rgba(0, 0, 0, 0.18);
}
.wheel-hub__bolt {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 34%;
    height: 34%;
    transform: translate(-50%, -50%);
    background: linear-gradient(140deg, #6d635c, #2c2724);
    clip-path: polygon(25% 3%, 75% 3%, 100% 50%, 75% 97%, 25% 97%, 0 50%);
}

/* Steam venting from under the frame while she runs. */
.wheel-steam {
    position: absolute;
    left: 8%;
    bottom: 4%;
    width: 46%;
    height: 40%;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s ease;
}
.wheel-steam.is-venting { opacity: 1; }
.wheel-steam i {
    position: absolute;
    bottom: 0;
    left: calc(var(--s) * 34%);
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255, 255, 255, 0.4), transparent 68%);
    filter: blur(5px);
}
.wheel-steam.is-venting i {
    animation: steam-rise 2.1s ease-out infinite;
    animation-delay: calc(var(--s) * 0.55s);
}
@keyframes steam-rise {
    from { transform: translateY(10px) scale(0.55); opacity: 0; }
    35%  { opacity: 0.75; }
    to   { transform: translateY(-70px) scale(1.5); opacity: 0; }
}

.wheel-rig__chassis {
    display: flex;
    align-items: stretch;
    gap: 6px;
    height: 18px;
    padding: 0 12px;
}
.chassis-pipe {
    width: 26px;
    border-radius: 0 0 6px 6px;
    background: linear-gradient(to right, var(--copper), var(--copper-lit) 45%, var(--copper));
    border: 1px solid var(--rig-edge);
    border-top: none;
}
.chassis-plate {
    flex: 1;
    border-radius: 0 0 var(--radius-md) var(--radius-md);
    border: 1px solid var(--rig-edge);
    border-top: none;
    /* Both layers in ONE background-image. The plate previously set its metal
       via the `background` shorthand and then its rivets via `background-image`,
       which silently replaced the gradient: harmless against a dark page,
       but on the two light themes the bar showed the page through it and the
       machine appeared to be standing on a white slab. */
    background-image:
        radial-gradient(circle 2px at 13px 5px, rgba(255, 255, 255, 0.10) 0 60%, transparent 70%),
        linear-gradient(to bottom, var(--rig-shell), var(--rig-edge));
    background-size: 26px 16px, auto;
    background-repeat: repeat-x, no-repeat;
}

/* ---------- The lever ---------- */
.wheel-lever { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.wheel-lever__promise { margin: 0 0 2px; text-align: center; font-size: 0.86rem; color: var(--text-secondary); }
.wheel-lever__promise strong { color: var(--text-primary); }
.wheel-lever__skip { min-height: 40px; }
.btn-lever {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    min-height: 48px;
    padding: 13px 26px 13px 16px;
    border: 1px solid var(--rig-edge);
    border-radius: var(--radius-pill);
    background: linear-gradient(to bottom, var(--brass-lit), var(--brass) 55%, var(--brass-dark));
    color: var(--plate-ink);
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: 1.02rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    box-shadow: 0 4px 0 var(--rig-edge), var(--shadow-sm);
    transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
@media (hover: hover) { .btn-lever:hover:not(:disabled) { filter: brightness(1.06); } }
.btn-lever:active:not(:disabled) { transform: translateY(3px); box-shadow: 0 1px 0 var(--rig-edge); }
.btn-lever:disabled { opacity: 0.65; cursor: progress; transform: translateY(3px); box-shadow: 0 1px 0 var(--rig-edge); }
.btn-lever__grip {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: radial-gradient(circle at 33% 28%, #f0f0f0, #6f2c1e 72%);
    border: 1px solid var(--rig-edge);
    flex: none;
}
.wheel-lever__note { margin: 0; font-size: 0.8rem; color: var(--text-muted); }
.wheel-lever__spent { margin: 0; }
.wheel-lever__next,
.wheel-result__destination,
.wheel-last__destination { font-size: 0.82rem; font-weight: 700; color: var(--color-primary); }

/* ---------- Results ---------- */
.wheel-result-slot { margin-bottom: 24px; scroll-margin-bottom: calc(var(--tabbar-height) + 18px); }

.wheel-result {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    gap: 20px;
    align-items: center;
    padding: 20px 22px;
    border-radius: var(--radius-lg);
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-left: 4px solid var(--brass);
    box-shadow: var(--shadow-md);
}
.wheel-result.is-overdrive { border-left-color: var(--gold-gear); }
.wheel-result--jackpot { border-left-color: var(--color-primary); box-shadow: var(--glow-accent), var(--shadow-md); }
.wheel-result--low_roll { border-left-color: var(--iron-lit); }
.wheel-result__prize { min-width: 0; }
.wheel-result__label {
    font-family: var(--font-display);
    font-size: clamp(1.25rem, 1rem + 1.2vw, 1.7rem);
    margin: 4px 0 10px;
}
.wheel-result__banked { margin: 10px 0 0; font-size: 0.82rem; color: var(--text-muted); }
.wheel-result__destination { display: inline-block; margin-top: 9px; }
.wheel-result__crew { min-width: 0; }
.wheel-result__line {
    margin: 0 0 8px;
    font-size: 1rem;
    color: var(--text-secondary);
    font-style: italic;
}
.wheel-result__meta { margin: 0; font-size: 0.8rem; color: var(--text-muted); }

.wheel-last {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 14px;
    padding: 14px 18px;
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--iron-lit);
}
.wheel-last.is-overdrive { border-left-color: var(--gold-gear); }
.wheel-last .eyebrow { flex-basis: 100%; }
.wheel-last__row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.wheel-last__label { font-family: var(--font-heading); font-weight: 700; }
.wheel-last__meta { margin: 0; font-size: 0.8rem; color: var(--text-muted); margin-left: auto; }
.wheel-last__destination { flex-basis: 100%; }

/* A won plate. Tier is carried by the left edge and the icon, never by colour
   alone: the amount and the currency name are always spelled out beside it. */
.prize-chip {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    padding: 8px 14px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--iron-lit);
}
.prize-chip--bronze    { border-left-color: var(--brass); }
.prize-chip--copper    { border-left-color: var(--copper); }
.prize-chip--starsteel { border-left-color: var(--color-primary); }
.prize-chip.is-golden  { border-left-color: var(--gold-gear); }
.prize-chip__icon { font-size: 1.15rem; }
.prize-chip__amt { font-size: 1.3rem; font-weight: 700; color: var(--text-primary); }
.prize-chip__name { font-size: 0.86rem; color: var(--text-secondary); }

/* ---------- Spec sheet ---------- */
.spec-sheet__head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 6px;
}
.spec-sheet__head h2 { margin: 4px 0 0; font-size: 1.05rem; }
.spec-sheet__intro { margin: 0 0 14px; font-size: 0.87rem; color: var(--text-secondary); max-width: 70ch; }
.table-scroll {
    /* Same containing-block rule as .staff-table-scroll above, and this is the
       box that proved it: /staff/catalog/ holds 128 .sr-only price labels inside
       this scroller, the rightmost landing at x=1359 against a 1265px viewport,
       so the whole document laid out 94px wider than the screen with nothing
       visibly overflowing. See docs/ux-rules.md "Layout and overflow". */
    position: relative;
    width: 100%;
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.table-scroll > .staff-catalog-table { max-width: none; }
/* min-width is what makes the scroll container actually work. At width:100%
   alone the table squeezed to the panel instead of overflowing, so
   .table-scroll saw nothing to scroll and the Pays and Chance columns were
   simply clipped off the right edge on a phone: the two columns the table
   exists for. */
.spec-sheet__table { width: 100%; min-width: 430px; border-collapse: collapse; font-size: 0.87rem; }
.spec-sheet { scroll-margin-top: calc(var(--header-height) + 16px); scroll-margin-bottom: calc(var(--tabbar-height) + 18px); }
.spec-sheet__cards { display: none; }
.spec-sheet__card {
    padding: 11px 12px;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-color);
    background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
}
.spec-sheet__card.is-golden {
    border-color: var(--gold-gear, #f5b426);
    background: color-mix(in srgb, var(--gold-gear, #f5b426) 12%, transparent);
}
.spec-sheet__card-head { display: flex; justify-content: space-between; gap: 10px; }
.spec-sheet__card-head .mono { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
.spec-sheet__card p { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin: 7px 0 0; color: var(--text-secondary); }
.spec-sheet__card a { display: inline-flex; align-items: center; min-height: 32px; font-weight: 700; }
.spec-sheet__table th,
.spec-sheet__table td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--border-subtle); }
.spec-sheet__table thead th {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    font-weight: 400;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.spec-sheet__table tbody th { font-weight: 600; }
.spec-sheet__table .num { text-align: right; }
.spec-sheet__table tbody tr.is-golden { background: color-mix(in srgb, var(--gold-gear) 9%, transparent); }
.spec-sheet__foot { margin: 14px 0 0; font-size: 0.8rem; color: var(--text-muted); }

.plate-tag {
    display: inline-block;
    padding: 2px 9px;
    border-radius: var(--radius-pill);
    font-size: 0.72rem;
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
}
.plate-tag--iron      { border-color: color-mix(in srgb, var(--iron-lit) 60%, var(--border-color)); }
.plate-tag--bronze    { border-color: color-mix(in srgb, var(--brass) 60%, var(--border-color)); }
.plate-tag--copper    { border-color: color-mix(in srgb, var(--copper) 60%, var(--border-color)); }
.plate-tag--starsteel { border-color: color-mix(in srgb, var(--color-primary) 60%, var(--border-color)); color: var(--color-primary); }
.tag--golden {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 8px;
    border-radius: var(--radius-pill);
    font-size: 0.68rem;
    font-weight: 700;
    background: color-mix(in srgb, var(--gold-gear) 22%, transparent);
    border: 1px solid color-mix(in srgb, var(--gold-gear) 55%, var(--border-color));
    color: var(--text-primary);
}

/* ---------- Light themes ----------
   The machine stays a machine: dark shell, warm metals. What changes is the
   contrast around it. On a pale page the untouched brass glares and the
   plate ink loses its shadow, so the metals drop a step and the label
   highlight comes off. */
body.theme-daylight .wheel-shop,
body.theme-comet .wheel-shop {
    --brass: #96682a;
    --brass-lit: #c8933f;
    --copper: #a5551f;
    --copper-lit: #c9743c;
    --starsteel: #4bab9c;
    --starsteel-lit: #b5e6dd;
    --gold-gear: #d99406;
    --gold-gear-lit: #f5c76a;
    --bulb: #ffcf8a;
    --plate-ink: #120d09;
}
/* No light-theme override for .wheel-label: the plates are the same dark metal
   in every theme (only the metal tones shift a step), so the label treatment
   must not change with the page either. An earlier `text-shadow: none` here
   dated from when the labels were dark ink on pale plates. */

/* ---------- Streak alarm ----------
   The one thing the wheel never told anyone: a streak breaks by being missed,
   and this page had no idea what time it was. A Commander six days into a
   seven-day cycle could sit here reading about Overdrive with no indication
   that skipping tonight sends them back to day one. Warn tone, not error: the
   pull is still available, which is the whole point of saying so. */
.wheel-alarm {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 8px;
    margin: 10px 0 0;
    padding: 9px 13px;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-warning, #b8830c);
    border-left-width: 4px;
    background: color-mix(in srgb, var(--color-warning, #b8830c) 12%, transparent);
    font-size: 0.85rem;
    line-height: 1.45;
}
.wheel-alarm__glyph { flex: none; }
.wheel-alarm--hot {
    border-color: var(--gold-gear, #f5b426);
    background: color-mix(in srgb, var(--gold-gear, #f5b426) 15%, transparent);
}

/* ---------- Lifetime record + recent pulls ----------
   total_spins has been on DailySpin since the wheel shipped and was rendered
   nowhere at all; longest_streak got one small line. A daily habit surface
   with no memory of the habit is the gap these two close. */
.wheel-record {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
    gap: 8px;
    margin-top: 14px;
}
.wheel-record__milestone {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 14px;
    padding: 10px 12px;
    border-left: 3px solid var(--brass);
    background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
}
.wheel-record__milestone strong { display: block; margin-top: 2px; }
.wheel-record__milestone p { margin: 0; font-size: 0.8rem; color: var(--text-muted); text-align: right; }
.wheel-record-panel { scroll-margin-bottom: calc(var(--tabbar-height) + 18px); }
.wheel-record__cell {
    padding: 9px 12px;
    border-radius: var(--radius-md);
    border: 1px solid var(--rig-edge, var(--color-border));
    background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
    min-width: 0;
}
.wheel-record__value {
    display: block;
    font-weight: 700;
    font-size: 1.02rem;
    line-height: 1.2;
    /* Wraps rather than blowing out the grid: a best haul reads "500 Stardust",
       which does not fit a 112px column on one line at any font size. */
    overflow-wrap: anywhere;
}
.wheel-record__label {
    display: block;
    margin-top: 2px;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.75;
}

.wheel-history { margin-top: 14px; }
.wheel-history__strip {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 4px;
    /* The strip scrolls sideways on a phone; the wrapper carries the keyboard
       affordance (see the odds table's own table-scroll region). */
    scrollbar-width: thin;
}
.wheel-history__item {
    flex: 0 0 auto;
    min-width: 84px;
    padding: 8px 10px;
    border-radius: var(--radius-md);
    border: 1px solid var(--rig-edge, var(--color-border));
    background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
    text-align: center;
}
.wheel-history__item.is-golden {
    border-color: var(--gold-gear, #f5b426);
    background: color-mix(in srgb, var(--gold-gear, #f5b426) 14%, transparent);
}
.wheel-history__day {
    display: block;
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.7;
}
.wheel-history__icon { display: block; font-size: 1.15rem; margin: 2px 0 1px; }
.wheel-history__amt { display: block; font-weight: 700; font-size: 0.86rem; }

/* ---------- Overdrive swap table ----------
   The spec sheet listed today's odds and left the player to work out what a
   five-day streak had bought them by diffing two faces from memory. This shows
   the trade as a trade. Rendered whether or not the valve is currently open:
   as a promise while pressure builds, as a receipt once it is. */
.spec-sheet__swaps { margin-top: 18px; }
.swap-list { display: grid; gap: 8px; margin: 0 0 4px; padding: 0; list-style: none; }
.swap-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    border: 1px solid var(--rig-edge, var(--color-border));
    background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
}
.swap-row__from { min-width: 0; opacity: 0.68; }
.swap-row__from .swap-row__pay { text-decoration: line-through; }
.swap-row__arrow { flex: none; opacity: 0.8; }
.swap-row__to { min-width: 0; color: var(--gold-gear-lit, #ffd977); font-weight: 600; }
.swap-row__name { display: block; font-size: 0.83rem; }
.swap-row__pay { display: block; font-size: 0.9rem; font-weight: 700; }
@media (max-width: 560px) {
    /* Three columns at 390px gives each side ~120px, which truncates a plate
       name and a payout on both sides at once. Stack, and turn the arrow. */
    .swap-row { grid-template-columns: minmax(0, 1fr); text-align: center; gap: 2px; }
    .swap-row__arrow { transform: rotate(90deg); }
}

/* ---------- Expected pull ----------
   Deliberately per-reward, never one headline number: the plates pay in two
   currencies and in items, and collapsing those needs an exchange rate this
   game does not have. See economy/wheel.py::face_expectation. */
.spec-expect {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    margin: 0 0 14px;
    font-size: 0.86rem;
}
.spec-expect__chip {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    padding: 3px 9px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--rig-edge, var(--color-border));
    background: var(--color-surface-2, rgba(255, 255, 255, 0.03));
}

/* ---------- Odds row <-> wedge ----------
   Focusable rows, because hover is not a route in on a phone or a keyboard.
   The outline is the standard focus treatment; the background is the "this row
   is the one lighting a wedge" cue. */
.spec-sheet__table tbody tr { transition: background-color 0.15s ease; }
.spec-sheet__table tbody tr[tabindex]:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
}
.spec-sheet__table tbody tr.is-highlighted {
    background: color-mix(in srgb, var(--color-primary) 14%, transparent);
}
.spec-sheet__table tbody tr.is-landed {
    background: color-mix(in srgb, var(--gold-gear, #f5b426) 16%, transparent);
}
.spec-sheet__won {
    margin-left: 6px;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--gold-gear-lit, #ffd977);
}

/* ---------- Reduced motion ----------
   The global body.reduced-motion / prefers-reduced-motion rules already clamp
   every duration, which is what turns the spin into an instant snap. These
   two go further and stop the idle loops entirely: a needle that judders and
   bulbs that breathe forever are exactly the ambient motion that preference
   is asking us to remove, and neither carries information. */
body.reduced-motion .marquee-bulb,
body.reduced-motion .boiler--hot .boiler__needle,
body.reduced-motion .boiler.is-running .boiler__needle,
body.reduced-motion .sparky__stencil-mark,
body.reduced-motion .wheel-steam i,
body.reduced-motion .wheel-ticker__arm { animation: none; }
body.reduced-motion .marquee-bulb { opacity: 1; }
body.reduced-motion .wheel-steam { display: none; }
@media (prefers-reduced-motion: reduce) {
    .marquee-bulb,
    .boiler--hot .boiler__needle,
    .boiler.is-running .boiler__needle,
    .sparky__stencil-mark,
    .wheel-steam i,
    .wheel-ticker__arm { animation: none; }
    .marquee-bulb { opacity: 1; }
    .wheel-steam { display: none; }
}
/* The winning wedge's sheen is NOT in that list on purpose. It is the only
   thing on the disc that says which plate won, and a calm-motion player has
   already given up the spin that would otherwise have shown them: removing the
   marking too would leave them with a result card and an anonymous wheel. It
   is a static gradient, not motion. */

/* ---------- Responsive ----------
   Below the tablet line the crew column stops being a column: Sparky shrinks
   to a bust beside the gauge so the wheel keeps the top of the screen, which
   is the thing the page exists for. */
@media (max-width: 900px) {
    .wheel-shop { grid-template-columns: minmax(0, 1fr); }
    .wheel-shop__crew {
        display: grid;
        grid-template-columns: 120px minmax(0, 1fr);
        grid-template-areas: 'portrait speech';
        gap: 12px 16px;
        align-items: start;
    }
    .sparky { grid-area: portrait; }
    .sparky-speech { grid-area: speech; min-height: 0; }
    .boiler { grid-area: gauge; }
    .boiler__dial { margin-left: auto; margin-right: auto; }
    .wheel-result { grid-template-columns: minmax(0, 1fr); }
}
/* On a phone the wheel has to be reachable without scrolling past a portrait.
   `display: contents` promotes the crew's two children into .wheel-shop's own
   flex flow, while the pressure console stays attached to the wheel assembly.
   Sparky collapses to a bust-and-nameplate strip above her line. */
@media (max-width: 560px) {
    /* align-items must be reset: the base rule sets `start` for the desktop
       grid, and the same declaration in a flex container means "size to
       content" rather than "top-align", which collapsed the rig to 197px of a
       335px column and took the wheel down to 165px with it. */
    .wheel-shop { display: flex; flex-direction: column; align-items: stretch; gap: 14px; }
    .wheel-shop__crew { display: contents; }
    .sparky { order: 1; display: flex; align-items: center; gap: 12px; }
    .sparky__frame { width: 92px; flex: none; }
    .sparky__plate { margin-top: 0; flex: 1; min-width: 0; }
    .sparky-speech { order: 2; }
    .wheel-shop__rig { order: 3; }
    .wheel-last__meta { margin-left: 0; flex-basis: 100%; }
    .wheel-stage { padding-bottom: calc(var(--tabbar-height) + 18px); }
    .wheel-legend { justify-content: flex-start; }
    .wheel-legend__title { flex-basis: 100%; }
    .spec-sheet__cards { display: grid; gap: 8px; }
    .table-scroll { display: none; }
    .wheel-record__milestone { align-items: flex-start; flex-direction: column; }
    .wheel-record__milestone p { text-align: left; }
}

/* ===== CRAFTING ===== */
.crafting-catalog-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
}

/* Same top-accent-by-rarity convention as .inventory-item (data-rarity set
   from item.rarity), so a recipe card reads at a glance the same way an
   inventory tile does. */
.crafting-recipe-card { position: relative; overflow: hidden; }
.crafting-recipe-card::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--rarity-accent, transparent);
}
.crafting-recipe-card[data-rarity="uncommon"]  { --rarity-accent: var(--color-success); }
.crafting-recipe-card[data-rarity="rare"]      { --rarity-accent: var(--color-info); }
.crafting-recipe-card[data-rarity="epic"]      { --rarity-accent: var(--color-violet); }
.crafting-recipe-card[data-rarity="legendary"] { --rarity-accent: var(--color-reward); }
.crafting-recipe-card.is-uncraftable { opacity: 0.6; }

.crafting-craft-now {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 10px;
}

.crafting-ingredient-list { list-style: none; margin: 8px 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.crafting-ingredient-list li { display: flex; align-items: center; gap: 6px; font-size: 0.9em; }
/* Never colour-only: each row also carries a ✓/✗ glyph, not just a tint. */
.crafting-ingredient-list li.is-met { color: var(--text-primary); }
.crafting-ingredient-list li.is-short { color: var(--color-warning-text); }

/* Workbench: NPC portrait beside the recipe picker + panel. Stacks on
   narrow screens the same way .mw-compare does. */
.crafting-workbench {
    display: grid;
    grid-template-columns: minmax(220px, 300px) 1fr;
    gap: 20px;
    align-items: start;
}
.crafting-workbench > * { min-width: 0; }
@media (max-width: 768px) {
    .crafting-workbench { grid-template-columns: 1fr; }
}
.crafting-npc { text-align: center; }
.crafting-npc__portrait {
    width: 100%;
    aspect-ratio: 3 / 4;
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.crafting-npc__portrait img { width: 100%; height: 100%; object-fit: contain; }
.crafting-npc__placeholder { font-size: 3.4em; opacity: 0.6; }
.crafting-npc__name { font-family: var(--font-heading); font-weight: 700; margin: 10px 0 2px; }
.crafting-npc__line { color: var(--text-muted); font-style: italic; min-height: 2.6em; }

/* NPC Requests hub: a single active quest, not a picker over a whole catalog
   like crafting's, so it's one narrow centered column rather than a
   sidebar+content grid. Portrait box shape mirrors .crafting-npc__portrait
   (see docs/personas-framework.md: each domain owns its own class family
   until a shared layer is extracted). */
.quest-hub { max-width: 520px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
.quest-npc { text-align: center; }
.quest-npc__portrait {
    width: 140px;
    aspect-ratio: 3 / 4;
    margin: 0 auto;
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.quest-npc__portrait img { width: 100%; height: 100%; object-fit: contain; }
.quest-npc__placeholder { font-size: 3em; opacity: 0.6; }
.quest-npc__name { font-family: var(--font-heading); font-weight: 700; margin: 10px 0 2px; }
.quest-npc__line { color: var(--text-muted); font-style: italic; min-height: 2.4em; }
.quest-progress-row { margin: 0 0 12px; }
.quest-progress-row.is-short { color: var(--color-error-text); }
.quest-caps { display: flex; flex-direction: column; gap: 4px; }
.quest-caps__row { margin: 0; font-size: 0.9rem; color: var(--text-muted); }

/* Fixed 100x100 anchor so the panel can't stretch to fit whatever text sits
   beside it: same "displayed at 100x100" contract as mechanics.Item.image,
   reached here via apparel.services.resolve_item_display_image so this
   prefers the pet-compositor art over a manual image upload, same as every
   other apparel surface. */
.crafting-item-summary { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 16px; }
/* Extra breathing room only where this sits right under the recipe <select>
   (the workbench panel); the catalog card has nothing above it to clear. */
#crafting-workbench-panel .crafting-item-summary { margin-top: 20px; }
.crafting-item-summary__header { flex: 1; min-width: 0; margin-bottom: 0; }
.crafting-item-thumb {
    flex: none;
    width: 100px;
    height: 100px;
    object-fit: cover;
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
}
.crafting-item-thumb--icon {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.6rem;
}

.crafting-ingredients { display: flex; flex-direction: column; gap: 6px; margin: 12px 0; }
.crafting-ingredient { display: flex; align-items: center; gap: 6px; font-size: 0.95em; }
.crafting-ingredient.is-short { color: var(--color-warning-text); }

.crafting-quantity-row { display: flex; align-items: center; gap: 8px; margin: 10px 0; }

/* ---- Site News (/news/, /staff/news/) ---- */
/* --color-danger is not a token in this file: every use of it fell through to a
   hardcoded hex that ignored the palette, and on the light themes that hex measured
   3.2:1 against white. The real token is --color-error-text. */
.field-error { color: var(--color-error-text); font-size: 0.85rem; margin: 4px 0 0; }
.news-post-form { display: flex; flex-direction: column; gap: 16px; max-width: none; }
.news-post-form label { display: flex; flex-direction: column; gap: 6px; font-size: 0.85rem; color: var(--text-muted); }
.news-post-form input[type="text"], .news-post-form textarea { width: 100%; }
.news-post-form textarea { resize: vertical; font-family: var(--font-mono, monospace); }
.news-post-form__actions { display: flex; gap: 10px; }

.news-card-list { display: flex; flex-direction: column; gap: 14px; }
.news-card { padding: 18px 20px; }
.news-card__date { display: block; color: var(--text-muted); font-size: 0.78rem; margin-bottom: 6px; }
.news-card__title { margin: 0; font-family: var(--font-heading); font-size: 1.15rem; }
.news-card__excerpt { margin: 8px 0 0; color: var(--text-secondary); line-height: 1.5; }

.news-detail { max-width: 760px; margin: 0 auto; }
.news-detail__date { color: var(--text-muted); font-size: 0.85rem; margin: 0 0 18px; }
.news-detail__body { line-height: 1.6; }
.news-detail__body p { margin: 0 0 1em; }
.crafting-quantity-row input[type="number"] { width: 5em; }

/* ---- Stud Market ---- */
.page-intro-card { margin-bottom: 20px; }
.page-intro-card p { margin: 0 0 8px; line-height: 1.5; }
.page-intro-card p:last-child { margin-bottom: 0; }
.card-grid { display: grid; gap: 18px; }
/* Shared by the Stud Market and Manage Studs grids: a fixed two columns, squishing to
   one column under the usual 768px breakpoint. */
.card-grid--stud-two { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.stud-card, .stud-manage-card, .stud-request-card { padding: 16px; }
.stud-card__header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.stud-card__header .panel-title { margin: 2px 0 4px; font-size: 1.05rem; }
.stud-card__image { width: 64px; height: 64px; flex: none; object-fit: contain; border-radius: var(--radius-md); background: var(--bg-surface); border: 1px solid var(--border-subtle); }
.stud-card__image--empty { display: flex; align-items: center; justify-content: center; font-size: 1.9rem; }
.stud-card__meta { display: flex; flex-wrap: wrap; gap: 6px 16px; color: var(--text-secondary); font-size: 0.85rem; margin-bottom: 12px; }
.stacked-form { display: flex; flex-direction: column; gap: 12px; }
.stacked-form label, .stacked-form legend { color: var(--text-muted); font-size: 0.85rem; }
.stacked-form label > input, .stacked-form label > select, .stacked-form label > textarea { display: block; width: 100%; margin-top: 5px; }
.stacked-form fieldset { border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); padding: 10px 12px; }
.choice-row { display: flex; flex-direction: row; align-items: center; gap: 8px; color: var(--text-secondary) !important; margin-top: 6px; }
.choice-row input { width: auto !important; margin: 0 !important; }
.form-grid--two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.inline-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.inline-actions form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.inline-actions input[type="text"] { max-width: 240px; }
.stud-request-card { margin-bottom: 16px; }
.stud-request-card .panel-title { margin: 4px 0 8px; }
@media (max-width: 768px) {
    .form-grid--two, .card-grid--stud-two { grid-template-columns: 1fr; }
    .stud-card__header { align-items: flex-start; }
    .inline-actions form { width: 100%; }
}

/* Discrete touch controls share the 44px target floor. Inline prose links are
   intentionally excluded: they are not standalone targets and remain covered by
   the WCAG inline-link exception. This rule is last so component-specific compact
   button classes cannot silently win the cascade on a coarse pointer.

   Text-like inputs/textarea are included here too: the base rule at the top of
   this file (`input[type="text"], ...`) sets padding/font-size but no min-height,
   so every plain text field computed to ~40px, 4px under this same floor, on
   every form in the game. `select` already cleared it via the shared :where()
   above; these five input types plus textarea did not, and nothing guarded the
   gap since TouchTextFieldFloorTests only checks the 16px font floor. */
@media (pointer: coarse) {
    :where(.btn, button, select, summary, input[type="submit"], input[type="button"]) {
        min-height: 44px;
    }
    :where(
        input[type="text"], input[type="password"], input[type="email"],
        input[type="number"], input[type="search"], textarea
    ) {
        min-height: 44px;
    }
    :where(.btn, button, summary, input[type="submit"], input[type="button"]) {
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* ============================================================
   Captain's Handbook (onboarding)
   templates/onboarding/*, partials/_dashboard_onboarding_card, _onboarding_welcome
   Vesper's signal mentor styles support the Handbook presentation; her art remains
   pending and the designed placeholder is the current presentation contract.
   ============================================================ */

/* ---------- Mentor persona (Vesper) ---------- */
.mentor { margin: 0; display: flex; flex-direction: column; gap: 12px; }
.mentor__frame {
    position: relative;
    container-type: inline-size;
    /* Inline-size containment makes an auto-sized block collapse to its border
       box. Give each surface a real layout width, then let its max-width and
       aspect-ratio choose the compact displayed size. */
    width: 100%;
    aspect-ratio: 3 / 4;
    max-width: 260px;
    margin-inline: auto;
    border-radius: var(--radius-lg);
    overflow: hidden;
    border: 1px solid var(--border-color);
    background: linear-gradient(160deg,
        color-mix(in srgb, var(--color-primary) 16%, var(--bg-surface)) 0%,
        var(--bg-surface) 60%,
        color-mix(in srgb, var(--color-primary) 8%, var(--bg-subtle)) 100%);
    box-shadow: var(--shadow-md, 0 2px 8px rgba(0, 0, 0, 0.2)), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
/* contain, not cover: a hand-drawn 3:4 plate fills the 3:4 frame either way,
   but Vesper's square (500x500) server-generated portrait must letterbox over
   the frame's gradient rather than crop the Noodle Dragon's long body. */
.mentor__art { width: 100%; height: 100%; object-fit: contain; display: block; }

/* Placeholder until Vesper's plates land (personas/registry.py::art_ready): a
   stencilled navigator's plate, a designed placeholder, not a broken image. */
.mentor__stencil {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    background-image: repeating-linear-gradient(135deg,
        color-mix(in srgb, var(--color-primary) 5%, transparent) 0 12px,
        transparent 12px 24px);
    color: color-mix(in srgb, var(--color-primary) 70%, var(--text-secondary));
}
.mentor__stencil { padding-inline: 7%; }
.mentor__stencil-mark { font-size: 30cqw; line-height: 1; opacity: 0.85; }
.mentor__stencil-name { font-family: var(--font-mono); font-size: 9cqw; font-weight: 700; letter-spacing: 0.14em; opacity: 0.7; max-width: 100%; text-align: center; overflow-wrap: anywhere; }
.mentor__stencil-sub { font-family: var(--font-mono); font-size: 5cqw; letter-spacing: 0.22em; opacity: 0.5; max-width: 100%; text-align: center; overflow-wrap: anywhere; }

.mentor__stencil--sm {
    position: static;
    width: 72px;
    height: 72px;
    border-radius: 50%;
    border: 1px solid color-mix(in srgb, var(--color-primary) 40%, var(--border-color));
    margin: 0 auto 4px;
    container-type: normal;
    background: var(--bg-subtle);
}
.mentor__stencil--sm .mentor__stencil-mark { font-size: 2.4rem; }

.mentor__speech {
    margin: 0;
    padding: 12px 14px;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--color-primary);
    background: var(--bg-subtle);
    font-size: 0.94rem;
    line-height: 1.5;
    color: var(--text-secondary);
}
.mentor__speech-name {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-primary);
    margin-bottom: 4px;
}
.mentor__speech-line { display: block; }

/* ---------- Vesper's live generated portrait ---------- */
/* Her portrait is a server-generated square composite that rerolls on a cadence:
   the rotation is her canon (a distributed, drifting VESPAR relay-mind), so the
   frame leans into a "live signal" read rather than hiding it. Size modifiers let
   the same frame serve the Handbook hero, the welcome modal, and the dashboard
   nudge. All motion below ends transparent/visible-at-rest so the global
   reduced-motion clamp (which jumps to the final keyframe) leaves it clean. */
.mentor__frame--signal { aspect-ratio: 1 / 1; }
.mentor__frame--md { max-width: 190px; }
.mentor__frame--sm { max-width: 120px; }

/* Each visit, her signal re-forms: a gentle fade-in of the freshly-loaded art. */
.mentor__frame--signal .mentor__art { animation: mentor-signal-in 0.8s ease both; }
@keyframes mentor-signal-in { from { opacity: 0; } to { opacity: 1; } }

/* A faint sheen sweeping across, so the portrait reads as a live transmission
   rather than a still. Ends off-frame and transparent on purpose. */
.mentor__sheen {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(115deg, transparent 42%,
        color-mix(in srgb, var(--color-primary) 22%, transparent) 50%, transparent 58%);
    mix-blend-mode: screen;
    transform: translateX(-120%);
    animation: mentor-sheen 6.5s ease-in-out 1.2s infinite;
}
@keyframes mentor-sheen {
    0% { transform: translateX(-120%); opacity: 0; }
    10% { opacity: 1; }
    26% { transform: translateX(120%); opacity: 0; }
    100% { transform: translateX(120%); opacity: 0; }
}

/* In-world "signal reformed <timesince> ago" readout along the bottom edge.
   Hidden on the compact dashboard frame, where there is no room for it. */
.mentor__readout {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
    padding: 12px 8px 5px;
    pointer-events: none;
    font-family: var(--font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    text-align: center;
    color: color-mix(in srgb, var(--color-primary) 60%, #ffffff);
    background: linear-gradient(to top, rgba(0, 0, 0, 0.62), transparent);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}
.mentor__frame--sm .mentor__readout { display: none; }

/* Welcome-modal introduction: centered portrait with her spoken welcome below. */
.mentor--intro { align-items: center; text-align: center; margin: 8px 0 14px; }
.mentor--intro .mentor__speech { text-align: left; max-width: 44ch; }

/* ---------- Handbook page ---------- */
.handbook { display: flex; flex-direction: column; gap: 18px; }
.handbook__hero {
    display: grid;
    grid-template-columns: 1fr;
    gap: 18px;
    padding: 20px;
}
@media (min-width: 768px) {
    .handbook__hero { grid-template-columns: minmax(0, 240px) minmax(0, 1fr); align-items: center; }
    .handbook__hero.handbook__hero--neutral { grid-template-columns: 1fr; }
}
.handbook__progress { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

.handbook-progress__bar {
    height: 12px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    overflow: hidden;
}
.handbook-progress__fill {
    display: block;
    height: 100%;
    background: var(--gradient-primary);
    border-radius: inherit;
    transition: width 0.4s ease;
}
.handbook-progress__label { margin: 0; color: var(--text-secondary); }
.handbook-progress__claim { color: var(--color-reward); font-weight: 700; }
.handbook-progress__done { margin: 0; color: var(--color-reward); font-weight: 700; }
.handbook-progress__note { margin: 0; color: var(--text-muted); font-size: 0.9rem; }

.handbook-reminder { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.handbook-reminder .hint { margin: 0; }
.handbook-reminder__note { margin: 0; }

/* ---------- Chapters ---------- */
.handbook-chapter {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 18px;
}
.handbook-chapter__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 4px; }
.handbook-chapter__title { margin: 0; font-size: 1.15rem; }
.handbook-chapter__intro { margin: 0 0 14px; color: var(--text-secondary); }
.handbook-chapter--locked { border-style: dashed; opacity: 0.72; }
.handbook-chapter__lockhint { margin: 0; color: var(--text-muted); font-size: 0.92rem; }

.handbook-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }

/* ---------- Step card ---------- */
.handbook-step {
    display: flex;
    gap: 12px;
    padding: 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.handbook-step.is-done { border-color: color-mix(in srgb, var(--color-primary) 35%, var(--border-color)); }
.handbook-step.is-claimable {
    border-color: color-mix(in srgb, var(--color-reward) 55%, var(--border-color));
    box-shadow: var(--glow-reward);
}
.handbook-step__marker {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    font-weight: 700;
    border: 1px solid var(--border-color);
    background: var(--bg-surface);
    color: var(--text-muted);
}
.handbook-step.is-done .handbook-step__marker {
    color: #fff;
    background: var(--color-primary);
    border-color: var(--color-primary);
}
.handbook-step__body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 8px; }
.handbook-step__headrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.handbook-step__title { margin: 0; font-size: 1.02rem; }
.handbook-step__blurb { margin: 0; color: var(--text-secondary); font-size: 0.94rem; line-height: 1.5; }
.handbook-step__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 2px; }
.handbook-step__claim { margin: 0; }
.handbook-step__receipt { font-family: var(--font-mono); font-size: 0.82rem; color: var(--color-reward); font-weight: 700; }
.handbook-step__reward { font-family: var(--font-mono); font-size: 0.82rem; color: var(--text-muted); }

/* ---------- Dashboard nudge card (extends .starter-cta) ---------- */
.handbook-cta { position: relative; }
/* Mentor layout: Vesper's portrait as a left aside, the nudge copy (with her
   spoken line) as the body. Stacks on narrow screens. */
.handbook-cta--mentor {
    display: grid;
    grid-template-columns: 120px minmax(0, 1fr);
    gap: 18px;
    align-items: start;
}
.handbook-cta__aside { width: 120px; }
.handbook-cta__body { min-width: 0; }
.handbook-cta__body > p:last-child { margin-bottom: 0; }
.handbook-cta__voice {
    margin: 0 0 12px;
    padding: 9px 12px;
    border-left: 3px solid var(--color-primary);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    color: var(--text-secondary);
    font-size: 0.9rem;
    line-height: 1.45;
}
.handbook-cta__voice-name {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-primary);
    margin-bottom: 3px;
}
@media (max-width: 560px) {
    .handbook-cta--mentor { grid-template-columns: 1fr; }
    .handbook-cta__aside { width: 100px; }
}
.handbook-cta__dismiss-form { position: absolute; top: 10px; right: 10px; margin: 0; }
.handbook-cta__dismiss {
    border: none;
    background: transparent;
    cursor: pointer;
    color: var(--text-muted);
    font-size: 1rem;
    line-height: 1;
    padding: 6px;
    border-radius: var(--radius-sm, 6px);
    min-width: 44px;
    min-height: 44px;
}
@media (hover: hover) { .handbook-cta__dismiss:hover { color: var(--text-primary); background: var(--bg-hover); } }
.handbook-cta__bar {
    height: 10px;
    margin: 12px 0 6px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    overflow: hidden;
}
.handbook-cta__fill { display: block; height: 100%; background: var(--gradient-primary); border-radius: inherit; transition: width 0.4s ease; }
.handbook-cta__count { margin: 0 0 14px; color: var(--text-muted); font-size: 0.86rem; font-family: var(--font-mono); }

/* ---------- Welcome modal (revealModal / modalShell) ---------- */
.handbook-welcome {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
}
.handbook-welcome__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.6); }
.handbook-welcome__card {
    position: relative;
    width: min(460px, 100%);
    max-height: 90dvh;
    overflow-y: auto;
    background: var(--bg-surface);
    border: 1px solid color-mix(in srgb, var(--color-primary) 40%, var(--border-color));
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg, 0 10px 40px rgba(0, 0, 0, 0.4));
    padding: 24px;
    text-align: center;
}
.handbook-welcome__title { margin: 6px 0 10px; }
.handbook-welcome__line { margin: 0 0 12px; color: var(--text-primary); font-size: 1.02rem; line-height: 1.5; }
.handbook-welcome__note { margin: 0 0 18px; color: var(--text-secondary); font-size: 0.92rem; line-height: 1.5; }
.handbook-welcome__actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.handbook-welcome__actions form { margin: 0; }

/* ============================================================
   FORUM
   ============================================================ */
.forum-category-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 14px;
}
.forum-category-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    text-decoration: none;
    color: inherit;
    transition: border-color 0.15s ease;
}
@media (hover: hover) {
    .forum-category-card:hover { border-color: var(--color-primary); }
}
.forum-category-card__name { display: flex; align-items: center; gap: 7px; margin: 0; font-family: var(--font-heading); font-size: 1.1rem; }
.forum-category-card__lock { font-size: .9em; }
.forum-category-card__audience { color: var(--color-primary); font-family: var(--font-mono); font-size: .67rem; letter-spacing: .05em; text-transform: uppercase; }
.forum-category-card__desc { margin: 0; color: var(--text-secondary); font-size: 0.9rem; }
.forum-category-card__count { color: var(--text-muted); font-size: 0.85rem; }
.forum-category-card__latest { display: block; margin-top: auto; padding-top: 8px; color: var(--text-secondary); font-size: .82rem; line-height: 1.35; overflow-wrap: anywhere; }
.forum-category-card__latest span { display: block; color: var(--color-primary); font-family: var(--font-mono); font-size: .64rem; letter-spacing: .06em; text-transform: uppercase; }
.forum-private-note { margin: 8px 0 0; color: var(--color-primary); font-family: var(--font-mono); font-size: .72rem; letter-spacing: .04em; }

.forum-thread-list { display: flex; flex-direction: column; gap: 2px; }
.forum-thread-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 14px 16px;
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    text-decoration: none;
    color: inherit;
}
.forum-thread-row:nth-child(odd) { background: var(--bg-subtle); }
@media (hover: hover) {
    .forum-thread-row:hover { border-color: var(--border-color); background: var(--bg-hover); }
}
.forum-thread-row--pinned { border-color: color-mix(in srgb, var(--color-primary) 30%, transparent); }
.forum-thread-row__title { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.forum-thread-row__title > span:last-child { min-width: 0; overflow-wrap: anywhere; }
.forum-thread-row__meta { display: flex; flex-wrap: wrap; gap: 14px; color: var(--text-muted); font-size: 0.85rem; }

.forum-post {
    padding: 16px;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-subtle);
    background: var(--bg-surface);
    margin-bottom: 12px;
}
.forum-post--starter { border-color: color-mix(in srgb, var(--color-primary) 35%, var(--border-subtle)); }
.forum-post__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: baseline;
    font-size: 0.85rem;
    color: var(--text-muted);
    margin-bottom: 8px;
}
.forum-post__date { color: var(--text-muted); }
.forum-post__edited { font-style: italic; }
.forum-post__permalink { color: var(--text-muted); font-family: var(--font-mono); font-size: .72rem; text-decoration: none; }
.forum-post__permalink:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.forum-post__body { line-height: 1.6; overflow-wrap: break-word; }
.forum-post__edit-form textarea { width: 100%; }
.forum-post__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.forum-post__actions form { margin: 0; }

.forum-reactions-wrap { margin: 10px 0; }
.forum-reactions { display: flex; flex-wrap: wrap; gap: 6px; }
.forum-reactions form { margin: 0; }
.forum-reaction-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-color);
    background: var(--bg-subtle);
    color: var(--text-secondary);
    font-size: 0.85rem;
    line-height: 1.4;
    cursor: pointer;
}
.forum-reaction-chip.is-active {
    border-color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 16%, var(--bg-subtle));
    color: var(--text-primary);
}
@media (hover: hover) {
    .forum-reaction-chip:hover { border-color: var(--color-primary); }
}
.forum-reaction-names { margin: 6px 0 0; font-size: 0.8rem; color: var(--text-muted); }

.forum-category-manage-list { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.forum-category-create-form,
.forum-category-manage-list form { display: grid; gap: 12px; }
.forum-category-manage-list form label,
.forum-category-create-form label { display: grid; gap: 5px; color: var(--text-secondary); font-size: .86rem; }
.forum-category-manage-list form input:not([type="checkbox"]),
.forum-category-manage-list form textarea,
.forum-category-manage-list form select,
.forum-category-create-form input:not([type="checkbox"]),
.forum-category-create-form textarea,
.forum-category-create-form select { width: 100%; }

.forum-replies { display: flex; flex-direction: column; gap: 2px; }
.forum-reply {
    margin-left: 0;
    padding-left: 14px;
    border-left: 2px solid var(--border-color);
}
.forum-reply[data-depth="1"] { margin-left: 14px; }
.forum-reply[data-depth="2"] { margin-left: 28px; }
.forum-reply[data-depth="3"] { margin-left: 42px; }
.forum-reply[data-depth="4"] { margin-left: 56px; }
.forum-reply[data-depth="5"] { margin-left: 70px; }
.forum-reply--collapsed { margin-left: 70px; padding: 10px 14px; border-left: 2px solid var(--border-color); }
.forum-reply--collapsed summary { cursor: pointer; color: var(--color-primary); font-size: 0.9rem; }
.forum-reply--collapsed[open] summary { margin-bottom: 10px; }

.forum-reply-form textarea { width: 100%; }
.forum-reply-form__replying-to { font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 8px; }
.forum-thread-quick-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.forum-thread-quick-nav a { padding: 7px 10px; border: 1px solid var(--border-subtle); border-radius: var(--radius-pill); color: var(--text-secondary); font-size: .82rem; text-decoration: none; }
@media (hover: hover) { .forum-thread-quick-nav a:hover { border-color: var(--color-primary); color: var(--text-primary); } }
.forum-composer-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 6px 0 10px; }
.forum-composer-tools a { margin-left: auto; color: var(--text-secondary); font-size: .8rem; }
.forum-composer-count { margin-left: auto; color: var(--text-muted); font-family: var(--font-mono); font-size: .72rem; }
.forum-composer-tools .forum-composer-count + a { margin-left: 0; }

/* BBCode editor: a compact formatting instrument shared by profiles, pets,
   Comms Relay, forums, news comments, and staff-authored news. The editor
   keeps the writing surface primary, while the preview uses the same server
   renderer as saved content. */
.bbcode-editor {
    display: grid;
    overflow: visible;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    box-shadow: 0 8px 24px color-mix(in srgb, var(--bg-body) 18%, transparent);
}
.bbcode-editor__toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 10px;
    padding: 9px 11px;
    border-bottom: 1px solid var(--border-subtle);
    background: linear-gradient(90deg, color-mix(in srgb, var(--color-primary) 7%, var(--bg-surface)), var(--bg-subtle));
}
.bbcode-editor__group {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding-right: 10px;
    border-right: 1px solid var(--border-subtle);
}
.bbcode-editor__group-label {
    margin-right: 2px;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: .62rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
}
.bbcode-editor__tool,
.bbcode-editor__more-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    min-height: 36px;
    padding: 6px 9px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--bg-surface);
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: .77rem;
    font-weight: 700;
    line-height: 1.1;
    cursor: pointer;
    text-decoration: none;
    transition: border-color .15s ease, background-color .15s ease, color .15s ease, transform .15s ease;
}
.bbcode-editor__tool--underlined { text-decoration: underline; text-underline-offset: 2px; }
.bbcode-editor__tool--struck { text-decoration: line-through; text-decoration-thickness: 1px; }
@media (hover: hover) {
    .bbcode-editor__tool:hover,
    .bbcode-editor__more-toggle:hover {
        border-color: color-mix(in srgb, var(--color-primary) 62%, var(--border-color));
        background: color-mix(in srgb, var(--color-primary) 10%, var(--bg-surface));
        color: var(--text-primary);
        transform: translateY(-1px);
    }
}
.bbcode-editor__tool:focus-visible,
.bbcode-editor__more-toggle:focus-visible,
.bbcode-editor__tab:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}
.bbcode-editor__more {
    position: relative;
}
.bbcode-editor__more-toggle {
    list-style: none;
}
.bbcode-editor__more-toggle::-webkit-details-marker { display: none; }
.bbcode-editor__more-toggle::after {
    content: '⌄';
    margin-left: 6px;
    color: var(--text-muted);
    font-size: .9rem;
}
.bbcode-editor__more[open] .bbcode-editor__more-toggle::after { content: '⌃'; }
.bbcode-editor__more-menu {
    position: absolute;
    z-index: 5;
    top: calc(100% + 8px);
    right: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 260px;
    max-width: min(420px, calc(100vw - 32px));
    padding: 8px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-elevated);
    box-shadow: 0 14px 34px color-mix(in srgb, var(--bg-body) 32%, transparent);
}
.bbcode-editor__tabs {
    display: flex;
    align-items: center;
    gap: 3px;
    min-height: 46px;
    padding: 0 12px;
    border-bottom: 1px solid var(--border-subtle);
    background: var(--bg-subtle);
}
.bbcode-editor__tab {
    align-self: stretch;
    min-width: 74px;
    padding: 8px 10px;
    border: 0;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--text-muted);
    font-family: var(--font-heading);
    font-size: .82rem;
    font-weight: 700;
    cursor: pointer;
}
.bbcode-editor__tab.is-active {
    border-bottom-color: var(--color-primary);
    color: var(--text-primary);
}
.bbcode-editor__render-status {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-left: auto;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: .67rem;
    letter-spacing: .04em;
    text-transform: uppercase;
}
.bbcode-editor__render-status::before {
    width: 7px;
    height: 7px;
    border: 1px solid var(--color-primary);
    border-top-color: transparent;
    border-radius: 50%;
    content: '';
    animation: spin .8s linear infinite;
}
.bbcode-editor__workspace {
    min-width: 0;
    background: var(--bg-surface);
}
.bbcode-editor__write,
.bbcode-editor__preview {
    min-width: 0;
    padding: 14px;
}
.bbcode-editor__label {
    display: block;
    margin-bottom: 7px;
    color: var(--text-secondary);
    font-size: .84rem;
    font-weight: 700;
}
.bbcode-editor__write textarea {
    display: block;
    width: 100%;
    min-height: 154px;
    resize: vertical;
    border-color: var(--border-color);
    background: var(--bg-body);
    line-height: 1.55;
}
.bbcode-editor__preview {
    min-height: 212px;
    background: linear-gradient(135deg, color-mix(in srgb, var(--color-primary) 4%, var(--bg-surface)), var(--bg-surface));
}
.bbcode-editor__preview-empty {
    display: grid;
    justify-items: center;
    gap: 7px;
    min-height: 180px;
    align-content: center;
    color: var(--text-secondary);
    text-align: center;
}
.bbcode-editor__preview-empty strong { color: var(--text-primary); font-family: var(--font-heading); }
.bbcode-editor__preview-empty span:last-child { max-width: 42ch; font-size: .82rem; }
.bbcode-editor__preview-mark {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 46%, var(--border-color));
    border-radius: 50%;
    background: color-mix(in srgb, var(--color-primary) 11%, var(--bg-surface));
    color: var(--color-primary);
    font-size: 1.05rem;
}
.bbcode-editor__preview-error {
    padding: 10px 12px;
    border: 1px solid color-mix(in srgb, var(--color-error-text) 46%, var(--border-color));
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-error-text) 8%, var(--bg-surface));
    color: var(--color-error-text);
}
.bbcode-editor__preview-content,
.bbcode-preview__content {
    min-width: 0;
    overflow-wrap: anywhere;
    line-height: 1.55;
}
.bbcode-preview__content > :first-child { margin-top: 0; }
.bbcode-preview__content > :last-child { margin-bottom: 0; }
.bbcode-editor__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    padding: 9px 14px 11px;
    border-top: 1px solid var(--border-subtle);
    color: var(--text-muted);
    font-size: .75rem;
}
.bbcode-editor__hint {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
    flex-wrap: wrap;
}
.bbcode-editor__hint a { color: var(--color-primary); }
.bbcode-editor__count {
    margin-left: auto;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: .7rem;
}
@media (max-width: 768px) {
    .bbcode-editor__toolbar { gap: 7px; padding: 8px; }
    .bbcode-editor__group { padding-right: 7px; }
    .bbcode-editor__group-label { font-size: .58rem; }
    .bbcode-editor__more-menu {
        right: auto;
        left: 50%;
        transform: translateX(-50%);
        min-width: min(300px, calc(100vw - 28px));
    }
    .bbcode-editor__write,
    .bbcode-editor__preview { padding: 12px; }
    .bbcode-editor__footer { align-items: flex-start; padding-inline: 12px; }
}
@media (hover: none), (pointer: coarse) {
    .bbcode-editor__tool,
    .bbcode-editor__more-toggle { min-height: 44px; }
    .bbcode-editor__tab { min-height: 44px; }
}
@media (max-width: 480px) {
    .bbcode-editor__toolbar { align-items: flex-start; }
    .bbcode-editor__group { flex-wrap: wrap; }
    .bbcode-editor__group-label { flex-basis: 100%; margin-bottom: 1px; }
    .bbcode-editor__more-menu { max-width: calc(100vw - 24px); }
    .bbcode-editor__hint { line-height: 1.45; }
    .bbcode-editor__count { width: 100%; margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .bbcode-editor__tool,
    .bbcode-editor__more-toggle { transition: none; }
    .bbcode-editor__render-status::before { animation: none; }
}

@media (max-width: 768px) {
    .forum-thread-row { align-items: flex-start; padding: 12px; }
    .forum-thread-row__title { flex: 1 1 100%; }
    .forum-thread-row__meta { gap: 6px 10px; font-size: .78rem; }
    .forum-reply[data-depth="1"],
    .forum-reply[data-depth="2"],
    .forum-reply[data-depth="3"],
    .forum-reply[data-depth="4"],
    .forum-reply[data-depth="5"],
    .forum-reply--collapsed { margin-left: 0; padding-left: 8px; }
    .forum-composer-tools a { margin-left: 0; }
}

/* Friends: the shared relationship-status action partial (templates/partials/
   _friend_action.html), reused on the profile page, players list rows and the
   friends page's own request rows. Avatar thumbs reuse .player-avatar-thumb from
   the players list rather than a second set of sizing rules. */
.friend-action { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; flex-shrink: 0; }
.friend-action form { margin: 0; }
.friend-action-inline-form { display: inline-block; }
.friends-page-section { margin-bottom: 0; }
.friends-page-loading {
    display: flex;
    align-items: center;
    gap: 8px;
    max-height: 0;
    margin: 0;
    overflow: hidden;
    opacity: 0;
    color: var(--text-secondary);
    font-size: .82rem;
    transition: max-height .18s ease, margin .18s ease, opacity .18s ease;
}
.friends-page-loading.htmx-request {
    max-height: 32px;
    margin: -4px 0 14px;
    opacity: 1;
}
.friends-page-loading__mark {
    width: 12px;
    height: 12px;
    border: 2px solid color-mix(in srgb, var(--color-primary) 35%, transparent);
    border-top-color: var(--color-primary);
    border-radius: 50%;
    animation: spin .7s linear infinite;
}
.friends-summary {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
    margin-bottom: 18px;
}
.friends-summary__item {
    display: grid;
    gap: 3px;
    padding: 14px 16px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-cool);
}
.friends-summary__value {
    font-family: var(--font-display);
    font-size: 1.65rem;
    line-height: 1;
    color: var(--text-primary);
}
.friends-summary__label { color: var(--text-secondary); font-size: .8rem; }
.friends-requests-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 18px;
    margin-bottom: 18px;
}
.friends-request-list, .friends-roster-list { display: grid; gap: 8px; }
.friends-request-row, .friends-friend-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 70px;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
}
.friends-request-row__user, .friends-friend-row__user {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    color: var(--text-primary);
    font-weight: 700;
}
.friends-row-identity { display: grid; gap: 3px; min-width: 0; }
.friends-row__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.friends-row__meta { color: var(--text-muted); font-size: .72rem; font-weight: 500; }
.friends-roster-panel .panel__header { align-items: flex-start; }
.friends-roster-toolbar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: 4px 14px;
    margin-bottom: 16px;
}
.friends-search-form { display: flex; align-items: end; gap: 10px; }
.friends-search-form__field { display: grid; flex: 1; gap: 6px; min-width: 0; }
.friends-search-form label {
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: .66rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
}
.friends-search-form input { width: 100%; min-height: 40px; }
.friends-search-hint { margin: 0 0 2px; color: var(--text-muted); font-size: .76rem; }
#friends-page-body:has(.friends-search-form.htmx-request) #friends-grid {
    opacity: .55;
    pointer-events: none;
    transition: opacity .12s ease;
}
.friends-empty-note { margin: 0; }
.friends-empty-state {
    display: grid;
    justify-items: center;
    gap: 8px;
    padding: 34px 18px;
    border: 1px dashed var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-cool);
    text-align: center;
}
.friends-empty-state h3, .friends-empty-state p { margin: 0; }
.friends-empty-state h3 { font-family: var(--font-heading); font-size: 1rem; }
.friends-empty-state p { max-width: 44ch; color: var(--text-secondary); }
.friends-empty-state__glyph { font-size: 1.8rem; }

@media (max-width: 768px) {
    .friends-summary { gap: 8px; }
    .friends-summary__item { padding: 12px 10px; }
    .friends-summary__value { font-size: 1.35rem; }
    .friends-summary__label { font-size: .72rem; }
    .friends-requests-grid { grid-template-columns: 1fr; gap: 12px; }
    .friends-request-row, .friends-friend-row { align-items: flex-start; flex-direction: column; gap: 10px; }
    .friends-request-row__user, .friends-friend-row__user { width: 100%; }
    .friend-action { justify-content: flex-start; width: 100%; }
    .friend-action form { flex: 1 1 auto; }
    .friend-action .btn { width: 100%; }
    .friends-roster-toolbar { grid-template-columns: 1fr; }
    .friends-search-form { align-items: stretch; }
    .friends-search-form .btn { min-width: 84px; }
    .friends-search-hint { margin: 0; }
}
@media (pointer: coarse) {
    #friends-page-body .btn-sm { min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
    .friends-page-loading { transition: none; }
    .friends-page-loading__mark { animation: none; }
}

/* Comms Relay: restrained panels and readable message bubbles. The conversation
   surface stays useful without JavaScript; HTMX only refreshes the timeline while
   it is open. */
.conversation-list-stack { display: grid; gap: 18px; }
.conversation-list { display: grid; gap: 8px; }
.conversation-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 68px;
    padding: 12px 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    color: var(--text-primary);
    text-decoration: none;
}
@media (hover: hover) {
    .conversation-row:hover { background: var(--bg-hover); text-decoration: none; }
}
.conversation-row--unread { border-color: color-mix(in srgb, var(--color-primary) 58%, var(--border-color)); }
.conversation-row__glyph { flex: 0 0 34px; font-size: 1.35rem; text-align: center; }
.conversation-row__body { display: grid; gap: 3px; min-width: 0; flex: 1; }
.conversation-row__meta, .conversation-row__preview { color: var(--text-muted); font-size: .8rem; }
.conversation-row__preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conversation-row__actions, .conversation-thread__actions, .message-compose-footer {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.conversation-invite-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 12px 0;
    border-top: 1px solid var(--border-subtle);
}
.conversation-invite-row:first-of-type { border-top: 0; }
.conversation-invite-row h3 { margin: 0; font-size: .98rem; }
.conversation-invite-row p { margin: 4px 0 0; }
.conversation-empty { display: grid; justify-items: center; gap: 8px; padding: 34px 18px; text-align: center; color: var(--text-secondary); }
.conversation-empty h3, .conversation-empty p { margin: 0; }
.conversation-empty__glyph { font-size: 2rem; }
.conversation-empty--thread { min-height: 120px; }
.messaging-compose-panel form { display: grid; gap: 10px; }
.messaging-compose-panel fieldset { display: grid; gap: 8px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 12px; }
.messaging-compose-panel legend { padding: 0 6px; color: var(--text-secondary); font-family: var(--font-heading); }
.messaging-compose-panel select[multiple] { min-height: 132px; }
.message-compose-footer { justify-content: space-between; margin-top: 4px; }
.message-compose-footer .form-hint { margin: 0; }
.conversation-thread { display: grid; gap: 16px; }
.conversation-thread__header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.conversation-thread__header .page-title { margin: 2px 0 0; }
.conversation-thread__members { display: flex; flex-wrap: wrap; gap: 5px; margin: 10px 0 0; }
.conversation-member-chip { color: var(--text-secondary); font-size: .8rem; }
.conversation-member-chip--pending { color: var(--text-muted); font-style: italic; }
.conversation-invite-callout { padding: 18px; border: 1px solid color-mix(in srgb, var(--color-warning) 50%, var(--border-color)); border-radius: var(--radius-md); background: var(--color-warning-bg); }
.conversation-invite-callout h2, .conversation-invite-callout p { margin-top: 0; }
.message-timeline { display: grid; gap: 8px; }
.message-timeline__list { display: grid; gap: 10px; align-content: start; max-height: min(62vh, 720px); max-height: min(62dvh, 720px); overflow-y: auto; padding: 4px 4px 12px; scroll-behavior: smooth; }
.message-earlier { display: flex; justify-content: center; }
.message-earlier:empty { display: none; }
.message-char-count { color: var(--text-muted); font-size: .75rem; white-space: nowrap; }
.conversation-archived summary { cursor: pointer; list-style: none; }
.conversation-archived summary .panel-title { display: inline; }
.conversation-archived > summary::before { content: '▸ '; color: var(--text-muted); }
.conversation-archived[open] > summary::before { content: '▾ '; }
.conversation-row--archived { opacity: .85; }
.conversation-row--archived .conversation-row__body { text-decoration: none; }
.message-bubble { max-width: min(75ch, 88%); justify-self: start; padding: 10px 13px; border: 1px solid var(--border-color); border-radius: 4px 14px 14px 14px; background: var(--bg-subtle); }
.message-bubble--own { justify-self: end; border-color: color-mix(in srgb, var(--color-primary) 40%, var(--border-color)); border-radius: 14px 4px 14px 14px; background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-surface)); }
.message-bubble--blocked { opacity: .82; }
.message-bubble__meta { display: flex; gap: 10px; align-items: baseline; color: var(--text-secondary); font-size: .78rem; }
.message-bubble__meta time { color: var(--text-muted); }
.message-bubble__body { overflow-wrap: anywhere; line-height: 1.5; }
.message-bubble__body p:first-child { margin-top: 5px; }
.message-bubble__body p:last-child { margin-bottom: 0; }
.message-bubble__removed { margin: 5px 0 0; color: var(--text-muted); font-style: italic; }
.message-bubble__actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 8px; }
.message-bubble__actions details { position: relative; }
.message-bubble__actions details form { display: grid; gap: 6px; min-width: 220px; margin-top: 8px; padding: 10px; border: 1px solid var(--border-color); border-radius: var(--radius-md); background: var(--bg-elevated); }
.message-bubble__blocked { margin-top: 6px; }
.message-bubble__blocked summary { cursor: pointer; color: var(--text-muted); }
.message-composer { display: grid; gap: 8px; border-top: 1px solid var(--border-subtle); padding-top: 14px; }
.message-composer textarea { width: 100%; }
.conversation-manage { border-top: 1px solid var(--border-subtle); padding-top: 12px; }
.conversation-manage summary { cursor: pointer; color: var(--text-secondary); font-family: var(--font-heading); font-weight: 600; }
.conversation-manage form { display: flex; align-items: end; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.conversation-manage form label { flex-basis: 100%; }
.conversation-manage select { min-width: min(100%, 320px); }
.conversation-member-list { display: grid; gap: 6px; margin: 14px 0 0; padding: 0; list-style: none; }
.conversation-member-list li { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid var(--border-subtle); }
.profile-social-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.profile-social-actions form { margin: 0; }

@media (max-width: 768px) {
    .conversation-thread__header { display: grid; }
    .conversation-thread__actions { width: 100%; }
    .conversation-thread__actions .btn { flex: 1; }
    .message-bubble { max-width: 94%; }
    .conversation-invite-row { align-items: flex-start; flex-direction: column; }
    .conversation-invite-row .conversation-row__actions { width: 100%; }
    .conversation-invite-row .conversation-row__actions .btn { min-height: 44px; }
    .message-compose-footer { align-items: stretch; flex-direction: column; }
    .message-compose-footer .btn { width: 100%; }
}

@media (max-width: 480px) {
    .conversation-row { padding-inline: 10px; }
    .conversation-row .state-chip { display: none; }
    .message-bubble { max-width: 100%; }
    .message-timeline { padding-inline: 0; }
}

/* Staff Anomaly Hub (/staff/anomalies/) */
.anomaly-hub__nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 16px;
}
.anomaly-hub__nav a {
    display: inline-flex;
    flex-direction: column;
    gap: 2px;
    min-height: 44px;
    padding: 8px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    color: var(--text-primary);
    font-weight: 700;
}
@media (hover: hover) { .anomaly-hub__nav a:hover { border-color: var(--color-primary); text-decoration: none; } }
.anomaly-hub__nav span { color: var(--text-muted); font-size: 0.72rem; font-weight: 400; }
.anomaly-hub__note {
    color: var(--text-secondary);
    max-width: 70ch;
    margin: 0 0 1.5rem;
}
.anomaly-section {
    margin-bottom: 2.5rem;
}
.anomaly-section__title {
    margin: 0 0 0.75rem;
}
.anomaly-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1.25rem;
    padding: 0.6rem 0.85rem;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    margin-bottom: 0.75rem;
}
.anomaly-summary__stat {
    color: var(--text-secondary);
    font-size: 0.9rem;
    white-space: nowrap;
}
.anomaly-summary__stat strong {
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}
.anomaly-summary__stat--flag strong {
    color: var(--color-warning-ink);
}
.anomaly-export {
    margin-inline-start: auto;
    display: inline-flex;
    gap: 0.4rem;
}
.anomaly-table-wrap {
    overflow-x: auto;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
}
.anomaly-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.88rem;
}
.anomaly-table th,
.anomaly-table td {
    padding: 0.45rem 0.7rem;
    text-align: left;
    border-bottom: 1px solid var(--border-subtle);
    white-space: nowrap;
    vertical-align: top;
}
.anomaly-table thead th {
    position: sticky;
    top: 0;
    background: var(--bg-elevated);
    color: var(--text-secondary);
    font-weight: 600;
}
.anomaly-table tbody tr:last-child td {
    border-bottom: none;
}
.anomaly-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.anomaly-sub {
    display: block;
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 400;
}
.anomaly-row--flag {
    background: var(--color-warning-bg);
}
.anomaly-cell--flag {
    font-weight: 700;
    color: var(--color-warning-ink);
}
.anomaly-flag {
    display: inline-block;
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--color-warning-ink);
    background: var(--color-warning-bg);
    border: 1px solid var(--color-warning-border);
    border-radius: 4px;
    padding: 0.05rem 0.3rem;
    margin-inline-end: 0.35rem;
}
.anomaly-reasons {
    white-space: normal;
    max-width: 24rem;
}
.anomaly-reason {
    display: inline-block;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: 4px;
    padding: 0.05rem 0.35rem;
    margin: 0.1rem 0.15rem 0.1rem 0;
    font-size: 0.78rem;
}
.anomaly-cap-note,
.anomaly-empty {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: 0.6rem 0 0;
}

/* ============================================================
   Frontier interface shell
   The shell changes framing, atmosphere, and token values while reusing the
   existing server-rendered navigation and responsive layout. At 1024px and
   below the current bottom tab bar remains the structural navigation.
   ============================================================ */
body.interface-frontier {
    --gradient-primary: linear-gradient(120deg, var(--color-primary) 0%, var(--color-violet) 58%, var(--color-reward) 100%);
    --vignette: radial-gradient(125% 125% at 50% 18%, transparent 52%, color-mix(in srgb, var(--bg-body) 45%, transparent) 100%);
}

.frontier-shell {
    position: relative;
    isolation: isolate;
    max-width: 1280px;
    min-height: 100vh;
    min-height: 100dvh;
    margin: 0 auto;
    overflow: clip;
    background: var(--bg-body);
    border-inline: 1px solid color-mix(in srgb, var(--border-color) 78%, transparent);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-violet) 10%, transparent), var(--shadow-lg);
}

.frontier-shell__hero {
    position: absolute;
    inset: 0 0 auto;
    z-index: -1;
    height: 190px;
    background-image: linear-gradient(180deg, color-mix(in srgb, var(--bg-body) 12%, transparent), var(--bg-body) 92%), var(--frontier-header-art, none);
    background-position: center top;
    background-size: cover;
    opacity: .94;
    pointer-events: none;
}

.frontier-shell__hero::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, color-mix(in srgb, var(--color-violet) 14%, transparent), transparent 38%, color-mix(in srgb, var(--color-primary) 10%, transparent));
    mix-blend-mode: screen;
}

body.interface-frontier .site-header {
    background: color-mix(in srgb, var(--bg-surface) 78%, transparent);
    border-bottom-color: color-mix(in srgb, var(--border-color) 88%, transparent);
}

body.interface-frontier .site-body {
    background: color-mix(in srgb, var(--bg-body) 90%, transparent);
}

body.interface-frontier .site-sidebar {
    background-image: linear-gradient(180deg, color-mix(in srgb, var(--bg-surface) 86%, transparent), var(--bg-body) 86%), var(--frontier-rail-art, none);
    background-position: center top;
    background-size: cover;
    border-right-color: var(--border-color);
}

body.interface-frontier .sidebar-nav a,
body.interface-frontier .sidebar-nav-group > summary {
    border-radius: 10px;
}

body.interface-frontier .sidebar-nav a.is-active,
body.interface-frontier .sidebar-nav-group[open] > summary {
    background: color-mix(in srgb, var(--color-violet) 14%, var(--bg-hover));
    color: var(--text-primary);
}

body.interface-frontier .sidebar-nav a.is-active::before {
    background: var(--color-primary);
    box-shadow: 0 0 10px color-mix(in srgb, var(--color-primary) 52%, transparent);
}

body.interface-frontier .panel,
body.interface-frontier .conditions-strip,
body.interface-frontier .news-strip {
    border-color: color-mix(in srgb, var(--border-color) 86%, transparent);
    border-radius: 14px;
    box-shadow: 0 12px 32px color-mix(in srgb, var(--bg-body) 25%, transparent);
}

/* Restated after the shared rule above: it sets `border-color` wholesale, which keeps
   the strip's 3px leading edge but repaints it in the panel border colour and loses the
   accent that marks the band as new. */
body.interface-frontier .news-strip { border-left-color: var(--color-primary); }

body.interface-frontier .page-title {
    letter-spacing: -0.045em;
}

body.interface-frontier .site-body > .page-container {
    max-width: none;
    padding: 30px clamp(20px, 3.2vw, 42px) 66px;
}

body.interface-frontier .page-header {
    padding-top: 6px;
}

.theme-preview--frontier,
.theme-preview--frontier-1,
.theme-preview--frontier-2,
.theme-preview--frontier-3 {
    background: linear-gradient(135deg, #15102a 0%, #284363 58%, #8764b7 100%);
}
.theme-preview--frontier-2 { background: linear-gradient(135deg, #182238 0%, #3d6b70 55%, #c78b73 100%); }
.theme-preview--frontier-3 { background: linear-gradient(135deg, #101d31 0%, #246878 55%, #4e7b9d 100%); }

.interface-style-choices,
.frontier-theme-picker { margin-top: 18px; }

.theme-card--frontier .theme-card__status { color: var(--color-primary); }

.theme-studio-layout {
    display: grid;
    grid-template-columns: minmax(220px, .7fr) minmax(0, 1.6fr);
    gap: 18px;
    align-items: start;
}
.theme-studio-layout > *,
.theme-studio-layout .panel,
.theme-studio-layout form,
.theme-studio-layout fieldset,
.theme-studio-layout .form-grid { min-width: 0; max-width: 100%; }
.theme-studio-list { display: grid; gap: 8px; margin-bottom: 16px; }
.theme-studio-card {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 58px;
    padding: 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    color: var(--text-primary);
}
.theme-studio-card.is-selected { border-color: var(--color-primary); text-decoration: none; }
@media (hover: hover) {
    .theme-studio-card:hover { border-color: var(--color-primary); text-decoration: none; }
}
.theme-studio-card__swatch { width: 30px; height: 30px; flex: 0 0 auto; border-radius: 9px; background: var(--theme-swatch-bg); box-shadow: inset 0 0 0 5px var(--theme-swatch), 0 0 0 1px var(--border-color); }
.theme-studio-card__preview { display: grid; place-items: center; width: 84px; height: 48px; flex: 0 0 auto; overflow: hidden; border: 1px solid var(--border-color); border-radius: 8px; background: var(--bg-body); }
.theme-studio-card__preview img { display: block; width: 100%; height: 100%; object-fit: cover; }
.theme-studio-card__preview .theme-studio-card__swatch { width: 32px; height: 32px; }
.theme-studio-card__copy { display: grid; gap: 3px; min-width: 0; flex: 1; }
.theme-studio-card__copy span { color: var(--text-muted); font-family: var(--font-mono); font-size: .68rem; overflow-wrap: anywhere; }
.theme-studio-card .state-chip { white-space: nowrap; }
.theme-studio-editor .panel__header { align-items: flex-start; }
.theme-studio-guide { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 18px; padding: 16px 18px; border: 1px solid color-mix(in srgb, var(--color-primary) 38%, var(--border-color)); border-radius: var(--radius-lg); background: color-mix(in srgb, var(--color-primary) 7%, var(--bg-surface)); }
.theme-studio-guide strong { display: block; margin-top: 4px; font-family: var(--font-heading); font-size: 1.05rem; }
.theme-studio-guide p { max-width: 62ch; margin: 4px 0 0; color: var(--text-secondary); font-size: .84rem; }
.theme-studio-guide__steps { display: flex; flex-wrap: wrap; gap: 8px; color: var(--text-muted); font-family: var(--font-mono); font-size: .7rem; text-transform: uppercase; white-space: nowrap; }
.theme-studio-guide__steps span { display: inline-flex; align-items: center; gap: 5px; padding: 6px 8px; border: 1px solid var(--border-subtle); border-radius: var(--radius-pill); }
.theme-studio-guide__steps b { color: var(--color-primary); }
.theme-upload-fieldset,
.theme-color-fieldset { margin: 18px 0 0; padding: 14px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); }
.theme-upload-fieldset legend,
.theme-color-fieldset legend { padding: 0 6px; font-family: var(--font-heading); font-weight: 700; }
.theme-upload-field { display: grid; gap: 6px; align-content: start; }
.theme-upload-field__spec { color: var(--color-primary); font-family: var(--font-mono); font-size: .68rem; letter-spacing: .04em; text-transform: uppercase; }
.theme-studio-artwork-status { display: grid; gap: 8px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border-subtle); }
.theme-studio-artwork-status__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.theme-studio-artwork-status__grid > div { display: grid; gap: 6px; min-width: 0; color: var(--text-secondary); font-size: .78rem; }
.theme-studio-artwork-status__grid img { display: block; width: 100%; height: 82px; object-fit: cover; border: 1px solid var(--border-subtle); border-radius: 8px; background: var(--bg-body); }
.theme-studio-artwork-status__empty { display: grid; place-items: center; min-height: 82px; border: 1px dashed var(--border-color); border-radius: 8px; color: var(--text-muted); font-size: .78rem; }
.theme-color-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.theme-color-field { display: grid; gap: 5px; color: var(--text-secondary); font-size: .76rem; font-weight: 700; }
.theme-color-input { width: 100%; min-height: 42px; padding: 3px; border: 1px solid var(--border-color); border-radius: 7px; background: var(--bg-body); cursor: pointer; }
.theme-publication-box { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border-subtle); }
.theme-publication-box p { margin: 4px 0 0; color: var(--text-secondary); font-size: .84rem; line-height: 1.45; }

.frontier-preview-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 22px;
    padding: 12px 14px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 45%, var(--border-color));
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--color-primary) 8%, var(--bg-surface));
    color: var(--text-secondary);
}
.frontier-preview-header { display: flex; align-items: end; justify-content: space-between; gap: 20px; }
.frontier-preview-condition { display: flex; align-items: center; gap: 16px; }
.frontier-preview-condition .conditions-strip__body { flex: 1; }
.frontier-preview-grid { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(260px, .8fr); gap: 18px; margin-top: 18px; }
.frontier-preview-card { display: grid; grid-template-columns: 1fr auto; gap: 8px 12px; margin-top: 14px; padding: 16px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.frontier-preview-card p { grid-column: 1 / -1; margin: 0; color: var(--text-secondary); }
.frontier-preview-progress { grid-column: 1 / -1; height: 8px; overflow: hidden; border-radius: var(--radius-pill); background: var(--bg-hover); }
.frontier-preview-progress span { display: block; height: 100%; border-radius: inherit; background: var(--gradient-primary); }
.frontier-preview-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.frontier-preview-stats > div { display: grid; gap: 8px; padding: 14px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-subtle); }
.frontier-preview-stats strong { font-family: var(--font-display); font-size: 1.35rem; }

@media (min-width: 1024px) {
    body.interface-frontier .site-sidebar { width: 202px; padding: 22px 12px; }
}

@media (width < 1024px) {
    .frontier-shell { min-height: 0; margin: 0; border-inline: 0; box-shadow: none; }
    .frontier-shell__hero { height: 128px; }
    body.interface-frontier .site-body > .page-container { padding: 22px 16px 60px; }
    .theme-studio-layout { grid-template-columns: minmax(0, 1fr); }
    .frontier-preview-grid { grid-template-columns: 1fr; }
    .theme-color-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
    .frontier-preview-banner,
    .frontier-preview-header,
    .frontier-preview-condition,
    .theme-publication-box { align-items: stretch; flex-direction: column; }
    .theme-studio-card { align-items: flex-start; }
    .theme-studio-card .state-chip { white-space: normal; text-align: right; }
    .theme-color-grid { grid-template-columns: 1fr; }
}

@media (width < 768px) {
    .theme-studio-guide { align-items: stretch; flex-direction: column; gap: 12px; }
    .theme-studio-guide__steps { white-space: normal; }
    .theme-studio-artwork-status__grid { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
    .frontier-shell__hero { scroll-behavior: auto; }
}

/* ── Social sharing ──────────────────────────────────────────────────────────
 * The share button cluster on pet_detail / user_profile, and the standalone
 * public share pages (game:share_pet / game:share_profile) that carry the OG
 * card a pasted link unfurls in Discord/Facebook. See game/social.py. */
.share-cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}
.share-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.share-menu-wrap { position: relative; display: inline-flex; gap: 2px; }
.share-btn--toggle { padding-inline: 10px; }
.share-menu {
    position: absolute;
    z-index: 20;
    top: calc(100% + 8px);
    right: 0;
    min-width: 224px;
    padding: 6px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    box-shadow: var(--shadow-md);
}
.share-menu button,
.share-menu a {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 44px;
    gap: 8px;
    padding: 9px 11px;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    background: transparent;
    text-align: left;
    text-decoration: none;
    font: inherit;
    cursor: pointer;
}
.share-menu button:focus-visible,
.share-menu a:focus-visible {
    background: var(--bg-hover);
    color: var(--color-primary);
    outline: none;
}
@media (hover: hover) {
    .share-menu button:hover,
    .share-menu a:hover {
        background: var(--bg-hover);
        color: var(--color-primary);
    }
}
.share-status {
    font-size: 0.8rem;
    color: var(--color-primary);
    padding: 2px 10px;
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-pill);
    white-space: nowrap;
}
.pet-detail-share,
.profile-share {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    margin-top: 16px;
}
.share-section-label {
    color: var(--text-muted);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* Public share landing pages (anonymous: no site header, so center the card). */
.share-page {
    max-width: 760px;
    margin: 0 auto;
    padding: 32px 16px 48px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    min-height: 70dvh;
    justify-content: center;
    text-align: center;
}
.share-card-panel {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 24px;
    padding: 28px;
}
.share-card-art { flex-shrink: 0; }
.share-card-art img {
    border-radius: var(--radius-lg);
    max-width: 100%;
    height: auto;
    box-shadow: var(--shadow-sm);
}
.share-card-art__placeholder {
    width: 200px;
    height: 200px;
    display: grid;
    place-items: center;
    font-size: 5rem;
    color: var(--color-primary);
    background: var(--bg-cool);
    border-radius: var(--radius-lg);
}
.share-card-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}
.share-card-body h1 { margin: 4px 0; }
.share-card-subtitle {
    margin: 0;
    color: var(--color-primary);
    font-weight: 700;
}
.share-card-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
    justify-content: center;
    margin: 2px 0 0;
}
.share-fact {
    display: inline-flex;
    flex-direction: column;
    gap: 2px;
    align-items: center;
}
.share-fact span {
    color: var(--text-muted);
    font-size: 0.67rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.share-fact strong {
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: 0.86rem;
}
.share-card-context {
    margin: 2px 0 0;
    color: var(--text-secondary);
}
.share-card-bio {
    max-width: 52ch;
    color: var(--text-muted);
    margin-top: 4px;
    line-height: 1.5;
}
.share-cta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    justify-content: center;
    margin-top: 12px;
}
.share-card-panel .share-cluster { justify-content: center; margin-top: 6px; }

@media (min-width: 640px) {
    .share-card-panel {
        flex-direction: row;
        align-items: center;
        text-align: left;
    }
    .share-card-body { align-items: flex-start; }
    .share-card-facts,
    .share-cta { justify-content: flex-start; }
    .share-card-panel .share-cluster { justify-content: flex-start; }
}

@media (max-width: 480px) {
    .share-menu { min-width: min(224px, calc(100vw - 32px)); }
    .share-cta { width: 100%; }
    .share-cta .btn { width: 100%; }
}

/* ============================================================
   Frontier reference shell
   The live Frontier reference uses four visual layers: a scenic masthead,
   a compact resource strip, a painted navigation rail, and a translucent
   content field. Keep these layers Frontier-only so Legacy remains byte-for-
   byte in spirit and route content stays server-rendered and reusable.
   ============================================================ */
body.interface-frontier {
    --frontier-shell-border: color-mix(in srgb, var(--color-primary) 58%, var(--border-color));
    --frontier-nav-start: color-mix(in srgb, var(--color-violet) 62%, var(--bg-surface));
    --frontier-nav-end: color-mix(in srgb, var(--color-primary) 64%, var(--bg-body));
    --frontier-nav-sub-end: color-mix(in srgb, var(--color-primary) 28%, var(--bg-surface));
    --frontier-panel: color-mix(in srgb, var(--color-violet) 16%, var(--bg-surface));
    --frontier-field: var(--bg-body);
    background-color: var(--bg-body);
    background-image:
        linear-gradient(180deg, color-mix(in srgb, var(--bg-body) 10%, transparent), color-mix(in srgb, var(--bg-body) 36%, transparent)),
        var(--frontier-rail-art, none),
        radial-gradient(circle at 16% 18%, color-mix(in srgb, var(--color-primary) 26%, transparent) 0 1px, transparent 1.5px),
        radial-gradient(circle at 76% 11%, color-mix(in srgb, var(--color-reward) 20%, transparent) 0 1px, transparent 1.5px),
        radial-gradient(circle at 42% 66%, color-mix(in srgb, var(--color-violet) 28%, transparent) 0 1px, transparent 1.5px),
        var(--vignette);
    background-position: center, center, center, center, center, center;
    background-size: cover, cover, 176px 164px, 231px 207px, 263px 241px, auto;
    background-attachment: fixed;
}

body.interface-frontier .frontier-shell {
    max-width: 1252px;
    margin: 8px auto 24px;
    overflow: clip;
    border: 1px solid var(--frontier-shell-border);
    border-radius: 100px;
    background: var(--frontier-field);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 14%, transparent), 0 24px 70px rgba(3, 4, 16, .52);
}

body.interface-frontier .frontier-shell__hero {
    position: relative;
    inset: auto;
    z-index: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 150px;
    padding: 0 24px;
    opacity: 1;
    overflow: hidden;
    border-radius: 99px 99px 0 0;
    background-image:
        linear-gradient(180deg, color-mix(in srgb, var(--bg-body) 12%, transparent), color-mix(in srgb, var(--bg-body) 50%, transparent)),
        var(--frontier-header-art, none);
    background-position: center top;
    background-size: cover;
}

body.interface-frontier .frontier-shell__hero::after {
    z-index: 0;
    background:
        linear-gradient(90deg, color-mix(in srgb, var(--color-violet) 22%, transparent), transparent 42%, color-mix(in srgb, var(--color-primary) 18%, transparent)),
        linear-gradient(180deg, transparent 55%, color-mix(in srgb, var(--bg-body) 46%, transparent));
    mix-blend-mode: normal;
}

.frontier-shell__brand {
    position: relative;
    z-index: 1;
    max-width: 100%;
    color: #fff;
    font-family: var(--font-display);
    font-size: clamp(2.2rem, 6vw, 4.75rem);
    font-style: italic;
    font-weight: 700;
    letter-spacing: .08em;
    line-height: 1;
    text-align: center;
    text-shadow: 0 2px 0 color-mix(in srgb, var(--color-violet) 60%, #000), 0 0 18px color-mix(in srgb, var(--color-primary) 46%, transparent);
    white-space: nowrap;
}
.frontier-shell__brand span { color: color-mix(in srgb, var(--color-primary) 72%, #fff); }
@media (hover: hover) {
    .frontier-shell__brand:hover { color: #fff; text-decoration: none; filter: brightness(1.08); }
}

body.interface-frontier .site-brand { display: none; }
body.interface-frontier .site-header {
    position: sticky;
    top: 0;
    z-index: 100;
    height: 53px;
    padding: 0 24px;
    background: color-mix(in srgb, var(--bg-surface) 52%, transparent);
    border-top: 1px solid color-mix(in srgb, var(--color-primary) 48%, transparent);
    border-bottom-color: color-mix(in srgb, var(--color-primary) 46%, var(--border-color));
    backdrop-filter: saturate(1.25) blur(10px);
    -webkit-backdrop-filter: saturate(1.25) blur(10px);
}
body.interface-frontier .site-nav-right { gap: 10px; }
body.interface-frontier .weather-chip,
body.interface-frontier .nav-currency {
    border-color: color-mix(in srgb, var(--color-primary) 48%, var(--border-color));
    background: color-mix(in srgb, var(--color-violet) 22%, transparent);
}
body.interface-frontier .weather-chip { color: var(--text-primary); }
body.interface-frontier .nav-currency { color: var(--text-primary); }

/* .state-chip--active/--done read straight off --color-primary in Legacy, which is safe
   there because every Legacy palette is hand-curated for it. A Frontier draft's accent is
   an arbitrary staff-picked hex validated (at publish only) against --bg-body alone, not
   against the --bg-subtle/panel surfaces these chips actually sit on, so the same chip
   that reads fine in Legacy can wash out here. Route the text through --text-primary
   (validated against all six Frontier backgrounds) and leave the accent-tinted border. */
body.interface-frontier .state-chip--active,
body.interface-frontier .state-chip--done {
    color: var(--text-primary);
}

body.interface-frontier .site-body {
    min-height: calc(100dvh - 203px);
    background-color: var(--bg-body);
    background-image:
        linear-gradient(180deg, color-mix(in srgb, var(--bg-body) 48%, transparent), color-mix(in srgb, var(--bg-body) 76%, transparent)),
        var(--frontier-rail-art, none),
        radial-gradient(circle at 17% 12%, color-mix(in srgb, var(--color-primary) 20%, transparent) 0 1px, transparent 1.5px),
        radial-gradient(circle at 82% 22%, color-mix(in srgb, var(--color-violet) 25%, transparent) 0 1px, transparent 1.5px),
        linear-gradient(135deg, color-mix(in srgb, var(--color-violet) 6%, transparent), transparent 44%);
    background-position: center, center, center, center, center;
    background-size: cover, cover, 190px 178px, 247px 221px, auto;
    background-attachment: fixed;
}

@media (min-width: 1024px) {
    body.interface-frontier .site-sidebar {
        width: 202px;
        /* The scenic chrome occupies 212px before the rail begins at the top of
           the page. The rail surface follows the app column to the footer; the
           inner list below owns the column cap so a short menu stays compact
           while expanded groups still scroll inside their own surface. */
        min-height: 0;
        height: auto;
        max-height: none;
        padding: 0;
        position: relative;
        top: auto;
        border-right-color: var(--frontier-shell-border);
        border-radius: 0;
        align-self: stretch;
        overflow: visible;
        background-image:
            linear-gradient(180deg, color-mix(in srgb, var(--color-violet) 24%, transparent), color-mix(in srgb, var(--bg-body) 68%, transparent) 82%),
            var(--frontier-rail-art, none);
        background-position: center top;
        background-size: cover;
    }

    body.interface-frontier .site-sidebar .sidebar-nav {
        position: sticky;
        top: 53px;
        /* Once the rail is sticky, its visible viewport starts at this 53px
           offset. The shell's 212px initial top would leave dead space below
           the menu and make a fitting expansion scroll too early. */
        max-height: min(100%, calc(100vh - 53px));
        max-height: min(100%, calc(100dvh - 53px));
        overflow-x: hidden;
        overflow-y: auto;
        overscroll-behavior: auto;
        /* Do not reserve a scrollbar lane while the menu fits. The browser should
           create the scroll affordance only when an expanded group actually overflows. */
        scrollbar-gutter: auto;
    }

    body.interface-frontier .frontier-shell__hero { height: 203px; }
    body.interface-frontier .site-header { margin-top: -53px; }

    body.interface-frontier .sidebar-nav { gap: 0; }
    body.interface-frontier .sidebar-nav a,
    body.interface-frontier .sidebar-nav-group > summary {
        min-height: 62px;
        padding: 17px 18px;
        border-bottom: 1px solid color-mix(in srgb, var(--color-primary) 42%, var(--border-color));
        border-radius: 0;
        background: linear-gradient(180deg, color-mix(in srgb, var(--color-violet) 58%, transparent), color-mix(in srgb, var(--bg-body) 78%, transparent));
        color: color-mix(in srgb, var(--color-primary) 72%, #fff);
        font-weight: 700;
        text-shadow: 0 0 5px color-mix(in srgb, var(--color-primary) 62%, transparent), 0 0 15px color-mix(in srgb, var(--color-violet) 70%, transparent);
    }
    body.interface-frontier .sidebar-nav-group__items a {
        min-height: 38px;
        padding: 9px 12px;
        background: linear-gradient(180deg, color-mix(in srgb, var(--color-primary) 28%, transparent), color-mix(in srgb, var(--color-violet) 32%, transparent));
        color: color-mix(in srgb, var(--color-primary) 84%, #fff);
        font-size: .86rem;
        text-shadow: 0 0 5px color-mix(in srgb, var(--color-primary) 64%, transparent);
    }
    body.interface-frontier .sidebar-nav a.is-active,
    body.interface-frontier .sidebar-nav-group[open] > summary {
        background: linear-gradient(180deg, var(--frontier-nav-start), var(--frontier-nav-end));
        color: #fff;
        text-shadow: 0 0 5px color-mix(in srgb, var(--color-primary) 72%, #fff), 0 0 16px var(--color-primary);
    }
    body.interface-frontier .sidebar-nav-group[open] .sidebar-nav-group__items a {
        background: linear-gradient(180deg, var(--frontier-nav-sub-end), color-mix(in srgb, var(--color-violet) 36%, transparent));
    }
    body.interface-frontier .sidebar-nav a.is-active::before { display: none; }
    body.interface-frontier .sidebar-nav a:focus-visible,
    body.interface-frontier .sidebar-nav-group > summary:focus-visible {
        outline: 3px solid var(--color-primary);
        outline-offset: -3px;
    }
    @media (hover: hover) {
        body.interface-frontier .sidebar-nav a:hover,
        body.interface-frontier .sidebar-nav-group > summary:hover {
            background: linear-gradient(180deg, color-mix(in srgb, var(--color-primary) 62%, var(--color-violet)), var(--color-primary));
            color: #fff;
            letter-spacing: .035em;
        }
    }

    body.interface-frontier .site-body > .page-container {
        max-width: none;
        padding: 25px 25px 60px;
        border-radius: 0 0 99px 0;
        background: color-mix(in srgb, var(--bg-body) 90%, transparent);
    }
}

body.interface-frontier .panel,
body.interface-frontier .conditions-strip,
body.interface-frontier .news-strip {
    border-color: color-mix(in srgb, var(--color-violet) 72%, var(--border-color));
    border-radius: 20px;
    background: color-mix(in srgb, var(--frontier-panel) 86%, transparent);
    box-shadow: 0 10px 28px color-mix(in srgb, var(--bg-body) 24%, transparent);
}

/* Restated after the shared rule above: it sets `border-color` wholesale, which keeps
   the strip's 3px leading edge but repaints it in the panel border colour and loses the
   accent that marks the band as new. */
body.interface-frontier .news-strip { border-left-color: var(--color-primary); }
body.interface-frontier .page-title {
    font-size: clamp(1.8rem, 3.4vw, 2.55rem);
    letter-spacing: -.025em;
}
body.interface-frontier .page-subtitle { max-width: 56ch; }
body.interface-frontier .eyebrow { letter-spacing: .2em; }
body.interface-frontier .conditions-strip { align-items: center; }
body.interface-frontier .conditions-strip__body { min-width: 0; }

@media (min-width: 768px) {
    /* The URL reference keeps the active work column dominant and the summary
       column visible. The legacy orbit is retained for Legacy only. */
    body.interface-frontier .command-grid {
        grid-template-columns: minmax(0, 1fr) minmax(240px, .62fr);
        grid-template-areas: "command glance";
        gap: 18px;
    }
    body.interface-frontier .command-grid::before { display: none; }
    body.interface-frontier .command-column {
        display: grid;
        grid-area: command;
        gap: 18px;
        align-content: start;
    }
    body.interface-frontier .command-column > section,
    body.interface-frontier .command-recently,
    body.interface-frontier .command-glance {
        grid-area: auto;
        align-self: start;
    }
    body.interface-frontier .command-glance { grid-area: glance; }
}

@media (width < 768px) {
    body.interface-frontier .command-grid { display: block; }
    body.interface-frontier .command-column { display: grid; gap: 18px; }
    body.interface-frontier .command-glance { margin-top: 18px; }
}

@media (max-width: 560px) {
    body.interface-frontier .frontier-shell__hero {
        height: 104px;
        border-radius: 0;
    }
    .frontier-shell__brand {
        font-size: clamp(1.8rem, 10vw, 2.65rem);
        letter-spacing: .045em;
    }
    body.interface-frontier .site-header {
        height: 56px;
        padding: 0 10px;
    }
    body.interface-frontier .site-body > .page-container {
        padding: 18px 14px 60px;
        border-radius: 0;
    }
    body.interface-frontier .conditions-strip {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        align-items: center;
        gap: 12px;
        padding: 14px;
    }
    body.interface-frontier .conditions-strip > .btn,
    body.interface-frontier .conditions-strip > a {
        grid-column: 1 / -1;
        justify-self: start;
    }
}

@media (width < 1024px) {
    body.interface-frontier .frontier-shell {
        max-width: none;
        margin: 0;
        border-inline: 0;
        border-radius: 0;
        box-shadow: none;
    }
    body.interface-frontier .frontier-shell__hero {
        height: 124px;
        border-radius: 0;
    }
    body.interface-frontier .site-header {
        height: 56px;
        padding-inline: 14px;
    }
    body.interface-frontier .site-body > .page-container {
        border-radius: 0;
    }
}

@media (max-width: 560px) {
    body.interface-frontier .frontier-shell__hero { height: 104px; }
}

@media (prefers-reduced-motion: reduce) {
    body.interface-frontier .sidebar-nav a,
    body.interface-frontier .sidebar-nav-group > summary,
    .frontier-shell__brand { transition: none; }
}

/* ============================================================
   Frontier responsive fallback
   Frontier's scenic shell is a desktop frame. Below the desktop rail
   breakpoint, keep the established mobile/tablet structure and use the
   selected Frontier tokens only for restrained surface accents.
   ============================================================ */
@media (width < 1024px) {
    body.interface-frontier {
        background-image: none;
        background-attachment: scroll;
    }

    body.interface-frontier .frontier-shell {
        max-width: none;
        min-height: 0;
        margin: 0;
        overflow: visible;
        border: 0;
        border-radius: 0;
        background: transparent;
        box-shadow: none;
    }

    body.interface-frontier .frontier-shell__hero { display: none; }
    body.interface-frontier .site-brand { display: inline; }

    body.interface-frontier .site-header {
        height: var(--header-height);
        padding: 0 14px;
        background: color-mix(in srgb, var(--bg-surface) 84%, transparent);
        border-top: 0;
        border-bottom-color: var(--border-color);
    }

    body.interface-frontier .site-nav-right { gap: 14px; }

    body.interface-frontier .site-body {
        min-height: 0;
        background: transparent;
        background-image: none;
        background-attachment: scroll;
    }

    body.interface-frontier .site-body > .page-container {
        max-width: 1100px;
        padding: 24px 20px 60px;
        border-radius: 0;
        background: transparent;
    }

    body.interface-frontier .page-header { padding-top: 0; }
    body.interface-frontier .page-title {
        font-size: clamp(1.7rem, 1.15rem + 2.3vw, 2.45rem);
        letter-spacing: -0.02em;
    }

    body.interface-frontier .panel {
        border-color: var(--border-color);
        border-radius: var(--radius-lg);
        background: var(--bg-surface);
        box-shadow: var(--shadow-sm);
    }

    body.interface-frontier .conditions-strip {
        display: flex;
        grid-template-columns: none;
        gap: 16px;
        padding: 15px 20px;
        margin-bottom: 20px;
        border-color: var(--border-subtle);
        border-radius: var(--radius-lg);
        box-shadow: none;
    }

    /* Restore the normal one-column phone and 1.6fr/1fr tablet dashboard
       geometry. The named Frontier command/glance layout starts at desktop. */
    body.interface-frontier .command-grid {
        display: grid;
        grid-template-columns: 1fr;
        grid-template-areas: none;
        gap: 18px;
    }
    body.interface-frontier .command-grid::before { display: none; }
    body.interface-frontier .command-column {
        display: grid;
        grid-area: auto;
        gap: 18px;
        align-content: start;
    }
    body.interface-frontier .command-glance {
        grid-area: auto;
        margin-top: 0;
    }
}

@media (min-width: 768px) and (max-width: 1023px) {
    body.interface-frontier .command-grid {
        grid-template-columns: 1.6fr 1fr;
    }
}

@media (max-width: 380px) {
    body.interface-frontier .site-header { padding: 0 12px; }
}

/* ---- Frontier responsive cleanup ----
   The shell is desktop-only, but its header still carries desktop context at tablet
   widths. Keep the conditions and summary readable instead of compressing them into
   narrow flex items. */
@media (width < 1024px) {
    body.interface-frontier .weather-chip,
    body.interface-frontier .site-clock--header { display: none; }
    body.interface-frontier .account-menu-clock { display: flex; }
    body.interface-frontier .site-nav-right { gap: 10px; }
}

@media (min-width: 768px) and (max-width: 1023px) {
    /* The summary panel needs the full tablet reading width; keeping it as a narrow
       second column turns every stat and action into a tall stack. */
    body.interface-frontier .command-grid { grid-template-columns: 1fr; }
}

@media (max-width: 480px) {
    /* The icon, long consequence copy, and Star Map action each get a readable row on
       phones. This prevents the body from collapsing to ~47px beside the two side items. */
    body.interface-frontier .conditions-strip {
        align-items: stretch;
        flex-direction: column;
        gap: 10px;
        padding: 14px 16px;
    }
    body.interface-frontier .conditions-strip__icon { align-self: flex-start; }
    body.interface-frontier .conditions-strip__body {
        flex: 0 1 auto;
        width: 100%;
    }
    body.interface-frontier .conditions-strip > .btn,
    body.interface-frontier .conditions-strip > a { align-self: flex-start; }

    /* A single resource opener is easier to scan and leaves the notification and
       account controls their own space. The hidden chips remain in the DOM for updates. */
    body.interface-frontier .currency-cluster { display: none; }
    body.interface-frontier .nav-currency-more__3,
    body.interface-frontier .nav-currency-more__4 { display: none; }
    body.interface-frontier .nav-currency-more__label { display: inline; }
    body.interface-frontier .nav-currency-more {
        flex: 0 0 auto;
        min-width: 76px;
        min-height: 38px;
        padding: 6px 8px;
        font-size: .72rem;
    }
}

@media (max-width: 380px) {
    body.interface-frontier .site-nav-right { gap: 6px; }
    body.interface-frontier [data-surface="command-post"] .page-header__actions {
        display: grid;
        grid-template-columns: 1fr;
        gap: 10px;
        width: 100%;
    }
    body.interface-frontier [data-surface="command-post"] .page-header__actions > .btn {
        width: 100%;
        justify-content: center;
    }
}

@media (width < 1024px) and (pointer: coarse) {
    body.interface-frontier .nav-currency-more { min-width: 76px; min-height: 44px; }
    body.interface-frontier .notif-bell-toggle,
    body.interface-frontier .account-menu-toggle { min-width: 44px; }
}

/* ---- Left-side mobile/tablet navigation ----
   This preference mirrors only the responsive navigation affordances. The
   desktop mouse rail and the order of the five primary destinations stay unchanged. */
@media (width < 1024px) {
    /* The Menu opener moves to the leading edge when the drawer is mirrored. It is one
       button ordered by CSS, not a second copy of the markup: two mutually-exclusive
       template branches put the same id in the file twice, which a static reader cannot
       tell apart from a real duplicate. The five primary destinations keep their order. */
    body.mobile-nav-mirrored .mobile-tabbar > button { order: -1; }

    body.mobile-nav-mirrored .nav-drawer {
        right: auto;
        left: 0;
        border-left: 0;
        border-right: 1px solid var(--border-color);
    }
}

/* Landscape touch tablets can cross the desktop width breakpoint while still
   needing the navigation on the opposite side. Keep ordinary desktop mouse
   layouts unchanged; this only mirrors the full rail for coarse-pointer devices. */
@media (min-width: 1024px) and (hover: none) and (pointer: coarse) {
    body.mobile-nav-mirrored .site-sidebar {
        order: 2;
        border-right: 0;
        border-left: 1px solid var(--border-subtle);
    }

    body.mobile-nav-mirrored .site-sidebar .sidebar-nav a.is-active::before {
        right: -14px;
        left: auto;
        border-radius: 3px 0 0 3px;
    }

    body.interface-frontier.mobile-nav-mirrored .site-sidebar {
        border-left-color: var(--frontier-shell-border);
        border-radius: 0;
    }

    body.interface-frontier.mobile-nav-mirrored .site-body > .page-container {
        border-radius: 0 0 0 99px;
    }
}

/* ============================================================
   Frontier navigation polish
   Keep the authored neon palette, but give the rail a calmer console rhythm:
   one clear surface per destination, a quieter nested hierarchy, and a
   scrollbar that reads as utility chrome instead of a second accent. These
   rules are Frontier-only; Legacy navigation keeps its existing treatment.
   ============================================================ */
html.frontier-document {
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--color-violet, #9B7CFF) 52%, var(--border-color, #40305A)) transparent;
}

body.interface-frontier .site-sidebar,
body.interface-frontier .nav-drawer,
body.interface-frontier .site-sidebar .sidebar-nav {
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--color-violet) 52%, var(--border-color)) transparent;
}

html.frontier-document::-webkit-scrollbar,
body.interface-frontier .site-sidebar::-webkit-scrollbar,
body.interface-frontier .site-sidebar .sidebar-nav::-webkit-scrollbar,
body.interface-frontier .nav-drawer::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

html.frontier-document::-webkit-scrollbar-track,
body.interface-frontier .site-sidebar::-webkit-scrollbar-track,
body.interface-frontier .site-sidebar .sidebar-nav::-webkit-scrollbar-track,
body.interface-frontier .nav-drawer::-webkit-scrollbar-track {
    background: transparent;
}

html.frontier-document::-webkit-scrollbar-thumb,
body.interface-frontier .site-sidebar::-webkit-scrollbar-thumb,
body.interface-frontier .site-sidebar .sidebar-nav::-webkit-scrollbar-thumb,
body.interface-frontier .nav-drawer::-webkit-scrollbar-thumb {
    min-height: 44px;
    border: 2px solid transparent;
    border-radius: 999px;
    background: color-mix(in srgb, var(--color-violet, #9B7CFF) 48%, var(--border-color, #40305A));
    background-clip: padding-box;
}

@media (hover: hover) {
    html.frontier-document::-webkit-scrollbar-thumb:hover,
    body.interface-frontier .site-sidebar::-webkit-scrollbar-thumb:hover,
    body.interface-frontier .site-sidebar .sidebar-nav::-webkit-scrollbar-thumb:hover,
    body.interface-frontier .nav-drawer::-webkit-scrollbar-thumb:hover {
        background: color-mix(in srgb, var(--color-primary, #64D8FF) 62%, var(--border-color, #40305A));
        background-clip: padding-box;
    }
}

body.interface-frontier .site-sidebar {
    background-image:
        linear-gradient(180deg, color-mix(in srgb, var(--bg-surface) 38%, transparent), color-mix(in srgb, var(--bg-body) 74%, transparent) 88%),
        var(--frontier-rail-art, none);
    box-shadow: inset -1px 0 color-mix(in srgb, var(--color-primary) 24%, transparent);
}

body.interface-frontier .site-sidebar .sidebar-nav,
body.interface-frontier .nav-drawer .sidebar-nav {
    gap: 6px;
}

body.interface-frontier .site-sidebar .sidebar-nav {
    padding: 10px 8px 16px;
}

body.interface-frontier .sidebar-nav > a,
body.interface-frontier .sidebar-nav-group > summary {
    min-height: 52px;
    padding: 10px 12px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 15%, transparent);
    border-radius: 12px;
    background: color-mix(in srgb, var(--bg-surface) 40%, transparent);
    color: var(--text-secondary);
    text-shadow: none;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease), color var(--dur) var(--ease);
}

body.interface-frontier .sidebar-nav-group {
    margin-top: 0;
}

body.interface-frontier .sidebar-nav-group__hint {
    color: var(--text-muted);
}

body.interface-frontier .sidebar-nav-group__chevron {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: .95rem;
}

body.interface-frontier .sidebar-nav-group__label {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: break-word;
}

body.interface-frontier .sidebar-nav > a {
    min-width: 0;
    overflow-wrap: anywhere;
}

body.interface-frontier .sidebar-nav a.is-active,
body.interface-frontier .sidebar-nav-group[open] > summary {
    border-color: color-mix(in srgb, var(--color-primary) 48%, var(--border-color));
    background: linear-gradient(135deg, color-mix(in srgb, var(--color-violet) 42%, var(--bg-surface)), color-mix(in srgb, var(--color-primary) 24%, var(--bg-body)));
    color: var(--text-primary);
    box-shadow: inset 3px 0 var(--color-primary), 0 6px 16px color-mix(in srgb, var(--bg-body) 24%, transparent);
}

body.interface-frontier .sidebar-nav-group__items {
    gap: 3px;
    margin: 2px 0 2px 8px;
    padding: 3px 0 3px 8px;
    border-left: 1px solid color-mix(in srgb, var(--color-primary) 30%, var(--border-subtle));
}

body.interface-frontier .sidebar-nav-group__items a,
body.interface-frontier .sidebar-nav-group[open] .sidebar-nav-group__items a {
    min-height: 36px;
    padding: 8px 10px;
    border: 1px solid transparent;
    border-radius: 9px;
    background: transparent;
    color: var(--text-secondary);
    font-size: .8rem;
    text-shadow: none;
}

body.interface-frontier .sidebar-nav-group__items a.is-active {
    border-color: color-mix(in srgb, var(--color-primary) 34%, transparent);
    background: color-mix(in srgb, var(--color-primary) 18%, var(--bg-surface));
    color: var(--text-primary);
}

@media (hover: hover) {
    body.interface-frontier .sidebar-nav > a:hover,
    body.interface-frontier .sidebar-nav-group > summary:hover {
        border-color: color-mix(in srgb, var(--color-primary) 38%, var(--border-color));
        background: color-mix(in srgb, var(--color-violet) 28%, var(--bg-hover));
        color: var(--text-primary);
        letter-spacing: normal;
    }

    body.interface-frontier .sidebar-nav-group__items a:hover {
        border-color: color-mix(in srgb, var(--color-primary) 24%, transparent);
        background: color-mix(in srgb, var(--color-violet) 18%, var(--bg-hover));
        color: var(--text-primary);
        letter-spacing: normal;
    }
}

body.interface-frontier .nav-drawer {
    background: var(--bg-elevated);
    border-left-color: color-mix(in srgb, var(--color-primary) 36%, var(--border-color));
}

body.interface-frontier .nav-drawer__head {
    border-bottom-color: color-mix(in srgb, var(--color-primary) 22%, var(--border-subtle));
}

body.interface-frontier .mobile-tabbar {
    background: var(--bg-surface);
    border-top-color: color-mix(in srgb, var(--color-primary) 34%, var(--border-color));
    box-shadow: 0 -10px 26px color-mix(in srgb, var(--bg-body) 34%, transparent);
}

body.interface-frontier .mobile-tab {
    border: 1px solid transparent;
    color: var(--text-muted);
}

body.interface-frontier .mobile-tab.is-active {
    border-color: color-mix(in srgb, var(--color-primary) 34%, transparent);
    background: color-mix(in srgb, var(--color-violet) 24%, transparent);
    color: var(--text-primary);
}

body.interface-frontier .mobile-tab.is-active .nav-ico {
    filter: drop-shadow(0 0 4px color-mix(in srgb, var(--color-primary) 62%, transparent));
}

/* ---------------------------------------------------------------------------
   Resonance Trials: the experimental Incursion track.

   Everything here is a plain token-driven treatment over markup that already says
   what it means in words. The banner, the Boon cards and the in-room controls are
   never distinguished by colour alone: each carries its own label, chip glyph and
   sentence, so a monochrome or high-contrast render loses nothing.
   --------------------------------------------------------------------------- */
.hint-ok { color: var(--color-success-ink, var(--text-primary)); }

.track-banner {
    display: flex; flex-direction: column; gap: 6px;
    padding: 12px 14px; margin: 0 0 14px;
    background: var(--bg-subtle);
    border: 1px solid color-mix(in srgb, var(--color-warning-ink) 45%, var(--border-color));
    border-left-width: 4px;
    border-radius: var(--radius-md);
}
.track-banner__label { margin: 0; }
.track-banner__warning { margin: 0; font-weight: 600; }
.track-banner__detail,
.track-banner__feedback { margin: 0; font-size: 0.86rem; color: var(--text-secondary); }

.boon-offer { margin: 0 0 16px; }
.boon-offer__intro { margin: 0 0 6px; }
.boon-offer__warning { margin: 0 0 10px; }
.boon-offer__grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px; margin: 0; padding: 0; list-style: none;
}
.boon-card {
    display: flex; flex-direction: column; gap: 8px;
    padding: 12px 14px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
.boon-card__name { margin: 0; font-size: 1rem; }
.boon-card__promise { margin: 0; font-weight: 600; }
.boon-card__fiction { margin: 0; font-style: italic; color: var(--text-muted); font-size: 0.86rem; }
.boon-card__key-facts {
    display: flex; flex-direction: column; gap: 5px; margin: 0;
    font-size: 0.82rem;
}
.boon-card__key-facts > div { display: flex; flex-direction: column; gap: 1px; }
.boon-card__key-facts dt {
    font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-muted);
}
.boon-card__key-facts dd { margin: 0; }
.boon-card__details {
    margin-top: auto; border-top: 1px solid var(--border-subtle); padding-top: 6px;
}
.boon-card__details > summary {
    cursor: pointer; color: var(--color-primary); font-size: 0.8rem; font-weight: 600;
}
.boon-card__facts { display: flex; flex-direction: column; gap: 6px; margin: 0; flex: 1; }
.boon-card__facts > div { display: flex; flex-direction: column; gap: 1px; }
.boon-card__facts dt {
    font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-muted);
}
.boon-card__facts dd { margin: 0; font-size: 0.86rem; }
.boon-card__take { align-self: flex-start; }

.boss-choice__boon { margin: 0 0 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.boon-control {
    display: flex; flex-direction: column; gap: 8px;
    margin: 10px 0; padding: 10px 12px;
    border: 1px solid var(--border-color); border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.boon-control__legend { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.boon-control__note { margin: 0; font-size: 0.84rem; color: var(--text-secondary); }
.boon-control__unavailable { margin: 10px 0; }
.boon-control__row { display: flex; flex-wrap: wrap; gap: 12px; }
.boon-control__field { display: flex; flex-direction: column; gap: 4px; flex: 1 1 160px; }
.boon-control__field > span { font-size: 0.78rem; color: var(--text-muted); }
.boon-control__check { display: flex; gap: 10px; align-items: flex-start; }
.boon-control__body { display: flex; flex-direction: column; gap: 3px; }
.field-note--boon { border-left: 3px solid color-mix(in srgb, var(--color-primary) 55%, var(--border-color)); }
.boon-control__result { margin: 8px 0 0; padding: 8px 10px; border-radius: var(--radius-sm); background: var(--bg-subtle); }
.route-node__surveyed { margin: 6px 0 0; font-size: 0.78rem; color: var(--color-primary); }
.boss-choice__boons { margin: 0 0 10px; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }

.boon-assist { display: flex; flex-direction: column; gap: 6px; margin: 10px 0; }

.track-choice { display: flex; flex-direction: column; gap: 8px; border: 0; padding: 0; margin: 0; }
.track-choice__options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.track-choice__summary { margin: 0; }
/* This option's body carries the experimental warning as a FULL SENTENCE inside a chip,
   which is not what .state-chip is shaped for: that class is nowrap because its usual
   contents are short status tokens ("◆ Reachable", "✦ Star-Touched"), and nowrap on a
   ~475px sentence is unbreakable content that sizes whatever holds it. Paired with
   min-width:0 (a flex item's automatic minimum is min-content, so without it the column
   cannot shrink to its grid track), this keeps the card inside its track instead of
   pushing the document wider than the viewport in the 768-1024px band. */
.track-option .mode-option__body { min-width: 0; }
.track-option .state-chip { white-space: normal; }
.track-option--closed { opacity: 0.62; cursor: not-allowed; }
.track-choice__detail { margin: 0; }
.track-choice__list { margin: 6px 0; padding-left: 18px; }
.track-choice__boons { margin: 6px 0; }

.experimental-history { margin-top: 28px; }
.debrief__boon-promise { margin: 0 0 8px; font-weight: 600; }
.debrief__boon-log { margin: 8px 0 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }

/* Coarse-pointer floors. Declared at the END of the file for the same reason the incursion
   board's own block is: several of these controls also carry .btn-sm / .input-sm, whose
   min-height is declared later in the sheet at the same specificity, so a rule written
   earlier would silently lose the cascade. */
@media (pointer: coarse) {
    .boon-card__take,
    .boon-assist__apply { min-height: 44px; }
    .boon-card__details > summary { min-height: 44px; display: flex; align-items: center; }
    .boon-control__field select,
    .boon-control__field .input { min-height: 44px; }
    .boon-control__check { min-height: 44px; align-items: center; }
    .boon-control__check input[type="checkbox"] { width: 24px; height: 24px; }
    .track-option { min-height: 44px; }
}

@media (max-width: 600px) {
    .track-choice__options { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Incursion board v2 (the beta presentation channel)

   Namespaced `.ib2-*` and appended as one block rather than woven through the live
   incursion styles above, so the two channels cannot fight in the cascade and so promoting
   or deleting v2 is a contiguous edit rather than an archaeological one.

   The identity is a tactical instrument panel: recessed wells, hairline rules, mono numerals,
   and one type-coloured band per encounter. Every colour is an existing token, so it survives
   all ten Legacy palettes, both Frontier modes and the per-theme contrast tests; nothing here
   opts out of theming.

   Motion is decoration only. Nothing here gates server state or a next action, and the global
   reduced-motion clamp already flattens it.
   ========================================================================== */

.ib2-page { --ib2-gap: 14px; }

/* ---- header ---- */
.ib2-header {
    display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between;
    gap: 6px 16px; margin-bottom: 10px;
}
.ib2-header__titles { min-width: 0; }
.ib2-title {
    margin: 2px 0 0; font-family: var(--font-display); font-size: var(--font-size-xl);
    line-height: 1.1; outline-offset: 3px;
}
/* Programmatic focus is not reliably :focus-visible, and restoreFocus() targets this when the
   room a player just resolved is gone. Without a ring they have no idea where they landed. */
.ib2-title:focus { outline: 2px solid var(--color-primary); }
.ib2-floor-condition {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; margin: 8px 0 0;
}
.ib2-floor-condition__blurb { font-size: var(--font-size-xs); color: var(--text-secondary); }
.ib2-header__actions {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px; justify-content: flex-start;
}
@media (min-width: 768px) {
    .ib2-header__actions { justify-content: flex-end; }
    .ib2-header__note { text-align: right; margin-left: auto; }
}
/* Full width and left-aligned rather than trailing the buttons: right-aligned it wrapped to
   three lines on a phone, and this note sits above the play surface. */
.ib2-header__note { flex-basis: 100%; margin: 0; max-width: 70ch; }

/* ---- command bar ---------------------------------------------------------
   Sticky because the haul and the forfeit are the two figures a player re-checks while
   deciding whether to push on, and a strip you have to scroll up to read is one you stop
   reading. --z-sticky, so it sits under the site header rather than over it. */
.ib2-bar {
    position: sticky; top: calc(var(--header-height) + 6px);
    /* One BELOW --z-sticky, which .site-header already occupies. At equal z-index the later
       element in the DOM wins, so any overlap (a taller header, a safe-area inset) would put
       this strip over the site navigation rather than under it. */
    z-index: calc(var(--z-sticky) - 1);
    display: flex; flex-direction: column; gap: 0;
    background: var(--bg-elevated);
    border: 1px solid var(--border-color); border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--ib2-gap);
}
.ib2-bar__cells {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1px; background: var(--border-subtle);
    border-radius: var(--radius-md) var(--radius-md) 0 0; overflow: hidden;
}
.ib2-bar__cell {
    display: flex; flex-direction: column; gap: 2px; min-width: 0;
    padding: 8px 12px; background: var(--bg-elevated);
}
.ib2-bar__label {
    font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted); white-space: nowrap;
}
.ib2-bar__value {
    font-family: var(--font-mono); font-size: 1.15rem; font-weight: 700; line-height: 1.15;
    color: var(--text-primary); overflow-wrap: anywhere;
}
.ib2-bar__value--haul { font-size: 0.86rem; font-weight: 700; color: var(--color-primary); }
.ib2-bar__value--loss { color: var(--color-warning-text); }
.ib2-bar__sub { font-size: 0.7rem; color: var(--text-muted); overflow-wrap: anywhere; }
/* Stress that is actually costing something reads as a warning; zero stays neutral, so the
   tint means "this is escalating" rather than decorating a number that has not moved. */
.ib2-bar__cell--loss:not([data-stress="0"]) .ib2-bar__value--loss { color: var(--color-error-text); }

.ib2-bar__detail { border-top: 1px solid var(--border-subtle); }
.ib2-bar__detail-summary {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 7px 12px; cursor: pointer; list-style: none;
    font-size: var(--font-size-xs); color: var(--text-secondary);
}
.ib2-bar__detail-summary::-webkit-details-marker { display: none; }
.ib2-bar__chevron { transition: transform var(--dur) var(--ease); }
.ib2-bar__detail[open] .ib2-bar__chevron { transform: rotate(180deg); }
.ib2-bar__detail-body { padding: 0 12px 10px; display: flex; flex-direction: column; gap: 5px; }
.ib2-bar__detail-row {
    display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
    gap: 4px 12px; margin: 0; font-size: var(--font-size-xs);
}
.ib2-bar__detail-label { color: var(--text-muted); }
.ib2-bar__detail-value { font-family: var(--font-mono); color: var(--text-primary); }
.ib2-bar__detail-note { margin: 2px 0 0; font-size: var(--font-size-xs); color: var(--text-muted); }

/* ---- layout ---- */
.ib2-shell { display: flex; flex-direction: column; gap: var(--ib2-gap); }
.ib2-layout { display: grid; grid-template-columns: 1fr; gap: var(--ib2-gap); align-items: start; }
.ib2-main, .ib2-rail { display: flex; flex-direction: column; gap: var(--ib2-gap); min-width: 0; }

/* ---- encounter ---- */
.ib2-encounter {
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md); overflow: hidden;
}
/* The band replaces a full-width art panel that cost ~190px. It still reserves a fixed shape,
   so real art can drop in later without shifting layout, but it costs a line not a panel. */
.ib2-encounter__band {
    display: flex; align-items: center; gap: 8px; padding: 7px 12px;
    background: var(--bg-subtle);
    border-bottom: 1px solid var(--ib2-band, var(--border-color));
}
.ib2-encounter__glyph {
    flex: 0 0 auto; display: grid; place-items: center;
    width: 32px; height: 32px; border-radius: var(--radius-sm);
    font-size: 1rem; background: var(--bg-elevated);
    box-shadow: inset 0 0 0 1px var(--ib2-band, var(--border-color));
}
.ib2-encounter__ident { min-width: 0; flex: 1 1 auto; }
.ib2-encounter__title {
    margin: 0; font-family: var(--font-heading); font-size: var(--font-size-lg); line-height: 1.2;
}
.ib2-encounter__type {
    display: block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--text-muted); margin-top: 2px;
}
.ib2-encounter__chip { flex: 0 0 auto; }
.ib2-encounter__body { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 8px; }
.ib2-encounter__flavour { margin: 0; font-size: var(--font-size-sm); color: var(--text-secondary); line-height: 1.45; }
.ib2-encounter__moment { margin: 0; font-size: var(--font-size-xs); color: var(--text-muted); font-style: italic; }

/* One band colour per node type, from the existing signal tokens. The type is also stated in
   words directly beneath the title, so this is emphasis and never the only signal. */
.ib2-band--boss           { --ib2-band: var(--color-reward); }
.ib2-band--battle         { --ib2-band: var(--color-error); }
.ib2-band--vault          { --ib2-band: var(--color-warning); }
.ib2-band--merchant       { --ib2-band: var(--color-primary); }
.ib2-band--faction_supply { --ib2-band: var(--color-info); }
.ib2-band--hazard         { --ib2-band: var(--color-error); }
.ib2-band--camp           { --ib2-band: var(--color-success); }

/* ---- stakes ---- */
.ib2-stakes {
    list-style: none; margin: 0; padding: 0;
    display: grid; grid-template-columns: 1fr; gap: 8px;
}
.ib2-stake {
    display: flex; flex-direction: column; gap: 2px;
    padding: 8px 10px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border-left: 3px solid var(--border-color);
}
.ib2-stake--reward { border-left-color: var(--color-success); }
.ib2-stake--penalty { border-left-color: var(--color-warning); }
.ib2-stake__label {
    font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted);
}
.ib2-stake__value { font-size: var(--font-size-sm); font-weight: 600; color: var(--text-primary); }
.ib2-stake__note { font-size: var(--font-size-xs); color: var(--text-muted); }

/* ---- solve block: hand, then slots, then commit ---- */
.ib2-solve {
    display: flex; flex-direction: column; gap: 10px;
    padding: 12px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border: 1px solid var(--border-subtle);
}
.ib2-solve__head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ib2-solve__title {
    margin: 0; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted);
}
.ib2-solve__count { font-size: var(--font-size-xs); color: var(--text-secondary); margin-right: auto; }
.ib2-solve__count strong { font-family: var(--font-mono); color: var(--text-primary); }
.ib2-solve__hint { margin: 0; font-size: var(--font-size-xs); color: var(--text-muted); line-height: 1.4; }
.ib2-hand { display: flex; flex-wrap: wrap; gap: 6px; }
.ib2-solve__unlocks { margin: 0; font-size: var(--font-size-xs); color: var(--text-muted); }
.ib2-solve__unlock { margin-left: 2px; }

.ib2-retreat { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }

/* ---- route ---- */
.ib2-route {
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md); padding: 12px 14px;
    display: flex; flex-direction: column; gap: 10px;
}
.ib2-route__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.ib2-route__title { margin: 2px 0 0; font-family: var(--font-heading); font-size: var(--font-size-base); }
.ib2-route__progress { font-family: var(--font-mono); font-size: var(--font-size-xs); color: var(--text-secondary); white-space: nowrap; }
/* The plot is the containing block for every absolutely-positioned node: see
   game/test_css_positioning.py, which fails if this loses position: relative. */
.ib2-route__plot {
    position: relative; border-radius: var(--radius-sm);
    /* 52px per row: the tallest node is 44px (a reachable one, at the coarse-pointer floor)
       plus breathing room. A fixed height packed a deep floor tighter than its own nodes. */
    min-height: calc(var(--ib2-route-rows, 5) * 52px);
    background: var(--bg-cool); border: 1px solid var(--border-subtle); overflow: hidden;
}
.ib2-route__edges { position: absolute; inset: 0; width: 100%; height: 100%; }
.ib2-route__edge { fill: none; stroke: var(--border-color); stroke-width: 0.6; vector-effect: non-scaling-stroke; }
.ib2-route__edge.is-open { stroke: var(--color-primary); opacity: 0.7; }
.ib2-route__nodes { position: absolute; inset: 0; }
.ib2-route__node {
    position: absolute; transform: translate(-50%, -50%);
    display: grid; place-items: center; gap: 1px;
    min-width: 34px; min-height: 34px; padding: 3px 5px;
    border-radius: var(--radius-sm); font-size: 0.95rem; line-height: 1;
    background: var(--bg-elevated); border: 1px solid var(--border-color); color: var(--text-secondary);
}
.ib2-route__node-name {
    font-size: 0.58rem; letter-spacing: 0.02em; color: var(--text-muted);
    max-width: 9ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ib2-route__node--available {
    cursor: pointer; color: var(--text-primary);
    border-color: var(--color-primary); box-shadow: var(--glow-accent);
}
.ib2-route__node--available.is-selected {
    background: var(--color-primary); color: var(--text-inverse);
}
.ib2-route__node--available.is-selected .ib2-route__node-name { color: var(--text-inverse); }
.ib2-route__node--cleared { color: var(--color-success-text); border-color: var(--color-success-border); }
.ib2-route__node--failed { color: var(--color-error-text); border-color: var(--color-error-border); }
.ib2-route__node--skipped { color: var(--text-muted); border-color: var(--border-subtle); border-style: dashed; opacity: 0.62; }
.ib2-route__node--locked { opacity: 0.62; }
.ib2-route__node:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
@media (hover: hover) {
    .ib2-route__node--available:not(.is-selected):hover { background: var(--bg-hover); }
}
.ib2-route__help, .ib2-route__boss { margin: 0; font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.45; }
.ib2-route__legend { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: var(--font-size-xs); color: var(--text-muted); }
.ib2-route__swatch { font-style: normal; margin-right: 3px; }
.ib2-route__swatch--cleared { color: var(--color-success-text); }
.ib2-route__swatch--available { color: var(--color-primary); }
.ib2-route__swatch--failed { color: var(--color-error-text); }
.ib2-route__swatch--skipped { color: var(--text-muted); }
.ib2-route__promises-title {
    margin: 0 0 5px; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted);
}
.ib2-route__promise-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.ib2-route__promise {
    display: flex; flex-direction: column; gap: 2px;
    padding: 6px 9px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border-left: 3px solid var(--border-color);
}
.ib2-route__promise-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.ib2-route__promise-name { font-size: var(--font-size-sm); font-weight: 600; }
.ib2-route__promise-kind {
    font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-muted);
}
.ib2-route__promise-text { font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.4; }

/* The boss lock, revealed from floor start. Uses the same .slot-req chips the room lock
   itself does, so a requirement reads identically wherever it appears. */
.ib2-boss-lock {
    display: flex; flex-direction: column; gap: 6px;
    padding: 9px 10px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border-left: 3px solid var(--color-reward);
}
.ib2-boss-lock__title {
    margin: 0; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted);
}
.ib2-boss-lock__slots { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.ib2-boss-lock__note { margin: 0; font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.4; }

/* ---- tablet ---- */
@media (min-width: 768px) {
    .ib2-bar__cells { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .ib2-stakes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- desktop: the route moves beside the encounter, so it costs no vertical space ---- */
@media (min-width: 1024px) {
    .ib2-layout { grid-template-columns: minmax(0, 1fr) 320px; }
    .ib2-rail {
        position: sticky;
        top: calc(var(--header-height) + 14px);
        max-height: calc(100vh - var(--header-height) - 28px);   /* fallback */
        max-height: calc(100dvh - var(--header-height) - 28px);
        overflow-y: auto; scrollbar-gutter: stable;
    }
    .ib2-route__plot { min-height: calc(var(--ib2-route-rows, 5) * 56px); }
}

/* ---- beta launch screen -------------------------------------------------
   Same content as the live launch screen, split by how often each part is needed. The live
   one measured 3.2 screens at 375x812 with Begin 2.58 screens down, because a 459px loadout
   fragment sat between the party and a 949px commit block of full-prose mode and track cards.
   Nothing is removed: attribution, the bench, the beta brief and the world context are
   disclosures, and a launch blocker is never behind one. */
.ib2-launch { display: flex; flex-direction: column; gap: var(--ib2-gap); max-width: 1120px; }
.ib2-launch__panel {
    display: flex; flex-direction: column; gap: 10px;
    padding: clamp(14px, 2vw, 22px); background:
        linear-gradient(145deg, color-mix(in srgb, var(--color-primary) 5%, var(--bg-surface)), var(--bg-surface));
    border: 1px solid color-mix(in srgb, var(--color-primary) 26%, var(--border-color));
    border-radius: var(--radius-lg); box-shadow: 0 10px 30px color-mix(in srgb, var(--color-primary) 7%, transparent);
}
.ib2-launch__title {
    margin: 0; font-family: var(--font-heading); font-size: clamp(1.1rem, 2vw, 1.35rem);
    letter-spacing: 0.01em;
}
.ib2-launch__left,
.ib2-launch__intro,
.ib2-launch__party-block,
.ib2-launch__commit { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.ib2-launch__left { gap: 16px; }
.ib2-launch__intro { padding-bottom: 2px; }
.ib2-launch__preflight { min-width: 0; }
.ib2-launch__squad { margin: 0; }
.ib2-launch__stats { margin: 0; font-size: var(--font-size-sm); color: var(--text-secondary); }
.ib2-launch__stats strong { color: var(--text-primary); font-family: var(--font-mono); }
.ib2-launch__section-head {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
    padding-bottom: 8px; border-bottom: 1px solid var(--border-subtle);
}
.ib2-launch__section-head h3 { margin: 2px 0 0; font-family: var(--font-heading); font-size: var(--font-size-base); }
.ib2-launch__section-count,
.ib2-launch__section-mark {
    flex: 0 0 auto; color: var(--color-primary); font-family: var(--font-mono);
    font-size: var(--font-size-xs); font-weight: 700;
}
.ib2-launch__section-mark { font-size: 1.1rem; }
.ib2-launch__party { margin: 0; }
.ib2-launch__commit {
    padding-top: 12px; border-top: 1px solid color-mix(in srgb, var(--color-primary) 28%, var(--border-subtle));
}
.ib2-launch__commit .hint { margin-top: 0; }
.ib2-launch__go { align-self: stretch; min-height: 48px; margin-top: 2px; font-size: var(--font-size-base); letter-spacing: 0.01em; }

/* The shared roster card also serves wider squad-manager surfaces. On the beta launch bench,
   the card is a compact explanation of one selectable pet: let its name and metadata each use
   the full text column instead of making them compete for one wrapped flex line. */
.ib2-launch .squad-pet-pick { align-items: flex-start; }
.ib2-launch .squad-pet-pick__body {
    display: grid; grid-template-columns: minmax(0, 1fr); align-content: center;
    gap: 4px; width: 100%;
}
.ib2-launch .squad-pet-pick__name,
.ib2-launch .squad-pet-pick__meta { width: 100%; min-width: 0; }

/* The loadout fragment: headline in the open, attribution behind one disclosure. */
.ib2-loadout {
    display: flex; flex-direction: column; gap: 8px;
    padding: 10px 12px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border: 1px solid var(--border-subtle);
}
.ib2-loadout__headline {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
}
.ib2-loadout__total { display: flex; align-items: baseline; gap: 5px; }
.ib2-loadout__total strong {
    font-family: var(--font-mono); font-size: 1.5rem; font-weight: 700; line-height: 1;
    color: var(--color-primary);
}
.ib2-loadout__total-label {
    font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-muted);
}
.ib2-loadout__meta { font-size: var(--font-size-xs); color: var(--text-secondary); }
.ib2-loadout__meta strong { color: var(--text-primary); }
.ib2-loadout__dice { margin: 0; }
.ib2-loadout__detail { border-top: 1px solid var(--border-subtle); padding-top: 6px; }
.ib2-loadout__detail-summary {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    cursor: pointer; list-style: none;
    font-size: var(--font-size-xs); color: var(--text-secondary); min-height: 32px;
}
.ib2-loadout__detail-summary::-webkit-details-marker { display: none; }
.ib2-loadout__detail[open] .ib2-bar__chevron { transform: rotate(180deg); }
.ib2-loadout__detail-body { display: flex; flex-direction: column; gap: 8px; padding-bottom: 4px; }

/* Mode and track: the same radio-card pattern, two across from 560px, with the long prose
   trimmed to the sentence that actually decides the choice. */
.ib2-modes { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
/* minmax(0, 1fr), never a bare 1fr. A bare `1fr` is `minmax(auto, 1fr)`, so a track's
   min-content width leaks out of the grid and widens the whole document: browser-measured,
   the beta track's "EXPERIMENTAL TRACK - RULES MAY CHANGE - SIMULATED REWARDS ONLY" chip
   pushed a 375px page to 551px. The chip is also allowed to wrap for the same reason: a
   nowrap chip sets a floor no column can go under. */
.ib2-modes__options { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; }
.ib2-mode { margin: 0; min-width: 0; }
.ib2-mode .mode-option__body { min-width: 0; }
.ib2-mode .state-chip { white-space: normal; }
.ib2-modes__note { margin: 0; }
.ib2-world { display: flex; flex-direction: column; gap: 8px; margin-top: var(--ib2-gap); }

@media (min-width: 560px) {
    .ib2-modes__options { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    /* Keep the launch decision in authored groups. Auto-placing every warning, disclosure and
       fieldset into alternating columns was the source of the desktop jank. */
    .ib2-launch__panel {
        display: grid; grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
        gap: 18px 24px; align-items: start;
    }
    .ib2-launch__title { grid-column: 1 / -1; }
    .ib2-launch__left { grid-column: 1; grid-row: 2; }
    .ib2-launch__commit { grid-column: 2; grid-row: 2; padding-top: 0; border-top: 0; }
}

@media (pointer: coarse) {
    .ib2-loadout__detail-summary { min-height: 44px; }
}

/* ---- coarse pointer ------------------------------------------------------
   At the END of the v2 block for the same cascade reason the live board has its own floor:
   several of these also carry .btn-xs / .btn-sm, whose min-height is declared later in this
   file at the same specificity, so a coarse rule written earlier silently loses. */
@media (pointer: coarse) {
    .ib2-route__node--available,
    .ib2-bar__detail-summary,
    .ib2-header__actions .btn { min-height: 44px; }
    .ib2-route__node--available { min-width: 44px; }
    .ib2-header__actions .btn { display: inline-flex; align-items: center; justify-content: center; }
}

/* ==========================================================================
   SQUAD MANAGER — pets/views.py::squad_manager
   --------------------------------------------------------------------------
   One contiguous block in its own .sq2-* namespace, appended at the end of the file
   so the squad presentation stays isolated from the Incursion board and can be audited
   as one unit.

   Every colour here is an existing token, so all ten Legacy palettes and both
   Frontier modes come along for free. The only literal hex are the four dice pip
   colours, which are semantic (a red die is red in every theme) and deliberately
   match the existing .die-* / .squad-preview-stat--* treatment rather than following
   the page.
   ========================================================================== */

/* ---- header ------------------------------------------------------------- */
.sq2-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}
.sq2-header__titles { min-width: 0; }
.sq2-header__titles .eyebrow { display: block; margin-bottom: 2px; }
/* One line keeps the page's primary content visible on a 375px phone before any squad. */
.sq2-subtitle {
    margin: 4px 0 0;
    max-width: 60ch;
    color: var(--text-secondary);
    font-size: var(--font-size-sm);
    line-height: 1.45;
}
.sq2-header__actions { flex: 0 0 auto; }

.sq2-explainer {
    margin: 0 0 18px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.sq2-explainer__summary {
    padding: 10px 14px;
    color: var(--text-secondary);
    font-size: var(--font-size-sm);
    font-weight: 600;
    cursor: pointer;
    list-style: none;
}
.sq2-explainer__summary::-webkit-details-marker { display: none; }
.sq2-explainer__summary::before { content: '\25B8  '; color: var(--text-muted); }
.sq2-explainer[open] .sq2-explainer__summary::before { content: '\25BE  '; }
@media (hover: hover) { .sq2-explainer__summary:hover { color: var(--text-primary); } }
.sq2-explainer__summary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.sq2-explainer__body { padding: 0 14px 12px; }
.sq2-explainer__body > :last-child { margin-bottom: 0; }

/* ---- the list ----------------------------------------------------------- */
/* Rows, not a card grid: a squad is at most squad_size_limit (3) pets, so the live
   page's two-column 380px-minimum card grid gave a very small amount of content a
   very large box. A full-width row fits the three slots side by side and stacks
   cleanly on a phone. */
.sq2-list { display: flex; flex-direction: column; gap: 10px; }
.sq2-row {
    background: var(--bg-surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 12px 14px;
}
/* The active squad is marked by an edge, plus the existing .badge-active word beside
   the name: never colour alone. */
.sq2-row--active { border-color: var(--color-primary); box-shadow: inset 3px 0 0 0 var(--color-primary); }
.sq2-row__body { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.sq2-row__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.sq2-name {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    min-width: 0;
    margin: 0;
    font-size: var(--font-size-lg);
}
.sq2-head-right { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* ---- dice readout ------------------------------------------------------- */
/* One figure, not five chips: the total is what a player compares squads by, and the
   per-colour split is the detail beneath it. */
.sq2-dice { display: flex; align-items: baseline; gap: 5px; flex-wrap: wrap; justify-content: flex-end; }
.sq2-dice__total { color: var(--text-primary); font-family: var(--font-mono); font-size: var(--font-size-lg); font-weight: 700; }
.sq2-dice__label { color: var(--text-muted); font-size: var(--font-size-xs); }
.sq2-dice__split { display: inline-flex; gap: 4px; flex-wrap: wrap; margin-left: 2px; }
.sq2-pip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 1px 6px;
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-pill);
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
}

/* ---- the menu ----------------------------------------------------------- */
/* .sq2-menu-wrap MUST stay position: relative: .sq2-menu is absolute, and without a
   positioned ancestor it resolves against the initial containing block and renders a
   full viewport-height down the page at the far left, while still "working". Same
   wrap/button/panel shape as the barracks group menu. Guard:
   game/test_css_positioning.py. */
.sq2-menu-wrap { position: relative; display: inline-flex; }
.sq2-menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    color: var(--text-secondary);
    background: transparent;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}
@media (hover: hover) { .sq2-menu-btn:hover { color: var(--text-primary); background: var(--bg-hover); border-color: var(--color-primary); } }
.sq2-menu-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.sq2-menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: var(--z-dropdown);
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 208px;
    padding: 6px;
    background: var(--bg-elevated);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
}
.sq2-menu form { display: block; }
.sq2-menu__item {
    display: block;
    width: 100%;
    padding: 8px 10px;
    color: var(--text-primary);
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    cursor: pointer;
}
@media (hover: hover) { .sq2-menu__item:hover { background: var(--bg-hover); } }
.sq2-menu__item:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
/* Delete is still clearly destructive, but as one item in a menu rather than the
   loudest element on the page repeated once per squad. */
.sq2-menu__item--danger { color: var(--color-error-text); }
@media (hover: hover) { .sq2-menu__item--danger:hover { background: var(--color-error-bg); } }
.sq2-menu__sub { display: block; }
.sq2-rename { display: flex; gap: 6px; padding: 4px 6px 6px; }
.sq2-rename .input { min-width: 0; flex: 1; }
.sq2-menu__reorder {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border-top: 1px solid var(--border-subtle);
    margin-top: 2px;
}
.sq2-menu__reorder-label { color: var(--text-muted); font-size: var(--font-size-xs); }
.sq2-menu__nudge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    color: var(--text-secondary);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    cursor: pointer;
}
@media (hover: hover) { .sq2-menu__nudge:hover { color: var(--text-primary); border-color: var(--color-primary); } }
.sq2-menu__nudge:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* ---- the three slots ---------------------------------------------------- */
/* The point of the redesign: the squad's actual contents, at a size that reads as the
   subject of the row. minmax(0, 1fr) per column, never a bare 1fr, so a long pet name
   cannot push the row wider than the page (see game/test_mobile_layout.py). */
.sq2-slots {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
    gap: 8px;
}
.sq2-slot {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 8px 10px;
    border-radius: var(--radius-md);
    font: inherit;
    text-align: left;
}
.sq2-slot--filled { background: var(--bg-subtle); border: 1px solid var(--border-subtle); }
/* An empty slot is a real control, not a gap: it is what makes the cap self-evident
   and gives an empty squad something to do besides read a sentence. */
.sq2-slot--empty {
    background: transparent;
    border: 1px dashed var(--border-color);
    color: var(--text-muted);
    cursor: pointer;
}
@media (hover: hover) { .sq2-slot--empty:hover { border-color: var(--color-primary); color: var(--text-secondary); background: var(--bg-hover); } }
.sq2-slot--empty:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.sq2-slot__art {
    width: 38px;
    height: 38px;
    flex: 0 0 auto;
    border-radius: 50%;
    object-fit: cover;
    background: var(--bg-hover);
}
.sq2-slot__art--pending { display: flex; align-items: center; justify-content: center; font-size: 1rem; }
.sq2-slot__plus {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex: 0 0 auto;
    border-radius: 50%;
    border: 1px dashed var(--border-color);
    font-size: 1.1rem;
    line-height: 1;
}
.sq2-slot__name {
    min-width: 0;
    overflow: hidden;
    color: var(--text-primary);
    font-weight: 600;
    font-size: var(--font-size-sm);
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sq2-slot__name--empty { color: var(--text-muted); font-weight: 500; }
.sq2-slot__meta { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; }
.sq2-slot__star { color: var(--color-warning); }
.sq2-slot__trait {
    max-width: 11ch;
    overflow: hidden;
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---- disclosures -------------------------------------------------------- */
.sq2-toggles { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.sq2-toggle {
    padding: 6px 10px;
    color: var(--text-secondary);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-xs);
    cursor: pointer;
}
@media (hover: hover) { .sq2-toggle:hover { color: var(--text-primary); border-color: var(--color-primary); } }
.sq2-toggle:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* Editing the roster is this page's primary job, so its control is the emphasised one. */
.sq2-toggle--primary { color: var(--text-primary); border-color: var(--border-color); }
.sq2-roster-wrap { display: contents; }
.sq2-details { display: flex; flex-direction: column; gap: 6px; width: 100%; margin-top: 2px; }
/* Same scrolling cap as the live roster, so a large crew is scannable without making
   the row unbounded. */
.sq2-roster {
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 100%;
    max-height: min(420px, 60dvh);
    margin-top: 4px;
    padding: 6px;
    overflow-y: auto;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
.sq2-roster__search { width: 100%; }

/* ---- phones ------------------------------------------------------------- */
@media (width < 640px) {
    .sq2-header { gap: 10px; }
    .sq2-header__actions { width: 100%; }
    .sq2-header__actions > .btn { width: 100%; }
    .sq2-row__head { flex-direction: column; align-items: stretch; gap: 8px; }
    .sq2-head-right { justify-content: space-between; }
    .sq2-dice { justify-content: flex-start; }
    /* One slot per line: three across a 375px screen leaves no room for a name. */
    .sq2-slots { grid-template-columns: minmax(0, 1fr); }
    .sq2-toggle { flex: 1; text-align: center; }
    /* Anchored to the button rather than the row's right edge, so it cannot hang off
       the side of a narrow screen. */
    .sq2-menu { right: auto; left: 0; min-width: min(260px, calc(100vw - 48px)); }
}

/* ---- coarse pointer ------------------------------------------------------
   At the END of the squad-manager block, for the same cascade reason the live squad card and the
   ib2 board both take theirs last: several of these also carry .btn-xs/.btn-sm, whose
   min-height is declared later in this file at equal specificity, so a coarse rule
   written earlier silently loses. */
@media (pointer: coarse) {
    .sq2-menu-btn,
    .sq2-menu__nudge,
    .sq2-toggle,
    .sq2-menu__item,
    .sq2-explainer__summary { min-height: 44px; }
    .sq2-menu-btn { min-width: 44px; }
    .sq2-menu__nudge { min-width: 44px; }
    .sq2-slot { min-height: 56px; }
    .sq2-toggle,
    .sq2-menu-btn,
    .sq2-menu__nudge { display: inline-flex; align-items: center; justify-content: center; }
}

/* ---- direct Incursion reward corrections --------------------------------
   The correction screen is a review surface first: the run list stays useful beside
   the preview on a wide staff screen, then becomes a single readable flow on tablets
   and phones. */
.staff-correction-grid {
    display: grid;
    grid-template-columns: minmax(280px, .8fr) minmax(0, 1.5fr);
    gap: 18px;
    align-items: start;
}
.staff-correction-grid > .panel { min-width: 0; }
.staff-correction-search-panel,
.staff-correction-detail,
.staff-correction-empty { padding: 20px; }
.staff-correction-search-panel { position: sticky; top: 1rem; }
.staff-correction-search-form { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end; }
.staff-correction-search-form .staff-search-form__field { min-width: 0; }
.staff-correction-search-form .input { width: 100%; }
.staff-correction-results { margin-top: 16px; }
.staff-correction-run-list { display: grid; gap: 8px; }
.staff-correction-run {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 10px;
    align-items: center;
    padding: 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: inherit;
    text-decoration: none;
    background: color-mix(in srgb, var(--surface-raised) 72%, transparent);
}
.staff-correction-run:focus-visible { border-color: var(--color-primary); background: var(--surface-raised); }
@media (hover: hover) {
    .staff-correction-run:hover { border-color: var(--color-primary); background: var(--surface-raised); }
}
.staff-correction-run__main,
.staff-correction-run__meta { display: grid; gap: 3px; min-width: 0; }
.staff-correction-run__main span,
.staff-correction-run__meta { color: var(--text-secondary); font-size: .82rem; }
.staff-correction-run__main span { overflow-wrap: anywhere; }
.staff-correction-run__meta { justify-items: end; text-align: right; white-space: nowrap; }
.staff-correction-run__arrow { color: var(--color-primary); font-size: 1.2rem; }
.staff-correction-detail .panel__header { gap: 12px; }
.staff-correction-detail .panel-intro { overflow-wrap: anywhere; }
.staff-correction-callout { display: grid; gap: 4px; margin: 14px 0; padding: 12px 14px; border-left: 3px solid var(--color-primary); background: color-mix(in srgb, var(--surface-raised) 82%, transparent); }
.staff-correction-callout--success { border-left-color: var(--color-reward-ink); }
.staff-correction-callout--warning { border-left-color: var(--color-warning-ink); }
.staff-correction-callout ul { margin: 4px 0 0 18px; }
.staff-correction-summary { display: grid; gap: 10px; margin: 16px 0; }
.staff-correction-summary div { display: grid; grid-template-columns: 130px minmax(0, 1fr); gap: 10px; }
.staff-correction-summary dt { color: var(--text-muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; }
.staff-correction-summary dd { margin: 0; overflow-wrap: anywhere; }
.staff-correction-table-wrap { overflow-x: auto; border: 1px solid var(--border-color); border-radius: var(--radius-md); }
.staff-correction-table { width: 100%; min-width: 520px; border-collapse: collapse; }
.staff-correction-table caption { padding: 10px 12px; color: var(--text-secondary); font-size: .82rem; text-align: left; }
.staff-correction-table th,
.staff-correction-table td { padding: 10px 12px; border-top: 1px solid var(--border-color); text-align: left; }
.staff-correction-table thead th { color: var(--text-muted); font-size: .75rem; text-transform: uppercase; letter-spacing: .06em; border-top: 0; }
.staff-correction-table td:not(:first-child),
.staff-correction-table th:not(:first-child) { text-align: right; white-space: nowrap; }
.staff-correction-negative { color: var(--color-error-ink); font-weight: 700; }
.staff-correction-reason { display: grid; gap: 6px; margin-top: 18px; }
.staff-correction-reason > span { font-weight: 700; }
.staff-correction-reason textarea { width: 100%; min-height: 100px; resize: vertical; }
.staff-correction-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-top: 14px; }
.staff-correction-empty { min-height: 280px; display: grid; place-content: center; gap: 8px; text-align: center; }
.staff-correction-empty__icon { font-size: 2rem; }
.inventory-item__qty--debt { color: var(--color-error-ink); font-weight: 700; }
.squad-pet-pick__meta { min-width: 0; overflow-wrap: anywhere; }
.squad-pet-pick .state-chip { white-space: normal; }
.squad-pet-feed { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
.squad-pet-feed select { width: 100%; min-width: 0; text-overflow: ellipsis; }
.squad-pet-feed .btn { min-width: 5.5rem; white-space: nowrap; }

@media (width <= 900px) {
    .staff-correction-grid { grid-template-columns: 1fr; }
    .staff-correction-search-panel { position: static; }
}
@media (width <= 600px) {
    .staff-correction-search-panel,
    .staff-correction-detail,
    .staff-correction-empty { padding: 15px; }
    .staff-correction-search-form { grid-template-columns: 1fr; }
    .staff-correction-search-form .btn { width: 100%; }
    .staff-correction-run { grid-template-columns: minmax(0, 1fr) auto; }
    .staff-correction-run__meta { grid-column: 1 / -1; grid-row: 2; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; text-align: left; }
    .staff-correction-run__arrow { grid-column: 2; grid-row: 1; }
    .staff-correction-summary div { grid-template-columns: 1fr; gap: 3px; }
}
@media (width <= 480px) {
    .squad-pet-feed { gap: 6px; }
    .squad-pet-feed .btn { min-width: 4.6rem; }
}
/* =========================================================================
   Weekly Raffle ("Lucky Draw")
   One contiguous block at the end of the file, same reasoning as the .ib2-*/
   .sq2-* blocks: nothing else can fight it in the cascade, and the whole
   feature is one contiguous edit to add, retune, or remove.
   ========================================================================= */
.raffle-page {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
}
.raffle-main { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.raffle-sidebar { min-width: 0; }
.raffle-section h2 { margin-top: 0; }

.raffle-prizes {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
    margin: 12px 0;
}
.raffle-prize-card {
    background: var(--bg-subtle);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: 14px;
    text-align: center;
}
.raffle-prize-number {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin-bottom: 6px;
}
.raffle-prize-icon img { border-radius: var(--radius-sm); object-fit: contain; }
.raffle-prize-emoji { font-size: 2rem; display: block; }
.raffle-prize-name { font-weight: 600; margin-top: 6px; }

.raffle-countdown {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
    padding-top: 12px;
    border-top: 1px solid var(--border-color);
}
.raffle-countdown-label { color: var(--text-muted); }
.raffle-countdown-timer { font-weight: 700; color: var(--color-primary); }

.raffle-buy-form { display: flex; flex-direction: column; gap: 10px; margin: 12px 0; }

.raffle-entries-section { margin-top: 16px; }
.raffle-entries-total {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 8px;
}
.raffle-entries-odds { color: var(--text-muted); font-size: 0.9rem; }
.raffle-entries-table { width: 100%; border-collapse: collapse; }
.raffle-entries-table th,
.raffle-entries-table td {
    text-align: left;
    padding: 6px 8px;
    border-bottom: 1px solid var(--border-color);
}
.raffle-entries-empty { color: var(--text-muted); }

.raffle-history { display: flex; flex-direction: column; gap: 12px; }
.raffle-history-entry { border-bottom: 1px solid var(--border-color); padding-bottom: 10px; }
.raffle-history-entry:last-child { border-bottom: none; padding-bottom: 0; }
.raffle-history-date { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 4px; }
.raffle-history-prizes { display: flex; flex-direction: column; gap: 6px; }
.raffle-history-prize { display: flex; align-items: flex-start; gap: 8px; }
.history-prize-icon { flex-shrink: 0; }
.raffle-history-list { display: flex; flex-direction: column; gap: 12px; }

@media (min-width: 900px) {
    .raffle-page { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
}
@media (width < 640px) {
    .raffle-prizes { grid-template-columns: minmax(0, 1fr); }
}

/* Site News comments: a flat list beneath a published post, plus one composer.
   Mirrors .forum-post/.forum-reply-form structurally (meta line, body, actions,
   a textarea composer), but kept as its own namespace rather than reusing the
   forum's own classes cross-domain: this is a comment list, not a second forum. */
.news-comments__heading { margin: 0 0 12px; }
.news-comment {
    padding: 14px;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-subtle);
    background: var(--bg-surface);
    margin-bottom: 10px;
}
.news-comment__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: baseline;
    font-size: 0.85rem;
    color: var(--text-muted);
    margin-bottom: 6px;
}
.news-comment__date { color: var(--text-muted); }
.news-comment__body { line-height: 1.6; overflow-wrap: break-word; }
.news-comment__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.news-comment__actions form { margin: 0; }

.news-comment-form { margin-top: 12px; }
.news-comment-form textarea { width: 100%; }
.news-comment-form__tools { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; }
.news-comment-form__tools a { color: var(--text-secondary); font-size: .8rem; }

/* Site News reactions, on both a post and a comment: mirrors .forum-reactions*
   exactly (curated six chips, dim/tint on the reacted one, a "who reacted"
   line), under its own namespace for the same cross-domain reason as the
   comment classes above. */
.news-reactions-wrap { margin: 10px 0; }
.news-reactions { display: flex; flex-wrap: wrap; gap: 6px; }
.news-reactions form { margin: 0; }
.news-reaction-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-color);
    background: var(--bg-subtle);
    color: var(--text-secondary);
    font-size: 0.85rem;
    line-height: 1.4;
    cursor: pointer;
}
.news-reactions--readonly .news-reaction-chip { cursor: default; }
.news-reaction-chip.is-active {
    border-color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 16%, var(--bg-subtle));
    color: var(--text-primary);
}
@media (hover: hover) {
    .news-reactions .news-reaction-chip:hover { border-color: var(--color-primary); }
}
.news-reaction-names { margin: 6px 0 0; font-size: 0.8rem; color: var(--text-muted); }

/* ============================================================
   DRESSING ROOM v2 (.dr2-*)
   Opt-in beta channel, `?v2=1`. Appended as ONE contiguous block at the end of
   this file for the same two reasons the .ib2-* and .sq2-* blocks are: the two
   channels can never fight in the cascade, and promoting or deleting v2 is one
   contiguous edit. Every colour is an existing token, so all ten Legacy palettes
   and both Frontier modes come along for free. The coarse-pointer floor sits at
   the END of this block, per the cascade rule the live board and squad card
   already pay for.
   ============================================================ */

/* The channel switch renders on BOTH channels, so it is deliberately not in the
   .dr2-* namespace: it belongs to neither one. */
.dr-channel-switch {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 12px 0 0;
}
.dr-channel-switch .hint { margin: 0; }

/* The live canvas: the pet drawn by the server, every changeable layer drawn here.
   .dr-canvas-frame is already position:relative with aspect-ratio 1/1, so this stack
   fills it and each layer is absolutely placed inside. */
/* overflow: hidden, unlike the sibling .dr-canvas-overlay which is deliberately
   visible so a chip can sit outside the frame. This stack must CLIP, because the
   compositor clips: Wand draws onto a fixed 500x500 canvas, so anything past the edge
   is cropped out of the render. Without this the canvas shows artwork the saved image
   will not contain, and an oversized layer (real ones measure 600x600 and 800x602)
   widens the whole document. */
/* isolation: isolate is load-bearing, not tidiness. `position: absolute` with
   `z-index: auto` does NOT create a stacking context, so without this the stack's own
   layers (z-index 1..N, one per equipped item) compete directly with the drag markers
   in the sibling .dr-canvas-overlay, which sit at z-index 2. On a real 8-item pet a
   layer at z-index 9 painted over every marker and elementFromPoint returned the
   artwork instead of the chip: the canvas rendered perfectly and nothing on it could
   be grabbed, with every server-side test green. Isolating keeps the layer z-indexes
   internal so the later sibling overlay always wins. */
.dr2-canvas-stack { position: absolute; inset: 0; overflow: hidden; isolation: isolate; }
.dr2-layer { position: absolute; }

/* A fixed layer (a vista, or a background whose catalog row pins it) is authored to
   fill the 500x500 canvas exactly, so it is anchored top-left and stretched to the
   frame: the same top-left-at-0,0 convention the compositor uses for these. */
.dr2-layer--body,
.dr2-layer--fixed,
.dr2-layer--backdrop { inset: 0; width: 100%; height: 100%; }
.dr2-layer--body,
.dr2-layer--fixed { object-fit: contain; }

/* A moveable layer is anchored by its CENTRE, matching _composite_apparel_layer's own
   half-width/half-height subtraction, and scaled with the fluid canvas because the
   compositor works at a fixed 500x500 while this frame does not. max-width:none is
   load-bearing: a global img rule would otherwise clamp a large sprite to the frame
   and silently shrink it out of agreement with the render. */
.dr2-layer--moveable {
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    transform: translate(-50%, -50%) scale(var(--dr-canvas-scale, 1));
    transform-origin: center center;
    /* A real <button> for the keyboard, stripped back to bare artwork. It is
       pointer-events: none on purpose: the stack handles pointerdown for the whole
       canvas so it can alpha-hit-test every layer and pick the one actually under the
       cursor. A button can still be tab-focused and key-operated while inert to the
       pointer, which is exactly the split wanted here. */
    padding: 0;
    border: 0;
    background: none;
    pointer-events: none;
    line-height: 0;
}
.dr2-layer--moveable > img { display: block; max-width: none; max-height: none; }
/* Telling one item from another was the weakest part of the canvas: with several
   similarly coloured pieces a dashed box alone does not say WHICH one you grabbed. Three
   signals now, none of them colour alone: a solid ring, a drop shadow that lifts the
   selected art off everything behind it, and (in the markup) the item's name in the
   status line under the canvas. */
.dr2-layer--moveable.is-selected {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
    filter: drop-shadow(0 0 6px var(--color-primary));
}
/* While something IS selected, everything else steps back. This is what actually makes a
   selection legible in a dense stack: the contrast is against the other layers, not
   against the artwork's own colours. */
.dr2-canvas-stack.has-selection .dr2-layer--moveable:not(.is-selected) { opacity: 0.45; }
.dr2-canvas-stack.has-selection .dr2-layer--body { opacity: 0.75; }

/* Hover naming: know WHAT you are about to grab before pressing. With two similar
   pieces stacked, this is the signal that actually answers "which one is this"; the
   selection dimming only helps once you have already committed to one. Dotted, so it
   never reads as the solid selection ring. */
.dr2-layer--moveable.is-hovered { outline: 2px dotted var(--color-primary); outline-offset: 2px; }

/* A locked layer is not draggable and says so. */
.dr2-layer--moveable.is-locked { cursor: not-allowed; }
.dr2-layer-view__btn.is-locked { color: var(--color-warning, var(--text-muted)); }
.dr2-layer--moveable:focus-visible {
    outline: 3px solid var(--color-primary);
    outline-offset: 2px;
}

/* touch-action: none, or the browser's own pan gesture wins and a touch drag scrolls
   the page instead of moving the layer: the same reason .dr-chip needs it. */
.dr2-canvas-stack[data-live-canvas] { cursor: grab; touch-action: none; }
.dr2-canvas-stack[data-live-canvas]:active { cursor: grabbing; }

.dr2-guide {
    position: absolute;
    z-index: 999;
    pointer-events: none;
    background: var(--color-primary);
    opacity: 0.75;
}
.dr2-guide--x { top: 0; bottom: 0; width: 1px; transform: translateX(-0.5px); }
.dr2-guide--y { left: 0; right: 0; height: 1px; transform: translateY(-0.5px); }
.dr2-guide[hidden] { display: none; }

/* The baked render, demoted from "the thing you wait for" to "the picture other pages
   use". Its own <img> is 500px intrinsic, so it needs the same fluid clamp the live
   frame gives the canvas. */
.dr2-saved-render { margin-top: 12px; }
.dr2-saved-render > summary {
    cursor: pointer;
    font-size: 0.85rem;
    color: var(--text-muted);
    padding: 6px 0;
}
.dr2-saved-render > summary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.dr2-saved-render img { max-width: 100%; height: auto; }

@media (pointer: coarse) {
    .dr2-saved-render > summary { min-height: 44px; display: flex; align-items: center; }
}

/* Reduced motion: the guides are static rules, but keep the grab cursor honest. */
body.reduced-motion .dr2-layer--moveable { transition-duration: 0.001s; }

@media (pointer: coarse) {
    /* A coarse pointer cannot aim at a few transparent pixels, so give the artwork a
       little forgiveness by widening the selected outline rather than the hit area
       (widening the box would break the alpha hit test the canvas depends on). */
    .dr2-layer--moveable.is-selected { outline-width: 3px; outline-offset: 3px; }
}

/* Transform controls on a v2 layer row. Wraps to its own line under the item's name
   and position actions, because a range input needs real width to be usable and a
   cramped one is worse than none. */
.dr2-transform {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    width: 100%;
    max-width: none;
    margin: 6px 0 0;
}
/* Grid, not flex: the row is label | slider | readout, and a 1fr middle track fills the
   space reliably. flex-grow on an <input type=range> did not: the UA gives it an
   intrinsic size that survived both `flex: 1 1 auto` and `flex: 1 1 0`, so the track
   stayed at its floor with 272px of the row sitting unused beside it. */
.dr2-transform__control {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 6px;
    font-size: 0.8rem;
    color: var(--text-muted);
}
/* One control per line at EVERY width, and the slider takes the room that leaves.
   A fixed 96px track carried the whole 0.25x-3x range in 55 steps of 1.7px each, so a
   single pixel of pointer travel jumped the size by about 5% and placing anything
   deliberately was impossible: reported from real play as "it goes up or down 10%".
   This used to be corrected below 768px only, which left the desktop slider, the one
   most likely to be used for fine work, as the worst of the three. */
.dr2-transform__control { width: 100%; }
.dr2-transform__control > input[type="range"] { width: 100%; min-width: 0; }
.dr2-transform__control > output { min-width: 3.5em; font-variant-numeric: tabular-nums; }
/* The mirror checkbox is a two-part row, not three, so it opts out of the grid.
   flex: 0 0 auto because the wrapping .dr2-transform above was shrinking it below its own
   content: measured 37px wide holding a 13px box plus the word "Mirror", i.e. the box and
   the label were overlapping. min-height gives the whole label a real touch target, since
   a 13px UA checkbox is nowhere near the coarse-pointer floor and the label is what a
   finger actually lands on. */
.dr2-transform__control--flip {
    display: flex;
    /* flex-direction is load-bearing: there is a global `label { display: flex;
       flex-direction: column }` in this file, so a label that only says `display: flex`
       inherits COLUMN and stacks its own checkbox above its own text. That is what made
       the Mirror control 37px wide and 54px tall with the box sitting over the word. */
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    flex: 0 0 max-content;
    gap: 6px;
    width: auto;
    min-width: max-content;
    min-height: 44px;
}

@media (width < 768px) {
    .dr2-transform { gap: 8px; }
}

@media (pointer: coarse) {
    .dr2-transform__control > input[type="range"] { min-height: 44px; }
    /* A 13px UA checkbox is the smallest target on the page by a wide margin. */
    .dr2-transform__control--flip input[type="checkbox"] { width: 22px; height: 22px; }
    .dr2-transform__control--flip { min-height: 44px; }
}

/* ---- v2 layout: canvas + workbench, composed in phone order ---------------
   Below 768px this is a single column in DOM order and the canvas is sticky, so the pet
   stays on screen while the workbench scrolls beneath it. From 768px the canvas gets a
   fixed height above a full-width workbench; from 1024px the canvas pins to the left,
   the On Pet rail appears, and the workbench takes the remaining width. One markup path
   throughout: the grid is the only thing that changes. */
.dr2-layout { display: grid; gap: 16px; }
.dr2-canvas-col, .dr2-workbench-col, .dr2-rail { min-width: 0; }

/* On Pet is a tab at EVERY width. A permanent column beside the shelf was built and then
   removed on measurement: this app's shell caps the content area at ~1018px no matter how
   wide the window is (verified at 1700px, where the panel is still 1060px), so a 210px
   rail costs the shelf two cards per row and never earns them back. Keeping the layer
   list permanently on screen is not worth browsing worse at every size. */
.dr2-subhead--rail { display: none; }

@media (width < 768px) {
    .dr2-canvas-col {
        position: sticky;
        top: 0;
        z-index: 3;
        padding-bottom: 8px;
        background: var(--bg-surface);
    }
    /* Cap the pinned canvas so it never eats the screen it is meant to sit above.
       dvh, not vh: a phone's address bar makes vh taller than the visible area. */
    .dr2-canvas-col .dr-canvas-frame { max-width: min(100%, 44dvh); }
}

@media (width >= 768px) {
    .dr2-canvas-col .dr-canvas-frame { max-width: 420px; }
}

/* Canvas width is tuned against the SHELL, not the viewport. The app's sidebar takes
   roughly 380px, so a 1440px window leaves about 1018px of content: a canvas pinned at
   its full 500px left the shelf column 234px, i.e. ONE card per row, which is worse than
   the channel this replaces. Measured in a browser at 1024 / 1280 / 1440 rather than
   guessed. */
/* ONE desktop breakpoint, and it is on the documented scale (480 / 768 / 1024 / 1280;
   see docs/ux-rules.md). This started as three off-scale tiers at 1200, 1440 and 1700,
   which the breakpoint guard rightly rejected. Collapsing them cost nothing real: this
   app's shell caps the content area at about 1018px however wide the window gets
   (measured at a 1700px viewport, where the panel is still 1060px), so tiers above 1280
   were tuning against width the page never receives.

   The pet was 40% of the panel, which is timid for the one thing the page is about. At
   480px it is 45%, and the shelf still fits four cards per row at 1440 and three at 1280.
   Three is a deliberate trade rather than an oversight: holding four at 1280 would need
   the canvas down at ~342px, smaller than the tablet layout gives it, and on a page whose
   whole subject is the companion the pet wins that argument. Side by side waits for 1280
   because at 1024 the shelf gets 244px and TWO cards, fewer than the single-column tablet
   layout manages at 768. Stacked beats squeezed. */
@media (width >= 1280px) {
    .dr2-canvas-col .dr-canvas-frame { max-width: 480px; }
}

@media (width >= 1280px) {
    /* minmax(0, ...) on BOTH tracks, never `auto` and never a bare `1fr`. Both are
       `minmax(auto, ...)`, so min-content leaks out and widens the whole document: the
       canvas column contains a 500px-intrinsic saved-render image, which pushed the page
       into horizontal overflow at every desktop width until these became explicit. The
       cap here tracks the canvas max-width above it. */
    .dr2-layout {
        grid-template-columns: minmax(0, 480px) minmax(0, 1fr);
        align-items: start;
    }
    .dr2-canvas-col { position: sticky; top: 16px; }
}

/* ---- workbench ---- */
.dr2-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
/* Two per row on a phone. The labels say what each tab HOLDS rather than naming it, which
   is the right trade at every width, but four of them wrapping one-per-line put 144px of
   tab bar between the pet and the shelf. A 2x2 grid halves that and keeps every label
   whole, which a horizontal scroller would not. */
@media (width < 480px) {
    .dr2-tabs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .dr2-tab { width: 100%; }
}
.dr2-tab {
    padding: 8px 14px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-color);
    background: var(--bg-subtle);
    color: var(--text-secondary);
    font-size: 0.9rem;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
@media (hover: hover) { .dr2-tab:hover { color: var(--text-primary); border-color: var(--color-primary); } }
.dr2-tab.is-active { color: var(--color-primary); border-color: var(--color-primary); background: var(--bg-hover); }
.dr2-tab:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.dr2-filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 12px; }
.dr2-filters__search { flex: 1 1 180px; }
.dr2-filters__search input { width: 100%; }
.dr2-chipbar { display: flex; flex-wrap: wrap; gap: 4px; }
.dr2-chip {
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-muted);
    font-size: 0.78rem;
    cursor: pointer;
}
.dr2-chip.is-active { color: var(--color-primary); border-color: var(--color-primary); background: var(--bg-hover); }
.dr2-chip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.dr2-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 10px; }
/* Measured at 285px tall for 64px of art: five stacked rows, of which the name alone
   took 50px by wrapping. The art is the thing being chosen, so it grew and everything
   else tightened around it. */
.dr2-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    text-align: center;
    padding: 10px 8px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.dr2-card.is-equipped { border-color: var(--color-primary); background: var(--bg-hover); }
.dr2-card form { width: 100%; max-width: none; margin: 0; }
.dr2-card__art {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 76px;
    height: 76px;
    border-radius: var(--radius-md);
    background: var(--bg-hover);
    font-size: 1.8em;
}
.dr2-card__art img { width: 100%; height: 100%; }
.dr2-card__art img { border-radius: var(--radius-md); object-fit: cover; }
/* Two lines maximum, then ellipsis. An item called "Starforged Antler Crown" otherwise
   sets the height of every card in its row. */
.dr2-card__name {
    font-weight: 600;
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.dr2-card .rarity-chip { font-size: 0.68rem; }
.dr2-card .state-chip { font-size: 0.7rem; }
.dr2-card__note { margin: 0; font-size: 0.75rem; color: var(--text-muted); }

.dr2-subhead { margin: 18px 0 8px; font-size: 0.95rem; }
.dr2-scene-extras > .dr2-subhead:first-child { margin-top: 0; }
.dr2-empty { text-align: center; padding: 32px 16px; color: var(--text-muted); }
.dr2-empty__icon { font-size: 2.2em; margin: 0 0 8px; opacity: 0.6; }
.dr2-empty p { margin: 0; }
.dr2-empty__actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; margin-top: 12px !important; }
.dr2-empty-filter { margin: 12px 0 0; color: var(--text-muted); font-size: 0.85rem; }

@media (pointer: coarse) {
    .dr2-tab { min-height: 44px; }
    .dr2-chip { min-height: 44px; display: flex; align-items: center; }
}

/* ---- v2 hub: get the pet onto the first screen -------------------------- */
.dr2-hub-head {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 12px;
}
.dr2-hub-head__titles { min-width: 0; }
.dr2-hub-head__titles .page-title { margin: 0; }
.dr2-hub-head__actions { display: flex; flex-wrap: wrap; gap: 8px; }

.dr2-hub { margin-bottom: 14px; }
.dr2-picker { display: flex; flex-direction: column; gap: 8px; }
.dr2-picker__search input { width: 100%; max-width: 320px; }

/* A single scrolling row, not a wrapping grid: the picker must stay a fixed, small
   height whatever the roster size, or a large crew pushes the canvas off the screen
   again, which is the whole problem this rebuild exists to fix. */
.dr2-picker__strip {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 6px;
    scrollbar-width: thin;
}
.dr2-pet {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    width: 78px;
    padding: 6px 4px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    cursor: pointer;
    text-align: center;
}
.dr2-pet.is-selected { border-color: var(--color-primary); background: var(--bg-hover); }
.dr2-pet:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }
@media (hover: hover) { .dr2-pet:hover { border-color: var(--color-primary); } }
.dr2-pet__art {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--bg-hover);
}
.dr2-pet__art img { width: 100%; height: 100%; object-fit: cover; }
.dr2-pet__name {
    font-size: 0.75rem;
    font-weight: 600;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dr2-pet__species { font-size: 0.68rem; color: var(--text-muted); }

@media (pointer: coarse) {
    .dr2-pet { width: 84px; }
}

/* Wardrobe collection figure. A number and a bar, not a catalogue: see the v2 wardrobe
   template for why un-owned pieces are not shown here. */
.dr2-collection {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin: 0 0 14px;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}
.dr2-collection__count { margin: 0; font-size: 0.9rem; }
.dr2-collection__bar {
    flex: 1 1 140px;
    min-width: 120px;
    height: 8px;
    border-radius: var(--radius-pill);
    background: var(--bg-hover);
    overflow: hidden;
}
.dr2-collection__bar > span { display: block; height: 100%; background: var(--color-primary); }

/* ---- the room's host, and the fitting-room framing --------------------------
   Belladonna has no portrait yet, so this has to work as a compact name-and-line strip
   first and gain a picture later without moving anything. Deliberately small: she is an
   accent, and the pet is the subject. */
.dr2-host {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 12px;
    padding: 8px 10px;
    border-left: 3px solid var(--color-violet, var(--color-primary));
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
}
/* The shared partial also carries its own .persona-surface* classes, which are sized
   for a full mentor plate (Crafting, the Handbook). Reset them inside this strip rather
   than forking the partial: she is an accent here, not the subject. */
.dr2-host .persona-surface,
.dr2-host__persona { display: flex; align-items: center; gap: 10px; width: 100%; margin: 0; padding: 0; border: 0; background: none; }
.dr2-host .persona-surface__portrait { flex: 0 0 auto; width: 44px; height: 44px; margin: 0; padding: 0; border: 0; background: none; }
.dr2-host .persona-surface__body,
.dr2-host__body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dr2-host__portrait,
.dr2-host__placeholder {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    object-fit: cover;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-hover);
    font-size: 1.3em;
}
.dr2-host__name { font-size: 0.78rem; color: var(--text-muted); }
.dr2-host__line { margin: 0; font-size: 0.9rem; font-style: italic; color: var(--text-secondary); }

/* The layer stack read as a garment rail. This is where the backstage framing actually
   lives: not in the host's portrait, which does not exist yet, but in the shape of the
   controls. A hanging rail above the cards, and each card hanging from it. */
.dr2-panel .dr-layers { position: relative; padding-top: 10px; }
.dr2-panel .dr-layers::before {
    content: "";
    position: absolute;
    top: 3px;
    left: 6px;
    right: 6px;
    height: 2px;
    border-radius: 2px;
    background: var(--border-color);
}

/* The canvas as a lit vanity: a soft pool of light behind the pet rather than a plain
   box. One inset ring in existing tokens, so every palette and both Frontier modes get it
   for free. */
.dr2-canvas-col .dr-canvas-frame {
    border-radius: var(--radius-md);
    box-shadow: inset 0 0 0 1px var(--border-color),
                0 0 34px -12px color-mix(in srgb, var(--color-primary) 45%, transparent);
}

/* ---- view-only visibility: hide / solo / hide-all ------------------------
   None of this changes the pet or the saved picture. Opacity rather than display:none on
   the canvas, so a hidden layer keeps its box and the layout underneath cannot shift as
   things are toggled; pointer-events off, so a hidden layer cannot be grabbed by a drag
   the player can no longer see. */
.dr2-layer.is-view-hidden { opacity: 0; pointer-events: none; }

.dr2-viewbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
}
.dr2-viewbar__note { margin: 6px 0 0; font-size: 0.8rem; color: var(--text-muted); }
.dr2-viewbar__spacer { flex: 1 1 auto; }
.dr2-viewbar__error { margin: 6px 0 0; font-size: 0.82rem; color: var(--color-warning, var(--text-primary)); }
.dr2-viewbar .btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.dr2-viewbar .btn.is-active { color: var(--color-primary); border-color: var(--color-primary); }

.dr2-layer-view { display: flex; gap: 2px; flex-shrink: 0; }
.dr2-layer-view__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text-muted);
    font-size: 0.85rem;
    cursor: pointer;
}
@media (hover: hover) { .dr2-layer-view__btn:hover { border-color: var(--border-color); color: var(--text-primary); } }
.dr2-layer-view__btn.is-off { color: var(--color-warning, var(--text-muted)); }
.dr2-layer-view__btn.is-active { color: var(--color-primary); border-color: var(--color-primary); }
.dr2-layer-view__btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

@media (pointer: coarse) {
    .dr2-layer-view__btn { width: 44px; height: 44px; }
}

/* ---- try-it-on --------------------------------------------------------------
   The preview must never be mistakable for something equipped, so it is marked three
   ways: reduced opacity, a dashed outline, and (in the markup) a status line and a
   per-card label. Never colour alone. */
/* Natural size, scaled with the canvas, exactly like a real moveable layer: this
   previously used inset:0 + 100% + object-fit, which blew an 18x89 sprite up to fill the
   whole 500x500 frame. A try-on has to show the piece at the size it would actually be. */
.dr2-layer--tryon {
    left: 50%;
    top: 50%;
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    transform: translate(-50%, -50%) scale(var(--dr-canvas-scale, 1));
    transform-origin: center center;
    z-index: 900;
    opacity: 0.7;
    outline: 2px dashed var(--color-warning, var(--color-primary));
    outline-offset: -4px;
    pointer-events: none;
}
.dr2-tryon { margin-top: 18px; padding-top: 12px; border-top: 1px dashed var(--border-color); }
.dr2-tryon__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
.dr2-tryon__head .dr2-subhead { margin: 0; }
.dr2-tryon__note { margin: 6px 0 10px; font-size: 0.82rem; color: var(--text-muted); }
.dr2-tryon .btn.is-active { color: var(--color-primary); border-color: var(--color-primary); }
/* The preview is draggable, so it needs to look grabbable. It is still pointer-events:
   none as an element; the stack recognises a press over its opaque pixels, the same way
   it does for real layers. */
.dr2-canvas-stack:has(.dr2-layer--tryon:not([style*="display: none"])) { cursor: grab; }
.dr2-card--tryon { border-style: dashed; }
.dr2-card--tryon.is-previewing { border-color: var(--color-warning, var(--color-primary)); border-style: solid; }

/* Phone: tighten the band above the canvas. The header, picker, panel heading and host
   line together put the pet 599px down an 812px screen, so a page whose whole point is
   looking at your companion showed about two thirds of one. Nothing is removed here,
   only sized down: the species label goes because it is already on the card art and
   repeated in the panel below. */
@media (width < 480px) {
    .dr2-pet { width: 64px; }
    .dr2-pet__art { width: 44px; height: 44px; }
    .dr2-pet__species { display: none; }
    .dr2-host { padding: 6px 8px; margin-bottom: 8px; }
    .dr2-host .persona-surface__portrait,
    .dr2-host__portrait,
    .dr2-host__placeholder { width: 36px; height: 36px; }
}

/* Rarity + stock on one line inside an item card. */
.dr2-card__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 4px 6px;
    margin: 0;
    font-size: var(--font-size-xs);
}

/* Per-card control disclosure in the layer stack. flex-basis: 100% so it always takes
   its own row inside the (now wrapping) card rather than squeezing the name. */
.dr2-layer-tools { flex: 0 0 100%; margin: 2px 0 0; font-size: var(--font-size-sm); }
.dr2-layer-tools > summary {
    cursor: pointer;
    color: var(--text-muted);
    padding: 4px 0;
    list-style: none;
}
.dr2-layer-tools > summary::-webkit-details-marker { display: none; }
.dr2-layer-tools > summary::before { content: '▸ '; }
.dr2-layer-tools[open] > summary::before { content: '▾ '; }
@media (hover: hover) {
    .dr2-layer-tools > summary:hover { color: var(--text-secondary); }
}
.dr2-layer-tools > summary:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}
.dr2-layer-tools__body {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 6px 0 2px;
}
/* The transform row sits under the Reset/Center pair rather than beside it: a range
   input needs real travel to be usable, and sharing a line with two buttons left it
   about 90px wide on a phone. */
.dr2-layer-tools__body > .dr2-transform { margin-top: 0; }

/* The canvas help disclosure. Styled as quiet text rather than a panel: it sits
   directly under the pet and a bordered box there reads as another control. */
.dr2-help { margin: 6px 0 0; font-size: var(--font-size-sm); }
.dr2-help > summary {
    cursor: pointer;
    color: var(--text-muted);
    padding: 4px 0;
    list-style: none;
}
.dr2-help > summary::-webkit-details-marker { display: none; }
.dr2-help > summary::before { content: '▸ '; }
.dr2-help[open] > summary::before { content: '▾ '; }
@media (hover: hover) {
    .dr2-help > summary:hover { color: var(--text-secondary); }
}
.dr2-help > summary:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}
.dr2-help__body {
    display: grid;
    gap: 4px;
    padding: 6px 0 0 10px;
    border-left: 2px solid var(--border-subtle);
    margin-left: 2px;
}
.dr2-help__body p { margin: 0; color: var(--text-secondary); }
.dr2-help__body strong { color: var(--text-primary); }

/* The backdrop's layer-stack swatch. A <span>, not an <img>, because a backdrop is
   painted rather than loaded, so .dr-layer-card__thumb's object-fit has nothing to act
   on and the box needs its own size. */
.dr2-backdrop-swatch {
    display: block;
    width: 28px;
    height: 28px;
    flex: 0 0 28px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
}

/* Coarse-pointer floor for the controls added in the design pass. These are all btn-xs,
   whose own min-height is declared further down the file at the same specificity, so this
   block has to sit at the END of the .dr2-* run or it loses the cascade and silently does
   nothing: the same trap the incursion board's controls already pay for. Measured at 26px
   before this. The moveable layers are deliberately NOT included: they are
   pointer-events: none (the stack handles the pointer) and exist as buttons only so the
   artwork is keyboard reachable, so forcing a 44px box would distort the render for a
   target nobody taps. */
/* Checkbox rows inside the v2 workbench. Scoped to .dr2-panel rather than to
   `.inline-row` itself, which is a generic class used all over the site: this is a
   dressing-room touch fix, not a site-wide restyle. A 13px UA checkbox in a 25px row is
   the smallest target on the page. */
@media (pointer: coarse) {
    .dr2-panel label.inline-row { min-height: 44px; }
    .dr2-panel label.inline-row input[type="checkbox"] { width: 22px; height: 22px; }
}

@media (pointer: coarse) {
    .dr2-viewbar .btn,
    .dr2-tryon .btn,
    .dr2-collection .btn,
    .dr2-hub-head .btn,
    .dr2-empty-filter .btn {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }
}

/* ============================================================
   INCURSION BETA — compact lock rows
   --------------------------------------------------------------------------
   The beta keeps one shared dice hand, but each lock used to repeat four pieces
   vertically: requirement, staged dice, placement hint, and controls. That turns
   a five-slot room into a long stack even though every slot is the same small
   decision. Keep the existing markup and server contract, but let the row read
   like a compact instrument panel: requirement | staged die | add | picker.

   This is scoped to .ib2-solve so the live board remains unchanged. The shared
   _room_lock.html partial still owns the no-JS fields and hidden posted values.
   ============================================================ */
.ib2-solve .lock-slot-v2 {
    display: grid;
    grid-template-columns: max-content minmax(42px, 1fr) max-content minmax(94px, 1.35fr);
    align-items: center;
    column-gap: 8px;
    row-gap: 4px;
    padding: 4px 8px;
}
.ib2-solve .lock-slot-v2__head {
    grid-column: 1;
    min-width: 0;
}
.ib2-solve .slot-stack {
    grid-column: 2;
    min-width: 0;
    min-height: 34px;
    flex-wrap: nowrap;
    overflow: hidden;
}
.ib2-solve .slot-empty {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ib2-solve .slot-controls {
    display: contents;
}
.ib2-solve .slot-arm {
    grid-column: 3;
    min-width: max-content;
}
.ib2-solve .slot-controls .slot-pick {
    grid-column: 4;
    min-width: 0;
    width: 100%;
}
.ib2-solve .lock-slot-v2 > .sacrifice-warn,
.ib2-solve .lock-slot-v2 > .sacrifice-tax {
    grid-column: 1 / -1;
}

/* Blind-mode odds and the optional modifier note remain readable without turning
   an empty slot back into a multi-line card. Warnings are allowed to expand below. */
.ib2-solve .slot-odds { white-space: nowrap; }
.ib2-solve .room-lock__modifier-note { grid-column: 1 / -1; margin: 0 0 2px; }

/* width < 768px, not max-width: 767px: same rule, but on the documented breakpoint
   scale (see docs/ux-rules.md) and in the range syntax the rest of this file uses. */
@media (width < 768px) {
    .ib2-solve .lock-slot-v2 {
        grid-template-columns: max-content minmax(38px, 1fr) max-content minmax(82px, 1.35fr);
        column-gap: 6px;
        padding: 4px 6px;
    }
    .ib2-solve .slot-arm { padding-inline: 7px; }
    .ib2-solve .slot-controls .slot-pick { font-size: 0.78rem; }
}

/* The hand is a rack, not a receipt. A tile represents one visible face/color
   combination, while its label keeps the exact count available to place. The
   Alpine component still chooses one physical die ID per activation. */
.ib2-hand .die-group {
    width: 42px;
    height: 36px;
    padding: 0;
}
.ib2-hand .die-group .die-face { line-height: 1; }
.ib2-hand .die-count {
    position: absolute;
    right: 3px;
    bottom: 2px;
    font-size: 0.56rem;
    font-weight: 800;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}
